From ea9941b9ecd7ccffc110d2dba06353f72e923b34 Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Fri, 8 May 2026 22:16:57 +0100 Subject: [PATCH] e2e: dispatch backup via source-group API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-host Run-backup is gone — the host_chrome partial still renders the button but it's hard-disabled with a tooltip pointing to per-source-group Run-now. The smoke test was clicking that disabled button and waiting forever for a URL change that would never happen. Replace the navigation-based dispatch with two API calls: create a source group covering the agent's /source mount, then POST to /api/hosts/{id}/source-groups/{gid}/run. The backup-status assertion at the end is unchanged — host record is still the source of truth. --- e2e/playwright/tests/lib/server.ts | 37 ++++++++++++++++++++++++++++++ e2e/playwright/tests/smoke.spec.ts | 23 +++++++++++-------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/e2e/playwright/tests/lib/server.ts b/e2e/playwright/tests/lib/server.ts index 80bd499..538cf30 100644 --- a/e2e/playwright/tests/lib/server.ts +++ b/e2e/playwright/tests/lib/server.ts @@ -107,6 +107,43 @@ export async function waitForHostStatus( throw new Error(`waitForHostStatus: timeout. Last seen: ${JSON.stringify(last)}`); } +export async function createSourceGroup( + request: APIRequestContext, + cookie: string, + hostID: string, + body: { name: string; includes: string[]; excludes?: string[] }, +): Promise { + const res = await request.post(`${baseURL}/api/hosts/${hostID}/source-groups`, { + headers: { cookie, 'content-type': 'application/json' }, + data: { + name: body.name, + includes: body.includes, + excludes: body.excludes ?? [], + retention_policy: {}, + retry_max: 0, + retry_backoff_seconds: 0, + }, + }); + if (!res.ok()) throw new Error(`createSourceGroup: ${res.status()} ${await res.text()}`); + const created = (await res.json()) as { id?: string; group?: { id?: string } }; + const id = created.id ?? created.group?.id; + if (!id) throw new Error(`createSourceGroup: no id in response: ${JSON.stringify(created)}`); + return id; +} + +export async function runSourceGroup( + request: APIRequestContext, + cookie: string, + hostID: string, + groupID: string, +): Promise { + const res = await request.post( + `${baseURL}/api/hosts/${hostID}/source-groups/${groupID}/run`, + { headers: { cookie } }, + ); + if (!res.ok()) throw new Error(`runSourceGroup: ${res.status()} ${await res.text()}`); +} + export async function getSessionCookie(page: Page): Promise { const cookies = await page.context().cookies(); const c = cookies.find((c) => c.name === 'rm_session'); diff --git a/e2e/playwright/tests/smoke.spec.ts b/e2e/playwright/tests/smoke.spec.ts index f67a806..c90d390 100644 --- a/e2e/playwright/tests/smoke.spec.ts +++ b/e2e/playwright/tests/smoke.spec.ts @@ -14,6 +14,8 @@ import { waitForPendingHostID, acceptPending, waitForHostStatus, + createSourceGroup, + runSourceGroup, getSessionCookie, } from './lib/server'; @@ -53,18 +55,19 @@ test.describe('smoke: enrol-via-announce → backup', () => { ); expect(readyHost.id).toBeTruthy(); - // Trigger a backup via the UI form-post (HX-Redirect to /jobs/{id}). - await page.goto(`${baseURL}/hosts/${readyHost.id}`); - await Promise.all([ - page.waitForURL(/\/jobs\//), - page.locator('form[action$="/run-backup"] button[type="submit"]').first().click(), - ]); + // Per-host Run-now is gone; backups are dispatched per + // source-group now. Create one that maps to the agent's + // /source mount, then kick it via the JSON API. + const groupID = await createSourceGroup(request, cookie, readyHost.id, { + name: 'default', + includes: ['/source'], + }); + await runSourceGroup(request, cookie, readyHost.id, groupID); // Wait for the host's last_backup_status to flip to 'succeeded'. - // The job page itself is harder to assert on (it uses - // server-pushed updates and a reload-on-finish pattern); the - // host record is the source of truth and is what the dashboard - // surfaces. + // The host record is the source of truth: it's what the + // dashboard projects from job-completion events on the WS + // channel. const finishedHost = await waitForHostStatus( request, cookie, (h) => h.id === readyHost.id && h.last_backup_status === 'succeeded',