test(invites): document empty-updates guard + Path A rollback test

Quality-review polish: a one-line comment over the empty-updates guard
in reinstateInvite explains why removing it would re-leak a confusing
Drizzle error. A new test verifies that when Path A (revoked->active)
fails its post-state check, the original token is preserved by the
SQLite transaction rollback (not replaced by the would-be new token).
This commit is contained in:
Jannis Braun
2026-04-28 20:24:15 +02:00
parent ee405cba7c
commit b7557aa6fa
2 changed files with 24 additions and 0 deletions
@@ -445,4 +445,22 @@ describe('reinstateInvite', () => {
const row = testDb.select().from(schema.inviteLinks).where(eq(schema.inviteLinks.id, inv.id)).get();
expect(row?.maxUses).toBe(1);
});
it('Path A — rolls back token rotation when caller did not bump enough', () => {
const adminId = seedAdmin();
const inv = createInvite({ name: 'a', maxUses: 1, expiresAt: null }, adminId);
const originalToken = inv.token;
// Exhaust then revoke
testDb.update(schema.inviteLinks).set({ usedCount: 1 }).where(eq(schema.inviteLinks.id, inv.id)).run();
revokeInvite(inv.id);
// Try to reinstate without bumping maxUses — would-be Path A but post-state check rejects
expect(() => reinstateInvite(inv.id, {})).toThrow(InviteValidationError);
// Verify rollback: original token preserved, revokedAt still set, usedCount still at limit
const row = testDb.select().from(schema.inviteLinks).where(eq(schema.inviteLinks.id, inv.id)).get();
expect(row?.token).toBe(originalToken);
expect(row?.revokedAt).not.toBeNull();
expect(row?.usedCount).toBe(1);
});
});