From 6f718121c7d59798bfde5cc8b0a16543e7e26e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 10 Sep 2026 05:07:15 +0200 Subject: [PATCH] test(api): use the real caps in the limiter mocks, and drop a stale pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three consent-surface tests mocked the shared AI-consent bucket at 60 where it is 20, and the analytics-read and MCP mocks named 60 against real caps of 120. Nothing asserts the number today, so nothing was wrong on the wire — but the mocked verdict now carries `limit` all the way to `X-RateLimit-Limit`, and the first test that reads that header off a mock would pin the fiction. The response-shape file's header pointed at a document that is not in the repo; it says what it means directly instead. It also now records the one thing the gate cannot see: `meta` is a loose object on purpose, so undeclared keys inside it survive the parse and the strip comparison does not reach them. --- src/app/api/consent/ai/__tests__/route.test.ts | 2 +- .../api/consent/ai/latest/__tests__/route.test.ts | 4 ++-- src/app/api/consent/ai/web/__tests__/route.test.ts | 2 +- .../insights/comprehensive/__tests__/route.test.ts | 2 +- .../insights/correlations/__tests__/route.test.ts | 4 ++-- src/app/mcp/__tests__/route.test.ts | 4 ++-- tests/integration/openapi-response-shape.test.ts | 13 ++++++++++--- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/app/api/consent/ai/__tests__/route.test.ts b/src/app/api/consent/ai/__tests__/route.test.ts index 8f4a55859..4a9cb42f3 100644 --- a/src/app/api/consent/ai/__tests__/route.test.ts +++ b/src/app/api/consent/ai/__tests__/route.test.ts @@ -114,7 +114,7 @@ describe("POST /api/consent/ai", () => { vi.mocked(getSession).mockResolvedValue(SESSION_OK as never); vi.mocked(checkConsentRateLimit).mockResolvedValue({ allowed: false, - limit: 60, + limit: 20, remaining: 0, resetAt: Date.now() + 60_000, }); diff --git a/src/app/api/consent/ai/latest/__tests__/route.test.ts b/src/app/api/consent/ai/latest/__tests__/route.test.ts index db3ed6ddb..b486eb6b7 100644 --- a/src/app/api/consent/ai/latest/__tests__/route.test.ts +++ b/src/app/api/consent/ai/latest/__tests__/route.test.ts @@ -104,7 +104,7 @@ describe("GET /api/consent/ai/latest", () => { vi.mocked(getSession).mockResolvedValue(SESSION_OK as never); vi.mocked(checkConsentRateLimit).mockResolvedValue({ allowed: false, - limit: 60, + limit: 20, remaining: 0, resetAt: Date.now() + 60_000, }); @@ -203,7 +203,7 @@ describe("DELETE /api/consent/ai/latest", () => { vi.mocked(getSession).mockResolvedValue(SESSION_OK as never); vi.mocked(checkConsentRateLimit).mockResolvedValue({ allowed: false, - limit: 60, + limit: 20, remaining: 0, resetAt: Date.now() + 60_000, }); diff --git a/src/app/api/consent/ai/web/__tests__/route.test.ts b/src/app/api/consent/ai/web/__tests__/route.test.ts index 6d5651727..baecb7a59 100644 --- a/src/app/api/consent/ai/web/__tests__/route.test.ts +++ b/src/app/api/consent/ai/web/__tests__/route.test.ts @@ -84,7 +84,7 @@ describe("POST /api/consent/ai/web", () => { vi.mocked(getSession).mockResolvedValue(SESSION_OK as never); vi.mocked(checkConsentRateLimit).mockResolvedValue({ allowed: false, - limit: 60, + limit: 20, remaining: 0, resetAt: Date.now() + 60_000, }); diff --git a/src/app/api/insights/comprehensive/__tests__/route.test.ts b/src/app/api/insights/comprehensive/__tests__/route.test.ts index 6f776d239..e6dde951a 100644 --- a/src/app/api/insights/comprehensive/__tests__/route.test.ts +++ b/src/app/api/insights/comprehensive/__tests__/route.test.ts @@ -117,7 +117,7 @@ beforeEach(() => { // v1.15.20 — default to an allowing analytics-read budget. vi.mocked(checkAnalyticsReadRateLimit).mockResolvedValue({ allowed: true, - limit: 60, + limit: 120, remaining: 119, resetAt: Date.now() + 60_000, }); diff --git a/src/app/api/insights/correlations/__tests__/route.test.ts b/src/app/api/insights/correlations/__tests__/route.test.ts index bc481a4f4..aa9ddb6d4 100644 --- a/src/app/api/insights/correlations/__tests__/route.test.ts +++ b/src/app/api/insights/correlations/__tests__/route.test.ts @@ -103,7 +103,7 @@ beforeEach(() => { // v1.15.20 — default to an allowing analytics-read budget. vi.mocked(checkAnalyticsReadRateLimit).mockResolvedValue({ allowed: true, - limit: 60, + limit: 120, remaining: 119, resetAt: Date.now() + 60_000, }); @@ -230,7 +230,7 @@ describe("GET /api/insights/correlations", () => { vi.mocked(getSession).mockResolvedValue(SESSION_OK as never); vi.mocked(checkAnalyticsReadRateLimit).mockResolvedValue({ allowed: false, - limit: 60, + limit: 120, remaining: 0, resetAt: Date.now() + 60_000, }); diff --git a/src/app/mcp/__tests__/route.test.ts b/src/app/mcp/__tests__/route.test.ts index 3780faff5..d132bd115 100644 --- a/src/app/mcp/__tests__/route.test.ts +++ b/src/app/mcp/__tests__/route.test.ts @@ -156,7 +156,7 @@ beforeEach(() => { vi.mocked(isApiGloballyEnabled).mockResolvedValue(true); vi.mocked(checkMcpRateLimit).mockResolvedValue({ allowed: true, - limit: 60, + limit: 120, remaining: 119, resetAt: Date.now() + 60_000, }); @@ -353,7 +353,7 @@ describe("/mcp — rate limit", () => { validToken(); vi.mocked(checkMcpRateLimit).mockResolvedValue({ allowed: false, - limit: 60, + limit: 120, remaining: 0, resetAt: Date.now() + 30_000, }); diff --git a/tests/integration/openapi-response-shape.test.ts b/tests/integration/openapi-response-shape.test.ts index 89ee4f550..59c640c99 100644 --- a/tests/integration/openapi-response-shape.test.ts +++ b/tests/integration/openapi-response-shape.test.ts @@ -8,7 +8,7 @@ * five domains against a three-domain contract while the body was invalid * against its own published schema. * - * Two responses are covered — the ones the audit found broken: + * Two responses are covered — the two that were broken: * * - `GET /api/sync/changes`, the multi-domain delta the native client mirrors * its whole history from. @@ -17,9 +17,16 @@ * * The check is stricter than `safeParse` alone. Zod strips undeclared keys * rather than refusing them, so a pass would say nothing about a field the - * server sends and the contract omits — exactly the direction both High - * findings drifted in. Comparing the parsed value back to the raw body catches + * server sends and the contract omits — exactly the direction both of those + * drifted in. Comparing the parsed value back to the raw body catches * the strip: if anything was dropped, the schema is short of the wire. + * + * One limit worth knowing before leaning on that half: `meta` is declared as a + * loose object, deliberately, because real refusals put `removedIn`, + * `replacedBy` and per-integration context there. Loose keys survive the parse, + * so the strip comparison does not reach inside `meta`. A `meta` field that + * ever becomes load-bearing for a client needs its own declaration and its own + * assertion. */ import { NextRequest } from "next/server"; import { beforeEach, describe, expect, it, vi } from "vitest";