Files
ucvl-home-vision/test_devices.js
T

51 lines
4.0 KiB
JavaScript
Raw Normal View History

const test=require('node:test'),assert=require('node:assert/strict'),m=require('./web/device-model.js');
const sites=[{id:'floor',name:'一楼'},{id:'bed',name:'卧室',parentId:'floor'},{id:'office',name:'办公室'}];
const devices=[{id:'lamp',name:'卧室台灯',deviceType:'灯',siteId:'bed',managerName:'乙',ownerName:'甲',tags:['照明','常用']},{id:'desk',name:'办公灯',deviceType:'灯',siteId:'office',managerName:'甲',ownerName:'乙',tags:['照明']},{id:'phone',name:'手机',deviceType:'手机',siteId:'bed',managerName:'乙',ownerName:'乙',tags:[]}];
test('type then area and area then type reuse the same device identities',()=>{
const a=m.groups(devices,['type','space'],sites),b=m.groups(devices,['space','type'],sites);
assert.deepEqual(a.find(g=>g.items.some(x=>x.id==='lamp')).labels,['灯','一楼','卧室']);
assert.deepEqual(b.find(g=>g.items.some(x=>x.id==='lamp')).labels,['一楼','卧室','灯']);
assert.equal(a.flatMap(g=>g.items).length,3);assert.equal(b.flatMap(g=>g.items).length,3);
});
test('responsible person differs from owner and supports multitag grouping',()=>{
assert.equal(m.groups(devices,['manager'],sites).find(g=>g.labels[0]==='乙').items.length,2);
assert.equal(m.groups(devices,['owner'],sites).find(g=>g.labels[0]==='乙').items.length,2);
assert.equal(m.groups(devices,['tag'],sites).find(g=>g.labels[0]==='照明').items.length,2);
assert.equal(m.groups(devices,['tag'],sites).find(g=>g.labels[0]==='未标记').items[0].id,'phone');
});
test('spaces have no fixed depth and traversal handles corrupt cycles safely',()=>{
const deep=Array.from({length:2500},(_,i)=>({id:String(i),name:'Area '+i,parentId:i?String(i-1):''}));
assert.equal(m.path(deep,'2499').length,2500);assert.equal(m.descendants(deep,'0').size,2500);
assert.equal(m.path([{id:'a',parentId:'b'},{id:'b',parentId:'a'}],'a').length,2);
assert.equal(m.descendants([{id:'a',parentId:'b'},{id:'b',parentId:'a'}],'a').size,2);
});
test('identical area names remain separate groups by stable identifiers',()=>{
const areas=[{id:'a',name:'卧室'},{id:'b',name:'卧室'}];
assert.equal(m.groups([{siteId:'a',id:1},{siteId:'b',id:2}],['space'],areas).length,2);
});
test('camera recorder and general device records appear in one catalog without mutation',()=>{
const state={assets:[{id:'a',name:'Camera'}],recorders:[{id:'b',name:'Recorder'}],devices:[{id:'c',deviceType:'冰箱'}]},before=JSON.stringify(state);
assert.deepEqual(m.catalog(state).map(d=>d.deviceType),['摄像机','录像机','冰箱']);assert.equal(JSON.stringify(state),before);
assert.equal(m.groups(devices,[],sites).length,1);
});
test('same-name people with different references form separate responsibility groups',()=>{
const rows=[{id:'a',managerName:'张伟',managerRef:'user:one'},{id:'b',managerName:'张伟',managerRef:'user:two'}];
assert.equal(m.groups(rows,['manager'],[]).length,2);
});
test('network topology preserves explicit uplinks and remains usable with missing or cyclic parents',()=>{
const data=[{id:'r'},{id:'c',uplinkId:'r'},{id:'p',uplinkId:'hidden'}];
const tree=m.topology(data);assert.deepEqual(tree.children.get('r').map(x=>x.id),['c']);assert.equal(tree.parents.get('p'),'');
assert.equal(m.topology([{id:'a',uplinkId:'b'},{id:'b',uplinkId:'a'}]).children.get('').length,2);
});
test('network summary distinguishes recorder stream source from physical camera address',()=>{
const n=m.network({id:'a',managementHost:'192.168.8.160',networkAccess:'tailscale'},{cameras:[{assetId:'a',host:'192.168.3.135',sourceKind:'recorder',recorderId:'r',archiveSource:'recorder',archiveRecorderId:'r'}]});
assert.deepEqual(n.hosts,['192.168.8.160']);assert.deepEqual(n.via,['r']);assert.deepEqual(n.archiveVia,['r']);assert.equal(n.access,'tailscale');
});
test('configured camera address overrides an old inventory address and is searchable',()=>{
const rows=m.catalog({assets:[{id:'a',managementHost:'192.168.1.20'}],cameras:[{assetId:'a',host:'192.168.1.30',sourceKind:'direct'}]});
assert.deepEqual(rows[0].networkHosts,['192.168.1.30']);assert.equal(m.matches(rows[0],'192.168.1.30',[]),true);
});