import assert from 'node:assert/strict'; import test from 'node:test'; import { buildDevicePolicyRestore, createDevicePolicyService, fingerprintDirectDevices, normalizeDirectDevices, } from '../../src/server/services/devicePolicyService.js'; const directDevice = { id: 'dev_0011223344556677', ip: '192.168.50.7', mac: '00:11:22:33:44:55', interface: 'eth0', }; test('device policy rules match the full identity before the TPROXY fallback', () => { assert.deepEqual(normalizeDirectDevices([{ ...directDevice, mac: directDevice.mac.toUpperCase() }]), [directDevice]); assert.throws(() => normalizeDirectDevices([directDevice, directDevice]), /повторяющаяся/); assert.throws(() => normalizeDirectDevices([{ ...directDevice, interface: 'br-user' }]), /identity/); const restore = buildDevicePolicyRestore({ devices: [directDevice], chain: 'VPN_PROXY_DEVICE_POLICY', slot: 'B', tproxyPort: 7895, tproxyMark: '1', }); assert.equal(restore, [ '*mangle', '-F VPN_PROXY_DEVICE_POLICY_B', '-A VPN_PROXY_DEVICE_POLICY_B -i eth0 -s 192.168.50.7 -m mac --mac-source 00:11:22:33:44:55 -m comment --comment harbor-policy:dev_0011223344556677:direct -j RETURN', '-A VPN_PROXY_DEVICE_POLICY_B -p tcp -j TPROXY --on-port 7895 --tproxy-mark 1/1', '-A VPN_PROXY_DEVICE_POLICY_B -p udp -j TPROXY --on-port 7895 --tproxy-mark 1/1', 'COMMIT', '', ].join('\n')); assert.match(fingerprintDirectDevices([directDevice]), /^[a-f0-9]{64}$/); }); test('device policy swaps one prepared slot and preserves the active slot on failure', async () => { const commands = []; let failSwap = false; const generations = ['epoch-a', 'rules-b', 'rules-a']; const service = createDevicePolicyService({ chain: 'VPN_PROXY_DEVICE_POLICY', tproxyPort: 7895, tproxyMark: '1', nextGeneration: () => generations.shift(), now: () => new Date('2026-08-07T12:00:00.000Z'), run: (command, args, options) => { commands.push({ command, args, options }); if (failSwap && args.includes('-R')) return { status: 1, stderr: 'swap failed' }; return { status: 0, stdout: '', stderr: '' }; }, }); const initial = service.snapshot(); assert.equal((await service.apply([])).generation, initial.generation); assert.equal(commands.length, 0); const applied = await service.apply([directDevice]); assert.deepEqual(applied.appliedIds, [directDevice.id]); assert.equal(applied.changed, true); assert.deepEqual(commands.at(-1).args, [ '-w', '1', '-t', 'mangle', '-R', 'VPN_PROXY_DEVICE_POLICY', '1', '-j', 'VPN_PROXY_DEVICE_POLICY_B', ]); assert.equal(commands[0].command, 'iptables-restore'); assert.match(commands[0].options.input, /--mac-source 00:11:22:33:44:55/); assert.equal(commands.length, 2); assert.equal(commands.every(({ options }) => options.timeout === 2_000), true); const commandCount = commands.length; assert.equal((await service.apply([directDevice])).changed, false); assert.equal(commands.length, commandCount); failSwap = true; await assert.rejects(service.apply([]), /swap failed/); assert.deepEqual(service.snapshot().appliedIds, [directDevice.id]); assert.equal(service.snapshot().generation, applied.generation); const prepareCommands = []; const prepareFailure = createDevicePolicyService({ chain: 'VPN_PROXY_DEVICE_POLICY', tproxyPort: 7895, tproxyMark: '1', nextGeneration: () => 'prepare-epoch', run: (command, args, options) => { prepareCommands.push({ command, args, options }); return command === 'iptables-restore' ? { status: 1, stderr: 'prepare failed' } : { status: 0, stdout: '', stderr: '' }; }, }); await assert.rejects(prepareFailure.apply([directDevice]), /prepare failed/); assert.equal(prepareCommands.some(({ args }) => args.includes('-R')), false); assert.deepEqual(prepareFailure.snapshot().appliedIds, []); }); test('the maximum policy set stays inside the control request deadline', async () => { const devices = Array.from({ length: 512 }, (_, index) => { const mac = index.toString(16).padStart(12, '0').match(/../g).join(':'); return { id: `dev_${index.toString(16).padStart(16, '0')}`, ip: `192.168.${50 + Math.floor(index / 254)}.${(index % 254) + 1}`, mac, interface: 'eth0', }; }); const calls = []; const service = createDevicePolicyService({ chain: 'VPN_PROXY_DEVICE_POLICY', tproxyPort: 7895, tproxyMark: '1', nextGeneration: () => 'generation', run: (command, args, options) => { calls.push({ command, args, options }); return { status: 0, stdout: '', stderr: '' }; }, }); await service.apply(devices); assert.deepEqual(calls.map(({ command }) => command), ['iptables-restore', 'iptables']); assert.equal(calls.reduce((sum, { options }) => sum + options.timeout, 0), 4_000); });