From 48f29cc802e66f532d2c7e321eadb3ff004e878a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 2 Aug 2026 10:01:35 +0200 Subject: [PATCH] test(sharing): hold the revoker pair at the database A revoked grant with no revoker, or a revoker with no revocation, are both rows that cannot answer the question the table exists to answer. The migration already enforces the pair; this asserts it against real Postgres rather than trusting that the domain module is the only writer. --- .../account-grant-lifecycle.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/integration/account-grant-lifecycle.test.ts b/tests/integration/account-grant-lifecycle.test.ts index 0cc3470c5..53581494f 100644 --- a/tests/integration/account-grant-lifecycle.test.ts +++ b/tests/integration/account-grant-lifecycle.test.ts @@ -303,6 +303,34 @@ describe("account grant lifecycle", () => { ).toBeNull(); }); + it("refuses a revocation the record cannot attribute", async () => { + // The database holds the halves together, because a revoked row with no + // revoker and a revoker with no revocation are both records that cannot + // answer the question the table exists to answer. The domain module always + // writes the pair; this is the floor under it. + const { grant } = await acceptedGrant(); + const prisma = getPrismaClient(); + + await expect( + prisma.accountGrant.update({ + where: { id: grant.id }, + data: { revokedAt: new Date() }, + }), + ).rejects.toThrow(); + await expect( + prisma.accountGrant.update({ + where: { id: grant.id }, + data: { revokedBy: "GRANTOR" }, + }), + ).rejects.toThrow(); + + const row = await prisma.accountGrant.findUniqueOrThrow({ + where: { id: grant.id }, + }); + expect(row.revokedAt).toBeNull(); + expect(row.revokedBy).toBeNull(); + }); + it("refuses a second live grant for the same pair", async () => { const owner = await makeUser("owner"); const delegate = await makeUser("delegate");