@@ -279,4 +279,197 @@ test('texture conversion handling', async ({ components, spyComponents }) => {
279279 expect ( olderRegistry ! . status ) . toBe ( Registry . Status . OBSOLETE )
280280 } )
281281 } )
282+
283+ describe ( 'when a redeployment arrives before the first scene finishes conversion and the second one fails' , ( ) => {
284+ let olderEntityId : string
285+ let newerEntityId : string
286+
287+ beforeEach ( async ( ) => {
288+ olderEntityId = `texture-redeploy-older-${ Date . now ( ) } `
289+ newerEntityId = `texture-redeploy-newer-${ Date . now ( ) } `
290+
291+ // Deploy the older scene (textures have NOT completed yet)
292+ const olderEntity = createEntity ( { id : olderEntityId , pointers : [ '905,905' ] , timestamp : 1000 } )
293+ mockGenesisCityDeployment ( olderEntity )
294+ registriesToCleanUp . push ( olderEntityId )
295+ await components . messageProcessor . process ( createDeploymentMessage ( olderEntityId ) )
296+
297+ // Deploy the newer scene at the same pointers BEFORE older scene's textures complete
298+ const newerEntity = createEntity ( { id : newerEntityId , pointers : [ '905,905' ] , timestamp : 2000 } )
299+ mockGenesisCityDeployment ( newerEntity )
300+ registriesToCleanUp . push ( newerEntityId )
301+ await components . messageProcessor . process ( createDeploymentMessage ( newerEntityId ) )
302+
303+ // The older scene's textures complete — it was left as PENDING (not marked OBSOLETE)
304+ await components . messageProcessor . process ( createTextureEvent ( olderEntityId , 'windows' ) )
305+ await components . messageProcessor . process ( createTextureEvent ( olderEntityId , 'mac' ) )
306+
307+ // The newer scene's textures fail
308+ await components . messageProcessor . process ( createTextureEvent ( newerEntityId , 'windows' ) )
309+ await components . messageProcessor . process (
310+ createTextureEvent ( newerEntityId , 'mac' , {
311+ metadata : {
312+ entityId : newerEntityId ,
313+ platform : 'mac' ,
314+ statusCode : ManifestStatusCode . ASSET_BUNDLE_BUILD_FAIL ,
315+ isLods : false ,
316+ isWorld : false ,
317+ version : 'v1'
318+ }
319+ } )
320+ )
321+ } )
322+
323+ it ( 'should keep the older registry as complete since it was never marked obsolete' , async ( ) => {
324+ const olderRegistry = await components . db . getRegistryById ( olderEntityId )
325+
326+ expect ( olderRegistry ) . not . toBeNull ( )
327+ expect ( olderRegistry ! . status ) . toBe ( Registry . Status . COMPLETE )
328+ } )
329+
330+ it ( 'should have the older registry bundles as complete' , async ( ) => {
331+ const olderRegistry = await components . db . getRegistryById ( olderEntityId )
332+
333+ expect ( olderRegistry ! . bundles . assets . windows ) . toBe ( Registry . SimplifiedStatus . COMPLETE )
334+ expect ( olderRegistry ! . bundles . assets . mac ) . toBe ( Registry . SimplifiedStatus . COMPLETE )
335+ } )
336+
337+ it ( 'should mark the newer registry as failed' , async ( ) => {
338+ const newerRegistry = await components . db . getRegistryById ( newerEntityId )
339+
340+ expect ( newerRegistry ) . not . toBeNull ( )
341+ expect ( newerRegistry ! . status ) . toBe ( Registry . Status . FAILED )
342+ } )
343+
344+ it ( 'should still have an active registry at the pointers' , async ( ) => {
345+ const olderRegistry = await components . db . getRegistryById ( olderEntityId )
346+
347+ // The older registry was never marked OBSOLETE — it completed and is served
348+ expect ( olderRegistry ! . status ) . toBe ( Registry . Status . COMPLETE )
349+ } )
350+ } )
351+
352+ describe ( 'when the newer scene completes before the older one (out-of-order)' , ( ) => {
353+ let olderEntityId : string
354+ let newerEntityId : string
355+
356+ beforeEach ( async ( ) => {
357+ olderEntityId = `texture-ooo-older-${ Date . now ( ) } `
358+ newerEntityId = `texture-ooo-newer-${ Date . now ( ) } `
359+
360+ // Deploy both scenes before any textures arrive
361+ const olderEntity = createEntity ( { id : olderEntityId , pointers : [ '906,906' ] , timestamp : 1000 } )
362+ mockGenesisCityDeployment ( olderEntity )
363+ registriesToCleanUp . push ( olderEntityId )
364+ await components . messageProcessor . process ( createDeploymentMessage ( olderEntityId ) )
365+
366+ const newerEntity = createEntity ( { id : newerEntityId , pointers : [ '906,906' ] , timestamp : 2000 } )
367+ mockGenesisCityDeployment ( newerEntity )
368+ registriesToCleanUp . push ( newerEntityId )
369+ await components . messageProcessor . process ( createDeploymentMessage ( newerEntityId ) )
370+
371+ // Newer scene completes FIRST
372+ await components . messageProcessor . process ( createTextureEvent ( newerEntityId , 'windows' ) )
373+ await components . messageProcessor . process ( createTextureEvent ( newerEntityId , 'mac' ) )
374+
375+ // Older scene completes AFTER
376+ await components . messageProcessor . process ( createTextureEvent ( olderEntityId , 'windows' ) )
377+ await components . messageProcessor . process ( createTextureEvent ( olderEntityId , 'mac' ) )
378+ } )
379+
380+ it ( 'should mark the older registry as obsolete since a newer one is already complete' , async ( ) => {
381+ const olderRegistry = await components . db . getRegistryById ( olderEntityId )
382+
383+ expect ( olderRegistry ) . not . toBeNull ( )
384+ expect ( olderRegistry ! . status ) . toBe ( Registry . Status . OBSOLETE )
385+ } )
386+
387+ it ( 'should keep the newer registry as complete' , async ( ) => {
388+ const newerRegistry = await components . db . getRegistryById ( newerEntityId )
389+
390+ expect ( newerRegistry ) . not . toBeNull ( )
391+ expect ( newerRegistry ! . status ) . toBe ( Registry . Status . COMPLETE )
392+ } )
393+ } )
394+
395+ describe ( 'when three rapid deployments happen and none complete before the next arrives' , ( ) => {
396+ let entityAId : string
397+ let entityBId : string
398+ let entityCId : string
399+
400+ beforeEach ( async ( ) => {
401+ entityAId = `texture-triple-a-${ Date . now ( ) } `
402+ entityBId = `texture-triple-b-${ Date . now ( ) } `
403+ entityCId = `texture-triple-c-${ Date . now ( ) } `
404+
405+ // Deploy all three before any textures complete
406+ const entityA = createEntity ( { id : entityAId , pointers : [ '907,907' ] , timestamp : 1000 } )
407+ mockGenesisCityDeployment ( entityA )
408+ registriesToCleanUp . push ( entityAId )
409+ await components . messageProcessor . process ( createDeploymentMessage ( entityAId ) )
410+
411+ const entityB = createEntity ( { id : entityBId , pointers : [ '907,907' ] , timestamp : 2000 } )
412+ mockGenesisCityDeployment ( entityB )
413+ registriesToCleanUp . push ( entityBId )
414+ await components . messageProcessor . process ( createDeploymentMessage ( entityBId ) )
415+
416+ const entityC = createEntity ( { id : entityCId , pointers : [ '907,907' ] , timestamp : 3000 } )
417+ mockGenesisCityDeployment ( entityC )
418+ registriesToCleanUp . push ( entityCId )
419+ await components . messageProcessor . process ( createDeploymentMessage ( entityCId ) )
420+
421+ // A completes
422+ await components . messageProcessor . process ( createTextureEvent ( entityAId , 'windows' ) )
423+ await components . messageProcessor . process ( createTextureEvent ( entityAId , 'mac' ) )
424+
425+ // B fails
426+ await components . messageProcessor . process (
427+ createTextureEvent ( entityBId , 'mac' , {
428+ metadata : {
429+ entityId : entityBId ,
430+ platform : 'mac' ,
431+ statusCode : ManifestStatusCode . ASSET_BUNDLE_BUILD_FAIL ,
432+ isLods : false ,
433+ isWorld : false ,
434+ version : 'v1'
435+ }
436+ } )
437+ )
438+
439+ // C fails
440+ await components . messageProcessor . process (
441+ createTextureEvent ( entityCId , 'mac' , {
442+ metadata : {
443+ entityId : entityCId ,
444+ platform : 'mac' ,
445+ statusCode : ManifestStatusCode . ASSET_BUNDLE_BUILD_FAIL ,
446+ isLods : false ,
447+ isWorld : false ,
448+ version : 'v1'
449+ }
450+ } )
451+ )
452+ } )
453+
454+ it ( 'should keep entity A as complete since it successfully converted' , async ( ) => {
455+ const registryA = await components . db . getRegistryById ( entityAId )
456+
457+ expect ( registryA ) . not . toBeNull ( )
458+ expect ( registryA ! . status ) . toBe ( Registry . Status . COMPLETE )
459+ } )
460+
461+ it ( 'should mark entity B as failed' , async ( ) => {
462+ const registryB = await components . db . getRegistryById ( entityBId )
463+
464+ expect ( registryB ) . not . toBeNull ( )
465+ expect ( registryB ! . status ) . toBe ( Registry . Status . FAILED )
466+ } )
467+
468+ it ( 'should mark entity C as failed' , async ( ) => {
469+ const registryC = await components . db . getRegistryById ( entityCId )
470+
471+ expect ( registryC ) . not . toBeNull ( )
472+ expect ( registryC ! . status ) . toBe ( Registry . Status . FAILED )
473+ } )
474+ } )
282475} )
0 commit comments