import assert from 'node:assert/strict'; import test from 'node:test'; import { classifySyncError, harborReducer, initialHarborState, } from '../../.test-dist/src/web/state/harborReducer.js'; import { parseHarborState } from '../../.test-dist/src/web/api/harborClient.js'; import { createStateSnapshot } from '../../.test-dist/src/shared/contracts/state.js'; const snapshot = (revision, desiredServerId = '', serverIds = ['one', 'two']) => ({ apiVersion: 1, revision, profiles: [{ id: 'primary', desiredServerId, servers: serverIds.map((id) => ({ id })), }], selection: { desiredProfileId: 'primary', desiredServerId }, servers: serverIds.map((id) => ({ id })), }); const receive = (state, value) => harborReducer(state, { type: 'sync-succeeded', snapshot: value, receivedAt: `2026-07-11T12:00:0${value.revision}.000Z`, }); function deferred() { let resolve; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve }; } test('typed Harbor client validates unknown state and isolates wire compatibility fields', () => { const snapshot = createStateSnapshot({ storedState: {}, runtime: { running: false }, gatewayAuto: null, appMode: 'client', configExists: false, subscriptionHost: '', now: new Date('2026-07-11T12:00:00.000Z'), }); const parsed = parseHarborState({ ...snapshot, proxyPort: 9082, configExists: true, gatewayAuto: { available: true }, }); assert.deepEqual(parsed.clientRuntime, { proxyPort: 9082, configured: true, gatewayAvailable: true, }); assert.equal(Object.hasOwn(parsed, 'proxyPort'), false); let incompatible; try { parseHarborState({ ...snapshot, revision: -1 }); } catch (error) { incompatible = error; } assert.equal(incompatible.code, 'INCOMPATIBLE_API'); const failed = harborReducer(initialHarborState, { type: 'sync-failed', error: incompatible }); assert.equal(failed.transport.bootStatus, 'incompatible-api'); }); test('data invariant: an older polling promise cannot replace a newer mutation snapshot', async () => { let state = receive(initialHarborState, snapshot(1, 'one')); const poll = deferred(); const mutation = deferred(); const apply = (promise) => promise.then((value) => { state = receive(state, value); }); const pollApplied = apply(poll.promise); const mutationApplied = apply(mutation.promise); mutation.resolve(snapshot(3, 'two')); await mutationApplied; poll.resolve(snapshot(2, 'one')); await pollApplied; assert.equal(state.snapshot.revision, 3); assert.equal(state.snapshot.selection.desiredServerId, 'two'); }); test('an equal revision keeps the current snapshot identity', () => { const state = receive(initialHarborState, snapshot(4, 'one')); const next = receive(state, snapshot(4, 'two')); assert.equal(next.snapshot, state.snapshot); assert.equal(next.snapshot.selection.desiredServerId, 'one'); }); test('selection has no client-side shadow and follows canonical profile snapshots', () => { const state = receive(initialHarborState, snapshot(2, 'two')); assert.equal(Object.hasOwn(state, 'pendingServerId'), false); assert.equal(state.snapshot.profiles[0].desiredServerId, 'two'); }); test('data invariant: initial control outage is retryable without a fabricated snapshot', () => { const serverError = Object.assign(new Error('Internal Server Error'), { status: 500 }); const refused = new TypeError('fetch failed'); assert.equal(classifySyncError(serverError), 'control-unreachable'); assert.equal(classifySyncError(refused), 'control-unreachable'); const failed = harborReducer(initialHarborState, { type: 'sync-failed', error: serverError }); assert.equal(failed.transport.bootStatus, 'control-unreachable'); assert.equal(failed.snapshot, null); }); test('ready state becomes stale after three failures and recovers on success', () => { const known = snapshot(1, 'one'); let state = receive(initialHarborState, known); const error = new TypeError('fetch failed'); for (let attempt = 0; attempt < 3; attempt += 1) { state = harborReducer(state, { type: 'sync-failed', error }); } assert.equal(state.snapshot, known); assert.equal(state.transport.stale, true); assert.equal(state.transport.consecutiveFailures, 3); state = receive(state, snapshot(2, 'two')); assert.equal(state.transport.stale, false); assert.equal(state.transport.consecutiveFailures, 0); assert.equal(state.snapshot.selection.desiredServerId, 'two'); });