refactor(invites): thread tx through resolveCreatorUsername + tighten test assertions
Inside patchInvite/revokeInvite txn bodies, all reads now go through the tx proxy. Pre-Task-7 hygiene: locks in the consistent pattern that reinstateInvite (Task 7) and redeemInvite (Task 8) will copy. Createinvite test failures now pin to InviteValidationError, catching regressions where the wrong error class would otherwise pass silently.
This commit is contained in:
@@ -135,23 +135,23 @@ describe('createInvite', () => {
|
||||
|
||||
it('rejects empty name', () => {
|
||||
const adminId = seedAdmin();
|
||||
expect(() => createInvite({ name: '', maxUses: null, expiresAt: null }, adminId)).toThrow();
|
||||
expect(() => createInvite({ name: '', maxUses: null, expiresAt: null }, adminId)).toThrow(InviteValidationError);
|
||||
});
|
||||
|
||||
it('rejects name longer than 64 chars', () => {
|
||||
const adminId = seedAdmin();
|
||||
expect(() => createInvite({ name: 'x'.repeat(65), maxUses: null, expiresAt: null }, adminId)).toThrow();
|
||||
expect(() => createInvite({ name: 'x'.repeat(65), maxUses: null, expiresAt: null }, adminId)).toThrow(InviteValidationError);
|
||||
});
|
||||
|
||||
it('rejects non-positive maxUses', () => {
|
||||
const adminId = seedAdmin();
|
||||
expect(() => createInvite({ name: 'a', maxUses: 0, expiresAt: null }, adminId)).toThrow();
|
||||
expect(() => createInvite({ name: 'a', maxUses: -1, expiresAt: null }, adminId)).toThrow();
|
||||
expect(() => createInvite({ name: 'a', maxUses: 0, expiresAt: null }, adminId)).toThrow(InviteValidationError);
|
||||
expect(() => createInvite({ name: 'a', maxUses: -1, expiresAt: null }, adminId)).toThrow(InviteValidationError);
|
||||
});
|
||||
|
||||
it('rejects past expiresAt', () => {
|
||||
const adminId = seedAdmin();
|
||||
expect(() => createInvite({ name: 'a', maxUses: null, expiresAt: Date.now() - 1000 }, adminId)).toThrow();
|
||||
expect(() => createInvite({ name: 'a', maxUses: null, expiresAt: Date.now() - 1000 }, adminId)).toThrow(InviteValidationError);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -154,10 +154,16 @@ function rowToSummary(
|
||||
* Resolve the username to display for an invite's creator. Returns the live
|
||||
* username, `'Deleted User'` for tombstoned accounts (spec §3.1, §4.1), or
|
||||
* `null` if the FK is unresolvable (defensive — should not happen in practice).
|
||||
*
|
||||
* Accepts an optional Drizzle handle so callers inside a `db.transaction`
|
||||
* body can pass the `tx` proxy and keep the read on the same logical txn as
|
||||
* surrounding writes. Defaults to the outer `getDb()` for non-txn callers.
|
||||
*/
|
||||
function resolveCreatorUsername(creatorId: string): string | null {
|
||||
const db = getDb();
|
||||
const u = db.select({ username: schema.users.username, isDeleted: schema.users.isDeleted })
|
||||
function resolveCreatorUsername(
|
||||
creatorId: string,
|
||||
dbHandle: ReturnType<typeof getDb> = getDb(),
|
||||
): string | null {
|
||||
const u = dbHandle.select({ username: schema.users.username, isDeleted: schema.users.isDeleted })
|
||||
.from(schema.users)
|
||||
.where(eq(schema.users.id, creatorId))
|
||||
.get();
|
||||
@@ -342,13 +348,13 @@ export function patchInvite(id: string, req: UpdateInviteRequest): InviteLinkSum
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
// No-op: just return current summary
|
||||
return rowToSummary(row, resolveCreatorUsername(row.createdBy));
|
||||
return rowToSummary(row, resolveCreatorUsername(row.createdBy, tx));
|
||||
}
|
||||
|
||||
tx.update(schema.inviteLinks).set(updates).where(eq(schema.inviteLinks.id, id)).run();
|
||||
const updated = tx.select().from(schema.inviteLinks).where(eq(schema.inviteLinks.id, id)).get();
|
||||
if (!updated) throw new Error('Failed to read updated invite');
|
||||
return rowToSummary(updated, resolveCreatorUsername(updated.createdBy));
|
||||
return rowToSummary(updated, resolveCreatorUsername(updated.createdBy, tx));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,6 +378,6 @@ export function revokeInvite(id: string): InviteLinkSummary {
|
||||
tx.update(schema.inviteLinks).set({ revokedAt: Date.now() }).where(eq(schema.inviteLinks.id, id)).run();
|
||||
const updated = tx.select().from(schema.inviteLinks).where(eq(schema.inviteLinks.id, id)).get();
|
||||
if (!updated) throw new Error('Failed to read updated invite');
|
||||
return rowToSummary(updated, resolveCreatorUsername(updated.createdBy));
|
||||
return rowToSummary(updated, resolveCreatorUsername(updated.createdBy, tx));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user