import assert from 'node:assert/strict'; import {after,before,test} from 'node:test'; import {createServer} from 'vite'; let server,activation,x4; before(async()=>{ server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}}); activation=await server.ssrLoadModule('../../packages/sensor-ui/src/observationStart.ts'); x4=await server.ssrLoadModule('../../plugins/insta360-x4/frontend/src/observationStart.ts'); }); after(async()=>{await server?.close();}); const device=(id='camera-1')=>({id,online:true,prepared:true,layers:['color'],snapshot:{acquisition:'idle',context:{session_id:'session-1',execution:{node_id:'board-1'}}},camera_status:{connected:true,preview:0,recording:0}}); function transport(items,submit=()=>({state:'complete'})){ const commands=[];return {commands,inventory:async()=>({fresh:true,items}),submit:async c=>{commands.push(c);return submit(c);},operation:async()=>({state:'complete'})}; } test('opening two views shares one start; another camera keeps its own lifecycle',async()=>{ const a=device(),b=device('camera-2'),t=transport([a,b]); await Promise.all([activation.startCameraObservation(a,t),activation.startCameraObservation(a,t),activation.startCameraObservation(b,t)]); assert.equal(t.commands.length,2); assert.deepEqual(t.commands.map(c=>c.session.device_id).sort(),[a.id,b.id]); for(const c of t.commands){assert.equal(c.action_id,'start');assert.deepEqual(c.parameters,{record:false});} }); test('opening observer cannot replace active capture, recording or playback',async()=>{ for(const value of [{snapshot:{acquisition:'streaming'}},{snapshot:{acquisition:'starting'}},{snapshot:{acquisition:'stopping'}},{playback_id:'recorded-source'}]){ const a=device();Object.assign(a,{...value,snapshot:{...a.snapshot,...value.snapshot}});const t=transport([a]); await activation.startCameraObservation(a,t);assert.equal(t.commands.length,0); } }); test('stale inventory and replaced sessions never receive starts',async()=>{ const original=device(),replacement=device();replacement.snapshot.context.session_id='new-session'; for(const t of [transport([replacement]),{...transport([original]),inventory:async()=>({fresh:false,items:[original]})}]){ await assert.rejects(activation.startCameraObservation(original,t));assert.equal(t.commands.length,0); } }); test('X4 auto start uses preview only and attaches without restarting an active camera',async()=>{ const a=device(),t=transport([a]);await x4.startX4Observation(a,t);assert.deepEqual(t.commands.map(c=>c.action_id),['preview.start']); a.camera_status.preview=1;t.commands.length=0;await x4.startX4Observation(a,t);assert.equal(t.commands.length,0); }); test('stalled X4 preview recovers once for both lenses without any recording action',async()=>{ const a=device();a.camera_status.preview=1;const t=transport([a]); await Promise.all([x4.startX4Observation(a,t,true),x4.startX4Observation(a,t,true)]); assert.deepEqual(t.commands.map(c=>c.action_id),['preview.stop','preview.start']); }); test('X4 recovery requires confirmed stopped recording and successful preview stop',async()=>{ for(const recording of [1,null,-1]){const a=device();a.camera_status={connected:true,preview:1,recording};const t=transport([a]);await assert.rejects(x4.startX4Observation(a,t,true));assert.equal(t.commands.length,0);} const a=device();a.camera_status.preview=1;const t=transport([a],()=>({state:'unknown'})); await assert.rejects(x4.startX4Observation(a,t,true));assert.deepEqual(t.commands.map(c=>c.action_id),['preview.stop']); }); test('recovery cannot start a replacement camera session after its stop',async()=>{ const a=device();a.camera_status.preview=1;const b=device();b.snapshot.context.session_id='replacement'; const items=[a],t=transport(items,()=>{items[0]=b;return {state:'complete'};}); await assert.rejects(x4.startX4Observation(a,t,true));assert.deepEqual(t.commands.map(c=>c.action_id),['preview.stop']); });