From 1d5236a3266dcbf7392f101d5e8ca3a0c046b27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 20:37:48 +0100 Subject: [PATCH] test(ai): settle the ratchets after the empty-chain change The resolver tests queue their fixture rows through one helper instead of an `as never` per row, the no-provider status case also proves the single-provider resolver is never asked, and the size and comment baselines take the lower numbers the change left behind. --- .../baselines/source-file-lines.json | 6 +- .../baselines/source-function-lines.json | 6 +- src/__tests__/baselines/test-as-never.json | 6 +- .../baselines/version-stamp-comments.json | 4 +- src/lib/ai/__tests__/resolve-chain.test.ts | 90 +++++++++++-------- .../__tests__/status-provider-budget.test.ts | 20 +---- 6 files changed, 67 insertions(+), 65 deletions(-) diff --git a/src/__tests__/baselines/source-file-lines.json b/src/__tests__/baselines/source-file-lines.json index e6d9b833e..426c58a2d 100644 --- a/src/__tests__/baselines/source-file-lines.json +++ b/src/__tests__/baselines/source-file-lines.json @@ -1,7 +1,7 @@ { "$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.", "count": 84, - "total": 94047, + "total": 93880, "entries": { "app/medications/page-client.tsx": 839, "components/charts/mood-chart.tsx": 1166, @@ -30,7 +30,7 @@ "lib/ai/coach/types.ts": 1008, "lib/ai/codex-client.ts": 907, "lib/ai/prompts/insight-generator.ts": 1115, - "lib/ai/provider.ts": 1628, + "lib/ai/provider.ts": 1497, "lib/analytics/score/reader.ts": 998, "lib/analytics/sleep-night.ts": 1345, "lib/analytics/summaries-slice.ts": 1178, @@ -56,7 +56,7 @@ "lib/import/health-connect/import.ts": 1689, "lib/import/unzip-export-xml.ts": 825, "lib/insights/comprehensive-aggregator.ts": 868, - "lib/insights/comprehensive-generate.ts": 1674, + "lib/insights/comprehensive-generate.ts": 1670, "lib/insights/correlation-channel-series.ts": 1339, "lib/insights/correlation-discovery.ts": 1240, "lib/insights/correlations.ts": 1026, diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json index dd83b4bdc..4838bb877 100644 --- a/src/__tests__/baselines/source-function-lines.json +++ b/src/__tests__/baselines/source-function-lines.json @@ -1,7 +1,7 @@ { "$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.", - "count": 344, - "total": 111520, + "count": 343, + "total": 111236, "entries": { "app/achievements/page.tsx :: AchievementsPage": 206, "app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 275, @@ -287,7 +287,7 @@ "lib/import/labs-csv.ts :: importLabsCsv": 307, "lib/import/labs-csv.ts :: importLabsCsv > flush": 229, "lib/insights/comprehensive-aggregator.ts :: buildFromRollups": 217, - "lib/insights/comprehensive-generate.ts :: generateComprehensiveInsight": 876, + "lib/insights/comprehensive-generate.ts :: generateComprehensiveInsight": 872, "lib/insights/features.ts :: extractFeatures": 796, "lib/insights/mood-aggregates.ts :: fetchMoodAggregates": 246, "lib/insights/status-provider.ts :: runStatusCompletion": 215, diff --git a/src/__tests__/baselines/test-as-never.json b/src/__tests__/baselines/test-as-never.json index 20b560254..9500eeb7d 100644 --- a/src/__tests__/baselines/test-as-never.json +++ b/src/__tests__/baselines/test-as-never.json @@ -1,7 +1,7 @@ { "$comment": "as never casts in tests, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.", "count": 523, - "total": 5475, + "total": 5464, "entries": { "__tests__/api/auth/login/mfa-branch.test.ts": 10, "__tests__/api/auth/mfa/verify.test.ts": 9, @@ -196,7 +196,7 @@ "app/api/medications/[id]/intake/import/__tests__/route.test.ts": 9, "app/api/medications/[id]/schedule-revisions/__tests__/route.test.ts": 24, "app/api/medications/compliance/__tests__/route.test.ts": 10, - "app/api/medications/extract/__tests__/route.test.ts": 38, + "app/api/medications/extract/__tests__/route.test.ts": 37, "app/api/medications/intake/__tests__/route.test.ts": 59, "app/api/medications/intake/bulk/__tests__/dose-arrival-scope.test.ts": 9, "app/api/medications/intake/bulk/__tests__/route.test.ts": 68, @@ -300,7 +300,7 @@ "lib/ai/__tests__/provider-reasoning-effort.test.ts": 5, "lib/ai/__tests__/provider-response-timeout.test.ts": 2, "lib/ai/__tests__/provider.test.ts": 35, - "lib/ai/__tests__/resolve-chain.test.ts": 12, + "lib/ai/__tests__/resolve-chain.test.ts": 2, "lib/ai/__tests__/server-provider-health.test.ts": 1, "lib/ai/capabilities/__tests__/egress.test.ts": 1, "lib/ai/capabilities/__tests__/gate.test.ts": 1, diff --git a/src/__tests__/baselines/version-stamp-comments.json b/src/__tests__/baselines/version-stamp-comments.json index fc64b4a07..24c8dc829 100644 --- a/src/__tests__/baselines/version-stamp-comments.json +++ b/src/__tests__/baselines/version-stamp-comments.json @@ -1,7 +1,7 @@ { "$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.", "count": 2892, - "total": 9220, + "total": 9217, "entries": { "__tests__/account-payload-consumer-guard.test.ts": 2, "__tests__/acting-account-boundary-guard.test.ts": 7, @@ -331,7 +331,7 @@ "app/api/insights/ecg/[id]/route.ts": 2, "app/api/insights/ecg/route.ts": 1, "app/api/insights/feedback/route.ts": 2, - "app/api/insights/generate/__tests__/route.test.ts": 12, + "app/api/insights/generate/__tests__/route.test.ts": 11, "app/api/insights/glp1-plateau/route.ts": 2, "app/api/insights/glp1-timeline/route.ts": 4, "app/api/insights/health-status/route.ts": 3, diff --git a/src/lib/ai/__tests__/resolve-chain.test.ts b/src/lib/ai/__tests__/resolve-chain.test.ts index d25ac7afb..1b7fcf67c 100644 --- a/src/lib/ai/__tests__/resolve-chain.test.ts +++ b/src/lib/ai/__tests__/resolve-chain.test.ts @@ -24,6 +24,17 @@ beforeEach(() => { vi.resetAllMocks(); }); +/** Queue the user rows the resolver reads, in the order it reads them. */ +function userRows(...rows: Record[]) { + const findUnique = vi.mocked(prisma.user.findUnique); + for (const row of rows) findUnique.mockResolvedValueOnce(row as never); +} + +/** The operator's settings row. */ +function operator(settings: Record) { + vi.mocked(prisma.appSettings.findUnique).mockResolvedValue(settings as never); +} + describe("resolveProviderChain", () => { it("returns the default chain when user.aiProviderChain is null", async () => { vi.mocked(decryptCodexCreds).mockReturnValue({ @@ -32,9 +43,9 @@ describe("resolveProviderChain", () => { accountId: "acct", expiresAt: new Date(Date.now() + 3600_000), }); - vi.mocked(prisma.user.findUnique) + userRows( // user-level config read - .mockResolvedValueOnce({ + { aiProvider: null, aiModel: null, aiBaseUrl: null, @@ -42,19 +53,20 @@ describe("resolveProviderChain", () => { aiLocalKeyEncrypted: null, aiOpenaiKeyEncrypted: null, aiProviderChain: null, - } as never) + }, // codex tokens read - .mockResolvedValueOnce({ + { codexAccessTokenEncrypted: "enc-a", codexRefreshTokenEncrypted: "enc-r", codexTokenExpiresAt: new Date(Date.now() + 3600_000), codexConnectionStatus: "connected", - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }, + ); + operator({ adminAiKeyEncrypted: "enc-admin", adminAiModel: "gpt-4o-mini", adminAiBaseUrl: "https://api.openai.com/v1", - } as never); + }); const chain = await resolveProviderChain("user-1"); // Default chain trims out entries with no creds; codex (tokens @@ -69,7 +81,7 @@ describe("resolveProviderChain", () => { }); it("respects user-defined chain priority order", async () => { - vi.mocked(prisma.user.findUnique).mockResolvedValueOnce({ + userRows({ aiProvider: null, aiModel: null, aiBaseUrl: null, @@ -82,12 +94,12 @@ describe("resolveProviderChain", () => { { providerType: "anthropic", priority: 2, enabled: true }, { providerType: "admin-openai", priority: 3, enabled: false }, ], - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }); + operator({ adminAiKeyEncrypted: "enc-admin", adminAiModel: "gpt-4o-mini", adminAiBaseUrl: "https://api.openai.com/v1", - } as never); + }); const chain = await resolveProviderChain("user-1"); const types = chain.map((c) => c.providerType); @@ -95,7 +107,7 @@ describe("resolveProviderChain", () => { }); it("filters out chain entries the user has not credentialed", async () => { - vi.mocked(prisma.user.findUnique).mockResolvedValueOnce({ + userRows({ aiProvider: null, aiModel: null, aiBaseUrl: null, @@ -106,12 +118,12 @@ describe("resolveProviderChain", () => { { providerType: "anthropic", priority: 1, enabled: true }, { providerType: "openai", priority: 2, enabled: true }, ], - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }); + operator({ adminAiKeyEncrypted: null, adminAiModel: "gpt-4o-mini", adminAiBaseUrl: "https://api.openai.com/v1", - } as never); + }); const chain = await resolveProviderChain("user-1"); const types = chain.map((c) => c.providerType); @@ -119,7 +131,7 @@ describe("resolveProviderChain", () => { }); it("skips disabled entries even if the credential is present", async () => { - vi.mocked(prisma.user.findUnique).mockResolvedValueOnce({ + userRows({ aiProvider: null, aiModel: null, aiBaseUrl: null, @@ -130,12 +142,12 @@ describe("resolveProviderChain", () => { { providerType: "openai", priority: 1, enabled: false }, // disabled { providerType: "admin-openai", priority: 2, enabled: true }, ], - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }); + operator({ adminAiKeyEncrypted: "enc-admin", adminAiModel: "gpt-4o-mini", adminAiBaseUrl: "https://api.openai.com/v1", - } as never); + }); const chain = await resolveProviderChain("user-1"); const types = chain.map((c) => c.providerType); @@ -143,8 +155,8 @@ describe("resolveProviderChain", () => { }); it("returns an empty array when nothing is configured at all", async () => { - vi.mocked(prisma.user.findUnique) - .mockResolvedValueOnce({ + userRows( + { aiProvider: null, aiModel: null, aiBaseUrl: null, @@ -152,18 +164,19 @@ describe("resolveProviderChain", () => { aiLocalKeyEncrypted: null, aiOpenaiKeyEncrypted: null, aiProviderChain: null, - } as never) - .mockResolvedValueOnce({ + }, + { codexAccessTokenEncrypted: null, codexRefreshTokenEncrypted: null, codexTokenExpiresAt: null, codexConnectionStatus: "disconnected", - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }, + ); + operator({ adminAiKeyEncrypted: null, adminAiModel: "gpt-4o-mini", adminAiBaseUrl: "https://api.openai.com/v1", - } as never); + }); const chain = await resolveProviderChain("user-1"); expect(chain).toEqual([]); @@ -173,7 +186,7 @@ describe("resolveProviderChain", () => { // Only the local model on, its address missing; the person's own key and // the operator's key are both switched off. Nothing may serve behind those // switches, however the record's single provider field reads. - vi.mocked(prisma.user.findUnique).mockResolvedValueOnce({ + userRows({ aiProvider: "OPENAI", aiModel: null, aiBaseUrl: null, @@ -185,12 +198,12 @@ describe("resolveProviderChain", () => { { providerType: "openai", priority: 2, enabled: false }, { providerType: "admin-openai", priority: 3, enabled: false }, ], - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }); + operator({ adminAiKeyEncrypted: "enc-admin", adminAiModel: "gpt-4o-mini", adminAiBaseUrl: "https://api.openai.com/v1", - } as never); + }); expect(await resolveProviderChain("user-1")).toEqual([]); }); @@ -208,26 +221,27 @@ describe("resolveProviderChain", () => { aiCompatModel: "llama-3", role: "USER", }; - vi.mocked(prisma.user.findUnique) - .mockResolvedValueOnce({ ...gatewayRow, aiProviderChain: null } as never) - .mockResolvedValueOnce({ + userRows( + { ...gatewayRow, aiProviderChain: null }, + { codexAccessTokenEncrypted: null, codexRefreshTokenEncrypted: null, codexTokenExpiresAt: null, codexConnectionStatus: "disconnected", - } as never); - vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({ + }, + ); + operator({ adminAiKeyEncrypted: null, - } as never); + }); const chain = await resolveProviderChain("user-1"); expect(chain.map((c) => c.providerType)).toEqual(["openai-compatible"]); // A saved chain without the gateway is the person's word. - vi.mocked(prisma.user.findUnique).mockResolvedValueOnce({ + userRows({ ...gatewayRow, aiProviderChain: [{ providerType: "local", priority: 1, enabled: true }], - } as never); + }); expect(await resolveProviderChain("user-1")).toEqual([]); }); }); diff --git a/src/lib/insights/__tests__/status-provider-budget.test.ts b/src/lib/insights/__tests__/status-provider-budget.test.ts index 247c91459..31b8a73b4 100644 --- a/src/lib/insights/__tests__/status-provider-budget.test.ts +++ b/src/lib/insights/__tests__/status-provider-budget.test.ts @@ -167,22 +167,6 @@ describe("runStatusCompletion — capability at the wire", () => { expect(ledgerTotal).toBe(0); }); - it("sends nothing on an empty chain, whatever the single-provider resolver holds", async () => { - // The chain carries the person's provider switches; an empty one is no - // provider, never a quiet hop to one they switched off. - resolveProviderChain.mockResolvedValue([]); - resolveProvider.mockResolvedValue({ type: "admin-key" }); - - const outcome = await runStatusCompletion( - completionArgs({ capability: "statusText" }) as never, - ); - - expect(outcome).toEqual({ kind: "none", reason: "no_provider" }); - expect(resolveProvider).not.toHaveBeenCalled(); - expect(runRawCompletionWithFallback).not.toHaveBeenCalled(); - expect(ledgerTotal).toBe(0); - }); - it("re-checks exactly the resolved chain and refuses before any budget or provider work", async () => { resolveProviderChain.mockResolvedValue([ { providerType: "anthropic", instance: {} }, @@ -240,9 +224,13 @@ describe("runStatusCompletion — capability at the wire", () => { }); it("returns none with reason no_provider when the chain is empty", async () => { + // The chain carries the person's provider switches; an empty one is no + // provider, never a quiet hop to the single-provider resolver's pick. resolveProviderChain.mockResolvedValue([]); + resolveProvider.mockResolvedValue({ type: "admin-key" }); const outcome = await runStatusCompletion(completionArgs() as never); expect(outcome).toEqual({ kind: "none", reason: "no_provider" }); + expect(resolveProvider).not.toHaveBeenCalled(); expect(runRawCompletionWithFallback).not.toHaveBeenCalled(); }); });