// tests/repos/lan_devices.test.js import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { resetDb } from '../helpers/db.js'; import { migrateUp } from '../../lib/db/migrate.js'; import { pool } from '../../lib/db/pool.js'; import * as repo from '../../lib/db/repos/lan_devices.js'; beforeAll(async () => { await resetDb(); await migrateUp(); }); beforeEach(async () => { await resetDb(); await migrateUp(); }); describe('lan_devices repo', () => { it('seed: 17 known, 1 discovered (ASUS)', async () => { expect(await repo.listKnown()).toHaveLength(17); const disc = await repo.listDiscovered(); expect(disc).toHaveLength(1); expect(disc[0].mac).toBe('24:4b:fe:8e:09:a4'); expect(disc[0].flagged).toBe(true); }); it('upsertScan inserts unseen as new, updates known IP without clobbering name', async () => { await repo.upsertScan([ { mac: 'aa:bb:cc:dd:ee:ff', ip: '192.168.1.99', vendor: 'NewCo', randomized: false }, // new { mac: 'bc:a5:11:3e:06:88', ip: '192.168.1.77', vendor: 'Netgear', randomized: false } // known Orbi, IP changed ]); const orbi = await repo.get('bc:a5:11:3e:06:88'); expect(orbi.ip).toBe('192.168.1.77'); // ip updated expect(orbi.name).toBe('Orbi Satellite'); // name preserved expect(orbi.status).toBe('known'); // status preserved expect(orbi.present).toBe(true); const fresh = await repo.get('aa:bb:cc:dd:ee:ff'); expect(fresh.status).toBe('new'); }); it('markAbsent flips present for unseen; empty list is a no-op', async () => { await repo.upsertScan([{ mac: 'aa:bb:cc:dd:ee:ff', ip: '192.168.1.99', vendor: '', randomized: false }]); await repo.markAbsent(['aa:bb:cc:dd:ee:ff']); // only this one seen expect((await repo.get('bc:a5:11:3e:06:88')).present).toBe(false); // seeded device now absent expect((await repo.get('aa:bb:cc:dd:ee:ff')).present).toBe(true); const before = (await repo.get('aa:bb:cc:dd:ee:ff')).present; expect(await repo.markAbsent([])).toBe(0); // guard: no-op expect((await repo.get('aa:bb:cc:dd:ee:ff')).present).toBe(before); }); it('prune deletes stale new+absent (randomized >24h, others >14d); keeps known', async () => { await pool.query(`INSERT INTO lan_devices (mac, status, randomized, present, last_seen) VALUES ('11:11:11:11:11:11','new',true,false, now()-interval '2 days'), ('22:22:22:22:22:22','new',false,false, now()-interval '20 days'), ('33:33:33:33:33:33','new',true,false, now()-interval '1 hour'), ('44:44:44:44:44:44','known',true,false, now()-interval '99 days')`); const n = await repo.prune(); expect(n).toBe(2); // the two stale 'new' expect(await repo.get('33:33:33:33:33:33')).not.toBeNull(); // recent kept expect(await repo.get('44:44:44:44:44:44')).not.toBeNull(); // known kept }); it('update promotes + names a discovered device', async () => { await repo.update('24:4b:fe:8e:09:a4', { name: 'ASUS RT-AX88U', grp: 'Network', status: 'known', flagged: false }); expect(await repo.listDiscovered()).toHaveLength(0); const d = await repo.get('24:4b:fe:8e:09:a4'); expect(d.name).toBe('ASUS RT-AX88U'); expect(d.status).toBe('known'); }); });