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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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.

  • inviteSpentBy and reopenInvite:

- 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.

Closes #6786

commented

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.

  • inviteSpentBy and reopenInvite:

- 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.

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs