Files
harbor-net/test/server/gateway-presence.test.js
Dmitriy Petrov 56f5e408e3
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 6s
Handle rejected subscriptions and add local client compose
2026-07-13 20:23:37 +03:00

180 lines
6.2 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
applyGatewayPreference,
buildGatewayPresence,
createGatewayAutoState,
nextGatewayAutoState,
probeGatewayPresence,
readHostNetworkState,
verifyGatewayPresence,
} from '../../src/server/gatewayPresence.js';
import { createStateSnapshot } from '../../src/shared/contracts/state.js';
const subscriptionUrl = 'https://subscription.example/0123456789abcdef0123456789abcdef';
const nonce = '0123456789abcdef0123456789abcdef';
test('Gateway presence is authenticated by the shared subscription secret', async () => {
const payload = buildGatewayPresence({
appMode: 'gateway',
subscriptionUrl,
gatewayId: 'gateway-1',
nonce,
});
assert.equal(verifyGatewayPresence(payload, { subscriptionUrl, nonce }), true);
assert.equal(verifyGatewayPresence(payload, {
subscriptionUrl: 'https://subscription.example/fedcba9876543210fedcba9876543210',
nonce,
}), false);
const result = await probeGatewayPresence({
gateway: '192.168.50.111',
subscriptionUrl,
port: 4567,
nonce,
fetchImpl: async (url) => {
assert.equal(
url,
`http://192.168.50.111:4567/api/gateway-presence?nonce=${nonce}`,
);
return { ok: true, json: async () => payload };
},
});
assert.equal(result.gatewayId, 'gateway-1');
assert.equal(result.uiOrigin, 'http://192.168.50.111:4567');
assert.equal(Number.isFinite(Date.parse(result.verifiedAt)), true);
assert.equal(buildGatewayPresence({
appMode: 'gateway',
subscriptionUrl: 'https://subscription.example/public-feed',
gatewayId: 'gateway-1',
nonce,
}).available, false);
assert.equal(buildGatewayPresence({
appMode: 'gateway',
subscriptionUrl: 'https://subscription.example/0123456789abcdef',
gatewayId: 'gateway-1',
nonce,
}).available, true);
const sharedPublicValue = 'https://public.example/sing-box-configuration-v1';
const firstUrl = `${subscriptionUrl}?redirect=${encodeURIComponent(sharedPublicValue)}`;
const secondUrl = `https://subscription.example/fedcba9876543210fedcba9876543210?redirect=${encodeURIComponent(sharedPublicValue)}`;
const firstPayload = buildGatewayPresence({
appMode: 'gateway',
subscriptionUrl: firstUrl,
gatewayId: 'gateway-1',
nonce,
});
assert.equal(verifyGatewayPresence(firstPayload, {
subscriptionUrl: secondUrl,
nonce,
}), false);
});
test('host route freshness and Gateway failures drive a safe automatic fallback', () => {
const now = Date.now();
const statePath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-route-')), 'network.json');
fs.writeFileSync(statePath, JSON.stringify({
gateway: '192.168.50.111',
interface: 'en0',
mac: 'aa:bb:cc:dd:ee:ff',
observedAt: new Date(now).toISOString(),
}));
const network = readHostNetworkState(statePath, { now });
assert.equal(network.gateway, '192.168.50.111');
let state = nextGatewayAutoState(createGatewayAutoState(), {
network,
verifiedGateway: {
gatewayId: 'gateway-1',
uiOrigin: 'http://192.168.50.111:4567',
verifiedAt: new Date(now).toISOString(),
},
});
assert.equal(state.mode, 'gateway-direct');
assert.equal(state.uiOrigin, 'http://192.168.50.111:4567');
assert.equal(state.lastVerifiedAt, new Date(now).toISOString());
state = nextGatewayAutoState(state, { network });
state = nextGatewayAutoState(state, { network });
assert.equal(state.mode, 'gateway-direct');
state = nextGatewayAutoState(state, { network });
assert.equal(state.mode, 'local-vpn');
const newNetwork = { ...network, mac: '11:22:33:44:55:66' };
state = nextGatewayAutoState(nextGatewayAutoState(createGatewayAutoState(), {
network,
verifiedGateway: { gatewayId: 'gateway-1' },
}), { network: newNetwork });
assert.equal(state.mode, 'local-vpn');
assert.equal(readHostNetworkState(statePath, { now: now + 16_000 }), null);
fs.writeFileSync(statePath, JSON.stringify({
gateway: '192.168.50.111',
interface: 'en0',
mac: '',
observedAt: new Date(now).toISOString(),
}));
assert.equal(readHostNetworkState(statePath, { now }), null);
});
test('canonical route distinguishes fresh, stale, lost, disabled and local states', () => {
const storedState = {
revision: 1,
subscriptionUrl,
gatewayAutoEnabled: true,
};
const snapshot = (gatewayAuto, stored = storedState) => createStateSnapshot({
storedState: stored,
runtime: { running: true },
gatewayAuto,
appMode: 'client',
configExists: true,
subscriptionHost: 'subscription.example/…',
now: new Date('2026-07-13T12:00:00.000Z'),
}).route;
const fresh = {
...createGatewayAutoState(),
mode: 'gateway-direct',
gateway: { gateway: '192.168.50.111' },
gatewayId: 'gateway-1',
uiOrigin: 'http://192.168.50.111:4567',
lastVerifiedAt: '2026-07-13T11:59:59.000Z',
};
const found = snapshot(fresh);
assert.equal(found.mode, 'gateway-direct');
assert.equal(found.reason, 'gateway-found');
assert.equal(found.gatewayAddress, '192.168.50.111');
assert.equal(found.gatewayUiOrigin, 'http://192.168.50.111:4567');
assert.equal(found.lastVerifiedAt, '2026-07-13T11:59:59.000Z');
assert.equal(found.autoEnabled, true);
assert.equal(found.fallbackPreference, 'local-vpn');
assert.equal(snapshot({ ...fresh, failures: 1 }).reason, 'gateway-stale');
assert.equal(snapshot({ ...fresh, mode: 'local-vpn', gatewayId: '', lastError: 'lost' }).reason, 'gateway-lost');
assert.equal(snapshot(fresh, { ...storedState, gatewayAutoEnabled: false }).reason, 'disabled');
assert.equal(snapshot(createGatewayAutoState()).reason, 'local');
});
test('client can ignore and restore a verified Gateway without losing discovery', () => {
const detected = {
...createGatewayAutoState(),
mode: 'gateway-direct',
gatewayId: 'gateway-1',
};
const ignored = applyGatewayPreference(detected, false);
assert.equal(ignored.mode, 'local-vpn');
assert.equal(ignored.gatewayId, 'gateway-1');
assert.equal(applyGatewayPreference(ignored, true).mode, 'gateway-direct');
assert.equal(applyGatewayPreference(createGatewayAutoState(), true).mode, 'local-vpn');
});