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([]); }); });