@@ -54,6 +54,11 @@ describe("FilesService", () => {
5454 let gamesService : jest . Mocked < GamesService > ;
5555 let metadataService : jest . Mocked < MetadataService > ;
5656 let schedulerRegistry : jest . Mocked < SchedulerRegistry > ;
57+ let gameVersionRepository : {
58+ find : jest . Mock ;
59+ findOne : jest . Mock ;
60+ save : jest . Mock ;
61+ } ;
5762 let fsExtra : {
5863 access : jest . Mock ;
5964 pathExists : jest . Mock ;
@@ -85,10 +90,17 @@ describe("FilesService", () => {
8590 deleteTimeout : jest . fn ( ) ,
8691 } as any ;
8792
93+ gameVersionRepository = {
94+ find : jest . fn ( ) . mockResolvedValue ( [ ] ) ,
95+ findOne : jest . fn ( ) ,
96+ save : jest . fn ( ) ,
97+ } ;
98+
8899 service = new FilesService (
89100 gamesService ,
90101 metadataService ,
91102 schedulerRegistry ,
103+ gameVersionRepository as any ,
92104 ) ;
93105
94106 fsExtra . access . mockResolvedValue ( undefined ) ;
@@ -240,5 +252,135 @@ describe("FilesService", () => {
240252 } ) ;
241253 expect ( gamesService . save ) . not . toHaveBeenCalled ( ) ;
242254 } ) ;
255+
256+ it ( "should reject download when requested version does not exist" , async ( ) => {
257+ gamesService . findOneByGameIdOrFail . mockResolvedValue ( {
258+ id : 42 ,
259+ file_path : "/tmp/test-files/My Game (v1.0.0).zip" ,
260+ version : "v1.0.0" ,
261+ size : 1000n ,
262+ type : "WINDOWS_SETUP" ,
263+ early_access : false ,
264+ download_count : 0 ,
265+ } as any ) ;
266+
267+ const response = { setHeader : jest . fn ( ) } as any ;
268+
269+ await expect (
270+ service . download ( response , 42 , undefined , undefined , 18 , "v9.9.9" ) ,
271+ ) . rejects . toThrow ( NotFoundException ) ;
272+ } ) ;
273+ } ) ;
274+
275+ describe ( "listAvailableVersions" , ( ) => {
276+ it ( "should return sorted available versions" , async ( ) => {
277+ gamesService . findOneByGameIdOrFail . mockResolvedValue ( {
278+ id : 1 ,
279+ file_path : "/tmp/test-files/My Game (v1.0.0).zip" ,
280+ version : "v1.0.0" ,
281+ size : 1000n ,
282+ type : "WINDOWS_SETUP" ,
283+ early_access : false ,
284+ } as any ) ;
285+ gameVersionRepository . find . mockResolvedValue ( [
286+ {
287+ file_path : "/tmp/test-files/My Game (v1.0.0).zip" ,
288+ version : "v1.0.0" ,
289+ size : 1000n ,
290+ type : "WINDOWS_SETUP" ,
291+ early_access : false ,
292+ indexed_at : new Date ( "2026-01-01" ) ,
293+ } ,
294+ {
295+ file_path : "/tmp/test-files/My Game (v2.0.0).zip" ,
296+ version : "v2.0.0" ,
297+ size : 1000n ,
298+ type : "WINDOWS_SETUP" ,
299+ early_access : false ,
300+ indexed_at : new Date ( "2026-01-02" ) ,
301+ } ,
302+ ] as any ) ;
303+
304+ const result = await service . listAvailableVersions ( 1 , 18 ) ;
305+
306+ expect ( result . map ( ( v ) => v . version ) ) . toEqual ( [ "v2.0.0" , "v1.0.0" ] ) ;
307+ expect ( gamesService . findOneByGameIdOrFail ) . toHaveBeenCalledWith ( 1 , {
308+ loadDeletedEntities : false ,
309+ filterByAge : 18 ,
310+ } ) ;
311+ } ) ;
312+
313+ it ( "should sort mixed non-semver versions with best-effort fallback" , async ( ) => {
314+ gamesService . findOneByGameIdOrFail . mockResolvedValue ( {
315+ id : 1 ,
316+ file_path : "/tmp/test-files/My Game (vBuild 15-01-2024).zip" ,
317+ version : "vBuild 15-01-2024" ,
318+ size : 1000n ,
319+ type : "WINDOWS_SETUP" ,
320+ early_access : false ,
321+ } as any ) ;
322+ gameVersionRepository . find . mockResolvedValue ( [
323+ {
324+ file_path : "/tmp/test-files/My Game (v1.0.0.2).zip" ,
325+ version : "v1.0.0.2" ,
326+ size : 1000n ,
327+ type : "WINDOWS_SETUP" ,
328+ early_access : false ,
329+ indexed_at : new Date ( "2026-01-01" ) ,
330+ } ,
331+ {
332+ file_path : "/tmp/test-files/My Game (v2025-04-27).zip" ,
333+ version : "v2025-04-27" ,
334+ size : 1000n ,
335+ type : "WINDOWS_SETUP" ,
336+ early_access : false ,
337+ indexed_at : new Date ( "2026-01-02" ) ,
338+ } ,
339+ {
340+ file_path : "/tmp/test-files/My Game (vBuild 15-01-2024).zip" ,
341+ version : "vBuild 15-01-2024" ,
342+ size : 1000n ,
343+ type : "WINDOWS_SETUP" ,
344+ early_access : false ,
345+ indexed_at : new Date ( "2026-01-03" ) ,
346+ } ,
347+ ] as any ) ;
348+
349+ const result = await service . listAvailableVersions ( 1 , 18 ) ;
350+
351+ expect ( result . map ( ( v ) => v . version ) ) . toEqual ( [
352+ "v1.0.0.2" ,
353+ "v2025-04-27" ,
354+ "vBuild 15-01-2024" ,
355+ ] ) ;
356+ } ) ;
357+ } ) ;
358+
359+ describe ( "getLatestVersion" , ( ) => {
360+ it ( "should return the first sorted version" , async ( ) => {
361+ jest . spyOn ( service , "listAvailableVersions" ) . mockResolvedValue ( [
362+ {
363+ file_path : "/tmp/test-files/My Game (v2.0.0).zip" ,
364+ version : "v2.0.0" ,
365+ } ,
366+ {
367+ file_path : "/tmp/test-files/My Game (v1.0.0).zip" ,
368+ version : "v1.0.0" ,
369+ } ,
370+ ] as any ) ;
371+
372+ const result = await service . getLatestVersion ( 1 , 18 ) ;
373+
374+ expect ( result . version ) . toBe ( "v2.0.0" ) ;
375+ expect ( service . listAvailableVersions ) . toHaveBeenCalledWith ( 1 , 18 ) ;
376+ } ) ;
377+
378+ it ( "should throw if no versions are available" , async ( ) => {
379+ jest . spyOn ( service , "listAvailableVersions" ) . mockResolvedValue ( [ ] as any ) ;
380+
381+ await expect ( service . getLatestVersion ( 1 , 18 ) ) . rejects . toThrow (
382+ NotFoundException ,
383+ ) ;
384+ } ) ;
243385 } ) ;
244386} ) ;
0 commit comments