Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit fab8c2a1bb45e9578cbd139108b31deaf533afb9
parent 1b558050f453634f3f1fdcd3abb8db2ee23c4766
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 02:10:41 -0400

e2e: the homepage refusal test, the Build homepage test and the hub's refusal test hold BOTH the build and deploy queues — a regressed "preview needs deploy" guard, or a click turned into build-and-deploy, can then only queue

O4 review should-fix 1 (+ low 4 for the hub's test, which predates the
slice and held nothing). holdBuildAndDeployQueues in ops-api.spec, `before`
taken after both holds; sites-homepage's Build test holds both too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/e2e/ops-api.spec.ts | 40++++++++++++++++++++++++++++++----------
Meditor/e2e/sites-homepage.spec.ts | 30++++++++++++++++++------------
2 files changed, 48 insertions(+), 22 deletions(-)

diff --git a/editor/e2e/ops-api.spec.ts b/editor/e2e/ops-api.spec.ts @@ -864,6 +864,24 @@ async function listJobIds(): Promise<string[]> { .sort(); } +// Hold the `build` AND `deploy` queues with fabricated running jobs +// (/api/test/stuck-job; never released by the caller), so any build or deploy +// job that a refusal under test failed to stop can only QUEUE, never run. BOTH +// queues, because a regression can turn a refused call into a job on either +// one: a dropped "preview needs deploy" guard makes a build, a dropped preview +// check makes a deploy. The fabricated holders write no .meta.json, so they are +// invisible to listJobIds; take `before` after this. The next resetData +// cancels jobs newest first, so a queued job is removed before a holder's slot +// is freed and nothing is ever promoted. +async function holdBuildAndDeployQueues(request: APIRequestContext) { + for (const queue of ["build", "deploy"]) { + const hold = await request.get( + `${baseUrl}/api/test/stuck-job?queue=${encodeURIComponent(queue)}`, + ); + expect(hold.ok(), queue).toBe(true); + } +} + test("build-site and build-deploy each take siteId or siteIds, and refuse both or neither", async ({ request, }) => { @@ -1092,12 +1110,15 @@ test("build-site with a bare siteId starts one build-export job", async ({ // The hub's deploy path (release 7). Every refusal here is answered BEFORE a // job exists, which is what lets a runbook's `pnpm ops deploy-hub --wait` fail -// fast instead of queueing a deploy that can only fail. +// fast instead of queueing a deploy that can only fail. Both queues are held +// (release 11, O4 review): a regression of a refusal below would otherwise +// start a real hub build into export/out, or a real deploy. test("deploy-hub refuses no project, the homepage's project, and a bundle that is not the hub", async ({ request, }) => { await resetData("title-filter-channel"); await settings(); + await holdBuildAndDeployQueues(request); const before = await listJobIds(); // The fixture has no homepage.json at all: no project. @@ -1158,20 +1179,19 @@ test("deploy-hub refuses no project, the homepage's project, and a bundle that i // build), so the missing-build call is made only when this checkout has no // `homepage/out/index.html`, as deploy-hub's is (211d4666); // 2. that call asks for a PREVIEW, never production; -// 3. the whole test runs with the `deploy` queue held by a fabricated job -// (/api/test/stuck-job, never released here). A deploy job that a refusal -// failed to stop would only QUEUE — the listJobIds assertion then fails — -// and the next resetData cancels jobs newest first, so the queued deploy -// is removed before the holder's slot is freed. +// 3. the whole test runs with BOTH the `build` and the `deploy` queue held +// by fabricated jobs (holdBuildAndDeployQueues). A build or deploy job +// that a refusal failed to stop — `build-homepage {preview}` is a build +// if the "preview needs deploy" guard regresses — would only QUEUE; the +// listJobIds assertion then fails, and the next resetData cancels jobs +// newest first, so the queued job is removed before a holder's slot is +// freed. test("deploy-homepage refuses a bad preview name and a missing build; build-homepage refuses a preview without a deploy", async ({ request, }) => { await resetData("empty"); await settings(); - const hold = await request.get( - `${baseUrl}/api/test/stuck-job?queue=${encodeURIComponent("deploy")}`, - ); - expect(hold.ok()).toBe(true); + await holdBuildAndDeployQueues(request); const before = await listJobIds(); // A bad preview name is judged before homepage/out is even looked at, so diff --git a/editor/e2e/sites-homepage.spec.ts b/editor/e2e/sites-homepage.spec.ts @@ -7,13 +7,16 @@ // from a spec would be a real one. So: // - no spec clicks Deploy homepage, and none ticks Deploy after build and // then clicks Build homepage; -// - the one spec that clicks Build homepage first holds the `build` queue -// with a fabricated job (/api/test/stuck-job, never released here), so the -// build-homepage job it starts only ever QUEUES, and it is cancelled from -// its own lane while still queued. Its start function never runs: no log -// file, no child, nothing written to homepage/public or homepage/out. If -// the spec fails before its Cancel, the next resetData cancels jobs newest -// first, so the queued build is removed before the holder's slot is freed. +// - the one spec that clicks Build homepage first holds BOTH the `build` and +// the `deploy` queue with fabricated jobs (/api/test/stuck-job, never +// released here), so the job it starts only ever QUEUES — the +// build-homepage it expects on `build`, and equally a build-deploy-homepage +// on `deploy` if a regression turned the click into a build-and-deploy — +// and it is cancelled from its own lane while still queued. Its start +// function never runs: no log file, no child, nothing written to +// homepage/public or homepage/out. If the spec fails before its Cancel, the +// next resetData cancels jobs newest first, so the queued job is removed +// before a holder's slot is freed. import { readdir } from "node:fs/promises"; import { test, expect, type Page } from "@playwright/test"; @@ -165,11 +168,14 @@ test("Build homepage starts a build-homepage job on the build queue (held there, page, request, }) => { - // See the header: with the build queue held, the job can only queue. - const hold = await request.get( - `${baseUrl}/api/test/stuck-job?queue=${encodeURIComponent("build")}`, - ); - expect(hold.ok()).toBe(true); + // See the header: with both queues held, whatever the click starts can only + // queue. + for (const queue of ["build", "deploy"]) { + const hold = await request.get( + `${baseUrl}/api/test/stuck-job?queue=${encodeURIComponent(queue)}`, + ); + expect(hold.ok(), queue).toBe(true); + } await openSites(page); await expect(deployAfter(page)).not.toBeChecked();