import { describe, expect, it, vi } from 'vitest'; import { render } from 'svelte/server'; import { writable } from 'svelte/store'; import type { ProvisioningDetail, RecoveryObservation } from '../../src/lib/provisioning-detail.js'; import { provisioningNeedsRefresh } from '../../src/lib/ui/provisioning-presentation.js'; import ProvisioningPage from '../../src/routes/provisioning/[id]/+page.svelte'; vi.mock('$app/stores', () => ({ page: writable({ url: new URL('http://localhost/provisioning/operation-a'), data: { user: null } }) })); vi.mock('$app/navigation', () => ({ goto: vi.fn() })); const start = '2026-10-09T12:00:00Z'; const finish = '2026-10-09T12:01:00Z'; function fixture(state = 'done', recovery: RecoveryObservation | null = null): ProvisioningDetail { return { deployment: { id: 'operation-a', server: 't5_zombie_theater_1', action: 'restart', state, stage: 'Completed', server_deleted_at: null, server_exists: 1, started_at: start, finished_at: finish, triggered_by: 'watchdog:incident-a', requested_by: 'Maxis', error: null, warnings: '[]' }, generatedAt: finish, events: [], activity: { total: 0, latestId: null, failureExcerpt: null }, sources: [ { kind: 'watchdog', source_id: 'incident-a', label: 'Deleted watchdog incident', href: null } ], recipe: null, blueprintName: null, canResumeCleanup: false, canResumeRebuild: false, canResumeVerification: false, canCancelRemoval: false, collection: { id: 'collection-a', name: 'Evening lobbies' }, progress: { version: 1, phase: 'verify-game', phaseStartedAt: '2026-10-09T12:00:50Z', observedAt: finish, blocker: null, checks: [], checkpoints: [], command: null, recovery, recipeStatus: null }, agentOperation: { id: 'command-a', hostId: 'host-a', state: 'completed', createdAt: start, updatedAt: finish, error: null }, capacity: { evaluatedAt: finish, pools: [] }, warningMessages: [] }; } function pageBody(detail: ProvisioningDetail) { return render(ProvisioningPage, { props: { data: { detail, user: null, setupRequired: false } } }).body; } function recovery(state: RecoveryObservation['state']): RecoveryObservation { return { state, at: finish, message: 'Recovery status updated', ...(state === 'transferred' ? { operationId: 'rebuild-a' } : {}) }; } describe('provisioning detail summary and technical details', () => { it('puts requester, absolute start and one duration above the outcome', () => { const body = pageBody(fixture()); const summary = body.match(/
]*class="operation-summary[^>]*>[\s\S]*?<\/p>/)?.[0];
expect(summary).toBeDefined();
expect(summary).toContain('Requested by Maxis');
expect(summary).toContain('datetime="2026-10-09T12:00:00.000Z"');
expect(summary).toContain('Oct 9, 2026');
expect(summary).toContain('took 1m 0s');
expect(body.indexOf(summary!)).toBeLessThan(body.indexOf('id="outcome-title"'));
expect(body.match(/1m 0s/g)).toHaveLength(1);
expect(body).not.toContain('Recorded duration');
expect(body).not.toContain('Operation outcome');
expect(body).not.toContain('Updates every 5 seconds');
});
it('keeps operation, command and source IDs and copy controls in closed Technical details', () => {
const body = pageBody(fixture());
const technical = body.match(
/]*>${value}`);
expect(technical).toMatch(code);
expect(outside).not.toMatch(code);
expect(technical).toContain(`aria-label="Copy ${label}"`);
expect(outside).not.toContain(`aria-label="Copy ${label}"`);
}
expect(technical).toContain('Copy link');
expect(outside).not.toContain('Copy link');
expect(technical).toContain('datetime="2026-10-09T12:01:00.000Z"');
expect(outside).toContain('href="/collections/launches/collection-a"');
expect(outside).toContain('Source record no longer available');
});
it('shows one elapsed duration for active work and preserves phase timing and freshness', () => {
const detail = fixture('running');
detail.deployment.finished_at = null;
const body = pageBody(detail);
expect(body).toContain('1m 0s elapsed');
expect(body.match(/1m 0s/g)).toHaveLength(1);
expect(body).not.toContain('Total elapsed');
expect(body).toContain('Time in phase');
expect(body).toContain('10s');
expect(body).toContain('Last evaluated');
expect(body).toContain('Updates every 5 seconds');
});
it('does not invent a duration when the finish time is missing', () => {
const detail = fixture('interrupted');
detail.deployment.finished_at = null;
const body = pageBody(detail);
expect(body).toContain('Duration: Not recorded');
expect(body).not.toContain('took ');
expect(body).not.toContain(' elapsed');
expect(body).not.toContain('Updates every 5 seconds');
});
});
describe('provisioning refresh policy', () => {
it.each(['queued', 'waiting', 'running', 'awaiting-health'])(
'refreshes %s operations',
(state) => {
expect(provisioningNeedsRefresh(fixture(state))).toBe(true);
}
);
it.each(['done', 'failed', 'interrupted', 'warning', 'cancelled'])(
'stops refreshing %s operations without held recovery',
(state) => {
const detail = fixture(state);
expect(provisioningNeedsRefresh(detail)).toBe(false);
for (const value of ['released', 'transferred', 'unknown'] as const) {
detail.progress!.recovery = recovery(value);
expect(provisioningNeedsRefresh(detail)).toBe(false);
}
detail.progress = null;
expect(provisioningNeedsRefresh(detail)).toBe(false);
}
);
it.each(['released', 'transferred'] as const)(
'keeps failed restart recovery refreshing until %s without marking it live',
(state) => {
const detail = fixture('failed', recovery('held'));
expect(provisioningNeedsRefresh(detail)).toBe(true);
const held = pageBody(detail);
expect(held).toContain('Recovery capacity held');
expect(held).not.toContain('Updates every 5 seconds');
expect(held).not.toContain('View recovery operation');
detail.progress!.recovery = recovery(state);
expect(provisioningNeedsRefresh(detail)).toBe(false);
const settled = pageBody(detail);
expect(settled).not.toContain('Recovery capacity held');
expect(settled).not.toContain('Updates every 5 seconds');
expect(settled).toContain(
state === 'released' ? 'Recovery capacity released' : 'Recovery transferred'
);
if (state === 'transferred') {
expect(settled).toContain('href="/provisioning/rebuild-a"');
expect(settled).toContain('View recovery operation');
}
}
);
});