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 { parseNeighborSnapshot, readNeighborSnapshot } from '../../dist/server/adapters/neighbors.js'; import { createDeviceInventoryService, createVendorLookup, DEVICE_INVENTORY_SCHEMA_VERSION, deviceId, migrateDeviceInventoryState, } from '../../dist/server/services/deviceInventoryService.js'; import { fingerprintDirectDevices } from '../../dist/server/services/devicePolicyService.js'; import { createJsonStore } from '../../dist/server/services/stateStore.js'; test('container neighbors are hidden while a real 172 LAN device remains valid', () => { const observedAt = '2026-08-07T12:00:00.000Z'; const parsed = parseNeighborSnapshot([ { dst: '172.17.0.2', dev: 'docker0', lladdr: '00:11:22:33:44:51', state: ['REACHABLE'] }, { dst: '172.18.0.2', dev: 'br-1234', lladdr: '00:11:22:33:44:52', state: ['REACHABLE'] }, { dst: '172.19.0.2', dev: 'veth1234', lladdr: '00:11:22:33:44:53', state: ['REACHABLE'] }, { dst: '172.20.0.7', dev: 'eth0', lladdr: '00:11:22:33:44:54', state: ['REACHABLE'] }, ], observedAt); assert.deepEqual(parsed.map(({ ip, interface: deviceInterface }) => [ip, deviceInterface]), [ ['172.20.0.7', 'eth0'], ]); const storedDevice = (ip, deviceInterface) => ({ id: `dev_${ip.replaceAll('.', '').padEnd(16, '0').slice(0, 16)}`, alias: '', pinned: false, hostname: null, manufacturer: null, mac: ip === '172.17.0.2' ? '00:11:22:33:44:51' : '00:11:22:33:44:54', ip, interface: deviceInterface, firstSeenAt: observedAt, lastSeenAt: observedAt, source: 'neighbor', confidence: 'high', }); const migrated = migrateDeviceInventoryState({ schemaVersion: 2, devices: [storedDevice('172.17.0.2', 'docker0'), storedDevice('172.20.0.7', 'eth0')], traffic: { baselinesByMac: {}, totalsByMac: {}, rebaselineMacs: [] }, }); assert.deepEqual(migrated.devices.map(({ ip }) => ip), ['172.20.0.7']); }); test('malformed persisted devices and remote observations cannot enter canonical inventory', async (t) => { const observedAt = '2026-08-08T12:00:00.000Z'; const mac = '00:11:22:33:44:55'; const persisted = { id: 'legacy-device-id', alias: 42, pinned: 'yes', hostname: 42, manufacturer: [], mac, ip: '192.168.50.7', interface: 'eth0', firstSeenAt: observedAt, lastSeenAt: observedAt, source: null, confidence: 'untrusted', }; const migrated = migrateDeviceInventoryState({ schemaVersion: DEVICE_INVENTORY_SCHEMA_VERSION, devices: [ persisted, { ...persisted, mac: 'invalid' }, { ...persisted, mac: '00:11:22:33:44:66', ip: 'not-an-ip' }, { ...persisted, mac: '00:11:22:33:44:77', lastSeenAt: 'not-a-date' }, ], }); assert.deepEqual(migrated.devices, [{ ...persisted, id: deviceId(mac), alias: '', pinned: false, deprioritized: false, hostname: null, manufacturer: null, source: 'neighbor', confidence: 'high', }]); const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-guard-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {}, migrate: migrateDeviceInventoryState, }); store.remove(); const service = createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: [ { ip: '192.168.50.8', mac: '00:11:22:33:44:88', interface: 'eth0', observedAt, active: true }, { ip: 'bad', mac: '00:11:22:33:44:99', interface: 'eth0', observedAt, active: true }, { ip: '192.168.50.9', mac: 'invalid', interface: 'eth0', observedAt, active: true }, { ip: '192.168.50.10', mac: '00:11:22:33:44:aa', interface: 'docker0', observedAt, active: true }, { ip: '192.168.50.11', mac: '00:11:22:33:44:bb', interface: 'eth0', observedAt, active: 'yes' }, { ip: '192.168.50.12', mac: '00:11:22:33:44:cc', interface: 'eth0', observedAt: 'bad', active: true }, ], }), now: () => new Date(observedAt), }); const snapshot = await service.refresh(); assert.deepEqual(snapshot.devices.map(({ mac: deviceMac, ip }) => [deviceMac, ip]), [ ['00:11:22:33:44:88', '192.168.50.8'], ]); }); test('device inventory resolves hostnames without making reverse lookup a refresh dependency', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-hostname-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {} }); const observedAt = '2026-08-11T12:00:00.000Z'; const mac = '00:11:22:33:44:55'; let ip = '192.168.50.10'; let lookupFails = false; const lookups = []; const service = createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: [{ ip, mac, interface: 'br0', observedAt, active: true }], }), resolveHostname: async (address) => { lookups.push(address); if (lookupFails) throw new Error('reverse DNS unavailable'); return 'living-room.local.'; }, now: () => new Date(observedAt), }); let snapshot = await service.refresh(); assert.equal(snapshot.devices[0].hostname, 'living-room.local'); snapshot = await service.refresh(); assert.deepEqual(lookups, ['192.168.50.10']); ip = '192.168.50.11'; lookupFails = true; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].ip, ip); assert.equal(snapshot.devices[0].hostname, 'living-room.local'); assert.deepEqual(lookups, ['192.168.50.10', '192.168.50.11']); }); test('device inventory discovers, merges, persists metadata and expires devices after 30 days', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-devices-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {} }); const ouiPath = path.join(directory, 'oui.txt'); fs.writeFileSync(ouiPath, '00-11-22 (hex)\t\tExample Devices\n'); const vendor = createVendorLookup(ouiPath); assert.equal(vendor('00:11:22:33:44:55'), 'Example Devices'); assert.equal(vendor('02:11:22:33:44:55'), null); let current = new Date('2026-07-01T10:00:00.000Z'); let observation = { observedAt: current.toISOString(), observations: parseNeighborSnapshot([ { dst: '2001:db8::10', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['STALE'] }, { dst: '192.168.50.10', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['REACHABLE'] }, { dst: '192.168.50.10', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['STALE'] }, { dst: '192.168.50.11', dev: 'br0', state: ['INCOMPLETE'] }, ], current.toISOString()), error: null, }; let observeCalls = 0; const service = createDeviceInventoryService({ store, observe: async () => { observeCalls += 1; return observation; }, vendor, now: () => current, }); const [firstRefresh, sharedRefresh] = await Promise.all([service.refresh(), service.refresh()]); let snapshot = firstRefresh; assert.strictEqual(sharedRefresh, firstRefresh); assert.equal(observeCalls, 1); assert.equal(snapshot.devices.length, 1); assert.equal(snapshot.devices[0].manufacturer, 'Example Devices'); assert.equal(snapshot.devices[0].status, 'online'); assert.equal(snapshot.devices[0].confidence, 'high'); assert.equal(snapshot.devices[0].interface, 'br0'); observation = { observedAt: current.toISOString(), observations: parseNeighborSnapshot([ { dst: '192.168.50.10', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['REACHABLE'] }, { dst: '192.168.50.12', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['STALE'] }, ], current.toISOString()), error: null, }; snapshot = await service.refresh(); assert.equal(snapshot.devices.length, 1); assert.equal(snapshot.devices[0].confidence, 'ambiguous'); assert.equal(snapshot.devices[0].ip, '192.168.50.10'); current = new Date('2026-07-01T10:05:00.000Z'); observation = { observedAt: current.toISOString(), observations: parseNeighborSnapshot([ { dst: '192.168.50.10', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['STALE'] }, ], current.toISOString()), error: null, }; const firstSeen = snapshot.devices[0].lastSeenAt; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].lastSeenAt, firstSeen); assert.equal(snapshot.devices[0].status, 'recent'); snapshot = service.update(snapshot.devices[0].id, { alias: 'Телевизор', pinned: true }, snapshot.revision); const namedId = snapshot.devices[0].id; const restarted = createDeviceInventoryService({ store, observe: async () => observation, vendor, now: () => current }); assert.deepEqual(restarted.snapshot().devices[0], snapshot.devices[0]); assert.throws( () => restarted.update(snapshot.devices[0].id, { pinned: false }, snapshot.revision - 1), (error) => error.code === 'STATE_CONFLICT', ); current = new Date('2026-07-01T10:06:00.000Z'); observation = { observedAt: current.toISOString(), observations: parseNeighborSnapshot([ { dst: '192.168.50.18', dev: 'br0', lladdr: '00:11:22:33:44:55', state: ['REACHABLE'] }, ], current.toISOString()), error: null, }; snapshot = await restarted.refresh(); assert.equal(snapshot.devices.length, 1); assert.equal(snapshot.devices[0].id, namedId); assert.equal(snapshot.devices[0].ip, '192.168.50.18'); assert.equal(snapshot.devices[0].alias, 'Телевизор'); assert.equal(snapshot.devices[0].pinned, true); observation = { observedAt: current.toISOString(), observations: parseNeighborSnapshot([ { dst: '192.168.50.18', dev: 'br0', lladdr: '00:11:22:33:44:66', state: ['REACHABLE'] }, ], current.toISOString()), error: null, }; snapshot = await restarted.refresh(); const replacement = snapshot.devices.find(({ mac: deviceMac }) => deviceMac === '00:11:22:33:44:66'); assert.notEqual(replacement.id, namedId); assert.equal(replacement.alias, ''); assert.equal(replacement.pinned, false); snapshot = restarted.update(namedId, { deprioritized: true }, snapshot.revision); const deprioritized = snapshot.devices.find(({ id }) => id === namedId); assert.equal(deprioritized.pinned, false); assert.equal(deprioritized.deprioritized, true); assert.throws( () => restarted.update(namedId, { pinned: true, deprioritized: true }, snapshot.revision), (error) => error.code === 'REQUEST_INVALID', ); const persisted = createDeviceInventoryService({ store, observe: async () => observation, vendor, now: () => current }); snapshot = persisted.snapshot(); assert.equal(snapshot.devices.find(({ id }) => id === namedId).deprioritized, true); snapshot = persisted.update(replacement.id, { pinned: true }, snapshot.revision); assert.equal(snapshot.devices.find(({ id }) => id === replacement.id).pinned, true); observation = { observedAt: current.toISOString(), observations: [], error: 'source unavailable' }; snapshot = await persisted.refresh(); assert.equal(snapshot.devices.length, 2); assert.equal(snapshot.source.error, 'source unavailable'); current = new Date('2026-08-02T10:00:00.000Z'); observation = { observedAt: current.toISOString(), observations: [], error: 'source unavailable' }; snapshot = await persisted.refresh(); assert.equal(snapshot.devices.length, 2); assert.equal(snapshot.source.error, 'source unavailable'); observation = { observedAt: current.toISOString(), observations: [], error: null }; snapshot = await persisted.refresh(); assert.equal(snapshot.devices.length, 0); const failed = readNeighborSnapshot(() => ({ status: 1, stderr: 'not available' }), () => current); assert.deepEqual(failed.observations, []); assert.match(failed.error, /not available/); }); test('device traffic totals persist exact deltas across polls and process epochs', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-traffic-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {}, migrate: migrateDeviceInventoryState, }); const observedAt = '2026-08-07T12:00:00.000Z'; const mac = '00:11:22:33:44:55'; const neighbor = { observedAt, observations: [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true, }], error: null, }; let traffic = { epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, devices: [{ ip: '192.168.50.7', mac, interface: 'eth0', uploadBytes: '9007199254740993', downloadBytes: '100', proxyUploadBytes: '1000', proxyDownloadBytes: '2000', }], }; let trafficError = null; const createService = () => createDeviceInventoryService({ store, observe: () => neighbor, observeTraffic: () => { if (trafficError) throw trafficError; return traffic; }, }); let service = createService(); let snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254740993'); assert.equal(snapshot.devices[0].downloadBytes, '100'); assert.equal(snapshot.devices[0].trafficObservedAt, observedAt); assert.equal(snapshot.devices[0].proxyUploadBytes, '1000'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '2000'); assert.deepEqual(snapshot.source.traffic, { lastObservedAt: observedAt, error: null, proxy: { lastObservedAt: observedAt, error: null }, }); assert.deepEqual(snapshot.traffic, { gatewayBytes: '9007199254741093', proxyBytes: '3000', uploadBytes: '9007199254741993', downloadBytes: '2100', totalBytes: '9007199254744093', gatewayObservedAt: observedAt, proxyObservedAt: observedAt, observedAt, history: [], }); snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254740993'); assert.equal(snapshot.devices[0].downloadBytes, '100'); traffic = { ...traffic, devices: [{ ...traffic.devices[0], uploadBytes: '9007199254740995', downloadBytes: '150', proxyUploadBytes: '1010', proxyDownloadBytes: '2050', }], }; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254740995'); assert.equal(snapshot.devices[0].downloadBytes, '150'); assert.equal(snapshot.devices[0].proxyUploadBytes, '1010'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '2050'); assert.equal(snapshot.traffic.gatewayBytes, '9007199254741145'); assert.equal(snapshot.traffic.proxyBytes, '3060'); assert.equal(snapshot.traffic.uploadBytes, '9007199254742005'); assert.equal(snapshot.traffic.downloadBytes, '2200'); service = createService(); snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254740995'); assert.equal(snapshot.devices[0].downloadBytes, '150'); traffic = { ...traffic, epoch: 'epoch-b', generation: 'rules-b', devices: [{ ...traffic.devices[0], uploadBytes: '10', downloadBytes: '20', proxyUploadBytes: '3', proxyDownloadBytes: '4', }], }; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254741005'); assert.equal(snapshot.devices[0].downloadBytes, '170'); assert.equal(snapshot.devices[0].proxyUploadBytes, '1013'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '2054'); assert.equal(snapshot.traffic.totalBytes, '9007199254744242'); traffic = { ...traffic, devices: [{ ...traffic.devices[0], uploadBytes: '9', downloadBytes: '20' }], }; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254741005'); assert.match(snapshot.source.traffic.error, /уменьшился/); traffic = { ...traffic, observedAt: '2026-08-07T12:00:15.000Z', devices: [{ ...traffic.devices[0], uploadBytes: '12', downloadBytes: '25', proxyUploadBytes: '2', proxyDownloadBytes: '4', }], }; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254741007'); assert.equal(snapshot.devices[0].downloadBytes, '175'); assert.equal(snapshot.devices[0].proxyUploadBytes, '1013'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '2054'); assert.equal(snapshot.source.traffic.error, null); assert.match(snapshot.source.traffic.proxy.error, /уменьшился/); assert.equal(snapshot.traffic.proxyBytes, '3067'); assert.equal(snapshot.traffic.gatewayObservedAt, '2026-08-07T12:00:15.000Z'); assert.equal(snapshot.traffic.proxyObservedAt, observedAt); assert.equal(snapshot.traffic.observedAt, observedAt); trafficError = new Error('traffic unavailable'); snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '9007199254741007'); assert.equal(snapshot.source.traffic.lastObservedAt, '2026-08-07T12:00:15.000Z'); assert.equal(snapshot.source.traffic.error, 'traffic unavailable'); assert.equal(snapshot.traffic.totalBytes, '9007199254744249'); }); test('device traffic history stays in bounded service memory and survives client snapshots', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-history-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const filePath = path.join(directory, 'devices.json'); const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState }); const mac = '00:11:22:33:44:55'; let observedAt = '2026-08-07T12:00:00.000Z'; let uploadBytes = '100'; let proxyDownloadBytes = '20'; const createService = () => createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }], }), observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, devices: [{ ip: '192.168.50.7', mac, interface: 'eth0', uploadBytes, downloadBytes: '0', proxyUploadBytes: '0', proxyDownloadBytes, }], }), }); const service = createService(); assert.deepEqual((await service.refresh()).devices[0].trafficHistory, []); observedAt = '2026-08-07T12:00:15.000Z'; uploadBytes = '130'; proxyDownloadBytes = '25'; const snapshot = await service.refresh(); assert.equal(snapshot.trafficHistoryCapacity, 120); assert.deepEqual(snapshot.devices[0].trafficHistory, [{ observedAt, gatewayBytes: '30', proxyBytes: '5', uploadBytes: '30', downloadBytes: '5', }]); assert.deepEqual(snapshot.traffic.history, [{ observedAt, gatewayBytes: '30', proxyBytes: '5', uploadBytes: '30', downloadBytes: '5', }]); assert.deepEqual(service.snapshot().devices[0].trafficHistory, snapshot.devices[0].trafficHistory); assert.doesNotMatch(fs.readFileSync(filePath, 'utf8'), /trafficHistory/); assert.deepEqual(createService().snapshot().devices[0].trafficHistory, []); assert.deepEqual(createService().snapshot().traffic.history, []); }); test('device outbound history keeps sing-box routes and kernel Direct on separate reset-safe series', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-outbound-history-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const filePath = path.join(directory, 'devices.json'); const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState }); const mac = '00:11:22:33:44:55'; const id = deviceId(mac); let observedAt = '2026-08-12T12:00:00.000Z'; let epoch = 'epoch-a'; let vpn = ['100', '200']; let trackedDirect = ['10', '20']; let unknown = ['1', '2']; let directIpv4 = ['40', '60']; let gateway = ['100', '200']; let proxyIngress = ['10', '20']; const createService = () => createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }], }), observeTraffic: () => ({ epoch, generation: 'rules-a', observedAt, source: { error: null }, direct: { uploadBytes: directIpv4[0], downloadBytes: directIpv4[1] }, devices: [{ ip: '192.168.50.7', mac, interface: 'eth0', uploadBytes: gateway[0], downloadBytes: gateway[1], proxyUploadBytes: proxyIngress[0], proxyDownloadBytes: proxyIngress[1], directUploadBytes: directIpv4[0], directDownloadBytes: directIpv4[1], }], }), observeDomainTraffic: () => ({ epoch, observedAt, source: { error: null }, routes: [ { deviceId: id, source: 'gateway', outbound: 'vpn', uploadBytes: vpn[0], downloadBytes: vpn[1] }, { deviceId: id, source: 'proxy', outbound: 'direct', uploadBytes: trackedDirect[0], downloadBytes: trackedDirect[1] }, { deviceId: id, source: 'gateway', outbound: 'unknown', uploadBytes: unknown[0], downloadBytes: unknown[1] }, ], series: [], }), }); let service = createService(); let snapshot = await service.refresh(); assert.deepEqual(snapshot.devices[0].outboundTraffic, { observedAt, singboxObservedAt: observedAt, directIpv4ObservedAt: observedAt, vpnBytes: '300', directTrackedBytes: '30', directIpv4Bytes: '100', unknownBytes: '3', }); assert.deepEqual(snapshot.devices[0].outboundTrafficHistory, []); observedAt = '2026-08-12T12:00:15.000Z'; vpn = ['150', '250']; trackedDirect = ['30', '50']; unknown = ['4', '8']; directIpv4 = ['70', '90']; snapshot = await service.refresh(); assert.deepEqual(snapshot.devices[0].outboundTraffic, { observedAt, singboxObservedAt: observedAt, directIpv4ObservedAt: observedAt, vpnBytes: '400', directTrackedBytes: '80', directIpv4Bytes: '160', unknownBytes: '12', }); assert.deepEqual(snapshot.devices[0].outboundTrafficHistory, [{ observedAt, vpnBytes: '100', directTrackedBytes: '50', directIpv4Bytes: '60', unknownBytes: '9', }]); const globalBeforeReset = structuredClone(snapshot.traffic); const resetRevision = snapshot.revision; snapshot = await service.resetTraffic(resetRevision); assert.equal(snapshot.devices[0].uploadBytes, '0'); assert.equal(snapshot.devices[0].downloadBytes, '0'); assert.equal(snapshot.devices[0].proxyUploadBytes, '0'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '0'); assert.deepEqual(snapshot.devices[0].trafficHistory, []); assert.deepEqual(snapshot.devices[0].outboundTraffic, { observedAt, singboxObservedAt: observedAt, directIpv4ObservedAt: observedAt, vpnBytes: '0', directTrackedBytes: '0', directIpv4Bytes: '0', unknownBytes: '0', }); assert.deepEqual(snapshot.devices[0].outboundTrafficHistory, []); assert.deepEqual(snapshot.traffic, globalBeforeReset); assert.deepEqual(store.read().traffic.outboundBaselinesByDeviceId[id], { routeEpoch: 'epoch-a', directEpoch: 'epoch-a', vpnBytes: '400', directTrackedBytes: '80', directIpv4Bytes: '160', unknownBytes: '12', }); assert.equal(service.metricsSnapshot().domainTraffic.routes[0].uploadBytes, '150'); assert.equal(service.metricsSnapshot().directTraffic.series[0].uploadBytes, '70'); await assert.rejects( service.resetTraffic(resetRevision), (error) => error.code === 'STATE_CONFLICT', ); observedAt = '2026-08-12T12:00:30.000Z'; gateway = ['115', '225']; proxyIngress = ['13', '24']; vpn = ['160', '260']; trackedDirect = ['35', '55']; unknown = ['4', '8']; directIpv4 = ['75', '95']; service = createService(); snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '15'); assert.equal(snapshot.devices[0].downloadBytes, '25'); assert.equal(snapshot.devices[0].proxyUploadBytes, '3'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '4'); assert.deepEqual(snapshot.devices[0].outboundTraffic, { observedAt, singboxObservedAt: observedAt, directIpv4ObservedAt: observedAt, vpnBytes: '20', directTrackedBytes: '10', directIpv4Bytes: '10', unknownBytes: '0', }); observedAt = '2026-08-12T12:00:45.000Z'; epoch = 'epoch-b'; vpn = ['3', '4']; trackedDirect = ['1', '2']; unknown = ['0', '1']; directIpv4 = ['5', '6']; snapshot = await service.refresh(); assert.deepEqual(snapshot.devices[0].outboundTraffic, { observedAt, singboxObservedAt: observedAt, directIpv4ObservedAt: observedAt, vpnBytes: '7', directTrackedBytes: '3', directIpv4Bytes: '11', unknownBytes: '1', }); assert.deepEqual(snapshot.devices[0].outboundTrafficHistory.at(-1), { observedAt, vpnBytes: '0', directTrackedBytes: '0', directIpv4Bytes: '0', unknownBytes: '0', }); assert.doesNotMatch(fs.readFileSync(filePath, 'utf8'), /outboundTrafficHistory/); assert.doesNotMatch(fs.readFileSync(filePath, 'utf8'), /outboundTraffic/); }); test('global traffic stays monotonic when a device expires and returns in the same epoch', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-global-traffic-retention-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {}, migrate: migrateDeviceInventoryState, }); const mac = '00:11:22:33:44:55'; let observedAt = '2026-07-01T12:00:00.000Z'; let present = true; let uploadBytes = '100'; const service = createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: present ? [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }] : [], }), observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, devices: [{ mac, uploadBytes, downloadBytes: '0' }], }), }); assert.equal((await service.refresh()).traffic.totalBytes, '100'); present = false; observedAt = '2026-08-07T12:00:00.000Z'; let snapshot = await service.refresh(); assert.equal(snapshot.devices.length, 0); assert.equal(snapshot.traffic.totalBytes, '100'); present = true; snapshot = await service.refresh(); assert.equal(snapshot.traffic.totalBytes, '100'); uploadBytes = '110'; assert.equal((await service.refresh()).traffic.totalBytes, '110'); }); test('legacy dataplane samples preserve saved proxy totals while Gateway totals keep advancing', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-proxy-legacy-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {}, migrate: migrateDeviceInventoryState, }); const observedAt = '2026-08-07T12:00:00.000Z'; const mac = '00:11:22:33:44:55'; const neighbor = { observedAt, error: null, observations: [ { ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }, { ip: '172.17.0.2', mac: '00:11:22:33:44:66', interface: 'docker0', observedAt, active: true }, ], }; let row = { mac, uploadBytes: '10', downloadBytes: '20', proxyUploadBytes: '30', proxyDownloadBytes: '40', }; const service = createDeviceInventoryService({ store, observe: () => neighbor, observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, devices: [row], }), }); let snapshot = await service.refresh(); assert.equal(snapshot.devices.length, 1); assert.equal(snapshot.devices[0].proxyUploadBytes, '30'); row = { mac, uploadBytes: '15', downloadBytes: '27' }; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].uploadBytes, '15'); assert.equal(snapshot.devices[0].downloadBytes, '27'); assert.equal(snapshot.devices[0].proxyUploadBytes, '30'); assert.equal(snapshot.devices[0].proxyDownloadBytes, '40'); assert.equal(snapshot.source.traffic.proxy.error, null); }); test('a proxy regression rejects every device in that proxy sample atomically', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-proxy-atomic-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {}, migrate: migrateDeviceInventoryState, }); const observedAt = '2026-08-07T12:00:00.000Z'; const macs = ['00:11:22:33:44:55', '00:11:22:33:44:66']; const neighbor = { observedAt, error: null, observations: macs.map((mac, index) => ({ ip: `192.168.50.${index + 7}`, mac, interface: 'eth0', observedAt, active: true, })), }; let rows = [ { mac: macs[0], uploadBytes: '10', downloadBytes: '20', proxyUploadBytes: '30', proxyDownloadBytes: '40' }, { mac: macs[1], uploadBytes: '50', downloadBytes: '60', proxyUploadBytes: '70', proxyDownloadBytes: '80' }, ]; const service = createDeviceInventoryService({ store, observe: () => neighbor, observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, devices: rows, }), }); await service.refresh(); const before = structuredClone(store.read().traffic.proxy); rows = [ { mac: macs[0], uploadBytes: '11', downloadBytes: '22', proxyUploadBytes: '35', proxyDownloadBytes: '45' }, { mac: macs[1], uploadBytes: '52', downloadBytes: '63', proxyUploadBytes: '69', proxyDownloadBytes: '80' }, ]; const snapshot = await service.refresh(); const after = store.read().traffic.proxy; assert.deepEqual(after.baselinesByMac, before.baselinesByMac); assert.deepEqual(after.totalsByMac, before.totalsByMac); assert.match(snapshot.source.traffic.proxy.error, /уменьшился/); assert.equal(snapshot.devices.find(({ mac }) => mac === macs[0]).uploadBytes, '11'); }); test('device policy is independent from pinning, persists, and keeps the last applied mode on failure', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-policy-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {}, migrate: migrateDeviceInventoryState, initializeMissing: true, backupWhen: () => true, }); const observedAt = '2026-08-07T12:00:00.000Z'; const mac = '00:11:22:33:44:55'; let observations = [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true, }]; let activeDevices = []; let generation = 0; let policyEpoch = 'policy-epoch-a'; let failApply = false; let malformedAck = false; const appliedSets = []; const policySnapshot = () => ({ epoch: policyEpoch, generation: `policy-rules-${generation}`, fingerprint: fingerprintDirectDevices(activeDevices), observedAt, appliedIds: activeDevices.map(({ id }) => id), }); const createService = () => createDeviceInventoryService({ store, observe: () => ({ observedAt, observations, error: null }), observePolicy: policySnapshot, applyPolicies: async (devices) => { appliedSets.push(structuredClone(devices)); if (failApply) throw new Error('iptables unavailable'); if (malformedAck) return { ...policySnapshot(), fingerprint: 'invalid' }; activeDevices = structuredClone(devices); generation += 1; return policySnapshot(); }, }); let service = createService(); let snapshot = await service.refresh(); const id = snapshot.devices[0].id; assert.equal(snapshot.devices[0].desiredPolicy, 'vpn'); assert.equal(snapshot.devices[0].appliedPolicy, 'vpn'); snapshot = await service.setPolicy(id, 'direct', snapshot.revision); assert.deepEqual(appliedSets.at(-1), [{ id, ip: '192.168.50.7', mac, interface: 'eth0' }]); assert.equal(snapshot.devices[0].desiredPolicy, 'direct'); assert.equal(snapshot.devices[0].appliedPolicy, 'direct'); assert.equal(snapshot.devices[0].policyStatus, 'applied'); assert.equal(store.read().schemaVersion, DEVICE_INVENTORY_SCHEMA_VERSION); assert.equal(store.read().policy.schemaVersion, 1); service = createService(); snapshot = await service.reconcilePolicies(); assert.equal(snapshot.devices[0].appliedPolicy, 'direct'); snapshot = service.update(id, { pinned: true }, snapshot.revision); snapshot = service.update(id, { pinned: false }, snapshot.revision); assert.equal(snapshot.devices[0].appliedPolicy, 'direct'); policyEpoch = 'policy-epoch-b'; activeDevices = []; failApply = true; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].desiredPolicy, 'direct'); assert.equal(snapshot.devices[0].appliedPolicy, 'vpn'); assert.equal(snapshot.devices[0].policyStatus, 'failed'); failApply = false; snapshot = await service.refresh(); assert.equal(snapshot.devices[0].appliedPolicy, 'direct'); assert.equal(snapshot.devices[0].policyStatus, 'applied'); observations = [ { ...observations[0], interface: 'eth0' }, { ...observations[0], interface: 'eth1' }, ]; snapshot = await service.refresh(); assert.deepEqual(appliedSets.at(-1), []); assert.equal(snapshot.devices[0].confidence, 'ambiguous'); assert.equal(snapshot.devices[0].desiredPolicy, 'direct'); assert.equal(snapshot.devices[0].appliedPolicy, 'vpn'); assert.equal(snapshot.devices[0].policyStatus, 'pending'); snapshot = await service.setPolicy(id, 'vpn', snapshot.revision); assert.equal(snapshot.devices[0].policyStatus, 'applied'); observations = [{ ...observations[0], interface: 'eth0' }]; snapshot = await service.refresh(); snapshot = await service.setPolicy(id, 'direct', snapshot.revision); const secondMac = '00:11:22:33:44:66'; observations.push({ ip: '192.168.50.8', mac: secondMac, interface: 'eth0', observedAt, active: true, }); snapshot = await service.refresh(); const secondId = snapshot.devices.find((device) => device.mac === secondMac).id; failApply = true; await assert.rejects( service.setPolicy(secondId, 'direct', snapshot.revision), (error) => error.code === 'DEVICE_POLICY_APPLY_FAILED', ); snapshot = service.snapshot(); const byId = new Map(snapshot.devices.map((device) => [device.id, device])); assert.equal(byId.get(id).desiredPolicy, 'direct'); assert.equal(byId.get(id).appliedPolicy, 'direct'); assert.equal(byId.get(id).policyStatus, 'applied'); assert.equal(byId.get(secondId).desiredPolicy, 'direct'); assert.equal(byId.get(secondId).appliedPolicy, 'vpn'); assert.equal(byId.get(secondId).policyStatus, 'failed'); assert.equal(snapshot.source.policy.error, 'iptables unavailable'); failApply = false; malformedAck = true; await assert.rejects( service.setPolicy(secondId, 'direct', snapshot.revision), (error) => error.code === 'DEVICE_POLICY_APPLY_FAILED', ); snapshot = service.snapshot(); assert.equal(snapshot.devices.find((device) => device.id === secondId).appliedPolicy, 'vpn'); assert.match(snapshot.source.policy.error, /acknowledgement/); assert.throws( () => service.setPolicy(secondId, 'block', snapshot.revision), (error) => error.code === 'REQUEST_INVALID', ); }); test('device inventory v1 migration creates a versioned backup', (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-migration-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const filePath = path.join(directory, 'devices.json'); fs.writeFileSync(filePath, JSON.stringify({ schemaVersion: 1, revision: 4, devices: [] })); const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState, backupWhen: (before, after) => before?.schemaVersion !== after.schemaVersion, }); const migrated = store.read(); assert.equal(migrated.schemaVersion, DEVICE_INVENTORY_SCHEMA_VERSION); assert.equal(migrated.policy.schemaVersion, 1); assert.deepEqual(migrated.traffic.baselinesByMac, {}); assert.match(store.migration?.backupPath || '', /\.backup-v1-/); assert.ok(fs.existsSync(store.migration.backupPath)); }); test('device inventory backs up and normalizes a malformed additive policy checkpoint', (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-policy-migration-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const filePath = path.join(directory, 'devices.json'); fs.writeFileSync(filePath, JSON.stringify({ schemaVersion: 2, revision: 3, devices: [], traffic: { baselinesByMac: {}, totalsByMac: {}, rebaselineMacs: [] }, policy: { schemaVersion: 1, defaultMode: 'vpn', dataplaneEpoch: 42, byMac: { broken: { desired: 'direct', applied: 'vpn', status: 'failed' }, }, }, })); const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState, backupWhen: () => true, }); const migrated = store.read(); assert.equal(migrated.schemaVersion, DEVICE_INVENTORY_SCHEMA_VERSION); assert.equal(migrated.policy.schemaVersion, 1); assert.equal(migrated.policy.dataplaneEpoch, null); assert.deepEqual(migrated.policy.byMac, {}); assert.match(migrated.policy.lastError, /device policy checkpoint/); assert.match(store.migration?.backupPath || '', /\.backup-v2-/); assert.ok(fs.existsSync(store.migration.backupPath)); }); test('device inventory backs up malformed v2 traffic and re-baselines without double counting', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-corrupt-traffic-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const filePath = path.join(directory, 'devices.json'); const observedAt = '2026-08-07T12:00:00.000Z'; const macs = [ '00:11:22:33:44:55', '00:11:22:33:44:66', '00:11:22:33:44:77', '00:11:22:33:44:88', '00:11:22:33:44:99', ]; const [firstMac, secondMac, missingBaselineMac, missingTotalMac, expiredMac] = macs; const devices = macs.map((mac, index) => ({ id: `device-${index}`, alias: '', pinned: false, hostname: null, manufacturer: null, mac, ip: `192.168.50.${index + 7}`, interface: 'eth0', firstSeenAt: mac === expiredMac ? '2026-06-01T12:00:00.000Z' : observedAt, lastSeenAt: mac === expiredMac ? '2026-06-01T12:00:00.000Z' : observedAt, source: 'neighbor', confidence: 'high', })); fs.writeFileSync(filePath, JSON.stringify({ schemaVersion: 2, revision: 4, lastObservedAt: observedAt, lastError: null, devices, traffic: { epoch: 'epoch-a', generation: 'rules-a', lastObservedAt: observedAt, lastError: null, baselinesByMac: { [firstMac]: { epoch: 'epoch-a', uploadBytes: 'broken', downloadBytes: '100' }, [secondMac]: { epoch: 'epoch-a', uploadBytes: '50', downloadBytes: '60' }, [missingTotalMac]: { epoch: 'epoch-a', uploadBytes: '90', downloadBytes: '100' }, [expiredMac]: { epoch: 'epoch-a', uploadBytes: '110', downloadBytes: '120' }, }, totalsByMac: { [firstMac]: { uploadBytes: '500', downloadBytes: '600', observedAt }, [secondMac]: { uploadBytes: 'broken', downloadBytes: '700', observedAt }, [missingBaselineMac]: { uploadBytes: '800', downloadBytes: '900', observedAt }, [expiredMac]: { uploadBytes: 'broken', downloadBytes: '1000', observedAt }, }, }, })); const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState, backupWhen: () => true, }); const migrated = store.read(); assert.match(store.migration?.backupPath || '', /\.backup-v2-/); assert.ok(fs.existsSync(store.migration.backupPath)); assert.deepEqual(migrated.traffic.totalsByMac[firstMac], { uploadBytes: '500', downloadBytes: '600', observedAt, }); assert.equal(migrated.traffic.totalsByMac[secondMac], undefined); assert.deepEqual( new Set(migrated.traffic.rebaselineMacs), new Set([firstMac, secondMac, missingBaselineMac, missingTotalMac, expiredMac]), ); assert.equal(migrated.traffic.global.gateway.uploadBytes, '1300'); assert.equal(migrated.traffic.global.gateway.downloadBytes, '1500'); assert.deepEqual( new Set(migrated.traffic.global.gateway.rebaselineMacs), new Set([firstMac, secondMac, missingBaselineMac, missingTotalMac, expiredMac]), ); let counters = [ { mac: firstMac, uploadBytes: '200', downloadBytes: '300' }, { mac: secondMac, uploadBytes: '70', downloadBytes: '80' }, { mac: missingBaselineMac, uploadBytes: '110', downloadBytes: '120' }, { mac: missingTotalMac, uploadBytes: '130', downloadBytes: '140' }, ]; const service = createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: devices.filter(({ mac }) => mac !== expiredMac) .map(({ ip, mac, interface: deviceInterface }) => ({ ip, mac, interface: deviceInterface, observedAt, active: true, })), }), observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, devices: counters, }), }); let snapshot = await service.refresh(); let byMac = new Map(snapshot.devices.map((device) => [device.mac, device])); assert.equal(byMac.get(firstMac).uploadBytes, '500'); assert.equal(byMac.get(secondMac).uploadBytes, '0'); assert.equal(byMac.get(missingBaselineMac).uploadBytes, '800'); assert.equal(byMac.get(missingTotalMac).uploadBytes, '0'); assert.equal(byMac.has(expiredMac), false); assert.equal(snapshot.traffic.totalBytes, '2800'); assert.deepEqual(store.read().traffic.rebaselineMacs, []); assert.equal(snapshot.source.traffic.error, null); counters = [ { mac: firstMac, uploadBytes: '250', downloadBytes: '330' }, { mac: secondMac, uploadBytes: '75', downloadBytes: '90' }, { mac: missingBaselineMac, uploadBytes: '115', downloadBytes: '125' }, { mac: missingTotalMac, uploadBytes: '150', downloadBytes: '160' }, ]; snapshot = await service.refresh(); byMac = new Map(snapshot.devices.map((device) => [device.mac, device])); assert.equal(byMac.get(firstMac).uploadBytes, '550'); assert.equal(byMac.get(firstMac).downloadBytes, '630'); assert.equal(byMac.get(secondMac).uploadBytes, '5'); assert.equal(byMac.get(secondMac).downloadBytes, '10'); assert.equal(byMac.get(missingBaselineMac).uploadBytes, '805'); assert.equal(byMac.get(missingBaselineMac).downloadBytes, '905'); assert.equal(byMac.get(missingTotalMac).uploadBytes, '20'); assert.equal(byMac.get(missingTotalMac).downloadBytes, '20'); assert.equal(snapshot.traffic.totalBytes, '2945'); }); test('malformed proxy totals are backed up and an expired recovery marker is cleared', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-corrupt-proxy-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const filePath = path.join(directory, 'devices.json'); const mac = '00:11:22:33:44:55'; fs.writeFileSync(filePath, JSON.stringify({ schemaVersion: 2, revision: 1, devices: [{ id: 'dev_0123456789abcdef', alias: '', pinned: false, mac, ip: '192.168.50.7', interface: 'eth0', firstSeenAt: '2026-06-01T12:00:00.000Z', lastSeenAt: '2026-06-01T12:00:00.000Z', source: 'neighbor', confidence: 'high', }], traffic: { baselinesByMac: {}, totalsByMac: {}, rebaselineMacs: [], proxy: { schemaVersion: 1, baselinesByMac: { [mac]: { epoch: 'epoch-a', uploadBytes: '1', downloadBytes: '2' } }, totalsByMac: { [mac]: { uploadBytes: 'broken', downloadBytes: '4' } }, rebaselineMacs: [], }, }, })); const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState, backupWhen: () => true, }); assert.match(store.read().traffic.proxy.lastError, /proxy traffic checkpoint/); assert.ok(fs.existsSync(store.migration.backupPath)); const service = createDeviceInventoryService({ store, now: () => new Date('2026-08-07T12:00:00.000Z'), observe: () => ({ observedAt: '2026-08-07T12:00:00.000Z', observations: [], error: null }), observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt: '2026-08-07T12:00:00.000Z', source: { error: null }, devices: [], }), }); const snapshot = await service.refresh(); assert.equal(snapshot.devices.length, 0); assert.deepEqual(store.read().traffic.proxy.rebaselineMacs, []); assert.equal(snapshot.source.traffic.proxy.error, null); }); test('an old dataplane without domain traffic keeps inventory refresh and existing metrics available', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-domain-compat-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {} }); const service = createDeviceInventoryService({ store, observe: () => ({ observedAt: '2026-08-08T10:00:00.000Z', observations: [], error: null }), observeTraffic: () => ({ epoch: 'epoch-a', generation: 'rules-a', observedAt: '2026-08-08T10:00:00.000Z', source: { error: null }, direct: { uploadBytes: '0', downloadBytes: '0' }, devices: [], }), observeDomainTraffic: () => { throw new Error('Dataplane HTTP 404'); }, }); const snapshot = await service.refresh(); assert.equal(snapshot.devices.length, 0); assert.equal(service.metricsSnapshot().domainTraffic.source.error, 'Dataplane HTTP 404'); assert.deepEqual(service.metricsSnapshot().directTraffic, { epoch: 'epoch-a', observedAt: '2026-08-08T10:00:00.000Z', source: { error: null }, uploadBytes: '0', downloadBytes: '0', series: [], }); assert.equal(service.metricsSnapshot().traffic.totalBytes, '0'); }); test('direct IPv4 counters stay metrics-only and keep the last good transport snapshot', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-direct-metrics-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const store = createJsonStore({ filePath: path.join(directory, 'devices.json'), defaultValue: {} }); const observedAt = '2026-08-12T10:00:00.000Z'; const mac = '00:11:22:33:44:55'; let fail = false; let includeDirect = true; const service = createDeviceInventoryService({ store, observe: () => ({ observedAt, error: null, observations: [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }], }), observeTraffic: () => { if (fail) throw new Error('Dataplane direct counters unavailable'); return { epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null }, direct: includeDirect ? { uploadBytes: '56', downloadBytes: '78' } : undefined, devices: [{ mac, uploadBytes: '100', downloadBytes: '200', ...(includeDirect ? { directUploadBytes: '12', directDownloadBytes: '34' } : {}), }], }; }, }); await service.refresh(); assert.deepEqual(service.metricsSnapshot().directTraffic.series, [{ deviceId: deviceId(mac), uploadBytes: '12', downloadBytes: '34', }]); assert.equal(service.metricsSnapshot().directTraffic.uploadBytes, '56'); assert.equal(service.metricsSnapshot().directTraffic.downloadBytes, '78'); assert.equal(Object.hasOwn(service.snapshot().devices[0], 'directUploadBytes'), false); assert.doesNotMatch(JSON.stringify(store.read()), /directUploadBytes|directDownloadBytes/); fail = true; await service.refresh(); assert.deepEqual(service.metricsSnapshot().directTraffic.series, [{ deviceId: deviceId(mac), uploadBytes: '12', downloadBytes: '34', }]); assert.equal(service.metricsSnapshot().directTraffic.source.error, 'Dataplane direct counters unavailable'); fail = false; includeDirect = false; await service.refresh(); assert.deepEqual(service.metricsSnapshot().directTraffic.series, []); assert.equal(service.metricsSnapshot().directTraffic.source.error, null); });