From fc5afa9600abcca585d736a42d967b716efbfc99 Mon Sep 17 00:00:00 2001 From: xcs Date: Tue, 25 Aug 2026 18:17:03 +0800 Subject: [PATCH] =?UTF-8?q?route-coverage=20=E8=A1=A5=E4=B8=8B=E7=95=8C?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E9=98=B2=E3=80=8C=E6=95=B0=E4=B8=8D=E5=88=B0?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E4=B9=9F=E7=BB=BF=E3=80=8D;=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E5=91=8A=E8=AD=A6=E7=82=B9=E5=90=8D=E5=93=AA=E9=81=93?= =?UTF-8?q?=E4=BF=9D=E9=99=A9=E6=B2=A1=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 评审 Important:missing 为空有两种可能——真的不漏,或者根本没数到路由 (app.routes 形状变了、createServer 早退)。空集是任何集合的子集,不设 下界这条 fail-closed 测试会从「会红」退化成「永远绿」。补 registered.length > 50 的下界,反向验证过(临时调到 1000 确认真的报红)。 无库跳过时的 stderr 告警原来只说连不上库,没说清「这道保险这次没跑、 漏的是哪一类」——按评审裁决不改 skipIf 行为(仓库级惯例),只改措辞 点名到位。 --- .../app/__tests__/route-coverage.it.test.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/api/src/app/__tests__/route-coverage.it.test.ts b/apps/api/src/app/__tests__/route-coverage.it.test.ts index 9c81f0b..4117363 100644 --- a/apps/api/src/app/__tests__/route-coverage.it.test.ts +++ b/apps/api/src/app/__tests__/route-coverage.it.test.ts @@ -35,7 +35,9 @@ async function provisionDatabase(): Promise<{ url: string; stop(): Promise return { url: started.getConnectionUri(), stop: () => started.stop().then(() => undefined) }; } catch (error) { process.stderr.write( - `⚠️ 路由覆盖 it 跳过:连不上开发实例也起不了 Testcontainers(${error instanceof Error ? error.message : String(error)})\n`, + `⚠️ 路由覆盖 it 跳过 —— **这道 fail-closed 的安全保险这一次没有跑**:` + + `「写了路由处理器却没有契约声明 access」这一类漏网,本次构建没有检查。\n` + + ` 连不上开发实例也起不了 Testcontainers(${error instanceof Error ? error.message : String(error)})\n`, ); return undefined; } @@ -78,10 +80,17 @@ describe.skipIf(database === undefined)('路由覆盖', () => { it('Hono 里每条注册的路由都在归属注册表里', () => { const app = createServer(container!); const registry = appRouteRegistry(); - const missing = app.routes - .filter((r) => r.method !== 'ALL') - .filter((r) => !registry.has(r.method, r.path)) - .map((r) => `${r.method} ${r.path}`); + const registered = app.routes.filter((r) => r.method !== 'ALL'); + + /* + * 先钉下界:`missing` 为空有两种可能 —— 真的一条不漏,或者**根本没数到路由** + * (createServer 改用子应用组合、Hono 换了 routes 的形状、建 server 中途早退)。 + * 空集是任何集合的子集,不设下界的话后一种情况会伪装成前一种, + * 这道保险就从「会红」变成「永远绿」。 + */ + expect(registered.length, '一条路由都没数到 —— 先查 createServer 与 app.routes 的形状').toBeGreaterThan(50); + + const missing = registered.filter((r) => !registry.has(r.method, r.path)).map((r) => `${r.method} ${r.path}`); expect(missing, '这些路由没有声明归哪个端(契约加 access,或 EXTRA_ROUTES 补入)').toEqual([]); }); });