@@ -9,7 +9,17 @@ import { initWhisper, AudioSessionIos } from 'whisper.rn';
99import { Platform , PermissionsAndroid } from 'react-native' ;
1010import RNFS from 'react-native-fs' ;
1111import { whisperService , WHISPER_MODELS } from '../../../src/services/whisperService' ;
12+ import { backgroundDownloadService } from '../../../src/services/backgroundDownloadService' ;
1213
14+ jest . mock ( '../../../src/services/backgroundDownloadService' , ( ) => ( {
15+ backgroundDownloadService : {
16+ isAvailable : jest . fn ( ( ) => true ) ,
17+ downloadFileTo : jest . fn ( ) ,
18+ cancelDownload : jest . fn ( ( ) => Promise . resolve ( ) ) ,
19+ } ,
20+ } ) ) ;
21+
22+ const mockedBDS = backgroundDownloadService as jest . Mocked < typeof backgroundDownloadService > ;
1323const mockedAudioSessionIos = AudioSessionIos as jest . Mocked < typeof AudioSessionIos > ;
1424
1525const mockedRNFS = RNFS as jest . Mocked < typeof RNFS > ;
@@ -32,6 +42,15 @@ describe('WhisperService', () => {
3242 ( whisperService as any ) . stopFn = null ;
3343 ( whisperService as any ) . isReleasingContext = false ;
3444 ( whisperService as any ) . transcriptionFullyStopped = Promise . resolve ( ) ;
45+ ( whisperService as any ) . activeDownloadId = null ;
46+ // Default backgroundDownloadService mock
47+ mockedBDS . isAvailable . mockReturnValue ( true ) ;
48+ mockedBDS . downloadFileTo . mockReturnValue ( {
49+ downloadId : 0 ,
50+ downloadIdPromise : Promise . resolve ( 0 ) ,
51+ promise : Promise . resolve ( ) ,
52+ } as any ) ;
53+ mockedBDS . cancelDownload . mockResolvedValue ( undefined as any ) ;
3554 // Re-establish default AudioSessionIos mock implementations
3655 // (previous tests may have set mockRejectedValue which clearAllMocks doesn't reset)
3756 mockedAudioSessionIos . setCategory . mockResolvedValue ( undefined as any ) ;
@@ -85,71 +104,71 @@ describe('WhisperService', () => {
85104 const result = await whisperService . downloadModel ( 'tiny.en' ) ;
86105
87106 expect ( result ) . toBe ( '/mock/documents/whisper-models/ggml-tiny.en.bin' ) ;
88- expect ( RNFS . downloadFile ) . not . toHaveBeenCalled ( ) ;
107+ expect ( mockedBDS . downloadFileTo ) . not . toHaveBeenCalled ( ) ;
89108 } ) ;
90109
91- it ( 'downloads via RNFS when not present' , async ( ) => {
92- // First exists check (ensureModelsDirExists) = true, second (destPath) = false,
93- // third (validateModelFile) = true
110+ it ( 'downloads via backgroundDownloadService when not present' , async ( ) => {
94111 mockedRNFS . exists
95- . mockResolvedValueOnce ( true ) // dir exists
112+ . mockResolvedValueOnce ( true ) // dir exists
96113 . mockResolvedValueOnce ( false ) // model not yet downloaded
97- . mockResolvedValueOnce ( true ) ; // validation : file exists after download
114+ . mockResolvedValueOnce ( true ) ; // validateModelFile : file exists
98115 mockedRNFS . stat . mockResolvedValueOnce ( { size : 75 * 1024 * 1024 , isFile : ( ) => true } as any ) ;
99116
100- mockedRNFS . downloadFile . mockReturnValue ( {
101- jobId : 1 ,
102- promise : Promise . resolve ( { statusCode : 200 , bytesWritten : 75000000 } ) ,
117+ mockedBDS . downloadFileTo . mockReturnValue ( {
118+ downloadId : 1 ,
119+ downloadIdPromise : Promise . resolve ( 1 ) ,
120+ promise : Promise . resolve ( ) ,
103121 } as any ) ;
104122
105123 const result = await whisperService . downloadModel ( 'tiny.en' ) ;
106124
107- expect ( RNFS . downloadFile ) . toHaveBeenCalled ( ) ;
108- const callArgs = ( RNFS . downloadFile as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
109- expect ( callArgs . fromUrl ) . toBe ( WHISPER_MODELS [ 0 ] . url ) ;
125+ expect ( mockedBDS . downloadFileTo ) . toHaveBeenCalledWith ( expect . objectContaining ( {
126+ params : expect . objectContaining ( { url : WHISPER_MODELS [ 0 ] . url } ) ,
127+ destPath : '/mock/documents/whisper-models/ggml-tiny.en.bin' ,
128+ } ) ) ;
110129 expect ( result ) . toBe ( '/mock/documents/whisper-models/ggml-tiny.en.bin' ) ;
111130 } ) ;
112131
113132 it ( 'calls progress callback' , async ( ) => {
114133 mockedRNFS . exists
115- . mockResolvedValueOnce ( true ) // dir exists
134+ . mockResolvedValueOnce ( true ) // dir exists
116135 . mockResolvedValueOnce ( false ) // model doesn't exist
117- . mockResolvedValueOnce ( true ) ; // validation : file exists after download
136+ . mockResolvedValueOnce ( true ) ; // validateModelFile : file exists
118137 mockedRNFS . stat . mockResolvedValueOnce ( { size : 75 * 1024 * 1024 , isFile : ( ) => true } as any ) ;
119138
120- let capturedProgressFn : any ;
121- mockedRNFS . downloadFile . mockImplementation ( ( opts : any ) => {
122- capturedProgressFn = opts . progress ;
139+ let capturedOnProgress : ( ( b : number , t : number ) => void ) | undefined ;
140+ mockedBDS . downloadFileTo . mockImplementation ( ( opts : any ) => {
141+ capturedOnProgress = opts . onProgress ;
123142 return {
124- jobId : 1 ,
125- promise : Promise . resolve ( { statusCode : 200 , bytesWritten : 75000000 } ) ,
143+ downloadId : 1 ,
144+ downloadIdPromise : Promise . resolve ( 1 ) ,
145+ promise : Promise . resolve ( ) ,
126146 } as any ;
127147 } ) ;
128148
129149 const progressCb = jest . fn ( ) ;
130150 await whisperService . downloadModel ( 'tiny.en' , progressCb ) ;
131151
132- // Simulate progress
133- if ( capturedProgressFn ) {
134- capturedProgressFn ( { bytesWritten : 37500000 , contentLength : 75000000 } ) ;
152+ if ( capturedOnProgress ) {
153+ capturedOnProgress ( 37500000 , 75000000 ) ;
135154 expect ( progressCb ) . toHaveBeenCalledWith ( 0.5 ) ;
136155 }
137156 } ) ;
138157
139- it ( 'cleans up on non-200 status ' , async ( ) => {
158+ it ( 'cleans up partial file and rethrows when download fails ' , async ( ) => {
140159 mockedRNFS . exists
141- . mockResolvedValueOnce ( true )
142- . mockResolvedValueOnce ( false ) ;
160+ . mockResolvedValueOnce ( true ) // dir exists
161+ . mockResolvedValueOnce ( false ) ; // model not yet downloaded
162+ mockedRNFS . unlink . mockResolvedValue ( undefined as any ) ;
143163
144- mockedRNFS . downloadFile . mockReturnValue ( {
145- jobId : 1 ,
146- promise : Promise . resolve ( { statusCode : 500 , bytesWritten : 0 } ) ,
164+ mockedBDS . downloadFileTo . mockReturnValue ( {
165+ downloadId : 1 ,
166+ downloadIdPromise : Promise . resolve ( 1 ) ,
167+ promise : Promise . reject ( new Error ( 'network_lost' ) ) ,
147168 } as any ) ;
148169
149- mockedRNFS . unlink . mockResolvedValue ( undefined as any ) ;
150-
151- await expect ( whisperService . downloadModel ( 'tiny.en' ) ) . rejects . toThrow ( 'Download failed' ) ;
152- expect ( RNFS . unlink ) . toHaveBeenCalled ( ) ;
170+ await expect ( whisperService . downloadModel ( 'tiny.en' ) ) . rejects . toThrow ( 'network_lost' ) ;
171+ expect ( RNFS . unlink ) . toHaveBeenCalledWith ( '/mock/documents/whisper-models/ggml-tiny.en.bin' ) ;
153172 } ) ;
154173 } ) ;
155174
0 commit comments