From 28770aad09f2cc9f0237440fb5a0f46fa2353495 Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 30 Jul 2026 01:53:16 +0200 Subject: [PATCH] fix(downloads): report library navigation failures --- ...ownload-library-navigation.service.spec.ts | 44 +++++++++++++++++++ .../download-library-navigation.service.ts | 28 ++++++------ 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/libs/portal/downloads/feature/src/lib/download-library-navigation.service.spec.ts b/libs/portal/downloads/feature/src/lib/download-library-navigation.service.spec.ts index 9e57c2687..8901f0760 100644 --- a/libs/portal/downloads/feature/src/lib/download-library-navigation.service.spec.ts +++ b/libs/portal/downloads/feature/src/lib/download-library-navigation.service.spec.ts @@ -183,6 +183,50 @@ describe('DownloadLibraryNavigationService', () => { ]); }); + it('returns false when the router refuses an Xtream navigation', async () => { + db.getContentByXtreamId.mockResolvedValue({ + category_id: 7, + } as never); + router.navigate.mockResolvedValue(false); + + await expect(navigation.open(download())).resolves.toBe(false); + + expect(router.navigate).toHaveBeenCalledWith([ + '/workspace', + 'xtreams', + PLAYLIST_ID, + 'vod', + '7', + '41', + ]); + }); + + it.each(['xtream', 'stalker'] as const)( + 'returns false instead of rejecting when %s navigation fails', + async (source) => { + if (source === 'stalker') { + playlists.getPlaylistById.mockReturnValue( + of(STALKER_PLAYLIST) + ); + } + router.navigate.mockRejectedValue( + new Error('navigation rejected') + ); + + await expect(navigation.open(download())).resolves.toBe(false); + + expect(router.navigate).toHaveBeenCalledTimes(1); + } + ); + + it('returns false when the Xtream content lookup fails', async () => { + db.getContentByXtreamId.mockRejectedValue(new Error('database failed')); + + await expect(navigation.open(download())).resolves.toBe(false); + + expect(router.navigate).not.toHaveBeenCalled(); + }); + it('opens an Xtream episode at its series detail route', async () => { const item = download({ contentType: 'episode', diff --git a/libs/portal/downloads/feature/src/lib/download-library-navigation.service.ts b/libs/portal/downloads/feature/src/lib/download-library-navigation.service.ts index baeb9959e..12933ff1e 100644 --- a/libs/portal/downloads/feature/src/lib/download-library-navigation.service.ts +++ b/libs/portal/downloads/feature/src/lib/download-library-navigation.service.ts @@ -34,18 +34,18 @@ export class DownloadLibraryNavigationService { return false; } - const source = await this.resolveSourceType(item.playlistId); - if (source === null) { + try { + const source = await this.resolveSourceType(item.playlistId); + if (source === null) { + return false; + } + + return source === 'xtream' + ? await this.openXtreamItem(item, targetId) + : await this.openStalkerItem(item, targetId); + } catch { return false; } - - if (source === 'xtream') { - await this.openXtreamItem(item, targetId); - } else { - await this.openStalkerItem(item, targetId); - } - - return true; } private targetId(item: DownloadItem): number | null { @@ -92,7 +92,7 @@ export class DownloadLibraryNavigationService { private async openXtreamItem( item: DownloadItem, targetId: number - ): Promise { + ): Promise { const contentType = item.contentType === 'episode' ? 'series' : 'vod'; const content = await this.db.getContentByXtreamId( @@ -105,7 +105,7 @@ export class DownloadLibraryNavigationService { ? [contentType] : [contentType, String(categoryId), String(targetId)]; - await this.router.navigate( + return this.router.navigate( this.route('xtream', item.playlistId, segments) ); } @@ -193,7 +193,7 @@ export class DownloadLibraryNavigationService { private async openStalkerItem( item: DownloadItem, targetId: number - ): Promise { + ): Promise { const fallback = item.contentType === 'episode' ? 'series' : 'vod'; const openRecentItem = await this.stalkerOpenState( @@ -202,7 +202,7 @@ export class DownloadLibraryNavigationService { fallback ); - await this.router.navigate( + return this.router.navigate( this.route('stalker', item.playlistId, ['recent']), { state: { openRecentItem } } );