diff --git a/controllers/appController.spec.ts b/controllers/appController.spec.ts index 39c31a8..aad4235 100644 --- a/controllers/appController.spec.ts +++ b/controllers/appController.spec.ts @@ -94,6 +94,28 @@ describe('controllers/appController', () => { })); }); + test('getHome handles upstream failures without crashing', async () => { + (http.get as jest.Mock) + .mockRejectedValueOnce(new Error('403')) + .mockResolvedValueOnce({ data: { result: [{ imdb_id: '2' }] } }); + (fetchAndUpdatePosters as jest.Mock).mockResolvedValue(undefined); + (getLatest as jest.Mock).mockReturnValue(undefined); + + const req: any = { query: {}, user: {} }; + const res: any = { + locals: { APP_URL: 'http://app', CARD_TYPE: 'card' }, + render: jest.fn(), + }; + + await appController.getHome(req, res, jest.fn()); + + expect(setLatest).not.toHaveBeenCalled(); + expect(res.render).toHaveBeenCalledWith('index', expect.objectContaining({ + newMovies: [], + newSeries: [{ imdb_id: '2' }], + })); + }); + test('getHome returns cached results when available', async () => { (getLatest as jest.Mock).mockReturnValue({ movies: [{ imdb_id: 'm1' }], diff --git a/controllers/appController.ts b/controllers/appController.ts index ef1078f..f3301d2 100644 --- a/controllers/appController.ts +++ b/controllers/appController.ts @@ -82,20 +82,35 @@ const appController = { }); } - const [axiosMovieResponse, axiosSeriesResponse] = await Promise.all([ + const [movieResult, seriesResult] = await Promise.allSettled([ http.get(`https://${appConfig.VIDSRC_DOMAIN}/movies/latest/page-1.json`), http.get(`https://${appConfig.VIDSRC_DOMAIN}/tvshows/latest/page-1.json`), ]); - let newMovies = axiosMovieResponse.data.result || []; - let newSeries = axiosSeriesResponse.data.result || []; + let newMovies = + movieResult.status === 'fulfilled' ? movieResult.value.data.result || [] : []; + let newSeries = + seriesResult.status === 'fulfilled' ? seriesResult.value.data.result || [] : []; + + if (movieResult.status === 'rejected') { + console.warn('Failed to fetch latest movies; continuing with empty dataset'); + } + + if (seriesResult.status === 'rejected') { + console.warn('Failed to fetch latest series; continuing with empty dataset'); + } await Promise.all([ fetchAndUpdatePosters(newMovies), fetchAndUpdatePosters(newSeries), ]); - setLatest({ movies: newMovies, series: newSeries }); + const hasFeedFailure = + movieResult.status === 'rejected' || seriesResult.status === 'rejected'; + + if (!hasFeedFailure) { + setLatest({ movies: newMovies, series: newSeries }); + } res.render('index', { newMovies, @@ -132,7 +147,7 @@ const appController = { */ getView: asyncHandler(async (req: AuthRequest, res: Response) => { const query = req.params.q || ''; - const id = req.params.id; + const id = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id; const type = req.params.type as 'movie' | 'series'; const cookieHeader = @@ -143,8 +158,12 @@ const appController = { const preferredServer = match ? (match[1] as '1' | '2') : undefined; if (type === 'series') { - let season = req.params.season; - let episode = req.params.episode; + let season = Array.isArray(req.params.season) + ? req.params.season[0] + : req.params.season; + let episode = Array.isArray(req.params.episode) + ? req.params.episode[0] + : req.params.episode; if ((!season || !episode) && req.user) { const redirectTo = await getResumeRedirect(req.user.id, id);