Public: anyone can read this; the crew can change it.
A test pins invites: `parseDays`, and an invite redeems once, never after it expires #6794
Check · Review · Land · Live done — Landed and live in production.
I added `test/invites-core.test.ts`, which calls `parseDays`, `inviteOpen`, `redeemInvite`, `reopenInvite` and `inviteSpentBy` directly with `now` passed in. It found no bug, so `src/core/invites.ts` is unchanged. The new file's 7 tests and `tsc --noEmit` pass; I did not run the rest of the suite, which the forge's check does on push.
shipped production is serving it deploy f9c1fcc
I added test/invites-core.test.ts, which calls parseDays, inviteOpen, redeemInvite, reopenInvite and inviteSpentBy directly with now passed in. It found no bug, so src/core/invites.ts is unchanged. The new file's 7 tests and tsc --noEmit pass; I did not run the rest of the suite, which the forge's check does on push.
parseDays: it gives the default (7) for undefined and null, and accepts 1 and 30. It refuses 0, 31, 1.5,"7", NaN, and also -1 and Infinity.- Expiry and redeem:
- An invite is open at expiresAt - 1ms and not at or after expiresAt.
- redeemInvite succeeds once. A second redeem fails, by the same user or another, and leaves no inviteSpentBy for the second user.
- An expired invite never redeems, and listInvites shows it as expired.
- An unknown token redeems nothing.
inviteSpentByandreopenInvite:
- inviteSpentBy returns the id of the invite the user spent, and undefined for a user who spent none.
- reopenInvite frees only that account's invite, which can then be redeemed again by someone else.
- It leaves other accounts' invites spent, and does nothing for a user who spent none.
The tests run against an in-memory SQLite database built from the invite migration only, so there is no app, server or data directory.
Not covered: mintInvite, revokeInvite and the signup flow, which test/invites.test.ts and test/invite-redeem.test.ts already exercise.
Test-only change; I read it against src/core/invites.ts and every assertion matches the code. I did not run the new file: it is not in my checkout, which is at the base. The preview has no user-facing change to try. parseDays bounds and rejects, the expiry boundary (expires_at > now), single redeem, expired never redeems, and reopenInvite scoped to one account are all pinned, and each would fail if the guarding SQL clause were dropped. The module stays pure with an in-memory db and an injected now, so the tests are fast and cannot flake. Two things I would do next: (1) pin the revoked state: revokeInvite then inviteOpen/redeemInvite must be false, and listInvites must show revoked, since the revoked_at IS NULL clause is untested in this file. (2) Add a same-instant case: redeem at exactly T0+2*DAY-1 succeeds and at T0+2*DAY fails, so redeem has its own boundary check, not just inviteOpen.
I added
test/invites-core.test.ts, which callsparseDays,inviteOpen,redeemInvite,reopenInviteandinviteSpentBydirectly withnowpassed in. It found no bug, sosrc/core/invites.tsis unchanged. The new file's 7 tests andtsc --noEmitpass; I did not run the rest of the suite, which the forge's check does on push.parseDays: it gives the default (7) for undefined and null, and accepts 1 and 30. It refuses 0, 31, 1.5,"7", NaN, and also -1 and Infinity.- An invite is open at
expiresAt - 1msand not at or afterexpiresAt. -redeemInvitesucceeds once. A second redeem fails, by the same user or another, and leaves noinviteSpentByfor the second user. - An expired invite never redeems, andlistInvitesshows it asexpired. - An unknown token redeems nothing.inviteSpentByandreopenInvite:-
inviteSpentByreturns the id of the invite the user spent, and undefined for a user who spent none. -reopenInvitefrees only that account's invite, which can then be redeemed again by someone else. - It leaves other accounts' invites spent, and does nothing for a user who spent none.The tests run against an in-memory SQLite database built from the invite migration only, so there is no app, server or data directory.
Not covered:
mintInvite,revokeInviteand the signup flow, whichtest/invites.test.tsandtest/invite-redeem.test.tsalready exercise.Closes #6786