练习Practice
动手做Get your hands on it
练习跟着课文走 —— 每节课尾都有本课的练习。这一页是全部练习的总库,想集中刷题的时候来。每个练习都写清了它来自哪一节,卡住了就回去看那一节。Practice follows the lessons — every lesson ends with the exercises for that lesson. This page is the whole library, for when you want to drill in one sitting. Each exercise names the lesson it came from, so you can go back when you stall.
0 / 148个做对过you got right
练习Exercises
筛出 16 个练习(共 148 个) · 第 2 / 2 页。Showing 16 of 148 · page 2 / 2.来自From Debug Lab · Federation 十种典型故障Debug Lab · ten common Federation failures · Federation 考试Federation exam
L2Debug LabDebug Lab故障 2 · Cannot return null for non-nullable fieldFault 2 · Cannot return null for non-nullable field
查一个没有订单的用户,整个 data 变成了null,而且 errors 里有一条很长的消息。
You query a user who has no orders, the whole data turns into null, and errors carries one very long message.
$ node verify-schema.mjs
Query.orders: null
errors: [{
"message": "Cannot return null for non-nullable field Query.orders.",
"locations": [{ "line": 1, "column": 3 }],
"path": ["orders"]
}]
# orders 是 Query 上的非空字段:null 一路冒泡到根,整个 data 都没了
# (经 Apollo Server 请求时,这条错误还会带上 extensions.code: INTERNAL_SERVER_ERROR)$ node verify-schema.mjs
Query.orders: null
errors: [{
"message": "Cannot return null for non-nullable field Query.orders.",
"locations": [{ "line": 1, "column": 3 }],
"path": ["orders"]
}]
# orders is a non-null field on Query, so the null bubbles all the way to the root and the whole data is gone
# (through Apollo Server the error also carries extensions.code: INTERNAL_SERVER_ERROR)
1async orders(_, { userId }, { dataSources, correlationId }) {
2 const orders = await dataSources.orderDataSource.getOrdersByUserId(userId);
3 return orders; // 数据源可能返回 undefined
4}
5
6// 参考 schema.graphql:
7// type Query {
8// orders(userId: ID!): [Order!]! ← 双重非空
9// }
1async orders(_, { userId }, { dataSources, correlationId }) {
2 const orders = await dataSources.orderDataSource.getOrdersByUserId(userId);
3 return orders; // the data source may return undefined
4}
5
6// For reference, schema.graphql says:
7// type Query {
8// orders(userId: ID!): [Order!]! ← non-null twice
9// }
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · Federation 十种典型故障Debug Lab · ten common Federation failures · Federation 考试Federation exam
L2Debug LabDebug Lab故障 4 · PATCH 传了小写状态,返回 500Fault 4 · PATCH sends a lowercase status and gets a 500
Java 那边。mvn test 全过,但客户端传小写的 shipped 时服务返回 500。
This one is on the Java side. mvn test passes everything, but the service returns 500 when the client sends the lowercase shipped.
$ curl -i -X PATCH localhost:8080/api/orders/1/status \
-H 'Content-Type: application/json' -d '{"status":"shipped"}'
HTTP/1.1 500
{"timestamp":"...","status":500,"error":"Internal Server Error"}
# 服务端日志:
java.lang.IllegalArgumentException: No enum constant
com.techflow.orders.model.OrderStatus.shipped
at java.base/java.lang.Enum.valueOf(Enum.java:293)
at com.techflow.orders.model.OrderStatus.valueOf(OrderStatus.java:3)
at c.t.orders.controller.OrderController.updateOrderStatus(OrderController.java:71)
# mvn test:Tests run: 5, Failures: 0 ← 测试全过$ curl -i -X PATCH localhost:8080/api/orders/1/status \
-H 'Content-Type: application/json' -d '{"status":"shipped"}'
HTTP/1.1 500
{"timestamp":"...","status":500,"error":"Internal Server Error"}
# Server log:
java.lang.IllegalArgumentException: No enum constant
com.techflow.orders.model.OrderStatus.shipped
at java.base/java.lang.Enum.valueOf(Enum.java:293)
at com.techflow.orders.model.OrderStatus.valueOf(OrderStatus.java:3)
at c.t.orders.controller.OrderController.updateOrderStatus(OrderController.java:71)
# mvn test: Tests run: 5, Failures: 0 ← every test passes
1@PatchMapping("/api/orders/{id}/status")
2public ResponseEntity<Order> updateOrderStatus(
3 @PathVariable Long id,
4 @RequestBody Map<String, String> statusUpdate) {
5 OrderStatus status = OrderStatus.valueOf(statusUpdate.get("status"));
6 return ResponseEntity.ok(orderService.updateOrderStatus(id, status));
7}
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Loading:一秒之后自己跳走Loading: it moves to the next page by itself after one second · Cab BookingCab Booking
L2Debug LabDebug LabDebug Lab:测试全绿,浏览器里却卡在 LoadingDebug Lab: the tests are green, but the browser sits on Loading
四个测试全过。可是在浏览器里点了 Book a Cab、选了一辆车之后,页面一直停在 Loading,标题后面的点还在一个个地跳。下面这个
Loading 比源项目多了一个加载动画 ——还少了一个东西。All four tests pass. But in the browser, after Book a Cab and picking a cab, the page stays on Loading while the dots after the heading keep cycling. This Loading has one thing more than the source project, a loading animation — and one thing less.$ npx vitest run
✓ src/test/App.test.jsx (4 tests)
Test Files 1 passed (1)
Tests 4 passed (4)
# 浏览器里(npm run dev):点 Book a Cab,选一辆车
# 1 秒、2 秒、10 秒过去了 —— 标题一直在 Loading / Loading. / Loading.. / Loading... 之间跳
# 页面始终停在 data-testid="loading",不跳到确认页;控制台没有任何报错$ npx vitest run
✓ src/test/App.test.jsx (4 tests)
Test Files 1 passed (1)
Tests 4 passed (4)
# In the browser (npm run dev): press Book a Cab, then pick a cab
# 1 second, 2 seconds, 10 seconds go by — the heading keeps cycling Loading / Loading. / Loading.. / Loading...
# The page never leaves data-testid="loading" for the confirmation page, and the console shows no error
1const Loading = ({ onComplete }) => {
2 const [dots, setDots] = useState("");
3
4 // 加载动画:每 300ms 多一个点
5 useEffect(() => {
6 const id = setInterval(() => setDots((d) => (d.length >= 3 ? "" : d + ".")), 300);
7 return () => clearInterval(id);
8 }, []);
9
10 useEffect(() => {
11 const timer = setTimeout(() => {
12 if (onComplete) onComplete();
13 }, 1000);
14
15 return () => clearTimeout(timer);
16 }); // ← 依赖数组呢?
17
18 return (
19 <main data-testid="loading" className="loading-container">
20 <div className="spinner" aria-hidden="true" />
21 <h1>Loading{dots}</h1>
22 </main>
23 );
24};
1const Loading = ({ onComplete }) => {
2 const [dots, setDots] = useState("");
3
4 // loading animation: one more dot every 300ms
5 useEffect(() => {
6 const id = setInterval(() => setDots((d) => (d.length >= 3 ? "" : d + ".")), 300);
7 return () => clearInterval(id);
8 }, []);
9
10 useEffect(() => {
11 const timer = setTimeout(() => {
12 if (onComplete) onComplete();
13 }, 1000);
14
15 return () => clearTimeout(timer);
16 }); // ← where is the dependency array?
17
18 return (
19 <main data-testid="loading" className="loading-container">
20 <div className="spinner" aria-hidden="true" />
21 <h1>Loading{dots}</h1>
22 </main>
23 );
24};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 完整答案跑不起来 —— 一个扩展名的事The complete answer does not run — the cause is one file extension · Cab BookingCab Booking
L2Debug LabDebug LabDebug Lab:0 个测试跑起来Debug Lab: zero tests run
README 说「先运行完整答案熟悉流程」。
npm install 成功,npx vitest run 却是下面这个输出。注意最后一行的「no tests」。The README says to run the finished answer first to get used to the flow. npm install succeeds, and npx vitest run gives the output below. Look at the “no tests” on the last line. RUN v2.1.8 /Users/you/cab-booking-context
❯ src/test/App.test.jsx (0 test)
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL src/test/App.test.jsx [ src/test/App.test.jsx ]
Error: Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
Plugin: vite:import-analysis
File: /Users/you/cab-booking-context/src/context/CabContext.js:19:27
17 | >
18 | {children}
19 | </CabContext.Provider>
| ^
20 | );
21 | };
❯ TransformPluginContext._formatError node_modules/vite/dist/node/chunks/dep-CB_7IfJ-.js:49255:41
❯ TransformPluginContext.error node_modules/vite/dist/node/chunks/dep-CB_7IfJ-.js:49250:16
Test Files 1 failed (1)
Tests no tests
1import { createContext, useContext, useState } from "react";
2
3const CabContext = createContext();
4
5const CabProvider = ({ children }) => {
6 const [bookedCabDetails, setBookedCabDetails] = useState(null);
7 const [rideHistory, setRideHistory] = useState([]);
8
9 const updateBookedCabDetails = (details) => {
10 setBookedCabDetails(details);
11 setRideHistory([...rideHistory, details]);
12 };
13
14 return (
15 <CabContext.Provider
16 value={{ bookedCabDetails, updateBookedCabDetails, rideHistory }}
17 >
18 {children}
19 </CabContext.Provider>
20 );
21};
22
23const useCabContext = () => {
24 const context = useContext(CabContext);
25
26 if (!context) {
27 throw new Error("useCabContext must be used within a CabProvider");
28 }
29
30 return context;
31};
32
33export { CabProvider, useCabContext };
Source:
cab-booking-context/src/context/CabContext.js第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this