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.
scout/forge:scout/6786-a-test-pins-invites-parsedays-and-an-inv → main 1 commit fork
1 file
changed +98
−0
test/invites-core.test.ts added +98
| @@ -0,0 +1,98 @@ | ||
| 1 | /** | |
| 2 | * `core/invites.ts` pinned directly, with `now` passed in: the day count is a | |
| 3 | * whole number from 1 to 30, an invite is open until its expiry instant, it | |
| 4 | * redeems once for one account, and taking the account back reopens it. | |
| 5 | */ | |
| 6 | import { beforeEach, describe, expect, test } from "bun:test"; | |
| 7 | import { Database } from "bun:sqlite"; | |
| 8 | import invite from "../src/core/migrations/20261016200000-invite.ts"; | |
| 9 | import { | |
| 10 | DEFAULT_DAYS, | |
| 11 | MAX_DAYS, | |
| 12 | inviteOpen, | |
| 13 | inviteSpentBy, | |
| 14 | listInvites, | |
| 15 | mintInvite, | |
| 16 | parseDays, | |
| 17 | redeemInvite, | |
| 18 | reopenInvite, | |
| 19 | } from "../src/core/invites.ts"; | |
| 20 | ||
| 21 | const DAY = 86_400_000; | |
| 22 | const T0 = Date.parse("2026-10-01T00:00:00Z"); | |
| 23 | ||
| 24 | describe("parseDays", () => { | |
| 25 | test("the default for absent, the bounds for present", () => { | |
| 26 | expect(parseDays(undefined)).toEqual({ ok: true, value: DEFAULT_DAYS }); | |
| 27 | expect(parseDays(null)).toEqual({ ok: true, value: DEFAULT_DAYS }); | |
| 28 | expect(parseDays(1)).toEqual({ ok: true, value: 1 }); | |
| 29 | expect(parseDays(MAX_DAYS)).toEqual({ ok: true, value: MAX_DAYS }); | |
| 30 | for (const bad of [0, MAX_DAYS + 1, 1.5, "7", Number.NaN, -1, Infinity]) { | |
| 31 | expect(parseDays(bad)).toEqual({ ok: false, error: "invalid_days" }); | |
| 32 | } | |
| 33 | }); | |
| 34 | }); | |
| 35 | ||
| 36 | describe("an invite's life", () => { | |
| 37 | let db: Database; | |
| 38 | beforeEach(() => { | |
| 39 | db = new Database(":memory:"); | |
| 40 | db.exec(invite); | |
| 41 | }); | |
| 42 | ||
| 43 | test("open before its expiry, not at or after it", () => { | |
| 44 | const { token } = mintInvite(db, "owner", 2, T0); | |
| 45 | expect(inviteOpen(db, token, T0)).toBe(true); | |
| 46 | expect(inviteOpen(db, token, T0 + 2 * DAY - 1)).toBe(true); | |
| 47 | expect(inviteOpen(db, token, T0 + 2 * DAY)).toBe(false); | |
| 48 | expect(inviteOpen(db, token, T0 + 3 * DAY)).toBe(false); | |
| 49 | expect(inviteOpen(db, "invite_unknown", T0)).toBe(false); | |
| 50 | }); | |
| 51 | ||
| 52 | test("redeems once; a second redeem by anyone fails", () => { | |
| 53 | const { token } = mintInvite(db, "owner", 2, T0); | |
| 54 | expect(redeemInvite(db, token, "ada", T0 + 1)).toBe(true); | |
| 55 | expect(inviteOpen(db, token, T0 + 2)).toBe(false); | |
| 56 | expect(redeemInvite(db, token, "ada", T0 + 2)).toBe(false); | |
| 57 | expect(redeemInvite(db, token, "bob", T0 + 2)).toBe(false); | |
| 58 | expect(inviteSpentBy(db, "bob")).toBeUndefined(); | |
| 59 | }); | |
| 60 | ||
| 61 | test("an expired invite never redeems", () => { | |
| 62 | const { token } = mintInvite(db, "owner", 1, T0); | |
| 63 | expect(redeemInvite(db, token, "ada", T0 + DAY)).toBe(false); | |
| 64 | expect(redeemInvite(db, token, "ada", T0 + 5 * DAY)).toBe(false); | |
| 65 | expect(inviteSpentBy(db, "ada")).toBeUndefined(); | |
| 66 | expect(listInvites(db, T0 + DAY)[0]!.state).toBe("expired"); | |
| 67 | }); | |
| 68 | ||
| 69 | test("an unknown token redeems nothing", () => { | |
| 70 | mintInvite(db, "owner", 2, T0); | |
| 71 | expect(redeemInvite(db, "invite_nope", "ada", T0)).toBe(false); | |
| 72 | }); | |
| 73 | ||
| 74 | test("inviteSpentBy names the redeemer's invite", () => { | |
| 75 | const a = mintInvite(db, "owner", 2, T0); | |
| 76 | const b = mintInvite(db, "owner", 2, T0); | |
| 77 | redeemInvite(db, a.token, "ada", T0); | |
| 78 | expect(inviteSpentBy(db, "ada")).toBe(a.invite.id); | |
| 79 | expect(inviteSpentBy(db, "bob")).toBeUndefined(); | |
| 80 | expect(inviteSpentBy(db, "owner")).toBeUndefined(); | |
| 81 | expect(b.invite.id).not.toBe(a.invite.id); | |
| 82 | }); | |
| 83 | ||
| 84 | test("reopenInvite gives back only that account's invite, once more redeemable", () => { | |
| 85 | const a = mintInvite(db, "owner", 2, T0); | |
| 86 | const b = mintInvite(db, "owner", 2, T0); | |
| 87 | redeemInvite(db, a.token, "ada", T0); | |
| 88 | redeemInvite(db, b.token, "bob", T0); | |
| 89 | reopenInvite(db, "ada"); | |
| 90 | expect(inviteSpentBy(db, "ada")).toBeUndefined(); | |
| 91 | expect(inviteOpen(db, a.token, T0 + 1)).toBe(true); | |
| 92 | expect(inviteOpen(db, b.token, T0 + 1)).toBe(false); | |
| 93 | expect(inviteSpentBy(db, "bob")).toBe(b.invite.id); | |
| 94 | expect(redeemInvite(db, a.token, "cy", T0 + 1)).toBe(true); | |
| 95 | reopenInvite(db, "nobody"); | |
| 96 | expect(inviteSpentBy(db, "cy")).toBe(a.invite.id); | |
| 97 | }); | |
| 98 | }); |
new above
offline