@@ -277,10 +277,13 @@ describe("prebuild", () => {
277277 workloadType : "MICROVM" ,
278278 orgFlags : { [ FEATURE_FLAG . computeTemplatePrebuildWait ] : true } ,
279279 } ) ;
280+ const row = await createDeployment ( prisma , project ) ;
280281 const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
281282 const result = await service . prebuild ( {
282- ...deployment ,
283283 projectId : project . id ,
284+ deploymentId : row . id ,
285+ deploymentFriendlyId : row . friendlyId ,
286+ imageReference : IMAGE ,
284287 imageDigest : DIGEST ,
285288 } ) ;
286289
@@ -291,7 +294,7 @@ describe("prebuild", () => {
291294 image : `${ IMAGE } @${ DIGEST } ` ,
292295 machine_configs : [ { cpus : 0.5 , memory_mb : 512 } ] ,
293296 no_snap : true ,
294- deployment_id : "deployment_abc" ,
297+ deployment_id : row . friendlyId ,
295298 } ) ;
296299 } finally {
297300 await server . close ( ) ;
@@ -385,20 +388,23 @@ describe("prebuild", () => {
385388 } ) ;
386389
387390 postgresTest ( "does not wait on a CLI that stops reading logs" , async ( { prisma } ) => {
388- const server = await builder ( reply ( 201 , { template_source : "built" } ) ) ;
391+ const server = await builder ( reply ( 201 , { template_id : TEMPLATE_KEY } ) ) ;
389392 try {
390393 const project = await createProject ( prisma , {
391394 workloadType : "MICROVM" ,
392395 orgFlags : { [ FEATURE_FLAG . computeTemplatePrebuildWait ] : true } ,
393396 } ) ;
397+ const row = await createDeployment ( prisma , project ) ;
394398 // A sink that never accepts a chunk, so each write stays pending.
395399 const writer = new WritableStream < string > ( {
396400 write : ( ) => new Promise < void > ( ( ) => { } ) ,
397401 } ) . getWriter ( ) ;
398402 const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
399403 const result = await service . prebuild ( {
400- ...deployment ,
401404 projectId : project . id ,
405+ deploymentId : row . id ,
406+ deploymentFriendlyId : row . friendlyId ,
407+ imageReference : IMAGE ,
402408 imageDigest : DIGEST ,
403409 writer,
404410 } ) ;
@@ -454,13 +460,17 @@ describe("prebuild", () => {
454460 workloadType : "MICROVM" ,
455461 orgFlags : { [ FEATURE_FLAG . computeTemplatePrebuildWait ] : true } ,
456462 } ) ;
463+ const row = await createDeployment ( prisma , project ) ;
457464 const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
458465 const result = await service . prebuild ( {
459- ...deployment ,
460466 projectId : project . id ,
467+ deploymentId : row . id ,
468+ deploymentFriendlyId : row . friendlyId ,
469+ imageReference : IMAGE ,
461470 imageDigest : DIGEST ,
462471 } ) ;
463472 expect ( result . outcome ) . toBe ( "already_there" ) ;
473+ expect ( await storedTemplateKey ( prisma , row . id ) ) . toBe ( "a" . repeat ( 64 ) ) ;
464474 } finally {
465475 await server . close ( ) ;
466476 }
@@ -542,10 +552,11 @@ describe("prebuild", () => {
542552 imageReference : `${ IMAGE } @sha256:${ "b" . repeat ( 64 ) } ` ,
543553 } ) ;
544554 const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
545- await service . build ( `${ IMAGE } @${ DIGEST } ` , "shadow" , {
555+ const result = await service . build ( `${ IMAGE } @${ DIGEST } ` , "shadow" , {
546556 id : row . id ,
547557 friendlyId : row . friendlyId ,
548558 } ) ;
559+ expect ( result ) . toEqual ( { outcome : "failed" , reason : "not_stored" } ) ;
549560 expect ( await storedTemplateKey ( prisma , row . id ) ) . toBeNull ( ) ;
550561 } finally {
551562 await server . close ( ) ;
@@ -591,7 +602,8 @@ describe("prebuild", () => {
591602 imageReference : IMAGE ,
592603 imageDigest : DIGEST ,
593604 } ) ;
594- expect ( result . outcome ) . toBe ( "built" ) ;
605+ // The key is not known to be stored yet, so the caller sees a timeout.
606+ expect ( result . outcome ) . toBe ( "timeout" ) ;
595607 expect ( Date . now ( ) - startedAt ) . toBeLessThan ( 5_000 ) ;
596608
597609 release ! ( ) ;
@@ -628,7 +640,7 @@ describe("prebuild", () => {
628640 imageReference : IMAGE ,
629641 imageDigest : DIGEST ,
630642 } ) ;
631- expect ( result . outcome ) . toBe ( "built " ) ;
643+ expect ( result . outcome ) . toBe ( "failed " ) ;
632644 }
633645 expect ( await storedTemplateKey ( prisma , fresh . id ) ) . toBeNull ( ) ;
634646 expect ( await storedTemplateKey ( prisma , existing . id ) ) . toBe ( TEMPLATE_KEY ) ;
@@ -771,6 +783,123 @@ describe("requestBuilds", () => {
771783 }
772784 } ) ;
773785
786+ postgresTest (
787+ "reports a stored key the mirror already had as already_there" ,
788+ async ( { prisma } ) => {
789+ const server = await builder (
790+ reply ( 200 , { template_id : TEMPLATE_KEY , template_source : "mirror" } )
791+ ) ;
792+ try {
793+ const project = await createProject ( prisma ) ;
794+ const row = await createDeployment ( prisma , project , {
795+ status : "DEPLOYED" ,
796+ imageReference : PINNED ,
797+ } ) ;
798+ const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
799+
800+ const [ result ] = await service . requestBuilds ( { deploymentIds : [ row . id ] } ) ;
801+
802+ expect ( result ) . toEqual ( {
803+ deploymentId : row . id ,
804+ friendlyId : row . friendlyId ,
805+ outcome : "already_there" ,
806+ } ) ;
807+ expect ( await storedTemplateKey ( prisma , row . id ) ) . toBe ( TEMPLATE_KEY ) ;
808+ } finally {
809+ await server . close ( ) ;
810+ }
811+ }
812+ ) ;
813+
814+ postgresTest ( "reports an invalid template id as failed" , async ( { prisma } ) => {
815+ const server = await builder ( reply ( 200 , { template_id : "not-a-key" } ) ) ;
816+ try {
817+ const project = await createProject ( prisma ) ;
818+ const row = await createDeployment ( prisma , project , {
819+ status : "DEPLOYED" ,
820+ imageReference : PINNED ,
821+ } ) ;
822+ const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
823+
824+ const [ result ] = await service . requestBuilds ( { deploymentIds : [ row . id ] } ) ;
825+
826+ expect ( result ) . toEqual ( {
827+ deploymentId : row . id ,
828+ friendlyId : row . friendlyId ,
829+ outcome : "failed" ,
830+ reason : "invalid_template_id" ,
831+ } ) ;
832+ expect ( await storedTemplateKey ( prisma , row . id ) ) . toBeNull ( ) ;
833+ } finally {
834+ await server . close ( ) ;
835+ }
836+ } ) ;
837+
838+ postgresTest ( "reports failed when the store guard matches no row" , async ( { prisma } ) => {
839+ const project = await createProject ( prisma ) ;
840+ const row = await createDeployment ( prisma , project , {
841+ status : "DEPLOYED" ,
842+ imageReference : PINNED ,
843+ } ) ;
844+ // The deployment's image changes while the builder works, so the guard rejects the write.
845+ const server = await builder ( ( req , body , res ) => {
846+ prisma . workerDeployment
847+ . update ( {
848+ where : { id : row . id } ,
849+ data : { imageReference : `${ IMAGE } @sha256:${ "b" . repeat ( 64 ) } ` } ,
850+ } )
851+ . then ( ( ) => reply ( 200 , { template_id : TEMPLATE_KEY } ) ( req , body , res ) ) ;
852+ } ) ;
853+ try {
854+ const service = new TemplatePrebuildService ( prisma , config ( { url : server . url } ) ) ;
855+
856+ const [ result ] = await service . requestBuilds ( { deploymentIds : [ row . id ] } ) ;
857+
858+ expect ( result ) . toEqual ( {
859+ deploymentId : row . id ,
860+ friendlyId : row . friendlyId ,
861+ outcome : "failed" ,
862+ reason : "not_stored" ,
863+ } ) ;
864+ expect ( await storedTemplateKey ( prisma , row . id ) ) . toBeNull ( ) ;
865+ } finally {
866+ await server . close ( ) ;
867+ }
868+ } ) ;
869+
870+ postgresTest ( "reports failed when the key write errors" , async ( { prisma } ) => {
871+ const server = await builder ( reply ( 200 , { template_id : TEMPLATE_KEY } ) ) ;
872+ try {
873+ const project = await createProject ( prisma ) ;
874+ const row = await createDeployment ( prisma , project , {
875+ status : "DEPLOYED" ,
876+ imageReference : PINNED ,
877+ } ) ;
878+
879+ // A read-only transaction lets the lookup through but makes the update fail.
880+ const results = await $transaction (
881+ prisma ,
882+ "readOnlyTemplateStore" ,
883+ async ( tx ) => {
884+ await tx . $executeRaw `SET TRANSACTION READ ONLY` ;
885+ const service = new TemplatePrebuildService ( tx , config ( { url : server . url } ) ) ;
886+ return service . requestBuilds ( { deploymentIds : [ row . id ] } ) ;
887+ } ,
888+ { timeout : 30_000 }
889+ ) ;
890+
891+ expect ( results ?. [ 0 ] ) . toEqual ( {
892+ deploymentId : row . id ,
893+ friendlyId : row . friendlyId ,
894+ outcome : "failed" ,
895+ reason : "store_error" ,
896+ } ) ;
897+ expect ( await storedTemplateKey ( prisma , row . id ) ) . toBeNull ( ) ;
898+ } finally {
899+ await server . close ( ) ;
900+ }
901+ } ) ;
902+
774903 postgresTest ( "skips every deployment when no builder is configured" , async ( { prisma } ) => {
775904 const project = await createProject ( prisma ) ;
776905 const row = await createDeployment ( prisma , project , {
0 commit comments