-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathfree-debates.test.ts
More file actions
121 lines (104 loc) · 3.35 KB
/
Copy pathfree-debates.test.ts
File metadata and controls
121 lines (104 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
const findUniqueMock = vi.hoisted(() => vi.fn())
const updateManyMock = vi.hoisted(() => vi.fn())
vi.mock("@/lib/prisma", () => ({
prisma: {
user: {
findUnique: findUniqueMock,
updateMany: updateManyMock,
},
},
}))
import {
FREE_DEBATE_WINDOW_MS,
getFreeDebateStatus,
tryClaimFreeServerAccess,
} from "@/lib/free-debates"
describe("free debates", () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date("2026-07-15T12:00:00.000Z"))
findUniqueMock.mockReset()
updateManyMock.mockReset()
})
afterEach(() => {
vi.useRealTimers()
})
it("reports remaining and inactive when no window is open", async () => {
findUniqueMock.mockResolvedValue({
freeDebatesRemaining: 1,
freeDebateExpiresAt: null,
})
await expect(getFreeDebateStatus("u1")).resolves.toEqual({
remaining: 1,
active: false,
expiresAt: null,
})
})
it("reports active while the free window is open", async () => {
const expiresAt = new Date("2026-07-15T12:20:00.000Z")
findUniqueMock.mockResolvedValue({
freeDebatesRemaining: 0,
freeDebateExpiresAt: expiresAt,
})
await expect(getFreeDebateStatus("u1")).resolves.toEqual({
remaining: 0,
active: true,
expiresAt: expiresAt.toISOString(),
})
})
it("reuses an open free window without decrementing again", async () => {
findUniqueMock.mockResolvedValue({
freeDebatesRemaining: 0,
freeDebateExpiresAt: new Date("2026-07-15T12:20:00.000Z"),
})
await expect(tryClaimFreeServerAccess("u1")).resolves.toBe(true)
expect(updateManyMock).not.toHaveBeenCalled()
})
it("claims one remaining grant and opens a window", async () => {
findUniqueMock.mockResolvedValue({
freeDebatesRemaining: 1,
freeDebateExpiresAt: null,
})
updateManyMock.mockResolvedValue({ count: 1 })
await expect(tryClaimFreeServerAccess("u1")).resolves.toBe(true)
expect(updateManyMock).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
id: "u1",
freeDebatesRemaining: { gt: 0 },
}),
data: expect.objectContaining({
freeDebatesRemaining: { decrement: 1 },
freeDebateExpiresAt: new Date(Date.now() + FREE_DEBATE_WINDOW_MS),
}),
})
)
})
it("returns false when remaining is zero and the window is expired", async () => {
findUniqueMock
.mockResolvedValueOnce({
freeDebatesRemaining: 0,
freeDebateExpiresAt: new Date("2026-07-15T11:00:00.000Z"),
})
.mockResolvedValueOnce({
freeDebatesRemaining: 0,
freeDebateExpiresAt: new Date("2026-07-15T11:00:00.000Z"),
})
updateManyMock.mockResolvedValue({ count: 0 })
await expect(tryClaimFreeServerAccess("u1")).resolves.toBe(false)
})
it("wins a parallel claim race by reading the winner window", async () => {
findUniqueMock
.mockResolvedValueOnce({
freeDebatesRemaining: 1,
freeDebateExpiresAt: null,
})
.mockResolvedValueOnce({
freeDebatesRemaining: 0,
freeDebateExpiresAt: new Date("2026-07-15T12:30:00.000Z"),
})
updateManyMock.mockResolvedValue({ count: 0 })
await expect(tryClaimFreeServerAccess("u1")).resolves.toBe(true)
})
})