t32-adapter-behavior.mjs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import assert from 'node:assert/strict'
  2. import { readFile } from 'node:fs/promises'
  3. import { register } from 'node:module'
  4. register('./t32-adapter-io-loader.mjs',import.meta.url)
  5. const { createCesiumPipeModels }=await import('../../../src/modules/cockpit/services/cesiumPipeModels.js')
  6. globalThis.t32Models=[]
  7. globalThis.t32TerrainFails=false
  8. globalThis.window={location:{href:'http://localhost:8002/cockpit/',origin:'http://localhost:8002'}}
  9. const originalFetch=globalThis.fetch
  10. let missing=false
  11. const requested=[]
  12. globalThis.fetch=async(input,options)=>{
  13. const url=String(input)
  14. if(url.startsWith('blob:')) return originalFetch(input,options)
  15. requested.push(url)
  16. if(missing) return new Response('missing',{status:404})
  17. const name=url.split('/').at(-1)
  18. const json=await readFile(new URL('../../../public/cockpit/models/pipes/'+name,import.meta.url),'utf8')
  19. return new Response(json,{status:200})
  20. }
  21. const source={entities:{values:[]}}
  22. const added=[],removed=[]
  23. const viewer={terrainProvider:{},isDestroyed:()=>false,scene:{requestRender(){},primitives:{
  24. add(model){added.push(model)},remove(model){removed.push(model);model.destroy()},
  25. }}}
  26. const states=[]
  27. let adapter=createCesiumPipeModels(viewer,{source,onChange:state=>states.push(state),base:'/'})
  28. const pipe=(code,lon)=>({code,industry:'GAS',diameter:400,positions:[[lon,28.44],[lon+.00004,28.44]]})
  29. const network={pipes:[pipe('east',110.391),pipe('west',110.411)],components:[]}
  30. source.entities.values=network.pipes.map(row=>({id:'pipe:'+row.code,show:true}))
  31. await adapter.sync(network)
  32. assert.equal(added.length,2)
  33. assert.equal(states.at(-1).pending,2)
  34. assert.ok(source.entities.values.every(e=>e.show),'load promise does not remove fallback')
  35. for(const model of added) model.readyEvent.raiseEvent()
  36. assert.ok(source.entities.values.every(e=>!e.show),'ready switches complete pipes to models')
  37. assert.equal(requested.filter(url=>url.endsWith('spline.gltf')).length,1)
  38. assert.equal(added[0].gltf.buffers[0].uri,added[1].gltf.buffers[0].uri)
  39. assert.equal(added[0].gltf.buffers[0].uri,'http://localhost:8002/cockpit/models/pipes/spline.bin')
  40. await adapter.sync({...network,pipes:network.pipes.map(row=>({...row,name:'changed metadata'}))})
  41. assert.equal(added.length,2,'metadata-only change does not rebuild GPU batches')
  42. await adapter.sync({...network,pipes:[{...network.pipes[0],diameter:600},network.pipes[1]]})
  43. assert.equal(added.length,3,'only changed tile recreated')
  44. assert.equal(removed.length,1)
  45. assert.equal(source.entities.values[0].show,true)
  46. assert.equal(source.entities.values[1].show,false)
  47. added[2].readyEvent.raiseEvent()
  48. added[2].errorEvent.raiseEvent(new Error('late buffer failure'))
  49. assert.equal(source.entities.values[0].show,true)
  50. assert.equal(states.at(-1).failed,1)
  51. adapter.destroy()
  52. assert.ok(added.every(model=>model.destroyed))
  53. console.log('PASS real adapter readiness, shared URL, stable metadata, selective update, late error and cleanup')
  54. const terrainStates=[]
  55. globalThis.t32TerrainFails=true
  56. source.entities.values=[{id:'pipe:terrain',show:true}]
  57. adapter=createCesiumPipeModels(viewer,{source,onChange:state=>terrainStates.push(state),base:'/'})
  58. await adapter.sync({pipes:[pipe('terrain',110.431)],components:[]})
  59. assert.equal(terrainStates.at(-1).terrainFailed,true)
  60. assert.equal(terrainStates.at(-1).fallback,1)
  61. assert.equal(source.entities.values[0].show,true)
  62. adapter.destroy()
  63. console.log('PASS terrain failure preserves fallback and publishes reason')
  64. globalThis.t32TerrainFails=false
  65. missing=true
  66. const assetStates=[]
  67. source.entities.values=[{id:'component:404',show:true}]
  68. adapter=createCesiumPipeModels(viewer,{source,onChange:state=>assetStates.push(state),base:'missing-base/'})
  69. await adapter.sync({pipes:[],components:[{code:'404',kind:'valve',industry:'GAS',position:[110.431,28.44]}]})
  70. assert.equal(assetStates.at(-1).failed,1)
  71. assert.equal(assetStates.at(-1).fallback,1)
  72. assert.equal(source.entities.values[0].show,true)
  73. adapter.destroy()
  74. globalThis.fetch=originalFetch
  75. console.log('PASS template 404 reaches real adapter failure path and keeps symbol')