Files
harbor-net/test/web/harbor-state.test.js
dokril 34d8b681ad
Build and Deploy Gateway / build-and-push (push) Failing after 2s
Build and Deploy Gateway / deploy (push) Has been skipped
Refactor VPN proxy client implementation
2026-08-09 00:41:52 +03:00

135 lines
4.7 KiB
JavaScript

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,
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('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('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('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');
});