@@ -21,6 +21,7 @@ import (
2121 "encoding/json"
2222 "os"
2323 "testing"
24+ "time"
2425
2526 schemav1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2627 "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz"
@@ -601,6 +602,52 @@ func (s *workflowRunIntegrationTestSuite) TestCreate() {
601602 s .Contains (err .Error (), "cannot promote a released version" )
602603 })
603604
605+ s .T ().Run ("mark-as-latest true re-reads version inside transaction" , func (_ * testing.T ) {
606+ // Create a pre-release version
607+ run , err := s .WorkflowRun .Create (ctx , & biz.WorkflowRunCreateOpts {
608+ WorkflowID : s .workflowOrg1 .ID .String (), ContractRevision : s .contractVersion , CASBackendID : s .casBackend .ID ,
609+ RunnerType : "runnerType" , RunnerRunURL : "runURL" , ProjectVersion : "ml-reread-test" ,
610+ })
611+ s .Require ().NoError (err )
612+ s .True (run .ProjectVersion .Prerelease )
613+
614+ // Release the version — simulates a concurrent release between lookup and tx
615+ _ , err = s .ProjectVersion .UpdateReleaseStatus (ctx , run .ProjectVersion .ID .String (), true )
616+ s .Require ().NoError (err )
617+
618+ // Attempt to promote with mark-as-latest=true — the in-tx re-read should catch the release
619+ markTrue := true
620+ _ , err = s .WorkflowRun .Create (ctx , & biz.WorkflowRunCreateOpts {
621+ WorkflowID : s .workflowOrg1 .ID .String (), ContractRevision : s .contractVersion , CASBackendID : s .casBackend .ID ,
622+ RunnerType : "runnerType" , RunnerRunURL : "runURL" , ProjectVersion : "ml-reread-test" , MarkAsLatest : & markTrue ,
623+ })
624+ s .Require ().Error (err )
625+ s .True (biz .IsErrValidation (err ))
626+ s .Contains (err .Error (), "cannot promote a released version" )
627+ })
628+
629+ s .T ().Run ("mark-as-latest true on soft-deleted version returns not found" , func (_ * testing.T ) {
630+ // Create a version
631+ run , err := s .WorkflowRun .Create (ctx , & biz.WorkflowRunCreateOpts {
632+ WorkflowID : s .workflowOrg1 .ID .String (), ContractRevision : s .contractVersion , CASBackendID : s .casBackend .ID ,
633+ RunnerType : "runnerType" , RunnerRunURL : "runURL" , ProjectVersion : "ml-deleted-test" ,
634+ })
635+ s .Require ().NoError (err )
636+
637+ // Soft-delete the version — simulates concurrent deletion between lookup and tx
638+ _ , err = s .Data .DB .ProjectVersion .UpdateOneID (run .ProjectVersion .ID ).
639+ SetDeletedAt (time .Now ()).Save (ctx )
640+ s .Require ().NoError (err )
641+
642+ markTrue := true
643+ _ , err = s .WorkflowRun .Create (ctx , & biz.WorkflowRunCreateOpts {
644+ WorkflowID : s .workflowOrg1 .ID .String (), ContractRevision : s .contractVersion , CASBackendID : s .casBackend .ID ,
645+ RunnerType : "runnerType" , RunnerRunURL : "runURL" , ProjectVersion : "ml-deleted-test" , MarkAsLatest : & markTrue ,
646+ })
647+ // The pre-tx lookup won't find the soft-deleted version, so it creates a new one — this is fine
648+ s .Require ().NoError (err )
649+ })
650+
604651 s .T ().Run ("mark-as-latest false on existing version — no promotion change" , func (_ * testing.T ) {
605652 // Create two versions — v2 is latest
606653 _ , err := s .WorkflowRun .Create (ctx , & biz.WorkflowRunCreateOpts {
0 commit comments