feat(devices): Scan Now + Manual Add (IP option, MAC colon-mask) → 2.1.4
'Scan Now' triggers POST /api/devices/scan from the band header. '+ Add by MAC' renamed '+ Manual Add' with an optional IP field (addBody/addManual accept ip) and a MAC input that auto-inserts colons as you type. Frontend test 4/4; DB-backed api/repo tests written (run with the suite — skipped locally to avoid colliding with a concurrent test run on void_test). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,16 +41,18 @@ describe('/api/devices', () => {
|
||||
|
||||
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' });
|
||||
const res = await owner(request(app).post('/api/devices')).send({ mac: 'AA:BB:CC:DD:EE:FF', ip: '192.168.1.77', 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.ip).toBe('192.168.1.77');
|
||||
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 () => {
|
||||
it('POST / rejects a bad MAC and a bad IP', async () => {
|
||||
expect((await owner(request(app).post('/api/devices')).send({ mac: 'nope' })).status).toBe(400);
|
||||
expect((await owner(request(app).post('/api/devices')).send({ mac: 'aa:bb:cc:dd:ee:ff', ip: 'not-an-ip' })).status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,15 +53,28 @@ describe('devices band', () => {
|
||||
expect.objectContaining({ name: 'Orbi RBS50', grp: 'Network' }));
|
||||
});
|
||||
|
||||
it('manual add-by-MAC reveals a form and POSTs the new device', async () => {
|
||||
it('Manual Add reveals a form (with IP) and POSTs the new device; MAC field auto-inserts colons', async () => {
|
||||
const host = document.getElementById('h');
|
||||
await renderDevicesBand(host);
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
expect(host.querySelector('.dv-addtoggle').textContent).toBe('+ Manual Add');
|
||||
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';
|
||||
const [macI, ipI] = host.querySelectorAll('.dv-addform .dv-edit-name');
|
||||
macI.value = 'aabbccddeeff';
|
||||
macI.dispatchEvent(new window.Event('input')); // colon-mask
|
||||
expect(macI.value).toBe('aa:bb:cc:dd:ee:ff');
|
||||
ipI.value = '192.168.1.50';
|
||||
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' }));
|
||||
expect(api.post).toHaveBeenCalledWith('/api/devices', expect.objectContaining({ mac: 'aa:bb:cc:dd:ee:ff', ip: '192.168.1.50' }));
|
||||
});
|
||||
|
||||
it('Scan Now triggers the scheduled scan', async () => {
|
||||
const host = document.getElementById('h');
|
||||
await renderDevicesBand(host);
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
host.querySelector('.dv-scanbtn').click();
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
expect(api.post).toHaveBeenCalledWith('/api/devices/scan');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user