diff --git a/src/lib/__tests__/token.test.ts b/src/lib/__tests__/token.test.ts new file mode 100644 index 0000000..b2420b2 --- /dev/null +++ b/src/lib/__tests__/token.test.ts @@ -0,0 +1,63 @@ +import { createToken, validateToken } from '../token' +import { prisma } from '../prisma' + +jest.mock('../prisma', () => ({ + prisma: { + downloadToken: { + create: jest.fn(), + findUnique: jest.fn(), + }, + }, +})) + +const mockCreate = (prisma.downloadToken.create as jest.Mock) +const mockFindUnique = (prisma.downloadToken.findUnique as jest.Mock) + +describe('createToken', () => { + it('inserts a DownloadToken and returns the token string', async () => { + mockCreate.mockResolvedValue({ token: 'generated-token' }) + + const result = await createToken('dl-id-1') + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ downloadId: 'dl-id-1' }), + }) + ) + expect(result).toBe('generated-token') + }) +}) + +describe('validateToken', () => { + it('returns null when token not found', async () => { + mockFindUnique.mockResolvedValue(null) + expect(await validateToken('bad')).toBeNull() + }) + + it('returns null when token is expired', async () => { + mockFindUnique.mockResolvedValue({ + expiresAt: new Date(Date.now() - 1000), + download: { id: 'dl-1', filePath: '/storage/abc.mp4' }, + }) + expect(await validateToken('expired-tok')).toBeNull() + }) + + it('returns null when filePath is null', async () => { + mockFindUnique.mockResolvedValue({ + expiresAt: new Date(Date.now() + 3_600_000), + download: { id: 'dl-1', filePath: null }, + }) + expect(await validateToken('tok')).toBeNull() + }) + + it('returns downloadId and filePath for a valid token', async () => { + mockFindUnique.mockResolvedValue({ + expiresAt: new Date(Date.now() + 3_600_000), + download: { id: 'dl-1', filePath: '/storage/abc.mp4' }, + }) + expect(await validateToken('tok')).toEqual({ + downloadId: 'dl-1', + filePath: '/storage/abc.mp4', + }) + }) +}) diff --git a/src/lib/token.ts b/src/lib/token.ts new file mode 100644 index 0000000..d829a14 --- /dev/null +++ b/src/lib/token.ts @@ -0,0 +1,25 @@ +import { prisma } from './prisma' +import { config } from '../../config/app.config' + +export async function createToken(downloadId: string): Promise { + const expiresAt = new Date( + Date.now() + config.DOWNLOAD_LINK_TTL_HOURS * 60 * 60 * 1000 + ) + const record = await prisma.downloadToken.create({ + data: { downloadId, expiresAt }, + }) + return record.token +} + +export async function validateToken( + token: string +): Promise<{ downloadId: string; filePath: string } | null> { + const record = await prisma.downloadToken.findUnique({ + where: { token }, + include: { download: { select: { id: true, filePath: true } } }, + }) + if (!record) return null + if (record.expiresAt < new Date()) return null + if (!record.download.filePath) return null + return { downloadId: record.download.id, filePath: record.download.filePath } +}