import assert from 'node:assert/strict'; import {before,after,test} from 'node:test'; import {createServer} from 'vite'; import {readFileSync} from 'node:fs'; let server,start; before(async()=>{server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});({startPlanningSceneStream:start}=await server.ssrLoadModule('/src/core/missions/planningSceneStream.ts'));}); after(async()=>{await server?.close();}); const settle=()=>new Promise(resolve=>setImmediate(resolve)); function fixture(){ let sequence=0,state={active:true,revision:1,options:{mode:'3d'}},clock=0; const timers=new Map(),requests=[],applied=[],errors=[]; const stop=start({url:'/scene',snapshot:()=>state,now:()=>clock, schedule:(fn,ms)=>{const id=++sequence;timers.set(id,{fn,ms});return id;},cancel:id=>timers.delete(id), fetcher:(url,init)=>new Promise((resolve,reject)=>requests.push({url,init,resolve,reject})), apply:bytes=>applied.push(bytes),error:()=>errors.push(true)}); return {timers,requests,applied,errors,stop,setState:value=>{state=value;},setClock:value=>{clock=value;}, next:()=>{const [id,timer]=[...timers].find(([,t])=>t.ms!==2500);timers.delete(id);timer.fn();}, answer:(index,cursor='next',status=200)=>requests[index].resolve(new Response(status===204?null:new Uint8Array([1,2]),{status,headers:{'X-Planning-Scene-Cursor':cursor}}))}; } test('one pending fetch, native cadence, no-op response and cancellation',async()=>{ const f=fixture();assert.equal(f.requests.length,1); assert.equal(f.timers.size,1); // deadline only: no overlapping polling interval f.setClock(35);f.answer(0);await settle(); assert.equal(f.applied.length,1);assert.equal([...f.timers.values()][0].ms,65); f.next();assert.match(f.requests[1].url,/cursor=next/); f.answer(1,'next',204);await settle();assert.equal(f.applied.length,1); f.next();f.stop();assert.equal(f.requests[2].init.signal.aborted,true); f.answer(2);await settle();assert.equal(f.applied.length,1);assert.equal(f.timers.size,0); }); test('a changed mode or ended run discards the in-flight response and rebases',async()=>{ for(const next of [{active:true,revision:2,options:{mode:'top'}},{active:false,revision:2,options:{mode:'3d'}}]){ const f=fixture();f.setState(next);f.answer(0);await settle(); assert.equal(f.applied.length,0);f.next();assert.match(f.requests[1].url,/base=true/); f.answer(1);await settle();assert.equal(f.applied.length,1);f.stop(); } }); test('failure hides stale evidence, repairs geometry but retains admitted camera cursor',async()=>{ const f=fixture();f.answer(0);await settle();f.next(); f.requests[1].reject(new Error('offline'));await settle(); assert.equal(f.errors.length,1);assert.equal([...f.timers.values()][0].ms,500); f.next();assert.match(f.requests[2].url,/base=true/);assert.match(f.requests[2].url,/cursor=next/); f.stop();f.answer(2);await settle();assert.equal(f.applied.length,1); }); test('display changes and discarded responses retain camera identity; reset intent reaches server',async()=>{ const f=fixture();f.answer(0,'admitted-camera');await settle();f.next(); f.setState({active:true,revision:1,options:{mode:'3d',ceiling_m:3,reset:0}}); f.answer(1,'discarded-camera');await settle(); assert.equal(f.applied.length,1);f.next(); assert.match(f.requests[2].url,/cursor=admitted-camera/); assert.match(f.requests[2].url,/base=true/); assert.match(f.requests[2].url,/reset=0/); f.answer(2,'clipped');await settle(); f.setState({active:true,revision:1,options:{mode:'3d',ceiling_m:3,reset:1}}); f.next();assert.match(f.requests[3].url,/reset=1/); f.answer(3,'reset');await settle();f.stop(); }); test('unchanged terminal views stop transfer but notice a later revision',async()=>{ const f=fixture();f.setState({active:false,revision:2,options:{mode:'3d'}}); f.answer(0);await settle();f.next();f.answer(1);await settle();f.next(); assert.equal(f.requests.length,2); f.setState({active:false,revision:3,options:{mode:'3d'}});f.next(); assert.equal(f.requests.length,3);f.stop();f.answer(2);await settle(); }); test('cloud or fit expiring during delivery cannot revive green',async()=>{ for(const [cloud,fit] of [['1.8','1'],['.1','7.8'],['invalid','1']]){ const f=fixture();f.setClock(300); f.requests[0].resolve(new Response(new Uint8Array([1]),{headers:{ 'X-Planning-Scene-Cursor':'next','X-Planning-Presentation':'live', 'X-Planning-Cloud-Age':cloud,'X-Planning-Fit-Age':fit}})); await settle();assert.equal(f.applied.length,0);assert.equal(f.errors.length,1);f.stop(); } }); test('a live response waits for bounded browser delivery and passes its exact receipt identity',async()=>{ let release,delivery; let sequence=0,state={active:true,revision:1,options:{mode:'3d'}},clock=0; const timers=new Map(),requests=[]; const stop=start({url:'/scene',snapshot:()=>state,now:()=>clock, schedule:(fn,ms)=>{const id=++sequence;timers.set(id,{fn,ms});return id;},cancel:id=>timers.delete(id), fetcher:(url,init)=>new Promise(resolve=>requests.push({url,init,resolve})), apply:(_bytes,value)=>{delivery=value;return new Promise(resolve=>{release=resolve;});},error:assert.fail, }); clock=40; requests[0].resolve(new Response(new Uint8Array([1]),{headers:{ 'X-Planning-Scene-Cursor':'next','X-Planning-Presentation':'live', 'X-Planning-Cloud-Age':'.2','X-Planning-Fit-Age':'.1', 'X-Planning-Cloud-Revision':'3','X-Planning-Cloud-Sequence':'4', 'X-Planning-Display-Epoch':'5d8c5d13-dfde-4ab6-9da0-475ab1f22c12', 'X-Planning-Height-Min':'-1.2','X-Planning-Height-Max':'51.5', }})); await settle(); assert.deepEqual(delivery,{presentation:'live',cloudAgeMs:200,fitAgeMs:100,requestMs:40, cloudRevision:3,cloudSequence:4,displayEpoch:'5d8c5d13-dfde-4ab6-9da0-475ab1f22c12', heightMinM:-1.2,heightMaxM:51.5}); assert.equal(timers.size,1); assert.equal([...timers.values()][0].ms,2500); release();await settle(); assert.equal([...timers.values()][0].ms,60); stop(); }); test('profile scene can expand after native capture ends and retains Escape',()=>{ const source=readFileSync(new URL('../src/workspaces/spatial/SpatialWorkspace.tsx',import.meta.url),'utf8'); assert.match(source,/pointCloudVisible && \(visualProfile \|\| sourceUrl.trim\(\)\)/); assert.match(source,/