fix(finalist): program-scope guards on enroll/unenroll (code review)

- enrollFinalists: reject a roundId whose competition belongs to a
  different program than input.programId.
- unenroll: reject a project/round pair from different programs before
  any delete.
- Hoist ADMIN_CONFIRM attendee validation to a pre-pass so a bad entry
  in a multi-team batch fails before any project is partially written.
- Add regression tests for both cross-program guards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-06-04 15:49:13 +02:00
parent 8ee517f6ca
commit 34bb2bad57
3 changed files with 108 additions and 29 deletions

View File

@@ -204,4 +204,25 @@ describe('finalist.unenroll', () => {
})
expect(prsAfter).toBeNull()
})
it('rejects a project/round pair from different programs', async () => {
const admin = await createTestUser('SUPER_ADMIN')
userIds.push(admin.id)
const a = await setupUnenrollFixture(`unenroll-xprogram-a-${uid()}`)
const b = await setupUnenrollFixture(`unenroll-xprogram-b-${uid()}`)
programIds.push(a.program.id, b.program.id)
userIds.push(a.lead.id, a.member.id, b.lead.id, b.member.id)
const caller = createCaller(finalistRouter, {
id: admin.id,
email: admin.email,
role: 'SUPER_ADMIN',
})
// Program A's project + Program B's round → rejected before any delete.
await expect(
caller.unenroll({ projectId: a.project.id, roundId: b.liveFinalRound.id }),
).rejects.toThrow(/different programs/i)
})
})