Files
Void-Homelab/tests/api/devices.test.js
root 26463b5eb6 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>
2026-06-08 23:58:19 +10:00

59 lines
2.8 KiB
JavaScript

// tests/api/devices.test.js
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import request from 'supertest';
import { createApp } from '../../server.js';
import { resetDb } from '../helpers/db.js';
import { migrateUp } from '../../lib/db/migrate.js';
let app;
const owner = r => r.set('Authorization', 'Bearer test-token');
beforeAll(async () => { process.env.OWNER_TOKEN = 'test-token'; app = createApp(); });
beforeEach(async () => { await resetDb(); await migrateUp(); });
describe('/api/devices', () => {
it('GET / returns known devices grouped', async () => {
const res = await request(app).get('/api/devices');
expect(res.status).toBe(200);
const names = res.body.groups.map(g => g.name);
expect(names).toContain('Network');
const net = res.body.groups.find(g => g.name === 'Network');
expect(net.devices.some(d => d.name === 'Orbi Satellite')).toBe(true);
});
it('GET /discovered requires owner and lists new devices', async () => {
expect((await request(app).get('/api/devices/discovered')).status).toBe(401);
const res = await owner(request(app).get('/api/devices/discovered'));
expect(res.status).toBe(200);
expect(res.body.some(d => d.mac === '24:4b:fe:8e:09:a4')).toBe(true);
});
it('PATCH /:mac promotes + names (owner)', async () => {
const res = await owner(request(app).patch('/api/devices/24:4b:fe:8e:09:a4'))
.send({ name: 'ASUS Router', grp: 'Network', status: 'known' });
expect(res.status).toBe(200);
expect(res.body.name).toBe('ASUS Router');
expect((await owner(request(app).get('/api/devices/discovered'))).body).toHaveLength(0);
});
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', 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 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);
});
});