112 lines
7.9 KiB
JavaScript
112 lines
7.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import {before,after,test} from 'node:test';
|
|
import {readFileSync,readdirSync} from 'node:fs';
|
|
import {createServer} from 'vite';
|
|
let server,api,resolveContribution,wirelessContributions;
|
|
before(async()=>{
|
|
server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});
|
|
api=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/enrollment.ts');
|
|
({sensorContribution:resolveContribution}=await server.ssrLoadModule('../../packages/sensor-ui/src/extensions.ts'));
|
|
({wirelessContributions}=await server.ssrLoadModule('../../packages/sensor-ui/src/extensions.ts'));
|
|
});
|
|
after(async()=>{await server?.close();});
|
|
const initial={node_id:'node-one',available:true,fresh:true,runtime_id:'runtime-one',snapshot_revision:1,runtime_started_at:'2026-09-06T00:00:00Z',selected_device_id:'synthetic-ble'};
|
|
function fixture(){
|
|
let request,reads=0,posts=0,clock=Date.now();
|
|
const accepted=[];
|
|
const value=(status='running',revision=2)=>({...initial,snapshot_revision:revision,connected:status==='succeeded',connection_attempt:{schema_version:'missioncore.xgrids-k1-connection-attempt/v1',attempt_id:request.operation_id,connection_mode:'bridge',status,phase:'network_applied',control_state:status==='succeeded'?'ready':'unknown',safe_next_action:status==='succeeded'?'start-acquisition':'wait-for-current-attempt'}});
|
|
const transport={
|
|
submit:async input=>{posts++;request=input;return {operation_id:request.operation_id,state:'complete',result:value()};},
|
|
operation:async id=>({operation_id:id,state:'complete',result:value()}),
|
|
state:async()=>{reads++;return value('succeeded',3);},
|
|
};
|
|
return {transport,value,accepted,parameters:{device_id:'synthetic-ble',ssid:'synthetic-network',password:crypto.randomUUID()},
|
|
observer:{onState:s=>accepted.push(s),now:()=>clock,pause:async()=>{clock+=100000;}},
|
|
counts:()=>({reads,posts}),request:()=>request};
|
|
}
|
|
|
|
test('network ACK waits for the exact owned control bootstrap with one POST',async()=>{
|
|
const f=fixture();const result=await api.enroll(f.transport,initial,'connect',f.parameters,f.observer);
|
|
assert.equal(result.connected,true);assert.deepEqual(f.counts(),{reads:1,posts:1});
|
|
assert.equal(f.accepted[0].connection_attempt.status,'running');assert.equal(f.accepted.at(-1).connection_attempt.status,'succeeded');
|
|
assert.equal('password' in f.parameters,false);
|
|
});
|
|
test('lost POST response reads the same operation ID and never submits again',async()=>{
|
|
const f=fixture();const submit=f.transport.submit;
|
|
f.transport.submit=async input=>{await submit(input);throw new Error('response lost');};
|
|
let ids=[];const operation=f.transport.operation;
|
|
f.transport.operation=async id=>{ids.push(id);return operation(id);};
|
|
const result=await api.enroll(f.transport,initial,'connect',f.parameters,f.observer);
|
|
assert.equal(result.connected,true);assert.equal(f.counts().posts,1);assert.deepEqual(ids,[f.request().operation_id]);
|
|
assert.equal('password' in f.parameters,false);
|
|
});
|
|
test('late snapshot cannot restore readiness or overwrite newer attempt evidence',()=>{
|
|
const current={...initial,snapshot_revision:8,connected:false};
|
|
assert.equal(api.mergeEnrollmentState(current,{...initial,snapshot_revision:7,connected:true}),current);
|
|
const newer={...current,runtime_id:'runtime-two',runtime_started_at:'2026-09-06T00:01:00Z'};
|
|
assert.equal(api.mergeEnrollmentState(newer,{...initial,snapshot_revision:900,connected:true}),newer);
|
|
assert.equal(api.mergeEnrollmentState(current,newer).runtime_id,'runtime-two');
|
|
assert.equal(api.mergeEnrollmentState(current,{available:false}).fresh,false);
|
|
assert.equal(api.mergeEnrollmentState(current,{...initial,node_id:'another-board'}),current);
|
|
});
|
|
test('a response for another operation is never accepted',async()=>{
|
|
const f=fixture();const submit=f.transport.submit;
|
|
f.transport.submit=async input=>({...await submit(input),operation_id:'another-operation'});
|
|
await assert.rejects(api.enroll(f.transport,initial,'connect',f.parameters,f.observer),/другое действие/);
|
|
assert.equal(f.counts().reads,0);assert.equal(f.counts().posts,1);
|
|
});
|
|
test('foreign historical success cannot settle this attempt',async()=>{
|
|
const f=fixture();const submit=f.transport.submit;
|
|
f.transport.submit=async input=>({...await submit(input),result:{...initial,connected:true,connection_attempt:{...f.value('succeeded').connection_attempt,attempt_id:'foreign'}}});
|
|
f.transport.state=async()=>({...initial,connected:true,connection_attempt:{...f.value('succeeded').connection_attempt,attempt_id:'foreign'}});
|
|
await assert.rejects(api.enroll(f.transport,initial,'connect',f.parameters,f.observer),/пока не подтверждён/);
|
|
assert.equal(f.counts().posts,1);
|
|
});
|
|
test('runtime restart interrupts observation without another command',async()=>{
|
|
const f=fixture();f.transport.state=async()=>({...initial,runtime_id:'runtime-two',runtime_started_at:'2026-09-06T00:01:00Z'});
|
|
await assert.rejects(api.enroll(f.transport,initial,'connect',f.parameters,f.observer),/Сеанс K1 изменился/);
|
|
assert.equal(f.counts().posts,1);assert.equal(f.accepted.at(-1).runtime_id,'runtime-two');
|
|
});
|
|
test('closing the observer stops reads without cancelling/replaying a device command',async()=>{
|
|
const f=fixture();const controller=new AbortController();
|
|
f.observer.signal=controller.signal;f.observer.pause=async()=>controller.abort();
|
|
await assert.rejects(api.enroll(f.transport,initial,'connect',f.parameters,f.observer),/Наблюдение закрыто/);
|
|
assert.deepEqual(f.counts(),{reads:0,posts:1});
|
|
});
|
|
test('station refusal is the same message in onboard and LAB presentations',()=>{
|
|
const state={...initial,connection_attempt:{schema_version:'missioncore.xgrids-k1-connection-attempt/v1',attempt_id:'test',status:'failed',phase:'network_outcome_unknown',public_error_code:'k1-wifi-network-not-found'}};
|
|
assert.match(api.enrollmentNotice(state),/Проверьте название сети и пароль/);
|
|
assert.doesNotMatch(api.enrollmentNotice(state),/Bluetooth.*ошиб/);
|
|
});
|
|
test('sensor host can resolve zero or unrelated integrations and rejects ambiguity',()=>{
|
|
const alpha={kind:'alpha',Detail:()=>null},beta={kind:'beta',Detail:()=>null};
|
|
assert.equal(resolveContribution([],{kind:'alpha'}),undefined);
|
|
assert.equal(resolveContribution([alpha,beta],{kind:'beta'}),beta);
|
|
assert.equal(resolveContribution([alpha,{...alpha}],{kind:'alpha'}),undefined);
|
|
for(const name of readdirSync(new URL('../../../packages/sensor-ui/src/',import.meta.url))){
|
|
if(!/\.tsx?$/.test(name))continue;
|
|
assert.doesNotMatch(readFileSync(new URL('../../../packages/sensor-ui/src/'+name,import.meta.url),'utf8'),/xgrids|lixel|\bk1\b|K1Detail/,'vendor dependency in '+name);
|
|
}
|
|
});
|
|
test('wireless device choices come from installed contributions without a single-vendor slot',()=>{
|
|
const wired={kind:'wired',Detail:()=>null};
|
|
const alpha={kind:'alpha',wirelessEnrollment:{label:'Device alpha',View:()=>null}};
|
|
const beta={kind:'beta',wirelessEnrollment:{label:'Device beta',View:()=>null}};
|
|
assert.deepEqual(wirelessContributions([]),[]);
|
|
assert.deepEqual(wirelessContributions([wired,alpha,beta]),[alpha,beta]);
|
|
assert.deepEqual(wirelessContributions([alpha,{...alpha},beta]),[beta]);
|
|
});
|
|
test('onboard actions use current server policy and never infer authority from readiness',()=>{
|
|
const state={...initial,connected:true,allowed_actions:['verify-control-read-only']};
|
|
assert.equal(api.enrollmentAllowed(state,'connect'),false);
|
|
assert.equal(api.enrollmentAllowed(state,'scan'),false);
|
|
assert.equal(api.enrollmentAllowed(state,'verify'),true);
|
|
assert.equal(api.enrollmentAllowed({...state,fresh:false},'verify'),false);
|
|
});
|
|
test('delayed operation result cannot clear an observed board outage',()=>{
|
|
const current={...initial,fresh:false,available:false};
|
|
const result=api.mergeEnrollmentState(current,{...initial,fresh:undefined,snapshot_revision:2});
|
|
assert.equal(result.fresh,false);
|
|
assert.equal(api.mergeEnrollmentState(result,{...initial,fresh:true,snapshot_revision:3}).fresh,true);
|
|
});
|