// @vitest-environment node import { describe, expect, it } from 'vitest' import { acceptsAvif, isPrivateMediaMissingError, PRIVATE_MEDIA_FALLBACK_MIME_TYPE, privateMediaContentDisposition, privateMediaContentType, privateMediaHeaders, type PrivateMediaRange, } from '@/lib/privateMedia' const fullRange: PrivateMediaRange = { status: 200, headers: { 'Accept-Ranges': 'bytes', 'Content-Length': '100' }, } const partialRange: PrivateMediaRange = { status: 206, headers: { 'Accept-Ranges': 'bytes', 'Content-Length': '10', 'Content-Range': 'bytes 0-9/100' }, } const invalidRange: PrivateMediaRange = { status: 416, headers: { 'Content-Range': 'bytes */100' }, } describe('private media response rules (C193/C199)', () => { it('serves only allowlisted types inline; everything else degrades', () => { expect(privateMediaContentType('video/mp4')).toBe('video/mp4') expect(privateMediaContentType('video/quicktime')).toBe('video/quicktime') expect(privateMediaContentType('video/x-matroska')).toBe('video/x-matroska') expect(privateMediaContentType('audio/mpeg')).toBe('audio/mpeg') expect(privateMediaContentType('image/jpeg')).toBe('image/jpeg') expect(privateMediaContentType('text/html')).toBe(PRIVATE_MEDIA_FALLBACK_MIME_TYPE) expect(privateMediaContentType(null)).toBe(PRIVATE_MEDIA_FALLBACK_MIME_TYPE) }) // S27 — the content pieces' photo/audio families: a published photo must // render and an audio piece must play, so these render inline; markup // (SVG) and formats browsers do not render (HEIC) stay download-only. it('serves the content piece image and audio families inline', () => { for (const mimeType of [ 'image/png', 'image/webp', 'image/gif', 'image/avif', 'audio/mp4', 'audio/aac', 'audio/ogg', 'audio/opus', 'audio/wav', 'audio/webm', 'audio/flac', ]) { expect(privateMediaContentType(mimeType), mimeType).toBe(mimeType) } expect(privateMediaContentType('image/heic')).toBe(PRIVATE_MEDIA_FALLBACK_MIME_TYPE) expect(privateMediaContentType('image/svg+xml')).toBe(PRIVATE_MEDIA_FALLBACK_MIME_TYPE) }) it('builds both filename forms and encodes non-ASCII', () => { expect(privateMediaContentDisposition('reel.mp4', false)).toBe( 'inline; filename="reel.mp4"; filename*=UTF-8\'\'reel.mp4', ) expect(privateMediaContentDisposition('capa ção.png', true)).toBe( 'attachment; filename="capa_o.png"; filename*=UTF-8\'\'capa%20%C3%A7%C3%A3o.png', ) expect(privateMediaContentDisposition(null, false)).toContain('filename="arquivo"') }) it('keeps the range headers and adds the private streaming contract', () => { const headers = privateMediaHeaders({ range: partialRange, mimeType: 'video/mp4', filename: 'reel.mp4', download: false, }) expect(headers.get('Content-Range')).toBe('bytes 0-9/100') expect(headers.get('Content-Length')).toBe('10') expect(headers.get('Accept-Ranges')).toBe('bytes') expect(headers.get('Content-Type')).toBe('video/mp4') expect(headers.get('Content-Disposition')).toContain('inline') expect(headers.get('Cache-Control')).toBe('private, no-store') expect(headers.get('X-Content-Type-Options')).toBe('nosniff') expect(headers.get('Content-Security-Policy')).toBeNull() }) it('forces a download for unsafe types and for ?download=1', () => { const unsafe = privateMediaHeaders({ range: fullRange, mimeType: 'text/html', filename: 'roteiro.html', download: false, }) expect(unsafe.get('Content-Type')).toBe(PRIVATE_MEDIA_FALLBACK_MIME_TYPE) expect(unsafe.get('Content-Disposition')).toContain('attachment') expect(unsafe.get('Content-Security-Policy')).toBe("default-src 'none'") const explicit = privateMediaHeaders({ range: fullRange, mimeType: 'video/mp4', filename: 'reel.mp4', download: true, }) expect(explicit.get('Content-Disposition')).toContain('attachment') }) it('passes the 416 headers through untouched', () => { const headers = privateMediaHeaders({ range: invalidRange, mimeType: 'video/mp4', filename: 'reel.mp4', download: false, }) expect(headers.get('Content-Range')).toBe('bytes */100') expect(headers.get('Content-Type')).toBe('video/mp4') }) }) describe('private media AVIF negotiation (C248)', () => { it('accepts only an explicit image/avif token with a positive quality', () => { expect(acceptsAvif('image/avif')).toBe(true) expect(acceptsAvif('IMAGE/AVIF')).toBe(true) expect(acceptsAvif('text/html, image/avif')).toBe(true) expect(acceptsAvif('image/avif;q=0.5')).toBe(true) expect(acceptsAvif('image/avif; q=0.5, image/webp')).toBe(true) expect(acceptsAvif('image/avif;q=0')).toBe(false) expect(acceptsAvif('image/avif;q=0.000')).toBe(false) }) it('keeps the JPEG fallback for headers that never opted in', () => { expect(acceptsAvif(null)).toBe(false) expect(acceptsAvif(undefined)).toBe(false) expect(acceptsAvif('')).toBe(false) expect(acceptsAvif('*/*')).toBe(false) expect(acceptsAvif('image/*')).toBe(false) expect(acceptsAvif('image/jpeg, image/webp')).toBe(false) expect(acceptsAvif('image/avifjunk')).toBe(false) }) }) describe('private media missing-object classification (C246)', () => { it('recognizes the S3 and local-disk absence shapes', () => { expect(isPrivateMediaMissingError({ name: 'NoSuchKey' })).toBe(true) expect(isPrivateMediaMissingError({ name: 'NotFound' })).toBe(true) expect(isPrivateMediaMissingError({ Code: 'NoSuchKey' })).toBe(true) expect(isPrivateMediaMissingError({ $metadata: { httpStatusCode: 404 } })).toBe(true) expect(isPrivateMediaMissingError(Object.assign(new Error('ENOENT'), { code: 'ENOENT' }))).toBe( true, ) }) it('never classifies a read failure of an existing object as missing', () => { expect(isPrivateMediaMissingError(new Error('checksum mismatch'))).toBe(false) expect(isPrivateMediaMissingError({ name: 'BadDigest' })).toBe(false) expect( isPrivateMediaMissingError( Object.assign(new Error('socket hang up'), { $metadata: { httpStatusCode: 500 } }), ), ).toBe(false) expect(isPrivateMediaMissingError(null)).toBe(false) expect(isPrivateMediaMissingError('erro')).toBe(false) }) })