Skip to content

test(rest): getDiscovery 测试替身发 routes,不再发 #4828 已退役的 endpoints (#5674) - #5788

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-5674-rest-discovery-mock-routes
Aug 6, 2026
Merged

test(rest): getDiscovery 测试替身发 routes,不再发 #4828 已退役的 endpoints (#5674)#5788
baozhoutao merged 4 commits into
mainfrom
claude/issue-5674-rest-discovery-mock-routes

Conversation

@baozhoutao

@baozhoutao baozhoutao commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5674

前提复核(基线 origin/main fc5f536)

issue 写 14 个文件,派发单实测 15 个 —— 复核结论:15 个文件 / 16 处endpoints: { data: … },与派发单一致,issue 的 14 偏低。真实 producer(packages/metadata-protocol/src/protocol.ts:2767 起)发的是 routes(ApiRoutesSchema,在 DiscoverySchema:353必填键),endpoints 从来没被任何 producer 发过,#4828 已删除并由 discovery-schema-conformance.test.ts:197-201 钉死。前提成立。

openapi-endpoints.* / served-endpoints.test.ts:48 / request-schema-gate.conformance.test.ts:173endpoints 是「声明式端点清单」语义,不是 discovery 键 —— 甄别后不动,已复核。

逐文件复核:issue 担心的行为变更,实测中不存在

issue 说改成 routes 会让 if (discovery.routes) 从假翻真、handler 开始跑路由增补块(含 probeMcpServeable),所以要一次一个文件复核断言。逐个文件查完,没有一个文件会执行那个块 —— 原因不是块被跳过了,而是这 25 个替身没有一个被喂给 discovery handler:每个文件驱动的都是 /api/v1/data/:object/api/v1/meta/:type/:name/batch 等具体路由,没有一处取 GET /api/v1/discovery 的 handler 来调用。

这一点有个很强的旁证:全仓唯一真正调用 discovery handler 的两个 describe 就在 rest.test.ts(discovery — routes.mcpdiscovery — capabilities.transactionalBatch),而它们各自覆盖掉了 createMockProtocol() 的 getDiscovery,换成 { routes: { data: '', metadata: '' } }(rest.test.ts:2601)—— 那次覆盖本身就是在说「共享替身的键是错的,这里用不了」。

所以:没有任何文件的断言需要靠「块不执行」才成立,派发单里预留的那种例外(显式去掉 routes 键 + 注释说明)一处都没有用到。

改动(4 个 commit,26 个文件)

  1. 1d2c4f5 — 15 个文件 / 16 处 endpoints: { data: … }routes: { data: … }纯键名翻正,值保持作者原样:handler 每次请求都用服务端配置覆写 data/metadata/ui,这些槽位按构造就是占位符,键才是缺陷。另在最容易被抄的那个共享替身(rest.test.tscreateMockProtocol())上加了 4 行说明。
  2. 7d5ec37 — 另外 10 个文件写 endpoints: {},同一个退役键的退化写法。issue 与派发单的清单来自 endpoints: { data: 这个 grep,看不见它们。只修 15 个会让 issue 自己写下的第一条理由(「唯一还在拼写已退役键的地方」)原样留存,所以一并翻正为 routes: { data: '', metadata: '' } —— ApiRoutesSchemadata/metadata 必填,routes: {} 不是合法生产者形状;这个字面量取自仓库里早已存在的正确先例 rest.test.ts:2601,而不是另发明一种。这 10 个文件同样一处也不碰 discovery 路由。
  3. 8b92c51 — 新增 pin packages/rest/src/discovery-double-retired-key.test.ts。没有它,退役键可以随下一次复制粘贴无声回来:生产面的 conformance 测试只看真实 producer 发出的 body,fixture 层它看不见。
  4. d51adee — 修 pin 自身的一个缺陷(见下)。

pin 的边界写在文件头,不含糊:只扫 mockResolvedValue({ … }) 这一种形态(排除自身后实测 27 处 / 26 个文件,即本 PR 翻正的 26 处 + rest-route-ledger.conformance.test.tsmockResolvedValue({}) —— 后者 resolve 成裸 {},从来没拼过退役键);只断言否定面(不得有 endpoints),不要求必须有 routes —— handler 是 if (discovery.routes),将来有测试要驱动这条假分支是正当的,rest-route-ledger 那处就是现成的例子;并带一条 anti-vacuity 下限(#4642 的教训:扫不到东西的扫描器会以「全部干净」的样子通过)。

commit 4:pin 自身的缺陷,自查发现

扫描目录下所有 *.test.ts 包括 pin 自己,而它的 docblock 为了说明问题原样引用了带 endpoints: 的坏替身。它当时没红,只是因为 docblock 行把键前缀成了 * ,而 [{,]\s* 这个 lead-in 不接受 * —— 也就是说这条钉子的绿由注释排版决定,重排一次 docblock 它就会在自己的散文上失败。改为显式排除自身文件名并把理由写在常量旁,不继承这份侥幸。

反向验证(方向为事前预测的 RED)

预测:把任一文件改回 endpoints,新 pin 应当变红并点名该文件(它是新增的钉子,不是既有断言,所以是标准 Red 方向,不是 #5046 那种「诊断变多」或 #5018 那种反转)。两种拼法各验一次:

# ① rest-batch-size-cap.test.ts 改回 `endpoints: { data: … }`
AssertionError: … expected [ 'rest-batch-size-cap.test.ts' ] to deeply equal []
Test Files  1 failed (1) | Tests  1 failed | 1 passed (2)

# ② rest-dropped-fields.test.ts 改回 `endpoints: {}`(commit 4 之后复验)
AssertionError: … expected [ 'rest-dropped-fields.test.ts' ] to deeply equal []
Test Files  1 failed (1) | Tests  1 failed | 1 passed (2)

两次改回后均转绿。方向与预测一致。

测试

pnpm --workspace-concurrency=2 --filter @objectstack/rest test
 Test Files  56 passed (56)
      Tests  784 passed (784)

(改前基线 55 files / 782 tests 全绿,改后 56/784 —— 增量正是新 pin 的 2 条。)

其余本地闸门:pnpm lint(仓库级 ESLint,CI 同一条)干净;pnpm check:type-check-coverage OK(63/78 包 type-checked);node scripts/check-nul-bytes.mjs OK,并按控制字符自查规则对改动文件跑了 grep -naP 扫描,无命中。

@objectstack/rest 不声明 typecheck 脚本(它在 DEBT + TEST_DEBT 台账里),所以本包的 pnpm typecheck 无法运行 —— ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT。这是既有台账状态、不是本 PR 造成的;不伪造一条绿的 typecheck 证据。

界外发现

Changeset

tests-only,不发布任何东西,故无 changeset,已带 skip-changeset 标签(读回确认:tests, skip-changeset)。首个 opened 事件那次 Check Changeset 早于打标一步开跑、判红,属已知竞态;打标后由 labeled 事件触发的两次 Check Changeset 均为 skipped(豁免生效)。

claude added 3 commits August 6, 2026 05:19
`packages/rest/src` 下 15 个测试文件的 `getDiscovery` 替身返回
`endpoints: { data, metadata, ui, auth }`,但真实生产者
(`packages/metadata-protocol/src/protocol.ts` 的 `getDiscovery()`)发的是
`routes`(`ApiRoutesSchema`,`DiscoverySchema` 里是必填键),从来没有发过
`endpoints`。`endpoints` 只在 dispatcher 那条路径上作为 `routes` 的逐字副本
存在过,#4828 已按 ADR-0049 删除,并在
`discovery-schema-conformance.test.ts` 钉死「no `features`, no `endpoints`」。

按键名(`data`/`metadata`/`ui`/`auth`)可知这些替身本意就是 `routes`,只是
拼错了对象;纯键名翻正,值保持作者原样(handler 每次请求都会用服务端配置
覆写 `data`/`metadata`/`ui`,这些槽位按构造就是占位符)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
同一个已退役键的退化写法:另有 10 个文件写 `endpoints: {}`。issue 与派发单
的清单来自 `endpoints: { data:` 这个 grep,看不见它们,但它们拼的是同一个
#4828 已删除的 discovery 键,拷贝危害完全相同 —— 只修 15 个会让 issue 自己
写下的第一条理由(「唯一还在拼写已退役键的地方」)原样留存。

翻正为 `routes: { data: '', metadata: '' }`:`ApiRoutesSchema` 里
`data`/`metadata` 必填,所以 `routes: {}` 不是合法生产者形状;这个字面量与
`rest.test.ts:2601` 早已存在的正确替身(唯一真正调用 /discovery handler 的
那处)完全一致,取仓库内已有先例而非另发明一种。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
前两个 commit 把 25 个替身翻正了,但没有任何东西阻止它回来:生产面的
`discovery-schema-conformance.test.ts` 只看真实 producer 发出的 body,fixture
层它看不见 —— 而 issue 记这一笔的第一条理由就是「下一个写 rest 测试的人照抄
这个替身,退役键就在 fixture 层复活」。

新增 pin 扫描 `packages/rest/src/*.test.ts` 里 `mockResolvedValue({ … })` 形态的
getDiscovery 替身(当前 28 处 / 27 个文件,含两处本来就没拼错的),断言没有一处
带 `endpoints` 键,并带一条 anti-vacuity 下限(#4642 的教训:扫不到东西的扫描器
会以「全部干净」的样子通过)。

只断言否定面,不要求必须有 `routes`:`rest-server` 的 handler 是
`if (discovery.routes)`,将来有测试要驱动这条假分支是正当的。

反向验证(方向为事前预测的 RED):把 rest-batch-size-cap.test.ts 改回
`endpoints` → pin 立即失败并点名该文件
(expected [ 'rest-batch-size-cap.test.ts' ] to deeply equal []),改回后转绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 5:35am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@baozhoutao baozhoutao added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 6, 2026 — with Claude
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

@github-actions github-actions Bot added the tests label Aug 6, 2026
自查发现的自身缺陷:pin 扫描目录下所有 `*.test.ts`,**包括它自己** —— 而它的
docblock 为了说明问题,原样引用了带 `endpoints:` 的坏替身。它之所以没红,只是
因为 docblock 行把键前缀成了 `* `,而 `[{,]\s*` 这个 lead-in 不接受 `*`。也就是说
这条钉子的绿是由注释排版决定的:重排一次 docblock,它就会在自己的散文上失败。

改为显式排除自身文件名,并把理由写在常量旁边。同时把 anti-vacuity 注释里的
计数改成排除自身后的实测值:27 处 / 26 个文件(#5674 翻正的 26 处,加上
`rest-route-ledger.conformance.test.ts` —— 它 resolve 成裸 `{}`,从来没拼过退役键,
正是「只断言否定面」让它不受影响)。

反向验证(RED,事前预测)在两种拼法上都复验过:
- `rest-batch-size-cap.test.ts` 改回 `endpoints: { data: … }` → 点名该文件;
- `rest-dropped-fields.test.ts` 改回 `endpoints: {}` → 点名该文件。
两次改回后均转绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
@github-actions github-actions Bot added the size/m label Aug 6, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 05:50
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 3e18574 Aug 6, 2026
25 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5674-rest-discovery-mock-routes branch August 6, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

packages/rest 的 14 个 getDiscovery 测试替身返回 endpoints,一个真实生产者从未发过、且已在 #4828 退役的键

2 participants