From d72b3359650f85077ea917d107e6cd714e13a73d Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Sun, 18 Oct 2026 14:53:30 +0000 Subject: [PATCH] Reminder tests own their VAPID state (the gate runs with keys set) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deploy gate runs the suites against a release that reads the production env — which now carries VAPID keys — so phase 2's "reminders are hidden without VAPID" check failed there and the gate (correctly) refused to switch. That check now runs on its own instance with VAPID_PUBLIC_KEY/_PRIVATE_KEY forced empty, so it pins the unconfigured state whatever the operator's env holds. Verified twice: with no VAPID in the environment and with keys exported exactly like the deploy shell does (83 checks both ways). --- README.md | 4 ++-- tests/phase2-test.mjs | 47 +++++++++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d8fbe36..cd1fe6b 100644 --- a/README.md +++ b/README.md @@ -1529,7 +1529,7 @@ Things I chose while building, with the reasoning: ever sent while three really are. The settings copy says what the server now holds, the privacy page and `.env.example` say it too, and the phase-2 suite pins the whole path with a faked browser subscription: personal text in both languages, the generic fallback in both, - and the sender's preference order (81 checks). + and the sender's preference order (83 checks). 109. **Teach it back: the AI stops being the tutor and becomes the student.** The mode where the user explains a topic and an AI classmate probes the gaps — never a grader, never lecturing. @@ -2287,7 +2287,7 @@ Things I chose while building, with the reasoning: collapsed the session mid-run). Push reminders are all-or-nothing: no VAPID keys → no UI, no endpoints, nothing to misconfigure; `web-push` is the one added server dependency and the daily send is an explicit cron/timer script — the shipped `deploy/freezmo-reminders.{service,timer}` - pair. Verified by the phase-2 suite (81 checks: history and SRS shapes, the review/mistakes + pair. Verified by the phase-2 suite (83 checks: history and SRS shapes, the review/mistakes sessions, stats math, exam with timer and breakdowns, the VAPID-less hidden state, the configured subscribe/validate/test/delete path, and the reminder each device composes from its own notes in both languages; the suite runs its own server instance on a separate port and diff --git a/tests/phase2-test.mjs b/tests/phase2-test.mjs index 5b2d398..054f7d1 100644 --- a/tests/phase2-test.mjs +++ b/tests/phase2-test.mjs @@ -477,23 +477,40 @@ const browser = await launchBrowser(); await context.close(); } -/* 4. reminders hidden without VAPID ---------------------------------- */ +/* 4. reminders hidden without VAPID ---------------------------------- + Its own instance, with the VAPID variables forced empty: an operator's env + (production's, or the deploy gate's release) may well have keys, and this + check is about the state where reminders do not exist at all. */ { - const { context, page } = await makePage(browser); - await seed(page, { decks: [] }); - await page.goto(BASE, { waitUntil: "networkidle" }); - await page.waitForTimeout(800); - const status = await page.evaluate(async () => { - const response = await fetch("/api/push", { headers: { "x-ollama-key": "test-key" } }); - return { status: response.status, body: await response.json() }; + check("push: hidden-state port is free", !(await portInUse(3215))); + const app = spawnApp(3215, { + VAPID_PUBLIC_KEY: "", + VAPID_PRIVATE_KEY: "", + PUSH_DIR: path.join(REPO, "tests", ".tmp", "push-off"), }); - check("push: status says disabled without VAPID", status.body.enabled === false, JSON.stringify(status.body)); - await page.locator("nav.tabbar").getByRole("button").click(); - await page.getByRole("dialog").waitFor({ timeout: 6000 }); - await page.waitForTimeout(500); - check("push: daily goal setting visible", await page.getByText("Daily goal").isVisible()); - check("push: reminder UI hidden", (await page.getByText("Daily reminder").count()) === 0); - await context.close(); + try { + const up = await waitForHttp("http://127.0.0.1:3215/api/health"); + check("push: instance without VAPID is up", up); + if (up) { + const { context, page } = await makePage(browser); + await seed(page, { decks: [] }); + await page.goto("http://127.0.0.1:3215/", { waitUntil: "networkidle" }); + await page.waitForTimeout(800); + const status = await page.evaluate(async () => { + const response = await fetch("/api/push", { headers: { "x-ollama-key": "test-key" } }); + return { status: response.status, body: await response.json() }; + }); + check("push: status says disabled without VAPID", status.body.enabled === false, JSON.stringify(status.body)); + await page.locator("nav.tabbar").getByRole("button").click(); + await page.getByRole("dialog").waitFor({ timeout: 6000 }); + await page.waitForTimeout(500); + check("push: daily goal setting visible", await page.getByText("Daily goal").isVisible()); + check("push: reminder UI hidden", (await page.getByText("Daily reminder").count()) === 0); + await context.close(); + } + } finally { + app.stop(); + } } /* 5. reminders with VAPID: status, subscribe, delete, test send ------ */