@@ -75,12 +75,16 @@ func (s *InstitutionDeletionTestSuite) TestAdminCanDeleteInstitutionWithCascade(
7575 s .T ().Logf ("Users after deletion: %d" , len (usersAfter ))
7676 s .Equal (len (usersBefore )- 1 , len (usersAfter ), "only participant should be removed" )
7777
78- // Head and staff should still exist
78+ // Head and staff should still exist with individual roles
7979 headMe := Must (head .GetMe ())
8080 s .Equal (head .Name , headMe .Name , "head should still exist" )
81+ s .Require ().NotNil (headMe .Role , "head should have a role" )
82+ s .Equal (obj .RoleIndividual , headMe .Role .Role , "head should become individual" )
8183 staffMe := Must (staff .GetMe ())
8284 s .Equal (staff .Name , staffMe .Name , "staff should still exist" )
83- s .T ().Logf ("Head and staff survived institution deletion" )
85+ s .Require ().NotNil (staffMe .Role , "staff should have a role" )
86+ s .Equal (obj .RoleIndividual , staffMe .Role .Role , "staff should become individual" )
87+ s .T ().Logf ("Head and staff survived institution deletion with individual roles" )
8488
8589 // Participant's game should be gone
8690 games := Must (admin .ListGames ())
@@ -175,6 +179,125 @@ func (s *InstitutionDeletionTestSuite) TestAdminDeleteOrgMultipleUsersAndWorksho
175179 s .T ().Logf ("All participant games deleted, all non-participants survived" )
176180}
177181
182+ // TestDeleteInstitutionUnlinksMemberGamesDeletesParticipantGames tests that when an institution
183+ // is deleted, participant games are removed but head/staff games are preserved (unlinked from workshop).
184+ func (s * InstitutionDeletionTestSuite ) TestDeleteInstitutionUnlinksMemberGamesDeletesParticipantGames () {
185+ admin := s .DevUser ()
186+
187+ inst := Must (admin .CreateInstitution ("Game Unlink Org" ))
188+ instIDStr := inst .ID .String ()
189+
190+ head := s .CreateUser ("gu-head" )
191+ headInvite := Must (admin .InviteToInstitution (instIDStr , "head" , head .ID ))
192+ Must (head .AcceptInvite (headInvite .ID .String ()))
193+
194+ staff := s .CreateUser ("gu-staff" )
195+ staffInvite := Must (head .InviteToInstitution (instIDStr , "staff" , staff .ID ))
196+ Must (staff .AcceptInvite (staffInvite .ID .String ()))
197+
198+ // Create workshop with a participant
199+ workshop := Must (head .CreateWorkshop (instIDStr , "Game Unlink Workshop" ))
200+ wsIDStr := workshop .ID .String ()
201+
202+ invite := Must (head .CreateWorkshopInvite (wsIDStr , string (obj .RoleParticipant )))
203+ resp , err := s .AcceptWorkshopInviteAnonymously (* invite .InviteToken )
204+ s .NoError (err )
205+ participant := s .CreateUserWithToken (* resp .AuthToken )
206+
207+ // Head creates a game (member game — should be preserved)
208+ headGame := Must (head .UploadGame ("alien-first-contact" ))
209+ s .T ().Logf ("Head created game: %s (ID: %s)" , headGame .Name , headGame .ID )
210+
211+ // Staff creates a game (member game — should be preserved)
212+ staffGame := Must (staff .UploadGame ("alien-first-contact" ))
213+ s .T ().Logf ("Staff created game: %s (ID: %s)" , staffGame .Name , staffGame .ID )
214+
215+ // Participant creates a game (should be deleted)
216+ participantGame := Must (participant .UploadGame ("alien-first-contact" ))
217+ s .T ().Logf ("Participant created game: %s (ID: %s)" , participantGame .Name , participantGame .ID )
218+
219+ // Delete the institution
220+ MustSucceed (admin .DeleteInstitution (instIDStr ))
221+ s .T ().Logf ("Admin deleted institution" )
222+
223+ // Head's game should still exist (unlinked from workshop)
224+ fetchedHeadGame , err := head .GetGameByID (headGame .ID .String ())
225+ s .NoError (err , "head's game should survive institution deletion" )
226+ s .Equal (headGame .ID , fetchedHeadGame .ID )
227+ s .Nil (fetchedHeadGame .WorkshopID , "head's game should be unlinked from workshop" )
228+ s .T ().Logf ("Head's game survived and is unlinked" )
229+
230+ // Staff's game should still exist (unlinked from workshop)
231+ fetchedStaffGame , err := staff .GetGameByID (staffGame .ID .String ())
232+ s .NoError (err , "staff's game should survive institution deletion" )
233+ s .Equal (staffGame .ID , fetchedStaffGame .ID )
234+ s .Nil (fetchedStaffGame .WorkshopID , "staff's game should be unlinked from workshop" )
235+ s .T ().Logf ("Staff's game survived and is unlinked" )
236+
237+ // Participant's game should be gone (participant user deleted)
238+ _ , err = admin .GetGameByID (participantGame .ID .String ())
239+ s .Error (err , "participant's game should be deleted with institution" )
240+ s .T ().Logf ("Participant's game correctly deleted" )
241+
242+ // Head and staff should now have individual roles (not left without a role)
243+ headMe := Must (head .GetMe ())
244+ s .Require ().NotNil (headMe .Role , "head should still have a role after institution deletion" )
245+ s .Equal (obj .RoleIndividual , headMe .Role .Role , "head should become individual after institution deletion" )
246+ s .Nil (headMe .Role .Institution , "head should have no institution after institution deletion" )
247+ s .T ().Logf ("Head has individual role" )
248+
249+ staffMe := Must (staff .GetMe ())
250+ s .Require ().NotNil (staffMe .Role , "staff should still have a role after institution deletion" )
251+ s .Equal (obj .RoleIndividual , staffMe .Role .Role , "staff should become individual after institution deletion" )
252+ s .Nil (staffMe .Role .Institution , "staff should have no institution after institution deletion" )
253+ s .T ().Logf ("Staff has individual role" )
254+ }
255+
256+ // TestDeleteWorkshopUnlinksMemberGames tests that deleting a single workshop
257+ // unlinks member games and deletes participant games.
258+ func (s * InstitutionDeletionTestSuite ) TestDeleteWorkshopUnlinksMemberGames () {
259+ admin := s .DevUser ()
260+
261+ inst := Must (admin .CreateInstitution ("WS Del Org" ))
262+ instIDStr := inst .ID .String ()
263+
264+ head := s .CreateUser ("wsd-head" )
265+ headInvite := Must (admin .InviteToInstitution (instIDStr , "head" , head .ID ))
266+ Must (head .AcceptInvite (headInvite .ID .String ()))
267+
268+ workshop := Must (head .CreateWorkshop (instIDStr , "WS Del Workshop" ))
269+ wsIDStr := workshop .ID .String ()
270+
271+ invite := Must (head .CreateWorkshopInvite (wsIDStr , string (obj .RoleParticipant )))
272+ resp , err := s .AcceptWorkshopInviteAnonymously (* invite .InviteToken )
273+ s .NoError (err )
274+ participant := s .CreateUserWithToken (* resp .AuthToken )
275+
276+ // Head creates a game in the workshop context
277+ headGame := Must (head .UploadGame ("alien-first-contact" ))
278+ s .T ().Logf ("Head created game: %s" , headGame .ID )
279+
280+ // Participant creates a game
281+ participantGame := Must (participant .UploadGame ("alien-first-contact" ))
282+ s .T ().Logf ("Participant created game: %s" , participantGame .ID )
283+
284+ // Delete the workshop (not the institution)
285+ MustSucceed (head .DeleteWorkshop (wsIDStr ))
286+ s .T ().Logf ("Head deleted workshop" )
287+
288+ // Head's game should still exist (unlinked)
289+ fetchedHeadGame , err := head .GetGameByID (headGame .ID .String ())
290+ s .NoError (err , "head's game should survive workshop deletion" )
291+ s .Equal (headGame .ID , fetchedHeadGame .ID )
292+ s .Nil (fetchedHeadGame .WorkshopID , "head's game should be unlinked from workshop" )
293+ s .T ().Logf ("Head's game survived and is unlinked" )
294+
295+ // Participant's game should be gone
296+ _ , err = admin .GetGameByID (participantGame .ID .String ())
297+ s .Error (err , "participant's game should be deleted with workshop" )
298+ s .T ().Logf ("Participant's game correctly deleted" )
299+ }
300+
178301// TestHeadCannotDeleteInstitution tests that a head cannot delete their own institution.
179302func (s * InstitutionDeletionTestSuite ) TestHeadCannotDeleteInstitution () {
180303 admin := s .DevUser ()
0 commit comments