Files
NODEDC_MISSION_CORE/apps/control-station/test/k1ManualControl.test.mjs
T
DCCONSTRUCTIONS a3c15e11e9 Add packaged Insta360 X4 integration and recover paired Node channels
Discover independent camera instances and prepare their versioned runtime
from Node or remote Core. Add isolated SDK workers, camera controls, raw
dual-fisheye WebRTC preview, and shared action/region loading states.

Recover existing Node bindings over known Tailscale addresses after a Core
LAN address change. Preserve identities and trust, pin both peers, migrate
endpoints with revision checks, and require real heartbeats for online status.
Fix the Python client certificate profile for Go X509 verification.

Pin Design Guideline 8c53f73 and retain installer/build/acceptance history.
Node 0.8.19 is installed; X4 0.1.3-3 is bundled but hardware activation is pending.

Validation: qualified DG/Node builds and Go race tests; 31 fleet tests;
Python-to-Go certificate interoperability and live tailnet recovery with five
fresh heartbeats; prior 38 X4 tests and bounded remote WebRTC acceptance.
Clean-OS, replug/power autonomy, local X4 video and long-run stability remain open.
2026-09-10 09:21:24 +03:00

91 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import {before,after,test} from 'node:test';
import {createElement} from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import {createServer} from 'vite';
let server,Detail,presentation,enrolledDevice;
before(async()=>{
server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});
({K1Detail:Detail}=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/K1Detail.tsx'));
presentation=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/presentation.ts');
({enrolledDevice}=await server.ssrLoadModule('../../packages/sensor-ui/src/contracts.ts'));
});
after(async()=>{await server?.close();});
const device={id:'synthetic',kind:'k1',name:'K1 test',online:true,verified:true,prepared:true,
control:{can_start:true,can_stop:false,can_verify:true,generation:1,revision:1,acquisition_id:null},
snapshot:{context:{session_id:'exact-session',execution:{node_id:'test'}},acquisition:'idle'}};
const render=(value,enabled=true)=>renderToStaticMarkup(createElement(Detail,{device:value,enabled,
transport:{},back:()=>{},refresh:async()=>{},failure:()=>{},createRerunHost:()=>assert.fail('SSR must not start a viewer or hardware')}));
test('manual K1 surface admits one START only after current control proof',()=>{
const markup=render(device);
assert.match(markup,/Инициировать запуск/);
const start=(markup.match(/<button\b[\s\S]*?<\/button>/g)??[]).filter(button=>button.replace(/<[^>]+>/g,'')==='Инициировать запуск');
assert.equal(start.length,1);
assert.match(start[0],/<button[^>]*disabled=""/);
for(const label of ['Тип установки / носитель','Режим GNSS','Название проекта'])assert.ok(markup.includes(label));
assert.match(markup,/Настройки устройства/);
assert.doesNotMatch(markup,/Остановить устройство|Обновить просмотр|sensor-live-layout/);
const waiting={...device,online:false,verified:false,control:{...device.control,can_start:false,network_applied:true,reason_code:'application_authority_unavailable'}};
const pending=render(waiting);
assert.doesNotMatch(pending,/Инициировать запуск|Настройки устройства|sensor-live-layout/);
assert.match(pending,/Проверить состояние K1/);
assert.match(pending,/авторизовать/);
assert.equal(presentation.k1Status(waiting,true).label,'Нет связи с K1');
assert.equal(presentation.k1ManualState(device,false).canStart,false);
});
test('active acquisition exposes STOP and Rerun without another START',()=>{
const active={...device,control:{...device.control,can_start:false,can_stop:true},snapshot:{...device.snapshot,acquisition:'streaming'}};
const markup=render(active);
assert.match(markup,/Остановить устройство/);
assert.match(markup,/rerun-viewport__canvas/);
assert.doesNotMatch(markup,/>Статус</);
assert.match(markup,/>Пространственная сцена</);
for(const label of ['Движок','Слои','Отображение','Камера K1','Окно накопления облака точек'])assert.ok(markup.includes(label));
assert.ok(markup.includes('data-presented="false"'));
assert.ok(!markup.includes('Loading Rerun'));
assert.ok(!markup.includes('The data layer'));
assert.ok(!markup.includes('data-align="center"'));
assert.doesNotMatch(markup,/Восстановить просмотр|nodedc-activity-indicator/);
assert.doesNotMatch(markup,/Инициировать запуск|Обновить просмотр/);
assert.equal(presentation.k1ManualState(active,false).canStart,false);
assert.equal(presentation.k1ManualState(active,false).showStop,true);
});
test('calibration loader stays inside the primary action',()=>{
const starting={...device,control:{...device.control,can_start:false},snapshot:{...device.snapshot,acquisition:'starting'}};
const markup=render(starting);
const start=(markup.match(/<button\b[\s\S]*?<\/button>/g)??[]).filter(button=>button.replace(/<[^>]+>/g,'')==='Запускаем устройство');
assert.equal(start.length,1);
assert.match(start[0],/<button[^>]*aria-busy="true"/);
assert.equal((start[0].match(/class="nodedc-activity-indicator"/g)??[]).length,1);
assert.match(markup,/K1 калибруется и готовит облако точек/);
assert.doesNotMatch(markup,/Обновить просмотр/);
});
test('connection loss is visible independently of completed STOP',()=>{
const stopped={...device,online:false,verified:false,control:{...device.control,phase:'completed',can_start:false,network_applied:true}};
const markup=render(stopped);
assert.match(markup,/Нет связи с K1/);
assert.match(markup,/Проверить состояние K1/);
assert.doesNotMatch(markup,/Инициировать запуск|нет управления/);
});
test('enrollment handoff opens only the verified current session',()=>{
const inventory={fresh:true,items:[device],operations:[]};
assert.equal(enrolledDevice(inventory,'exact-session'),device);
assert.equal(enrolledDevice(inventory,'other-session'),null);
assert.equal(enrolledDevice({...inventory,fresh:false},'exact-session'),null);
assert.equal(enrolledDevice({...inventory,items:[{...device,verified:false}]},'exact-session'),null);
assert.equal(enrolledDevice({...inventory,items:[device,device]},'exact-session'),null);
});
test('completed scan with healthy control remains connected and permits a named next scan',()=>{
const stopped={...device,control:{...device.control,phase:'completed'}};
const markup=render(stopped);
assert.equal(presentation.k1Status(stopped,true).label,'Подключён');
assert.equal(presentation.k1ManualState(stopped,true).canStart,true);
assert.match(markup,/Инициировать запуск/);
assert.doesNotMatch(markup,/Проверить состояние K1|Подключить устройство|Устройство остановлено/);
assert.equal(presentation.k1Status(stopped,false).tone,'neutral');
});