@@ -1104,44 +1104,87 @@ func TestTFCEvents_UnlockRemovesTags(t *testing.T) {
11041104 }
11051105}
11061106
1107- // TestTriggerCleanupEvent_ContinuesOnMissingWorkspace verifies that if one
1108- // workspace in the .tfbuddy.yaml config no longer exists in TFC, the cleanup
1109- // loop skips it and still removes the lock tag from the remaining workspaces
1110- // (rather than crashing with a nil pointer dereference on the missing ws).
1111- func TestTriggerCleanupEvent_ContinuesOnMissingWorkspace (t * testing.T ) {
1107+ // TestTriggerCleanupEvent_ScopesCleanupToTriggeredWorkspaces verifies that
1108+ // merge cleanup only inspects workspaces touched by the MR. Unrelated
1109+ // workspaces in .tfbuddy.yaml must not be looked up or produce MR noise.
1110+ func TestTriggerCleanupEvent_ScopesCleanupToTriggeredWorkspaces (t * testing.T ) {
11121111 ws := & tfc_trigger.ProjectConfig {
11131112 Workspaces : []* tfc_trigger.TFCWorkspace {
1114- {Name : "deleted-ws" , Organization : "zapier-test" , Mode : "apply-before-merge" },
1115- {Name : "service-tfbuddy" , Organization : "zapier-test" , Mode : "apply-before-merge" },
1113+ {Name : "deleted-ws" , Organization : "zapier-test" , Mode : "apply-before-merge" , Dir : "deleted" },
1114+ {Name : "service-tfbuddy" , Organization : "zapier-test" , Mode : "apply-before-merge" , Dir : "service" },
11161115 }}
11171116
11181117 mockCtrl := gomock .NewController (t )
11191118 defer mockCtrl .Finish ()
11201119 testSuite := mocks .CreateTestSuite (mockCtrl , mocks.TestOverrides {ProjectConfig : ws }, t )
11211120
1122- // First workspace is missing from TFC
1123- testSuite .MockApiClient .EXPECT ().GetWorkspaceByName (gomock .Any (), "zapier-test" , "deleted-ws" ).
1124- Return (nil , fmt .Errorf ("resource not found" ))
1125- // Second workspace exists and has the locking tag
1121+ testSuite .MockGitMR .EXPECT ().GetInternalID ().Return (testSuite .MetaData .MRIID ).AnyTimes ()
1122+ testSuite .MockGitClient .EXPECT ().GetMergeRequest (
1123+ gomock .Any (), testSuite .MetaData .MRIID , testSuite .MetaData .ProjectNameNS ,
1124+ ).Return (testSuite .MockGitMR , nil )
1125+ testSuite .MockGitClient .EXPECT ().GetRepoFile (
1126+ gomock .Any (), testSuite .MetaData .ProjectNameNS , ".tfbuddy.yaml" , testSuite .MetaData .SourceBranch ,
1127+ ).Return (testSuite .MetaData .TFBuddyConfig , nil )
1128+ testSuite .MockGitClient .EXPECT ().GetMergeRequestModifiedFiles (
1129+ gomock .Any (), testSuite .MetaData .MRIID , testSuite .MetaData .ProjectNameNS ,
1130+ ).Return ([]string {"service/main.tf" }, nil )
1131+
1132+ // Only the triggered workspace is cleaned up.
11261133 testSuite .MockApiClient .EXPECT ().GetWorkspaceByName (gomock .Any (), "zapier-test" , "service-tfbuddy" ).
11271134 Return (& tfe.Workspace {ID : "service-tfbuddy" }, nil )
11281135 testSuite .MockApiClient .EXPECT ().GetTagsByQuery (gomock .Any (), "service-tfbuddy" , "tfbuddylock-101" ).
11291136 Return ([]string {"tfbuddylock-101" }, nil )
1130- testSuite .MockApiClient .EXPECT ().RemoveTagsByQuery (gomock .Any (), "service-tfbuddy" , "tfbuddylock-101" ).
1137+ testSuite .MockApiClient .EXPECT ().RemoveTagsByName (gomock .Any (), "service-tfbuddy" , [] string { "tfbuddylock-101" } ).
11311138 Return (nil )
11321139
1133- // handleError posts an MR comment when the first workspace fails
1134- testSuite .MockGitClient .EXPECT ().CreateMergeRequestComment (
1135- gomock .Any (), testSuite .MetaData .MRIID , testSuite .MetaData .ProjectNameNS ,
1136- "Error: error getting workspace: resource not found" ,
1137- ).Return (nil )
1138-
1139- // Summary discussion is posted with the successfully cleaned workspace
11401140 testSuite .MockGitClient .EXPECT ().CreateMergeRequestDiscussion (
11411141 gomock .Any (), testSuite .MetaData .MRIID , testSuite .MetaData .ProjectNameNS ,
11421142 "Released locks for workspaces: service-tfbuddy" ,
11431143 ).Return (testSuite .MockGitDisc , nil )
11441144
1145+ tCfg , _ := tfc_trigger .NewTFCTriggerConfig (& tfc_trigger.TFCTriggerOptions {
1146+ Action : tfc_trigger .PlanAction ,
1147+ Branch : testSuite .MetaData .SourceBranch ,
1148+ CommitSHA : "abcd12233" ,
1149+ ProjectNameWithNamespace : testSuite .MetaData .ProjectNameNS ,
1150+ MergeRequestIID : testSuite .MetaData .MRIID ,
1151+ TriggerSource : tfc_trigger .MergeRequestEventTrigger ,
1152+ })
1153+ trigger := tfc_trigger .NewTFCTrigger (testSuite .MockGitClient , testSuite .MockApiClient , testSuite .MockStreamClient , tCfg )
1154+ ctx , _ := otel .Tracer ("FAKE" ).Start (context .Background (), "TEST" )
1155+ if err := trigger .TriggerCleanupEvent (ctx ); err != nil {
1156+ t .Fatalf ("cleanup should not fail when unrelated workspaces exist, got: %v" , err )
1157+ }
1158+ }
1159+
1160+ // TestTriggerCleanupEvent_DoesNotReleaseOtherMRsLocks verifies that the
1161+ // cleanup does not steal a tag from another MR whose IID happens to contain
1162+ // the merging MR's IID as a substring. The TFC ListTags `q` parameter does
1163+ // partial matching, so a query for "tfbuddylock-101" also returns
1164+ // "tfbuddylock-1010" — cleanup must filter for an exact match before
1165+ // treating the workspace as one this MR locked.
1166+ func TestTriggerCleanupEvent_DoesNotReleaseOtherMRsLocks (t * testing.T ) {
1167+ ws := & tfc_trigger.ProjectConfig {
1168+ Workspaces : []* tfc_trigger.TFCWorkspace {
1169+ {Name : "shared-ws" , Organization : "zapier-test" , Mode : "apply-before-merge" },
1170+ }}
1171+
1172+ mockCtrl := gomock .NewController (t )
1173+ defer mockCtrl .Finish ()
1174+ testSuite := mocks .CreateTestSuite (mockCtrl , mocks.TestOverrides {ProjectConfig : ws }, t )
1175+
1176+ // MR #101 is merging. The workspace is locked by MR #1010 (different MR
1177+ // whose IID contains "101" as a substring). TFC's partial-match query
1178+ // returns the foreign tag.
1179+ testSuite .MockApiClient .EXPECT ().GetWorkspaceByName (gomock .Any (), "zapier-test" , "shared-ws" ).
1180+ Return (& tfe.Workspace {ID : "shared-ws" }, nil )
1181+ testSuite .MockApiClient .EXPECT ().GetTagsByQuery (gomock .Any (), "shared-ws" , "tfbuddylock-101" ).
1182+ Return ([]string {"tfbuddylock-1010" }, nil )
1183+
1184+ // Cleanup MUST NOT remove the foreign tag and MUST NOT include the
1185+ // workspace in the summary. (No RemoveTagsByName / RemoveTagsByQuery
1186+ // call is expected; gomock fails the test if one is made.)
1187+
11451188 testSuite .InitTestSuite ()
11461189
11471190 tCfg , _ := tfc_trigger .NewTFCTriggerConfig (& tfc_trigger.TFCTriggerOptions {
@@ -1155,7 +1198,7 @@ func TestTriggerCleanupEvent_ContinuesOnMissingWorkspace(t *testing.T) {
11551198 trigger := tfc_trigger .NewTFCTrigger (testSuite .MockGitClient , testSuite .MockApiClient , testSuite .MockStreamClient , tCfg )
11561199 ctx , _ := otel .Tracer ("FAKE" ).Start (context .Background (), "TEST" )
11571200 if err := trigger .TriggerCleanupEvent (ctx ); err != nil {
1158- t .Fatalf ("cleanup should not fail when a workspace is missing , got: %v" , err )
1201+ t .Fatalf ("cleanup should not fail, got: %v" , err )
11591202 }
11601203}
11611204
0 commit comments