diff --git a/backend/__tests__/unit/services/roomGrantRecord.test.js b/backend/__tests__/unit/services/roomGrantRecord.test.js index c0ad71d726..9db313dad3 100644 --- a/backend/__tests__/unit/services/roomGrantRecord.test.js +++ b/backend/__tests__/unit/services/roomGrantRecord.test.js @@ -239,3 +239,27 @@ test('a close whose write fails for one grant still names the grants it did revo const revoked = (await chainLines()).filter((l) => l.kind === 'grant.revoked'); expect(revoked.map((l) => l.data.grantIds)).toEqual([revokedIds]); }); + +test('a reject that lands between the close\'s read and its write keeps its revocation and is not named', async () => { + const grant = await service.mintGrant(input()); + const model = RoomGrant(); + const realFindOneAndUpdate = model.findOneAndUpdate.bind(model); + const rejectedAt = new Date(Date.now() - 1000); + // The refused mint's write lands after the close read its candidates. + const spy = jest.spyOn(model, 'findOneAndUpdate').mockImplementationOnce(async (...args) => { + await model.updateOne( + { grantId: grant.grantId, revokedAt: null }, + { $set: { revokedAt: rejectedAt, revocationReason: 'task.done' } }, + ); + return realFindOneAndUpdate(...args); + }); + const logged = jest.spyOn(console, 'error').mockImplementation(() => {}); + await close(); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + // onTaskClosed only logs a failed close, so an empty record needs a clean close. + expect(logged).not.toHaveBeenCalled(); + logged.mockRestore(); + expect((await model.findOne({ grantId: grant.grantId }).lean()).revokedAt).toEqual(rejectedAt); + expect((await chainLines()).filter((l) => l.kind === 'grant.revoked')).toEqual([]); +});