Files
video-downloader/src/app/api/downloads/__tests__/post.test.ts
T
anthonyandClaude Sonnet 5 38e52374c9 feat: name downloaded file after the video title and add a back-to-home link
The download endpoint served files under their internal uuid-based
storage name; it now builds the Content-Disposition filename from the
probed video title (persisted on submit) with the original extension,
falling back to the old behavior when no title is available. The
status page also gets an always-visible link back to the homepage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 15:53:14 +02:00

109 lines
3.6 KiB
TypeScript

import { POST } from '../route'
import { prisma } from '@/lib/prisma'
import { downloadRateLimiter } from '@/lib/rate-limit'
jest.mock('@/lib/prisma', () => ({
prisma: { download: { create: jest.fn() } },
}))
jest.mock('@/lib/rate-limit', () => ({
downloadRateLimiter: { isLimited: jest.fn() },
}))
const mockCreate = prisma.download.create as jest.Mock
const mockIsLimited = downloadRateLimiter.isLimited as jest.Mock
function req(body: object, ip = '1.2.3.4') {
return new Request('http://localhost/api/downloads', {
method: 'POST',
headers: { 'content-type': 'application/json', 'x-forwarded-for': ip },
body: JSON.stringify(body),
})
}
describe('POST /api/downloads', () => {
beforeEach(() => {
mockIsLimited.mockReturnValue(false)
mockCreate.mockResolvedValue({ uuid: 'test-uuid' })
})
it('returns 429 when rate limited', async () => {
mockIsLimited.mockReturnValue(true)
const res = await POST(req({ url: 'https://y.com', format: 'mp4', quality: 'best', subtitles: false }))
expect(res.status).toBe(429)
})
it('returns 400 when url is missing', async () => {
const res = await POST(req({ format: 'mp4', quality: 'best', subtitles: false }))
expect(res.status).toBe(400)
})
it('returns 400 when format is missing', async () => {
const res = await POST(req({ url: 'https://y.com', quality: 'best', subtitles: false }))
expect(res.status).toBe(400)
})
it('returns 400 when clipEnd is not greater than clipStart', async () => {
const res = await POST(req({
url: 'https://y.com', format: 'mp4', quality: 'best', subtitles: false,
clipStart: 30, clipEnd: 10,
}))
expect(res.status).toBe(400)
})
it('returns 201 with uuid on success', async () => {
const res = await POST(req({ url: 'https://youtube.com/watch?v=abc', format: 'mp4', quality: 'best', subtitles: false }))
expect(res.status).toBe(201)
expect(await res.json()).toEqual({ uuid: 'test-uuid' })
})
it('passes correct fields to prisma', async () => {
await POST(req({ url: 'https://y.com/watch?v=x', format: 'mp3', quality: '720p', subtitles: true }, '9.9.9.9'))
expect(mockCreate).toHaveBeenCalledWith({
data: expect.objectContaining({
url: 'https://y.com/watch?v=x',
format: 'mp3',
quality: '720p',
subtitles: true,
ipAddress: '9.9.9.9',
}),
})
})
it('stores subtitleLangs as a comma-joined string', async () => {
await POST(req({
url: 'https://y.com', format: 'mp4', quality: 'best', subtitles: true,
subtitleLangs: ['fr', 'en'],
}))
expect(mockCreate).toHaveBeenCalledWith({
data: expect.objectContaining({ subtitleLangs: 'fr,en' }),
})
})
it('stores clip range and audio quality when provided', async () => {
await POST(req({
url: 'https://y.com', format: 'mp3', quality: 'best', subtitles: false,
clipStart: 10, clipEnd: 30, audioQuality: '192',
}))
expect(mockCreate).toHaveBeenCalledWith({
data: expect.objectContaining({ clipStart: 10, clipEnd: 30, audioQuality: '192' }),
})
})
it('stores the video title when provided', async () => {
await POST(req({
url: 'https://y.com', format: 'mp4', quality: 'best', subtitles: false,
title: 'My Video',
}))
expect(mockCreate).toHaveBeenCalledWith({
data: expect.objectContaining({ title: 'My Video' }),
})
})
it('stores a null title when not provided', async () => {
await POST(req({ url: 'https://y.com', format: 'mp4', quality: 'best', subtitles: false }))
expect(mockCreate).toHaveBeenCalledWith({
data: expect.objectContaining({ title: null }),
})
})
})