e2e: dispatch backup via source-group API
CI / Test (rest) (pull_request) Successful in 7s
CI / Test (store) (pull_request) Successful in 6s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Lint (pull_request) Successful in 18s
CI / Build (linux/amd64) (pull_request) Successful in 7s
CI / Build (linux/arm64) (pull_request) Successful in 8s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m27s
CI / Test (server-http) (pull_request) Successful in 3m3s

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.
This commit is contained in:
2026-05-08 22:16:57 +01:00
parent 130b68226e
commit ea9941b9ec
2 changed files with 50 additions and 10 deletions
+37
View File
@@ -107,6 +107,43 @@ export async function waitForHostStatus(
throw new Error(`waitForHostStatus: timeout. Last seen: ${JSON.stringify(last)}`); 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<string> {
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<void> {
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<string> { export async function getSessionCookie(page: Page): Promise<string> {
const cookies = await page.context().cookies(); const cookies = await page.context().cookies();
const c = cookies.find((c) => c.name === 'rm_session'); const c = cookies.find((c) => c.name === 'rm_session');
+13 -10
View File
@@ -14,6 +14,8 @@ import {
waitForPendingHostID, waitForPendingHostID,
acceptPending, acceptPending,
waitForHostStatus, waitForHostStatus,
createSourceGroup,
runSourceGroup,
getSessionCookie, getSessionCookie,
} from './lib/server'; } from './lib/server';
@@ -53,18 +55,19 @@ test.describe('smoke: enrol-via-announce → backup', () => {
); );
expect(readyHost.id).toBeTruthy(); expect(readyHost.id).toBeTruthy();
// Trigger a backup via the UI form-post (HX-Redirect to /jobs/{id}). // Per-host Run-now is gone; backups are dispatched per
await page.goto(`${baseURL}/hosts/${readyHost.id}`); // source-group now. Create one that maps to the agent's
await Promise.all([ // /source mount, then kick it via the JSON API.
page.waitForURL(/\/jobs\//), const groupID = await createSourceGroup(request, cookie, readyHost.id, {
page.locator('form[action$="/run-backup"] button[type="submit"]').first().click(), name: 'default',
]); includes: ['/source'],
});
await runSourceGroup(request, cookie, readyHost.id, groupID);
// Wait for the host's last_backup_status to flip to 'succeeded'. // Wait for the host's last_backup_status to flip to 'succeeded'.
// The job page itself is harder to assert on (it uses // The host record is the source of truth: it's what the
// server-pushed updates and a reload-on-finish pattern); the // dashboard projects from job-completion events on the WS
// host record is the source of truth and is what the dashboard // channel.
// surfaces.
const finishedHost = await waitForHostStatus( const finishedHost = await waitForHostStatus(
request, cookie, request, cookie,
(h) => h.id === readyHost.id && h.last_backup_status === 'succeeded', (h) => h.id === readyHost.id && h.last_backup_status === 'succeeded',