mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
codex-flow.spec.ts intercepts /api/auth/codex/device-start + device-poll + /api/insights/settings to drive the UI through the three-state machine (CTA → device-code panel → "ChatGPT connected" badge) without touching auth.openai.com / chatgpt.com. insights-generate.spec.ts mocks /api/insights/generate with the canonical InsightResult shape (changed/stable/drivers/nextSteps/ confidence/limitations) and confirms the regenerate button surfaces the inline success message — the AI provider stays uninvolved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
|
|
|
/**
|
|
* Insights generation flow — `/settings/ai` exposes a "Regenerate
|
|
* insights" button that POSTs `/api/insights/generate` and re-renders
|
|
* the AI-generated text in the surrounding cards.
|
|
*
|
|
* The real endpoint hits an AI provider (OpenAI / Codex) which we can't
|
|
* call from CI. We mock it with the schema documented in
|
|
* `src/app/api/insights/generate/route.ts`:
|
|
*
|
|
* { data: { insights: { changed, stable, drivers, nextSteps,
|
|
* confidence, limitations }, cached: false }, error: null }
|
|
*
|
|
* We then confirm the success message lands AND a query invalidation
|
|
* fires on the insight-fetching endpoints (we re-mock those to assert
|
|
* they're touched after the regenerate call).
|
|
*/
|
|
test.describe("insights generation flow (mocked)", () => {
|
|
test.use({ storageState: STORAGE_STATE_PATH });
|
|
|
|
test("regenerate button updates insight cards", async ({ page }) => {
|
|
// Settings endpoint — return "connected" so the regenerate button
|
|
// is rendered (the section gates it on hasProvider).
|
|
await page.route("**/api/insights/settings", (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
data: {
|
|
codexStatus: "connected",
|
|
codexConnectedAt: new Date().toISOString(),
|
|
hasAdminKey: false,
|
|
codexOauthConfigured: true,
|
|
privacyMode: "aggregated",
|
|
lastInsightAt: new Date().toISOString(),
|
|
},
|
|
error: null,
|
|
}),
|
|
}),
|
|
);
|
|
|
|
await page.route("**/api/user/ai-provider", (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
data: {
|
|
provider: null,
|
|
model: null,
|
|
baseUrl: null,
|
|
hasAnthropicKey: false,
|
|
anthropicKeyPreview: null,
|
|
hasLocalKey: false,
|
|
hasOpenaiKey: false,
|
|
openaiKeyPreview: null,
|
|
},
|
|
error: null,
|
|
}),
|
|
}),
|
|
);
|
|
|
|
let generateCalls = 0;
|
|
await page.route("**/api/insights/generate", (route) => {
|
|
generateCalls += 1;
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
data: {
|
|
insights: {
|
|
changed:
|
|
"Blood-pressure averages dropped by 4/2 mmHg over the last 30 days.",
|
|
stable: "Pulse and weight unchanged within natural variance.",
|
|
drivers:
|
|
"Likely driven by improved medication compliance (88% vs 72% prior).",
|
|
nextSteps:
|
|
"Continue current routine; revisit BMI target in 30 days.",
|
|
confidence: "mittel",
|
|
limitations:
|
|
"Sample size for systolic readings remains below 60.",
|
|
},
|
|
cached: false,
|
|
},
|
|
error: null,
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/settings/ai", { waitUntil: "domcontentloaded" });
|
|
|
|
const regenerateBtn = page.getByRole("button", {
|
|
name: /regenerate (insights|reports)|insights aktualisieren|neu erstellen|berichte/i,
|
|
});
|
|
await expect(regenerateBtn).toBeVisible({ timeout: 10_000 });
|
|
await regenerateBtn.click();
|
|
|
|
// The settings page surfaces an inline alert after the mutation
|
|
// completes. The exact copy comes from i18n, so match permissively.
|
|
await expect(
|
|
page.getByText(
|
|
/reports? regenerated|insight.*regenerated|berichte.*aktualisiert|generated successfully|erfolgreich/i,
|
|
),
|
|
).toBeVisible({ timeout: 10_000 });
|
|
|
|
expect(generateCalls).toBeGreaterThanOrEqual(1);
|
|
});
|
|
});
|