import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { existsSync, readFileSync, unlinkSync } from 'fs' import { join } from 'path' import { createToken, reserveToken, completeToken, releaseToken, cleanupExpiredTokens } from './confirmationToken.js' const tokensFile = join(process.cwd(), 'server', 'data', 'pending-tokens.json') function removeTokensFile() { if (existsSync(tokensFile)) unlinkSync(tokensFile) } beforeEach(removeTokensFile) afterEach(removeTokensFile) 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) await cleanupExpiredTokens() expect(pending).toBeTruthy() expect(JSON.parse(readFileSync(tokensFile, 'utf8'))).toEqual([]) }) })