import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest' import { existsSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs' import { tmpdir } from 'os' import { join } from 'path' let testDir let tokensFile let createToken let reserveToken let completeToken let releaseToken let cleanupExpiredTokens function removeTokensFile() { if (existsSync(tokensFile)) unlinkSync(tokensFile) } beforeAll(async () => { testDir = mkdtempSync(join(tmpdir(), 'andreknie-token-test-')) tokensFile = join(testDir, 'pending-tokens.json') process.env.CONFIRMATION_TOKEN_FILE = tokensFile const service = await import('./confirmationToken.js') createToken = service.createToken reserveToken = service.reserveToken completeToken = service.completeToken releaseToken = service.releaseToken cleanupExpiredTokens = service.cleanupExpiredTokens }) beforeEach(removeTokensFile) afterEach(removeTokensFile) afterAll(() => { delete process.env.CONFIRMATION_TOKEN_FILE rmSync(testDir, { recursive: true, force: true }) }) describe('confirmation token lifecycle', () => { it('creates a pending token and reserves it once', async () => { const token = await createToken('contact', { name: 'Test', email: 'test@example.invalid' }, 1) const first = await reserveToken(token) const second = await reserveToken(token) expect(first.status).toBe('processing') expect(second).toBeNull() expect(JSON.parse(readFileSync(tokensFile, 'utf8')).find(entry => entry.token === token).status).toBe('processing') }) it('removes an expired token instead of returning it', async () => { const token = await createToken('contact', { email: 'expired@example.invalid' }, -1) expect(await reserveToken(token)).toBeNull() expect(JSON.parse(readFileSync(tokensFile, 'utf8'))).toEqual([]) }) it('removes a successfully processed token and its payload', async () => { const token = await createToken('contact', { email: 'done@example.invalid' }) await reserveToken(token) await completeToken(token) expect(JSON.parse(readFileSync(tokensFile, 'utf8'))).toEqual([]) }) it('releases a failed processing attempt for a controlled retry', async () => { const token = await createToken('contact', { email: 'retry@example.invalid' }) await reserveToken(token) await releaseToken(token) expect((await reserveToken(token)).status).toBe('processing') }) it('allows exactly one parallel reservation', async () => { const token = await createToken('contact', { email: 'parallel@example.invalid' }) const results = await Promise.all([reserveToken(token), reserveToken(token)]) expect(results.filter(Boolean)).toHaveLength(1) }) it('cleans expired pending and processing entries', async () => { const pending = await createToken('contact', { email: 'pending@example.invalid' }, -1) const processing = await createToken('contact', { email: 'processing@example.invalid' }, 1) await reserveToken(processing) const stored = JSON.parse(readFileSync(tokensFile, 'utf8')) stored.find(entry => entry.token === processing).expiresAt = new Date(Date.now() - 1000).toISOString() writeFileSync(tokensFile, JSON.stringify(stored), 'utf8') await cleanupExpiredTokens() expect(pending).toBeTruthy() expect(JSON.parse(readFileSync(tokensFile, 'utf8'))).toEqual([]) }) })