feat(devices): manually add a device by MAC (offline pre-register) → 2.1.3

'+ Add by MAC' in the band header → POST /api/devices → lan_devices.addManual
(status=known, present=false; enriched on next scan). Repo + API + frontend tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-08 23:12:47 +10:00
parent 7a5fd88c07
commit 88ef5786ee
9 changed files with 89 additions and 3 deletions

View File

@@ -38,4 +38,19 @@ describe('/api/devices', () => {
it('PATCH rejects a bad MAC', async () => {
expect((await owner(request(app).patch('/api/devices/not-a-mac')).send({ name: 'x' })).status).toBe(400);
});
it('POST / manually adds an offline device by MAC (owner, lowercased, status=known, absent)', async () => {
expect((await request(app).post('/api/devices').send({ mac: 'aa:bb:cc:dd:ee:ff' })).status).toBe(401);
const res = await owner(request(app).post('/api/devices')).send({ mac: 'AA:BB:CC:DD:EE:FF', name: 'Garage door', grp: 'Smart Home' });
expect(res.status).toBe(201);
expect(res.body.mac).toBe('aa:bb:cc:dd:ee:ff');
expect(res.body.status).toBe('known');
expect(res.body.present).toBe(false);
const band = await request(app).get('/api/devices');
expect(band.body.groups.find(g => g.name === 'Smart Home').devices.some(d => d.name === 'Garage door')).toBe(true);
});
it('POST / rejects a bad MAC', async () => {
expect((await owner(request(app).post('/api/devices')).send({ mac: 'nope' })).status).toBe(400);
});
});

View File

@@ -12,6 +12,7 @@ vi.mock('../../public/api.js', () => ({
return {};
}),
patch: vi.fn(async () => ({})),
post: vi.fn(async () => ({})),
del: vi.fn(async () => ({}))
}
}));
@@ -51,4 +52,16 @@ describe('devices band', () => {
expect(api.patch).toHaveBeenCalledWith('/api/devices/bc:a5:11:3e:06:88',
expect.objectContaining({ name: 'Orbi RBS50', grp: 'Network' }));
});
it('manual add-by-MAC reveals a form and POSTs the new device', async () => {
const host = document.getElementById('h');
await renderDevicesBand(host);
await new Promise(r => setTimeout(r, 0));
host.querySelector('.dv-addtoggle').click(); // reveal the form
const macI = host.querySelector('.dv-addform .dv-edit-name');
macI.value = 'aa:bb:cc:dd:ee:ff';
host.querySelector('.dv-addform .dv-add').click();
await new Promise(r => setTimeout(r, 0));
expect(api.post).toHaveBeenCalledWith('/api/devices', expect.objectContaining({ mac: 'aa:bb:cc:dd:ee:ff' }));
});
});