@@ -2,32 +2,56 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
22import app from "../../app" ;
33import * as ffmpeg from "../../lib/ffmpeg" ;
44
5+ const MEDIA_SERVER_SECRET = "test-secret" ;
6+ const AUTH_HEADERS = {
7+ "Content-Type" : "application/json" ,
8+ "x-media-server-secret" : MEDIA_SERVER_SECRET ,
9+ } ;
10+
11+ process . env . MEDIA_SERVER_WEBHOOK_SECRET = MEDIA_SERVER_SECRET ;
12+
13+ function audioPostRequest ( path : string , body : unknown ) : Request {
14+ return new Request ( `http://localhost${ path } ` , {
15+ method : "POST" ,
16+ headers : AUTH_HEADERS ,
17+ body : JSON . stringify ( body ) ,
18+ } ) ;
19+ }
20+
21+ function unauthenticatedAudioPostRequest ( path : string , body : unknown ) : Request {
22+ return new Request ( `http://localhost${ path } ` , {
23+ method : "POST" ,
24+ headers : { "Content-Type" : "application/json" } ,
25+ body : JSON . stringify ( body ) ,
26+ } ) ;
27+ }
28+
529describe ( "POST /audio/check" , ( ) => {
630 beforeEach ( ( ) => {
731 mock . restore ( ) ;
832 } ) ;
933
10- test ( "returns 400 for missing videoUrl " , async ( ) => {
34+ test ( "returns 401 without media server secret " , async ( ) => {
1135 const response = await app . fetch (
12- new Request ( "http://localhost/audio/check" , {
13- method : "POST" ,
14- headers : { "Content-Type" : "application/json" } ,
15- body : JSON . stringify ( { } ) ,
36+ unauthenticatedAudioPostRequest ( "/audio/check" , {
37+ videoUrl : "https://example.com/video.mp4" ,
1638 } ) ,
1739 ) ;
1840
41+ expect ( response . status ) . toBe ( 401 ) ;
42+ } ) ;
43+
44+ test ( "returns 400 for missing videoUrl" , async ( ) => {
45+ const response = await app . fetch ( audioPostRequest ( "/audio/check" , { } ) ) ;
46+
1947 expect ( response . status ) . toBe ( 400 ) ;
2048 const data = await response . json ( ) ;
2149 expect ( data . code ) . toBe ( "INVALID_REQUEST" ) ;
2250 } ) ;
2351
2452 test ( "returns 400 for invalid URL format" , async ( ) => {
2553 const response = await app . fetch (
26- new Request ( "http://localhost/audio/check" , {
27- method : "POST" ,
28- headers : { "Content-Type" : "application/json" } ,
29- body : JSON . stringify ( { videoUrl : "not-a-valid-url" } ) ,
30- } ) ,
54+ audioPostRequest ( "/audio/check" , { videoUrl : "not-a-valid-url" } ) ,
3155 ) ;
3256
3357 expect ( response . status ) . toBe ( 400 ) ;
@@ -44,10 +68,8 @@ describe("POST /audio/check", () => {
4468 const { default : appWithMock } = await import ( "../../app" ) ;
4569
4670 const response = await appWithMock . fetch (
47- new Request ( "http://localhost/audio/check" , {
48- method : "POST" ,
49- headers : { "Content-Type" : "application/json" } ,
50- body : JSON . stringify ( { videoUrl : "https://example.com/video.mp4" } ) ,
71+ audioPostRequest ( "/audio/check" , {
72+ videoUrl : "https://example.com/video.mp4" ,
5173 } ) ,
5274 ) ;
5375
@@ -65,10 +87,8 @@ describe("POST /audio/check", () => {
6587 const { default : appWithMock } = await import ( "../../app" ) ;
6688
6789 const response = await appWithMock . fetch (
68- new Request ( "http://localhost/audio/check" , {
69- method : "POST" ,
70- headers : { "Content-Type" : "application/json" } ,
71- body : JSON . stringify ( { videoUrl : "https://example.com/video.mp4" } ) ,
90+ audioPostRequest ( "/audio/check" , {
91+ videoUrl : "https://example.com/video.mp4" ,
7292 } ) ,
7393 ) ;
7494
@@ -83,27 +103,27 @@ describe("POST /audio/extract", () => {
83103 mock . restore ( ) ;
84104 } ) ;
85105
86- test ( "returns 400 for missing videoUrl " , async ( ) => {
106+ test ( "returns 401 without media server secret " , async ( ) => {
87107 const response = await app . fetch (
88- new Request ( "http://localhost/audio/extract" , {
89- method : "POST" ,
90- headers : { "Content-Type" : "application/json" } ,
91- body : JSON . stringify ( { } ) ,
108+ unauthenticatedAudioPostRequest ( "/audio/extract" , {
109+ videoUrl : "https://example.com/video.mp4" ,
92110 } ) ,
93111 ) ;
94112
113+ expect ( response . status ) . toBe ( 401 ) ;
114+ } ) ;
115+
116+ test ( "returns 400 for missing videoUrl" , async ( ) => {
117+ const response = await app . fetch ( audioPostRequest ( "/audio/extract" , { } ) ) ;
118+
95119 expect ( response . status ) . toBe ( 400 ) ;
96120 const data = await response . json ( ) ;
97121 expect ( data . code ) . toBe ( "INVALID_REQUEST" ) ;
98122 } ) ;
99123
100124 test ( "returns 400 for invalid URL format" , async ( ) => {
101125 const response = await app . fetch (
102- new Request ( "http://localhost/audio/extract" , {
103- method : "POST" ,
104- headers : { "Content-Type" : "application/json" } ,
105- body : JSON . stringify ( { videoUrl : "invalid-url" } ) ,
106- } ) ,
126+ audioPostRequest ( "/audio/extract" , { videoUrl : "invalid-url" } ) ,
107127 ) ;
108128
109129 expect ( response . status ) . toBe ( 400 ) ;
@@ -120,10 +140,8 @@ describe("POST /audio/extract", () => {
120140 const { default : appWithMock } = await import ( "../../app" ) ;
121141
122142 const response = await appWithMock . fetch (
123- new Request ( "http://localhost/audio/extract" , {
124- method : "POST" ,
125- headers : { "Content-Type" : "application/json" } ,
126- body : JSON . stringify ( { videoUrl : "https://example.com/video.mp4" } ) ,
143+ audioPostRequest ( "/audio/extract" , {
144+ videoUrl : "https://example.com/video.mp4" ,
127145 } ) ,
128146 ) ;
129147
@@ -143,13 +161,9 @@ describe("POST /audio/extract", () => {
143161 const { default : appWithMock } = await import ( "../../app" ) ;
144162
145163 const response = await appWithMock . fetch (
146- new Request ( "http://localhost/audio/extract" , {
147- method : "POST" ,
148- headers : { "Content-Type" : "application/json" } ,
149- body : JSON . stringify ( {
150- videoUrl : "https://example.com/video.mp4" ,
151- stream : false ,
152- } ) ,
164+ audioPostRequest ( "/audio/extract" , {
165+ videoUrl : "https://example.com/video.mp4" ,
166+ stream : false ,
153167 } ) ,
154168 ) ;
155169
@@ -174,13 +188,9 @@ describe("POST /audio/extract", () => {
174188 const { default : appWithMock } = await import ( "../../app" ) ;
175189
176190 const response = await appWithMock . fetch (
177- new Request ( "http://localhost/audio/extract" , {
178- method : "POST" ,
179- headers : { "Content-Type" : "application/json" } ,
180- body : JSON . stringify ( {
181- videoUrl : "https://example.com/video.mp4" ,
182- stream : false ,
183- } ) ,
191+ audioPostRequest ( "/audio/extract" , {
192+ videoUrl : "https://example.com/video.mp4" ,
193+ stream : false ,
184194 } ) ,
185195 ) ;
186196
@@ -190,3 +200,61 @@ describe("POST /audio/extract", () => {
190200 expect ( data . details ) . toContain ( "FFmpeg failed" ) ;
191201 } ) ;
192202} ) ;
203+
204+ describe ( "POST /audio/convert" , ( ) => {
205+ beforeEach ( ( ) => {
206+ mock . restore ( ) ;
207+ } ) ;
208+
209+ test ( "returns 401 without media server secret" , async ( ) => {
210+ const response = await app . fetch (
211+ unauthenticatedAudioPostRequest ( "/audio/convert" , {
212+ audioUrl : "https://example.com/audio.wav" ,
213+ } ) ,
214+ ) ;
215+
216+ expect ( response . status ) . toBe ( 401 ) ;
217+ } ) ;
218+
219+ test ( "returns 400 for missing audioUrl" , async ( ) => {
220+ const response = await app . fetch ( audioPostRequest ( "/audio/convert" , { } ) ) ;
221+
222+ expect ( response . status ) . toBe ( 400 ) ;
223+ const data = await response . json ( ) ;
224+ expect ( data . code ) . toBe ( "INVALID_REQUEST" ) ;
225+ } ) ;
226+
227+ test ( "returns audio stream when conversion succeeds" , async ( ) => {
228+ const mockAudioData = new Uint8Array ( [ 0x49 , 0x44 , 0x33 ] ) ;
229+
230+ mock . module ( "../../lib/ffmpeg" , ( ) => ( {
231+ canAcceptNewProcess : ffmpeg . canAcceptNewProcess ,
232+ checkHasAudioTrack : ffmpeg . checkHasAudioTrack ,
233+ extractAudio : ffmpeg . extractAudio ,
234+ extractAudioStream : ( ) => ( {
235+ stream : new ReadableStream < Uint8Array > ( {
236+ start ( controller ) {
237+ controller . enqueue ( mockAudioData ) ;
238+ controller . close ( ) ;
239+ } ,
240+ } ) ,
241+ cleanup : ( ) => { } ,
242+ } ) ,
243+ getActiveProcessCount : ffmpeg . getActiveProcessCount ,
244+ } ) ) ;
245+
246+ const { default : appWithMock } = await import ( "../../app" ) ;
247+
248+ const response = await appWithMock . fetch (
249+ audioPostRequest ( "/audio/convert" , {
250+ audioUrl : "https://example.com/audio.wav" ,
251+ } ) ,
252+ ) ;
253+
254+ expect ( response . status ) . toBe ( 200 ) ;
255+ expect ( response . headers . get ( "Content-Type" ) ) . toBe ( "audio/mpeg" ) ;
256+
257+ const buffer = await response . arrayBuffer ( ) ;
258+ expect ( new Uint8Array ( buffer ) ) . toEqual ( mockAudioData ) ;
259+ } ) ;
260+ } ) ;
0 commit comments