mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
176 lines
8.3 KiB
JavaScript
176 lines
8.3 KiB
JavaScript
'use strict';
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
const { environment, loadModule, backend, root, renderLoadedPage } = require('./helpers/runtime');
|
|
|
|
const EXPECTED_ICONS = [
|
|
'computer.svg', 'laptop.svg', 'phone.svg', 'tablet.svg',
|
|
'tv.svg', 'tvbox.svg', 'nas.svg', 'printer.svg',
|
|
'camera.svg', 'speaker.svg', 'game.svg', 'router.svg',
|
|
'switch.svg', 'ap.svg', 'server.svg', 'plug.svg',
|
|
'light.svg', 'sensor.svg', 'home.svg', 'watch.svg',
|
|
'car.svg', 'vr.svg', 'network.svg',
|
|
'airconditioner.svg', 'washer.svg', 'fridge.svg',
|
|
'waterpurifier.svg', 'airpurifier.svg',
|
|
'unknown.svg'
|
|
];
|
|
|
|
function pageContext(initial = []) {
|
|
const mocks = backend(initial);
|
|
const env = environment(mocks);
|
|
const cache = new Map();
|
|
const model = loadModule('device-manager.model', env, cache);
|
|
const service = loadModule('device-manager.service', env, cache);
|
|
const page = loadModule('view.device-manager.devices', env, cache);
|
|
return Object.assign({ env, page, model, service }, mocks);
|
|
}
|
|
|
|
test('all 29 distributed icons have one source and pinned attribution', () => {
|
|
const directory = path.join(root, 'htdocs/luci-static/resources/device-manager/device-icons');
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'sources.json')));
|
|
assert.match(manifest.revision, /^[a-f0-9]{40}$/);
|
|
for (const icon of EXPECTED_ICONS) {
|
|
const content = fs.readFileSync(path.join(directory, icon), 'utf8');
|
|
assert.match(content, /<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/);
|
|
assert.match(content, /<\/svg>/);
|
|
assert.ok(manifest.icons[icon].source.includes(manifest.revision));
|
|
}
|
|
assert.ok(fs.existsSync(path.join(directory, 'LICENSE')));
|
|
assert.ok(fs.existsSync(path.join(directory, 'NOTICE')));
|
|
});
|
|
|
|
test('vendor hints remain conservative and randomized MACs do not imply a vendor', () => {
|
|
const env = environment();
|
|
const model = loadModule('device-manager.model', env);
|
|
|
|
const cases = [
|
|
{ mac: '00:11:32:11:22:33', expected: 'nas' },
|
|
{ mac: '4C:FC:AA:11:22:33', expected: 'car' },
|
|
{ mac: '2C:26:17:11:22:33', expected: 'vr' },
|
|
{ mac: '00:1B:78:11:22:33', expected: 'unknown' },
|
|
{ mac: '10:12:FB:11:22:33', expected: 'camera' },
|
|
{ mac: '00:09:BF:11:22:33', expected: 'game' },
|
|
{ mac: '00:0E:58:11:22:33', expected: 'speaker' },
|
|
{ mac: 'F4:F5:E8:11:22:33', expected: 'unknown' },
|
|
{ mac: '00:1A:9A:11:22:33', expected: 'unknown' },
|
|
{ mac: '18:FE:34:11:22:33', expected: 'home' },
|
|
{ mac: '00:17:88:11:22:33', expected: 'light' },
|
|
{ mac: '54:EF:44:11:22:33', expected: 'home' },
|
|
{ mac: '10:2C:6B:11:22:33', expected: 'unknown' },
|
|
{ mac: '52:54:00:11:22:33', expected: 'unknown' },
|
|
{ mac: '00:15:6D:11:22:33', expected: 'network' },
|
|
{ mac: '00:0F:E2:11:22:33', expected: 'network' },
|
|
{ mac: '00:0C:42:11:22:33', expected: 'router' },
|
|
{ mac: 'B8:27:EB:11:22:33', expected: 'computer' },
|
|
{ mac: 'F0:18:98:11:22:33', expected: 'unknown' },
|
|
{ mac: '12:34:56:78:9A:BC', expected: 'unknown' }
|
|
];
|
|
|
|
for (const c of cases) {
|
|
const detected = model.detectDeviceType(c.mac, '', '', '');
|
|
assert.equal(detected, c.expected, `MAC ${c.mac} should be ${c.expected}, got ${detected}`);
|
|
const info = model.getTypeInfo(detected);
|
|
assert.equal(info.icon, detected + '.svg');
|
|
}
|
|
});
|
|
|
|
test('detectDeviceType refines classification using hostname or custom name clues', () => {
|
|
const env = environment();
|
|
const model = loadModule('device-manager.model', env);
|
|
|
|
const appleMac = 'F0:18:98:11:22:33';
|
|
assert.equal(model.detectDeviceType(appleMac, 'My-iPhone', '', ''), 'phone');
|
|
assert.equal(model.detectDeviceType(appleMac, 'MacBook-Pro', '', ''), 'laptop');
|
|
assert.equal(model.detectDeviceType(appleMac, 'iPad-Mini', '', ''), 'tablet');
|
|
assert.equal(model.detectDeviceType(appleMac, 'Apple-Watch-Ultra', '', ''), 'watch');
|
|
assert.equal(model.detectDeviceType(appleMac, 'Apple-TV-4K', '', ''), 'tvbox');
|
|
assert.equal(model.detectDeviceType(appleMac, 'HomePod-LivingRoom', '', ''), 'speaker');
|
|
|
|
const genericMac = '12:34:56:78:9A:BC';
|
|
assert.equal(model.detectDeviceType(genericMac, 'DiskStation', '', ''), 'nas');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Tesla-Model-Y', '', ''), 'car');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Oculus-Quest-3', '', ''), 'vr');
|
|
assert.equal(model.detectDeviceType(genericMac, 'HP-LaserJet', '', ''), 'printer');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Hikvision-IPC', '', ''), 'camera');
|
|
assert.equal(model.detectDeviceType(genericMac, 'PlayStation-5', '', ''), 'game');
|
|
assert.equal(model.detectDeviceType(genericMac, 'USW-Lite-16-PoE', '', ''), 'switch');
|
|
assert.equal(model.detectDeviceType(genericMac, 'UniFi-U6-Pro', '', ''), 'ap');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Gree-AirConditioner', '', ''), 'airconditioner');
|
|
assert.equal(model.detectDeviceType(genericMac, 'LivingRoom-空调', '', ''), 'airconditioner');
|
|
assert.equal(model.detectDeviceType(genericMac, 'LG-Washer', '', ''), 'washer');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Haier-洗衣机', '', ''), 'washer');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Samsung-Fridge', '', ''), 'fridge');
|
|
assert.equal(model.detectDeviceType(genericMac, '厨房冰箱', '', ''), 'fridge');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Xiaomi-WaterPurifier', '', ''), 'waterpurifier');
|
|
assert.equal(model.detectDeviceType(genericMac, '小米净水器', '', ''), 'waterpurifier');
|
|
assert.equal(model.detectDeviceType(genericMac, 'Mi-AirPurifier-Pro', '', ''), 'airpurifier');
|
|
assert.equal(model.detectDeviceType(genericMac, '卧室空气净化器', '', ''), 'airpurifier');
|
|
});
|
|
|
|
test('device table renders the device icon after the row number', async () => {
|
|
const ctx = pageContext();
|
|
const nasMac = '00:11:32:11:22:33';
|
|
ctx.replies['luci.device-manager.get_online_status'] = {
|
|
ok: true,
|
|
neighbors: [{ mac: nasMac, state: 'REACHABLE' }]
|
|
};
|
|
ctx.replies['luci-rpc.getHostHints'] = {
|
|
[nasMac]: { name: 'Synology-NAS', ipaddrs: ['192.168.1.50'] }
|
|
};
|
|
|
|
const viewNode = await renderLoadedPage(ctx.page);
|
|
|
|
// Check table thead has type column
|
|
const table = viewNode.querySelector('#device_manager_table');
|
|
const thList = table.querySelectorAll('th');
|
|
assert.equal(thList[1].classes.has('dm-type-th'), true);
|
|
|
|
// Check tbody row has the icon after the row number
|
|
const tbody = viewNode.querySelector('#device_manager_tbody');
|
|
const rows = tbody.querySelectorAll('tr');
|
|
assert.equal(rows.length, 1);
|
|
|
|
const row = rows[0];
|
|
const tdList = row.querySelectorAll('td');
|
|
assert.equal(tdList[1].classes.has('dm-type-cell'), true);
|
|
|
|
const iconImg = tdList[1].querySelector('img');
|
|
assert.ok(iconImg);
|
|
assert.equal(iconImg.classes.has('dm-device-icon'), true);
|
|
assert.match(iconImg.attrs.src, /device-icons\/nas\.svg/);
|
|
});
|
|
|
|
test('custom device type override can be saved and displayed', async () => {
|
|
const customMac = '00:11:32:55:66:77';
|
|
const initial = [
|
|
{ '.type': 'device', '.name': 'dev_001132556677', mac: customMac, name: 'Backup Server', type: 'server' }
|
|
];
|
|
const ctx = pageContext(initial);
|
|
ctx.replies['luci.device-manager.get_online_status'] = {
|
|
ok: true,
|
|
neighbors: [{ mac: customMac, state: 'REACHABLE' }]
|
|
};
|
|
const viewNode = await renderLoadedPage(ctx.page);
|
|
|
|
const row = viewNode.querySelector('#device_manager_tbody').querySelectorAll('tr')[0];
|
|
const iconImg = row.querySelectorAll('td')[1].querySelector('img');
|
|
assert.match(iconImg.attrs.src, /device-icons\/server\.svg/);
|
|
|
|
// Test saving a new type via service
|
|
await ctx.service.saveDevice(customMac, 'Backup Server', 'Rack 1', 'ungrouped', 'nas');
|
|
assert.equal(ctx.state.saved[0].type, 'nas');
|
|
});
|
|
|
|
test('ambiguous short tokens and general vendors do not override meaningful device names', () => {
|
|
const model = loadModule('device-manager.model', environment());
|
|
for (const [name, expected] of [
|
|
['switch', 'switch'], ['gateway', 'router'], ['Cambridge-PC', 'computer'],
|
|
['spring', 'unknown'], ['bookmark', 'unknown'], ['canvas', 'unknown'],
|
|
['nintendo-switch', 'game'], ['switch-PC', 'computer']
|
|
]) assert.equal(model.detectDeviceType('12:34:56:78:9A:BC', name), expected, name);
|
|
assert.equal(model.detectDeviceType('02:11:32:11:22:33', ''), 'unknown');
|
|
assert.equal(model.detectDeviceType('02:11:32:11:22:33', 'synology'), 'nas');
|
|
});
|