DrillLab
速查Reference

考场上会翻的那几页The pages you actually flip to

只收两个真实项目里实际用到的东西。命令都是这两个项目能跑的,报错都是审计时真实出现过的。Only what the two real projects actually use. Every command runs in one of them; every error message really came up during the audit.

§01

命令Commands

注意两个项目的差别:react-notes-app 没有 test script, node-subgraph Note the difference between the two projects: react-notes-app has no test script; node-subgraph has one.

react-notes-app
react-notes-app
npm install
装依赖Install dependencies
npm run dev
起 Vite 开发服务器,浏览器打开提示的地址Starts the Vite dev server. Open the address it prints.
npx vitest run
跑 4 个测试。注意:这个项目没有 test script,npm test 会报 Missing scriptRuns 4 tests. This project has no test script, so npm test reports Missing script.
npx vitest
watch 模式,改代码自动重跑Watch mode. Re-runs on every code change.
npm run q2
跑 Q2 的验证台(tsx q2/demo.ts)Runs the Q2 check harness (tsx q2/demo.ts).
npx tsc --noEmit
只做类型检查。这个项目会报 10 个测试文件的 TS2582/TS2304 —— 是脚手架缺陷,不是你的问题Type check only. This project reports TS2582/TS2304 in 10 test files. That is a defect in the starter code, not your bug.
npm run build
tsc && vite build。因为上面那个原因,在原项目里是失败的Runs tsc && vite build. It fails in the original project, for the reason above.
node-subgraph
graphql-federation-practice/node-subgraph
npm install
装依赖(原本没有 node_modules,必须先装)Install dependencies. There is no node_modules at first, so run this before anything else.
npm start
起服务器,Subgraph ready at http://0.0.0.0:4000/Starts the server. It prints: Subgraph ready at http://0.0.0.0:4000/
npm test
跑 10 个测试(有 test script)Runs 10 tests. This project does have a test script.
npm run test:watch
watch 模式Watch mode.
curl -X POST localhost:4000/ -H 'Content-Type: application/json' -d '{"query":"{ _service { sdl } }"}'
拿 federation SDL —— Router 启动时问的就是这个Fetches the federation SDL. This is exactly what the Router asks for at startup.
node verify-schema.mjs
进程内验证(自己写的脚本,不占端口)In-process check. A hand-written script; it does not bind a port.
java-service
graphql-federation-practice/java-service
mvn test
跑 5 个测试Runs 5 tests.
mvn spring-boot:run
起服务在 8080Starts the service on port 8080.
mvn -o test
离线跑(依赖已在 ~/.m2 里之后可用)Runs offline. Works once the dependencies are in ~/.m2.
mvn clean package -DskipTests
打包但跳过测试Packages the app, skipping tests.
通用小抄General cheatsheet
npm run
不带名字 → 列出这个项目所有可用 scriptWith no script name, lists every script this project defines.
npm ls <包名>npm ls <package>
看某个包实际装了哪个版本Shows which version of a package is actually installed.
node -v && npm -v
确认版本(本机 Node 22.21.1)Check versions. This machine runs Node 22.21.1.
npx <工具>npx <tool>
执行 node_modules/.bin 里的工具,不需要有 scriptRuns a tool from node_modules/.bin. No script entry needed.
只有四个名字能省掉 runOnly four names can skip run

teststartstoprestart。其余都必须写 npm run xxx ——npm build 不会跑你的 build script。test, start, stop, restart. Everything else needs npm run xxx. npm build does not run your build script.

§02

package.json 字段package.json fields

字段Field管什么What it controls两个项目里的值Values in the two projects
name包名Package namereact-notes-app / order-subgraph
private禁止发布到 npmBlocks publishing to npmreact-notes-app 有;subgraph 没有react-notes-app has it; subgraph does not
version自身版本号This package's own version number都是 1.0.01.0.0 in both
typemodule = 用 ESM(import/export); 不写 = CommonJS(require)module = ESM (import/export). Leave it out = CommonJS (require)两个都是 modulemodule in both
main包的入口文件The package's entry filesubgraph 是 src/index.jssrc/index.js in the subgraph
scriptsnpm run <名字> 能跑的命令Commands that npm run <name> can runreact: dev/build/q2;subgraph: start/test/test:watchreact: dev/build/q2; subgraph: start/test/test:watch
dependencies产品运行时需要Needed while the product runsreact+react-dom / apollo+graphql+dataloader
devDependencies只在开发/构建/测试时需要Needed only for development, build and testsvite/vitest/typescript / jest
jest 等工具名jest and other tool names内嵌配置 —— 找不到 jest.config.js 时看这里Inline config — look here when there is no jest.config.jssubgraph 的 jest 配置就在这里The subgraph keeps its jest config here
依赖清单会泄题The dependency list gives the question away

subgraph 的 dependencies 里有 dataloader, 而 TODO 里正好要求「用 DataLoader 防 N+1」。拿到新项目先读 dependencies,特殊的包就是考点。The subgraph lists dataloader in dependencies, and one TODO asks you to stop N+1 queries with DataLoader. On a new project, read dependencies first. An unusual package is the thing being tested.

§03

React Hooks(这道题用到的)React Hooks (the ones this exam uses)

TSXReact 速查React reference源项目From source
1// useState —— 初始值看不出类型时必须显式写泛型
2const [notes, setNotes] = useState<Note[]>([]); // [] 看不出装什么
3const [noteToEdit, setNoteToEdit] = useState<Note | null>(null);
4const [title, setTitle] = useState(""); // "" 已经说明是 string
5
6// 三种不可变更新
7setNotes((prev) => [...prev, item]); // 增
8setNotes((prev) => prev.filter((n) => n.id !== id)); // 删
9setNotes((prev) => prev.map((n) => (n.id === next.id ? next : n))); // 改(保序)
10
11// useEffect —— 依赖数组的三种写法
12useEffect(fn, []); // 只在首次渲染后跑一次
13useEffect(fn, [noteToEdit]); // 首次 + noteToEdit 变化时
14useEffect(fn); // 每次渲染后都跑 —— 几乎总是写错了
15
16// 受控输入
17<input value={title} onChange={(e) => setTitle(e.target.value)} />
18
19// 表单提交
20const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
21 event.preventDefault(); // 少了它页面会刷新,state 归零
22 ...
23};
24
25// 列表渲染
26{notes.map((note) => <NoteItem key={note.id} note={note} />)}
27// ↑ 用稳定 id,永远不要用 index
28
29// 事件处理器要传参数 -> 包一层箭头函数
30<button onClick={() => onDelete(note.id)}>Delete</button>
31<button onClick={onDelete(note.id)}> {/* ✗ 渲染时就执行了 */}
1// useState — write the generic when the initial value does not show the type
2const [notes, setNotes] = useState<Note[]>([]); // [] does not say what goes in
3const [noteToEdit, setNoteToEdit] = useState<Note | null>(null);
4const [title, setTitle] = useState(""); // "" already says string
5
6// Three immutable updates
7setNotes((prev) => [...prev, item]); // add
8setNotes((prev) => prev.filter((n) => n.id !== id)); // remove
9setNotes((prev) => prev.map((n) => (n.id === next.id ? next : n))); // edit, order kept
10
11// useEffect — three ways to write the dependency array
12useEffect(fn, []); // runs once, after the first render
13useEffect(fn, [noteToEdit]); // first render, then every noteToEdit change
14useEffect(fn); // after every render — almost always a mistake
15
16// Controlled input
17<input value={title} onChange={(e) => setTitle(e.target.value)} />
18
19// Form submit
20const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
21 event.preventDefault(); // without it the page reloads and state resets
22 ...
23};
24
25// Rendering a list
26{notes.map((note) => <NoteItem key={note.id} note={note} />)}
27// ↑ use a stable id, never the index
28
29// To pass an argument to a handler -> wrap it in an arrow function
30<button onClick={() => onDelete(note.id)}>Delete</button>
31<button onClick={onDelete(note.id)}> {/*already ran during render */}
Source: react-notes-app/src/
三条铁律Three hard rules

① 改 state 只能通过 setter,且必须造新对象(push / splice / arr[i]= 都会让界面不更新且不报错)。
② effect 里修改的 state 不能出现在它自己的依赖数组里。
③ 能从现有 state 算出来的值不要做成 state。
① Change state only through its setter, and always build a new object (push / splice / arr[i]= leave the interface stale, and raise no error).
② A state value that an effect writes must not appear in that effect's own dependency array.
③ If a value can be computed from state you already have, do not make it state.

§04

GraphQL SDL

GraphQL SDLSDL 速查SDL reference源项目From source
1# 可空性 —— 决定 resolver 的兜底策略
2field: String # 可空
3field: String! # 不可空
4field: [T] # 列表可空,元素可空
5field: [T!] # 列表可空,元素不可空
6field: [T]! # 列表不可空,元素可空
7field: [T!]! # 双重不可空 -> resolver 必须 ?? [],空列表是合法的
8
9# 内置标量:ID String Int Float Boolean
10# ID 序列化成字符串,别当数字用
11
12enum OrderStatus { PENDING PROCESSING SHIPPED DELIVERED CANCELLED }
13# 返回不在列表里的值会报错,大小写敏感
14
15type Query { # 读入口,多个字段并行执行
16 order(id: ID!): Order
17 orders(userId: ID!): [Order!]!
18}
19
20type Mutation { # 写入口,多个字段串行执行
21 createOrder(userId: ID!, items: [OrderItemInput!]!): Order!
22}
23
24input OrderItemInput { # 只能当参数,不能有 resolver
25 productId: ID!
26 quantity: Int!
27}
1# Nullability — decides what the resolver has to fall back to
2field: String # nullable
3field: String! # non-null
4field: [T] # list nullable, items nullable
5field: [T!] # list nullable, items non-null
6field: [T]! # list non-null, items nullable
7field: [T!]! # both non-null -> resolver needs ?? [], an empty list is valid
8
9# Built-in scalars: ID String Int Float Boolean
10# ID serializes to a string. Do not treat it as a number.
11
12enum OrderStatus { PENDING PROCESSING SHIPPED DELIVERED CANCELLED }
13# Returning a value that is not in the list is an error. Case sensitive.
14
15type Query { # read entry point, fields run in parallel
16 order(id: ID!): Order
17 orders(userId: ID!): [Order!]!
18}
19
20type Mutation { # write entry point, fields run one at a time
21 createOrder(userId: ID!, items: [OrderItemInput!]!): Order!
22}
23
24input OrderItemInput { # arguments only, cannot have resolvers
25 productId: ID!
26 quantity: Int!
27}
Source: graphql-federation-practice/node-subgraph/src/schema.graphql
§05

ResolverResolvers

JavaScriptResolver 速查Resolver reference已跑通Verified
1// 四个参数
2async fieldName(parent, args, context, info) { }
3// ↑上一层 ↑查询 ↑每请求 ↑元信息(本项目没用)
4// 返回值 参数 的袋子
5
6// 顶层 Query/Mutation 的 parent 无意义 -> 写成 _
7async orders(_, { userId }, { dataSources, correlationId }) { }
8
9// 字段 resolver 的 parent 至关重要
10async shippingInfo(parent, _, { loaders }) {
11 return loaders.shippingInfoLoader.load(parent.id);
12}
13
14// 这个项目 context 的确切结构
15{
16 dataSources: { orderDataSource, inventoryDataSource, shippingDataSource },
17 loaders: { shippingInfoLoader, orderLoader },
18 correlationId
19}
20
21// 结构化错误 + 放行已包装的错误(贯穿全项目的模式)
22try {
23 if (!userId) {
24 throw new GraphQLError('userId is required', {
25 extensions: { code: 'INVALID_INPUT', correlationId }
26 });
27 }
28 ...
29} catch (error) {
30 if (error instanceof GraphQLError) throw error; // ← 这一行不能少
31 throw new GraphQLError('Failed to ...', {
32 extensions: { code: 'SERVICE_ERROR', correlationId, originalError: error.message }
33 });
34}
35
36// DataLoader —— 两条硬契约
37new DataLoader(async keys => {
38 const rows = await 批量查询(keys);
39 return keys.map(k => byKey.get(k) ?? null); // 长度 === keys.length,顺序一一对应
40});
41// 永远不要 filter / sort / slice;「没有」用 null 占位
42// 必须每请求新建,否则缓存跨请求泄漏
1// The four arguments
2async fieldName(parent, args, context, info) { }
3// ↑value ↑query ↑per-request ↑metadata (unused here)
4// above args bag
5
6// On top-level Query/Mutation the parent means nothing -> write _
7async orders(_, { userId }, { dataSources, correlationId }) { }
8
9// On a field resolver the parent is what matters
10async shippingInfo(parent, _, { loaders }) {
11 return loaders.shippingInfoLoader.load(parent.id);
12}
13
14// The exact shape of context in this project
15{
16 dataSources: { orderDataSource, inventoryDataSource, shippingDataSource },
17 loaders: { shippingInfoLoader, orderLoader },
18 correlationId
19}
20
21// Structured errors + re-throw the already-wrapped ones (pattern used project-wide)
22try {
23 if (!userId) {
24 throw new GraphQLError('userId is required', {
25 extensions: { code: 'INVALID_INPUT', correlationId }
26 });
27 }
28 ...
29} catch (error) {
30 if (error instanceof GraphQLError) throw error; // ← this line is required
31 throw new GraphQLError('Failed to ...', {
32 extensions: { code: 'SERVICE_ERROR', correlationId, originalError: error.message }
33 });
34}
35
36// DataLoader — two hard contracts
37new DataLoader(async keys => {
38 const rows = await 批量查询(keys); // your own batch query
39 return keys.map(k => byKey.get(k) ?? null); // length === keys.length, same order
40});
41// Never filter / sort / slice. Use null as the placeholder for "not found"
42// Build a new one per request, or the cache leaks across requests
§06

Federation directive 与 entityFederation directives and entities

东西Thing含义Meaning
@key(fields: "id")「别的 subgraph 给出 id 就能定位同一个我」。 可以复合("isbn edition"),可以有多个Given an id from another subgraph, this one can find the same object. A key can be composite ("isbn edition"), and a type can have several
@external这个字段由别的 subgraph 定义,我只借来做身份识别Another subgraph defines this field; this one only borrows it to identify the object
@shareable允许多个 subgraph 定义同一个字段。 本项目 import 了但没用到Lets several subgraphs define the same field. This project imports it but never uses it
__resolveReference把 representation 变成本地对象。它的返回值就是下游字段 resolver 的 parentTurns a representation into a local object. What it returns becomes the parent for the field resolvers under it
_service自动生成的字段,返回 federation SDL。 Router 启动时查它A generated field that returns the federation SDL. The Router queries it at startup
_entities自动生成的字段,Router 运行时靠它做实体解析A generated field. At runtime the Router uses it to resolve entities
buildSubgraphSchema来自 @apollo/subgraph。 认识 federation directive,并自动加上面两个字段From @apollo/subgraph. It understands the federation directives and adds the two fields above
GraphQL SDL本地验证 FederationVerifying federation locally已跑通Verified
1# Router 会向你的 subgraph 发这两种请求 —— 本地验证就用它们
2
3# ① 启动时:拿 SDL
4{ _service { sdl } }
5
6# ② 运行时:解析实体(这是你的 User.orders 真正被调用的路径)
7query($r: [_Any!]!) {
8 _entities(representations: $r) {
9 ... on User { id orders { id status } }
10 }
11}
12# variables: { "r": [{ "__typename": "User", "id": "123" }] }
13
14# 复合 key 的 representation 要带全部 key 字段:
15# { "__typename": "Book", "isbn": "978-1", "edition": 2 }
1# The Router sends your subgraph these two requests — use them to verify locally
2
3# ① At startup: fetch the SDL
4{ _service { sdl } }
5
6# ② At runtime: resolve entities (this is the path your User.orders is really called on)
7query($r: [_Any!]!) {
8 _entities(representations: $r) {
9 ... on User { id orders { id status } }
10 }
11}
12# variables: { "r": [{ "__typename": "User", "id": "123" }] }
13
14# A representation for a composite key must carry every key field:
15# { "__typename": "Book", "isbn": "978-1", "edition": 2 }
§07

HTTP 状态码与 Spring 写法HTTP status codes and how Spring writes them

Code什么时候WhenSpring 写法How Spring writes it
200成功,有内容返回Success, with a bodyResponseEntity.ok(body)
201创建成功(POST)Created (POST)ResponseEntity.status(HttpStatus.CREATED).body(x)
204成功但没有内容(DELETE)Success with no body (DELETE)ResponseEntity.noContent().build()
400请求本身不合法The request itself is not valid@Valid 自动,或 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, msg)@Valid does it, or throw new ResponseStatusException(HttpStatus.BAD_REQUEST, msg)
404目标不存在The target does not exist不写 —— 让 service 的 EntityNotFoundException 冒到 @RestControllerAdviceDo not write it — let the service's EntityNotFoundException travel up to @RestControllerAdvice
JavaSpring 速查Spring reference已跑通Verified
1// 参数注解从哪取值
2@PathVariable Long id // /api/orders/{id}
3@RequestParam(required = false) String userId // ?userId=123
4@RequestBody Map<String, String> body // 请求体 JSON
5@Valid @RequestBody CreateOrderRequest request // 请求体 + Bean Validation
6
7// 字符串转 enum 的安全写法(valueOf 大小写敏感且会抛异常)
8String raw = statusUpdate.get("status");
9if (raw == null || raw.isBlank()) {
10 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "status is required");
11}
12final OrderStatus status;
13try {
14 status = OrderStatus.valueOf(raw.trim().toUpperCase());
15} catch (IllegalArgumentException ex) {
16 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown status: " + raw);
17}
18
19// correlation id(CorrelationIdFilter 放进 MDC,任何地方都能取)
20MDC.get("correlationId")
1// Where each parameter annotation reads from
2@PathVariable Long id // /api/orders/{id}
3@RequestParam(required = false) String userId // ?userId=123
4@RequestBody Map<String, String> body // the JSON request body
5@Valid @RequestBody CreateOrderRequest request // request body + Bean Validation
6
7// Safe way to turn a string into an enum (valueOf is case sensitive and throws)
8String raw = statusUpdate.get("status");
9if (raw == null || raw.isBlank()) {
10 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "status is required");
11}
12final OrderStatus status;
13try {
14 status = OrderStatus.valueOf(raw.trim().toUpperCase());
15} catch (IllegalArgumentException ex) {
16 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown status: " + raw);
17}
18
19// correlation id (CorrelationIdFilter puts it in MDC, so any code can read it)
20MDC.get("correlationId")
§08

报错对照表Error table

都是审计和课程里真实出现过的。先看这张表再改代码。Every one of these really came up during the audit or in a lesson. Read this table before you edit code.

报错 / 症状Error / symptom根因Root cause
npm error Missing script: "test"项目没有 test script → 用 npx vitest runThis project has no test script. Use npx vitest run
TS2582: Cannot find name 'test'tsconfig 没配测试框架全局类型。react-notes-app 原生就有这个问题,不是你的错tsconfig does not declare the test framework globals. react-notes-app ships with this problem; it is not your fault
ERR_MODULE_NOT_FOUND原生 ESM 里相对路径漏了 .js 扩展名A relative import in native ESM is missing the .js extension
Cannot use import statement outside a module"type": "module", 或 jest 缺 --experimental-vm-modules"type": "module" is missing, or jest is missing --experimental-vm-modules
Maximum update depth exceededuseEffect 依赖里含自己修改的 state; 或 onClick={fn()} 在渲染时就执行了A useEffect dependency holds state that the same effect writes; or onClick={fn()} already ran during render
没报错,数据对但界面不动No error, the data is right but the interface does not change改了原对象(push / splice / arr[i]= / obj.x=You changed the original object (push / splice / arr[i]= / obj.x=)
没报错,组件完全不显示No error, the component does not show at all组件名小写开头,被当成 HTML 标签The component name starts with a lower-case letter, so it is read as an HTML tag
没报错,列表空白但数据有值No error, the list is blank but the data is theremap 回调用了花括号却忘了 returnThe map callback uses braces but never returns
Unable to find an element by: [data-testid=...]testid 被改了,或那个元素被条件性移除了。报错里会打印整个 DOM,在里面搜相似 testidThe testid changed, or a condition removed that element. The error prints the whole DOM — search it for a similar testid
测试说找不到文字,但代码看着没错The test cannot find the text, but the code looks rightuserEvent 前面漏了 awaitA missing await before userEvent
Cannot return null for non-nullable fieldresolver 忘了 ?? []。会向上冒泡, 可能让整个 data 变 nullThe resolver forgot ?? []. It travels upward and can turn the whole data into null
xxx is not a function调了对象上不存在的方法。去定义处核对方法名You called a method the object does not have. Check the name where the object is defined
Cannot read properties of undefined (reading 'y')上一级路径写错了(如 dataSources.orderAPI 不存在)The path one level up is wrong (for example dataSources.orderAPI does not exist)
没报错,某个 GraphQL 字段一直是 nullNo error, one GraphQL field is always nullresolver 键名和 schema 字段名不一致,或挂在了错误的类型下。在 resolver 第一行 log 确认它有没有被调用The resolver key does not match the schema field name, or it sits under the wrong type. Log on the resolver's first line to see whether it runs at all
没报错,DataLoader 的数据串了No error, DataLoader hands back the wrong row for a keybatch 函数里用了 filter,破坏了长度/顺序契约The batch function used filter, which breaks the length and order contract
错误码不对(收到 SERVICE_ERROR 而不是 INVALID_INPUT)Wrong error code (SERVICE_ERROR arrives instead of INVALID_INPUT)catch 把自己抛的结构化错误重新包装了。 补 if (error instanceof GraphQLError) throw errorThe catch re-wrapped a structured error you threw yourself. Add if (error instanceof GraphQLError) throw error
Spring:Status expected:<201> but was:<200>Spring: Status expected:<201> but was:<200>POST 用了 ok();或者端点还是 return nullThe POST used ok(); or the endpoint still says return null
Spring:客户端输入错误返回了 500Spring: a bad client input comes back as 500Enum.valueOf 抛的 IllegalArgumentException 没被转成 400The IllegalArgumentException thrown by Enum.valueOf was never turned into a 400
Spring:查不存在的 id 返回 200 空 bodySpring: asking for an id that does not exist returns 200 with an empty body自己 catch 了 EntityNotFoundException, 全局处理器收不到The code caught EntityNotFoundException itself, so the global handler never sees it
Unknown directive "@xxx"@link 的 import 列表里漏了它, 或没用 buildSubgraphSchemaIt is missing from the @link import list, or you did not use buildSubgraphSchema
§09

Debug 清单Debug checklist

卡住的时候按顺序走一遍。大部分问题在第 3 步之前就解决了。When you are stuck, walk through this in order. Most problems are solved before step 3.

  1. 分层。这个报错来自 npm(命令/目录不对)、 工具(依赖没装)、还是我的代码?别一看红字就改业务代码。Find the layer. Did this error come from npm (wrong command or wrong directory), from a tool (dependencies not installed), or from my own code? Red text is not a reason to start editing business code.
  2. 只看第一条报错。类型错误和 GraphQL 错误都会连锁,修掉第一条后面可能自己消失。Read only the first error. Type errors and GraphQL errors come in chains. Fix the first one and the rest may go away by themselves.
  3. 如果没有报错,按症状查表(见上一节): 数据对但界面不动 / 组件不显示 / 列表空白 / 字段一直 null / 数据串了 —— 这五种各有固定病因。If there is no error, look the symptom up in the table (previous section): data right but interface still / component not showing / blank list / field always null / rows mixed up. Each of these five has one fixed cause.
  4. 确认代码有没有被执行。在最可疑的函数第一行放一个 log。「日志没打印」和「日志打印了但结果不对」 指向完全不同的方向。Check whether the code runs at all. Put a log on the first line of the most suspicious function. "No log printed" and "log printed but the result is wrong" point in completely different directions.
  5. 核对跨模块的名字和签名。方法名、context 键名、props 名、参数个数、位置参数 vs 对象参数。 这一步能抓住绝大多数「集成问题」。Check names and signatures across modules. Method names, context keys, prop names, how many arguments, positional arguments versus one object argument. This step catches most integration problems.
  6. 回去读契约。schema 的可空性、类型定义、README 里的约束、 测试断言的确切字符串。Go back and read the contract. Schema nullability, type definitions, the constraints in the README, the exact strings the test assertions use.
  7. 改完必须验证,而且要验到题面那一层。测试过 ≠ 做对了 —— 手动造一个测试覆盖不到的场景 (同名数据、多条数据、非法输入、不存在的 id)。After a fix, verify — and verify at the level the task asks about. Passing tests is not the same as being correct. Build a case by hand that the tests do not cover: records with the same name, several records, invalid input, an id that does not exist.
这个项目里三处「测试骗人」的地方Three places where the tests lie in this project

① Java 六个端点全 return null,5 个测试过 3 个。
② subgraph 四个 TODO 全空,10 个测试过 4 个 (其中 3 个是「返回空」的假通过)。
③ React 的删除/编辑测试只有一条数据, 按 title 删、先删再加都能过。
① All six Java endpoints return null, and 3 of 5 tests still pass.
② All four subgraph TODOs are empty, and 4 of 10 tests pass (3 of those pass only because returning nothing counts as a pass).
③ The React delete and edit tests use a single record, so deleting by title, or deleting then adding, both pass.

所以:绿色是及格线,不是正确性证明。So: green is the pass mark, not proof of correctness.