import { describe, expect, it } from 'vitest'; import { render } from 'svelte/server'; import type { ProvisioningDetail, ProvisioningProgress } from '../../src/lib/provisioning-detail.js'; import { provisioningActions } from '../../src/lib/provisioning.js'; import { isProvisioningDetail, isProvisioningEventPage, provisioningGroups, provisioningOutcome, provisioningRequester, provisioningReturn } from '../../src/lib/ui/provisioning-presentation.js'; import ProvisioningProgressView from '../../src/lib/ui/components/ProvisioningProgress.svelte'; const at = '2026-09-28T12:00:00Z'; function progress(patch: Partial = {}): ProvisioningProgress { return { version: 1, phase: 'verify-map', phaseStartedAt: at, observedAt: at, blocker: null, checks: [], checkpoints: [], command: null, recovery: null, recipeStatus: null, ...patch }; } function fixture(): ProvisioningDetail { return { deployment: { id: 'operation-a', server: 't5_zombie_theater_1', action: 'create', state: 'awaiting-health', stage: 'Waiting for a map', server_deleted_at: null, server_exists: 1, started_at: at, finished_at: null, triggered_by: 'ui', error: null, warnings: '[]' }, generatedAt: at, events: [], activity: { total: 0, latestId: null, failureExcerpt: null }, sources: [], recipe: null, blueprintName: null, canResumeCleanup: false, canResumeRebuild: false, canResumeVerification: false, canCancelRemoval: false, collection: null, progress: progress(), capacity: { evaluatedAt: at, pools: [] }, warningMessages: [] }; } describe('provisioning outcome and evidence presentation', () => { it('shows recorded usernames and preserves unknown manual requesters', () => { expect(provisioningRequester({ triggered_by: 'ui', requested_by: 'alice' })).toBe('alice'); expect(provisioningRequester({ triggered_by: 'rebalance:run', requested_by: 'bob' })).toBe('bob'); expect(provisioningRequester({ triggered_by: 'ui', requested_by: null })).toBe('Not recorded'); expect(provisioningRequester({ triggered_by: 'ui' })).toBe('Not recorded'); }); it.each(['watchdog:incident', 'maintenance:job', 'autoscaling:policy', 'campaign:run', 'beta:75', 'rebalance:run', 'host-drain'])( 'shows Maxis for system work from %s', triggered_by => { expect(provisioningRequester({ triggered_by, requested_by: null })).toBe('Maxis'); expect(provisioningRequester({ triggered_by })).toBe('Maxis'); }); it('distinguishes a failed creation retry from readiness verification and its linked recovery', () => { const detail = fixture(); detail.deployment.state = 'failed'; detail.canRetryWithRebuild = true; expect(provisioningOutcome(detail)).toMatchObject({ active: false, headline: 'Create failed' }); expect(provisioningOutcome(detail).description).toContain('new attempt for the same lobby and port'); detail.canRetryWithRebuild = false; detail.progress = progress({ recovery: { state: 'transferred', operationId: 'recovery-a', at, message: 'Rebuild requested' } }); expect(provisioningOutcome(detail).headline).toBe('Create failed'); expect(provisioningOutcome(detail).description).toContain('linked operation'); detail.progress = progress(); detail.canResumeVerification = true; expect(provisioningOutcome(detail).description).toContain('remaining restart and rebuild recovery steps'); }); it('distinguishes cancellation from confirmed cleanup and validates its link data', () => { const detail = fixture(); detail.deployment.state = 'cancelled'; detail.canCancelCreation = false; detail.cancellation = { cleanupId: 'cleanup-a', state: 'running', stage: 'Waiting for host', error: null }; expect(isProvisioningDetail(detail, detail.deployment.id)).toBe(true); expect(provisioningOutcome(detail).description).toContain('ports remain reserved'); detail.cancellation.state = 'done'; expect(provisioningOutcome(detail).description).toContain('have been removed'); expect(isProvisioningDetail({ ...detail, cancellation: { state: 'done' } }, detail.deployment.id)).toBe(false); }); it.each(provisioningActions)('describes cancelled %s as terminal, with no automatic execution claim', action => { const detail = fixture(); detail.deployment.action = action; detail.deployment.state = 'cancelled'; const outcome = provisioningOutcome(detail); expect(outcome.active).toBe(false); expect(outcome.headline).toContain('cancelled'); expect(outcome.description).toContain('remaining work will not run'); expect(outcome.description).not.toMatch(/verifying|automatically/); }); it('keeps backend-authorized cleanup actionable and active after identity deletion', () => { const detail = fixture(); detail.deployment.action = 'destroy'; detail.deployment.state = 'failed'; detail.deployment.server_deleted_at = at; detail.deployment.server_exists = 0; detail.canResumeCleanup = true; expect(provisioningOutcome(detail).description).toContain('Resume the remaining local cleanup'); detail.deployment.state = 'waiting'; detail.canResumeCleanup = false; detail.progress = progress({ phase: 'cleanup', checks: [] }); expect(provisioningOutcome(detail).active).toBe(true); expect(provisioningOutcome(detail).description).toContain('without repeating container removal'); expect(provisioningOutcome(detail).description).not.toContain('eligibility'); }); it('does not invent completed phases from a later phase or final outcome', () => { const detail = fixture(); const steps = provisioningGroups(detail).flatMap(group => group.steps); expect(steps.find(step => step.key === 'prepare')?.state).toBe('unknown'); expect(steps.find(step => step.key === 'verify-map')?.state).toBe('current'); detail.deployment.state = 'done'; expect(provisioningGroups(detail).flatMap(group => group.steps).every(step => step.state === 'unknown')).toBe(true); detail.progress = null; expect(provisioningGroups(detail)).toEqual([]); expect(provisioningOutcome(detail).description).toContain('evidence was not recorded'); }); it('shows passed container and telemetry game checks while waiting on the engine map', () => { const detail = fixture(); detail.progress = progress({ checks: [ { key: 'container', label: 'Container readiness', result: 'pass', source: 'agent', checkedAt: at, receivedAt: null, expected: 'healthy', actual: 'healthy', message: null }, { key: 'game', label: 'Game status', result: 'pass', source: 'telemetry', checkedAt: at, receivedAt: at, expected: null, actual: null, message: 'Fresh telemetry accepted' }, { key: 'map', label: 'Engine map', result: 'waiting', source: 'telemetry', checkedAt: at, receivedAt: at, expected: 'zombie_theater', actual: 'zombie_moon', message: null } ] }); const steps = provisioningGroups(detail).flatMap(group => group.steps); expect(steps.find(step => step.key === 'verify-container')?.state).toBe('done'); expect(steps.find(step => step.key === 'verify-game')?.state).toBe('done'); expect(steps.find(step => step.key === 'verify-map')?.state).toBe('waiting'); expect(steps.find(step => step.key === 'verify-game')?.label).not.toContain('RCON'); detail.deployment.state = 'done'; expect(provisioningOutcome(detail).description).not.toContain('RCON'); }); it('newer unknown readiness evidence supersedes an old completion checkpoint', () => { const detail = fixture(); detail.progress = progress({ checkpoints: [{ key: 'verify-game', label: 'Game passed', at, phase: 'verify-game' }], checks: [{ key: 'game', label: 'Game status', result: 'unknown', source: null, checkedAt: '2026-09-28T12:01:00Z', receivedAt: null, expected: null, actual: null, message: 'No fresh evidence' }] }); expect(provisioningGroups(detail).flatMap(group => group.steps).find(step => step.key === 'verify-game')).toMatchObject({ state: 'unknown', at: '2026-09-28T12:01:00Z' }); }); it.each(['failed', 'interrupted'])('shows where an unhealthy container stopped a %s operation', state => { const detail = fixture(); detail.deployment.state = state; detail.deployment.finished_at = at; detail.progress = progress({ phase: 'verify-container', checkpoints: [{ key: 'container-action', label: 'Host command completed', at, phase: 'container-action' }], checks: [{ key: 'container', label: 'Container readiness', result: 'waiting', source: 'agent', checkedAt: at, receivedAt: null, expected: 'healthy', actual: 'unhealthy', message: null }] }); const groups = provisioningGroups(detail); expect(groups.flatMap(group => group.steps).find(step => step.key === 'container-action')?.state).toBe('done'); expect(groups.find(group => group.key === 'verify')).toMatchObject({ state: 'failed', steps: [ { key: 'verify-container', state: 'failed', at }, { key: 'verify-game', state: 'cancelled', at: null }, { key: 'verify-map', state: 'cancelled', at: null }, { key: 'verify-plutonium', state: 'cancelled', at: null } ] }); const body = render(ProvisioningProgressView, { props: { detail } }).body; const phases = body.slice(0, body.indexOf('Latest recorded checks')); expect(phases.match(/Stopped here/g)).toHaveLength(2); expect(phases.match(/Not run/g)).toHaveLength(3); expect(phases).not.toContain('Waiting'); expect(phases).toMatch(/
  • { const detail = fixture(); detail.deployment.state = state; detail.progress = progress(); expect(provisioningGroups(detail).flatMap(group => group.steps).find(step => step.key === 'verify-map')?.state).toBe('failed'); detail.progress.checks = [{ key: 'map', label: 'Engine map', result: 'unknown', source: null, checkedAt: at, receivedAt: null, expected: 'zombie_theater', actual: null, message: 'No fresh evidence' }]; const steps = provisioningGroups(detail).flatMap(group => group.steps); expect(steps.find(step => step.key === 'verify-map')?.state).toBe('failed'); expect(steps.find(step => step.key === 'verify-plutonium')?.state).toBe('cancelled'); expect(steps.find(step => step.key === 'prepare')?.state).toBe('unknown'); }); it.each(['done', 'warning', 'cancelled', 'failed', 'interrupted'])('does not highlight stale waiting evidence as active on a %s operation', state => { const detail = fixture(); detail.progress = progress({ phase: 'verify-container', checks: [ { key: 'container', label: 'Container readiness', result: 'waiting', source: 'agent', checkedAt: at, receivedAt: null, expected: 'healthy', actual: 'starting', message: null } ] }); const activeBody = render(ProvisioningProgressView, { props: { detail } }).body; expect(activeBody).toMatch(/
  • { const detail = fixture(); detail.deployment.action = action; detail.progress = progress({ phase: 'verify-plutonium', checks: [ { key: 'plutonium-release', label: 'Production Plutonium release', result: 'pass', source: 'manifest', checkedAt: at, receivedAt: at, expected: null, actual: 5200, message: null }, { key: 'plutonium-version', label: 'Running Plutonium revision', result: 'unknown', source: 'rcon', checkedAt: at, receivedAt: null, expected: 5200, actual: null, message: 'Waiting for a runtime revision' } ] }); expect(isProvisioningDetail(detail, detail.deployment.id)).toBe(true); for (const [result, state] of [['unknown', 'unknown'], ['waiting', 'waiting'], ['fail', 'failed'], ['pass', 'done']] as const) { detail.progress.checks[1].result = result; const steps = provisioningGroups(detail).find(group => group.key === 'verify')!.steps; expect(steps.at(-1)).toMatchObject({ key: 'verify-plutonium', label: 'Verify Plutonium revision', state, at }); } }); it.each(['start', 'restart', 'stop', 'destroy'] as const)('does not add Plutonium verification to %s', action => { const detail = fixture(); detail.deployment.action = action; expect(provisioningGroups(detail).flatMap(group => group.steps).some(step => step.key === 'verify-plutonium')).toBe(false); }); it('does not claim historical creations verified Plutonium without evidence', () => { const detail = fixture(); detail.deployment.state = 'done'; detail.progress = progress({ checkpoints: [{ key: 'verify-map', label: 'Selected map verified', at, phase: 'verify-map' }] }); expect(provisioningGroups(detail).flatMap(group => group.steps).find(step => step.key === 'verify-plutonium')) .toMatchObject({ state: 'unknown', at: null }); }); it('preserves the failed request when later recovery checks pass', () => { const detail = fixture(); detail.deployment.action = 'restart'; detail.deployment.state = 'failed'; detail.progress = progress({ phase: 'verify-game', recovery: { state: 'released', at, message: 'Recovery verified and capacity released.' }, checks: [{ key: 'game', label: 'Game status', result: 'pass', source: 'rcon', checkedAt: at, receivedAt: null, expected: null, actual: 0, message: null }] }); expect(provisioningOutcome(detail).headline).toBe('Restart failed'); expect(provisioningGroups(detail).flatMap(group => group.steps).find(step => step.key === 'verify-game')?.state).toBe('done'); }); it('keeps confirmed agent removal separate from registry cleanup', () => { const detail = fixture(); detail.deployment.action = 'destroy'; detail.deployment.state = 'waiting'; detail.progress = progress({ phase: 'cleanup', checkpoints: [{ key: 'container-action', label: 'Agent confirmed removal', at, phase: 'container-action' }] }); const groups = provisioningGroups(detail); expect(groups.map(group => group.key)).toEqual(['request', 'command', 'cleanup']); expect(groups.flatMap(group => group.steps).find(step => step.key === 'container-action')?.state).toBe('done'); expect(groups.flatMap(group => group.steps).find(step => step.key === 'cleanup')?.state).toBe('waiting'); }); }); describe('provisioning response and return-context boundaries', () => { it('accepts optional retry eligibility only as an authoritative boolean', () => { const detail = fixture(); for (const canRetryWithRebuild of [undefined, false, true]) { expect(isProvisioningDetail({ ...detail, canRetryWithRebuild }, detail.deployment.id)).toBe(true); } for (const canRetryWithRebuild of ['true', 1, null]) { expect(isProvisioningDetail({ ...detail, canRetryWithRebuild }, detail.deployment.id)).toBe(false); } }); it('accepts persisted usernames and legacy details but rejects malformed requesters', () => { const detail = fixture(); for (const requested_by of [undefined, null, 'alice']) { expect(isProvisioningDetail({ ...detail, deployment: { ...detail.deployment, requested_by } }, detail.deployment.id)).toBe(true); } expect(isProvisioningDetail({ ...detail, deployment: { ...detail.deployment, requested_by: 42 } }, detail.deployment.id)).toBe(false); }); it('retains list filters without accepting an external or unrelated return destination', () => { expect(provisioningReturn('/provisioning?view=history&action=restart&server=Lobby+1&limit=50&redirect=https://evil.test').href) .toBe('/provisioning?view=history&action=restart&server=Lobby+1&limit=50'); expect(provisioningReturn('/provisioning?view=history&limit=25&offset=100').href) .toBe('/provisioning?view=history&limit=25&offset=100'); expect(provisioningReturn('/provisioning?view=history&source=campaign&status=failed&offset=25').href) .toBe('/provisioning?view=history&status=failed&source=campaign&offset=25'); for (const value of ['https://evil.test', '//evil.test', '/\\evil.test', '/\n/evil.test', '/settings', '/provisioning/other']) { expect(provisioningReturn(value).href).toBe('/provisioning'); } expect(provisioningReturn('/collections/launches/launch-a').href).toBe('/collections/launches/launch-a'); }); it('returns rebalance moves to their run while discarding unrelated navigation parameters', () => { expect(provisioningReturn('/capacity?rebalance=1&rebalanceRun=run-a&redirect=https://evil.test')) .toEqual({ href: '/capacity?rebalance=1&rebalanceRun=run-a', label: 'Back to rebalance' }); expect(provisioningReturn('/capacity?rebalance=1')).toEqual({ href: '/capacity?rebalance=1', label: 'Back to rebalance' }); expect(provisioningReturn('/capacity?rebalanceRun=run-a').href).toBe('/provisioning'); const detail = fixture(); detail.sources = [{ kind: 'rebalance', source_id: 'run-a', label: 'Rebalance', href: '/capacity?rebalance=1&rebalanceRun=run-a' }]; expect(isProvisioningDetail(detail, detail.deployment.id)).toBe(true); detail.sources[0].href = '//evil.test'; expect(isProvisioningDetail(detail, detail.deployment.id)).toBe(false); }); it('rejects incomplete nested snapshots before they replace a working detail', () => { const detail = fixture(); expect(isProvisioningDetail(detail, detail.deployment.id)).toBe(true); expect(isProvisioningDetail(detail, 'another-id')).toBe(false); for (const patch of [ { progress: undefined }, { progress: {} }, { progress: { ...detail.progress, checks: [null] } }, { capacity: { evaluatedAt: at, pools: [{ kind: 'restart', holders: null }] } }, { activity: { total: 0 } }, { warningMessages: {} }, { recipe: { files: [] } }, { sources: [{ kind: 'campaign', label: 'Campaign', source_id: 'a', href: 'javascript:alert(1)' }] } ]) expect(isProvisioningDetail({ ...detail, ...patch }, detail.deployment.id)).toBe(false); expect(isProvisioningDetail({ ...detail, progress: { ...detail.progress, command: { state: 'rejected', at, message: 'Rejected before submission' } } }, detail.deployment.id)).toBe(true); }); it('rejects an invalid history event without dropping the existing history view', () => { const page = { events: [{ id: 1, created_at: at, message: 'Queued' }], total: 1, totalEvents: 1, latestId: 1, watermark: 1, nextCursor: null }; expect(isProvisioningEventPage(page)).toBe(true); expect(isProvisioningEventPage({ ...page, events: [{ id: 1, created_at: 'invalid', message: 'Queued' }] })).toBe(false); }); });