import assert from 'node:assert/strict'; import test from 'node:test'; import { classifySyncError, harborReducer, initialHarborState, } from '../../src/web/state/harborReducer.js'; const snapshot = (revision, desiredServerId = '', serverIds = ['one', 'two']) => ({ apiVersion: 1, revision, selection: { 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('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('pending selection survives polling until canonical state acknowledges it', () => { let state = receive(initialHarborState, snapshot(1, 'one')); state = harborReducer(state, { type: 'select-server', serverId: 'two' }); state = receive(state, snapshot(2, 'one')); assert.equal(state.pendingServerId, 'two'); state = receive(state, snapshot(3, 'two')); assert.equal(state.pendingServerId, ''); }); test('pending selection is cleared when its server disappears', () => { let state = receive(initialHarborState, snapshot(1, 'one')); state = harborReducer(state, { type: 'select-server', serverId: 'two' }); assert.equal(receive(state, snapshot(2, 'one', ['one'])).pendingServerId, ''); }); test('initial 500 and connection refusal become retryable boot failures', () => { 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'); });