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'; 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, nonce, fetchImpl: async (url) => { assert.equal( url, `http://192.168.50.111:3456/api/gateway-presence?nonce=${nonce}`, ); return { ok: true, json: async () => payload }; }, }); assert.deepEqual(result, { gatewayId: 'gateway-1' }); assert.equal(buildGatewayPresence({ appMode: 'gateway', subscriptionUrl: 'https://subscription.example/public-feed', gatewayId: 'gateway-1', nonce, }).available, false); 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' }, }); assert.equal(state.mode, 'gateway-direct'); 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('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'); });