Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions controllers/appController.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ 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(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' }],
Expand Down
26 changes: 20 additions & 6 deletions controllers/appController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,23 @@ 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 || [] : [];
Comment on lines +90 to +93

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid caching fallback empties after partial feed failures

When one upstream latest-feed call fails, these lines coerce that dataset to [], and the handler still executes setLatest({ movies: newMovies, series: newSeries }) afterward; because helpers/cache.ts sets a 24-hour TTL, a transient 403/timeout can leave the home page missing movies or series for an entire day even after the API recovers. Consider skipping cache writes (or using a much shorter TTL) when either Promise.allSettled result is rejected.

Useful? React with 👍 / 👎.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex fix

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Updated getHome to avoid writing to the 24-hour latest cache when either upstream latest-feed request fails; it now computes hasFeedFailure from the Promise.allSettled results and only calls setLatest when both requests are fulfilled. This preserves resilient rendering while preventing stale partial data from being cached. controllers/appController.tsL85-L113
  • Added/updated test coverage to assert setLatest is not called when one latest-feed request rejects, guarding against regressions for the reviewer-reported scenario. controllers/appController.spec.tsL97-L117
  • Committed the change on the current branch with Conventional Commit message: fix: skip cache write on partial latest-feed failures (commit 2ef24f8).

Testing

  • ⚠️ curl -I https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled | head (network/proxy returned HTTP 403 in this environment).
  • yarn test controllers/appController.spec.ts --runInBand

Also completed the required PR handoff step via make_pr with title “fix: avoid caching partial latest-feed fallbacks” and a body summarizing these follow-up changes.

View task →


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),
Expand Down Expand Up @@ -132,7 +142,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 =
Expand All @@ -143,8 +153,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);
Expand Down
Loading