diff --git a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.spec.ts b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.spec.ts new file mode 100644 index 000000000..ccbd48a1b --- /dev/null +++ b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.spec.ts @@ -0,0 +1,83 @@ +import { TestBed } from '@angular/core/testing'; +import { TmdbEnrichmentService } from '@iptvnator/services'; +import { DashboardHeroTmdbService } from './dashboard-hero-tmdb.service'; + +describe('DashboardHeroTmdbService', () => { + const tvDetails = { + id: 100, + backdrop_path: '/serial.jpg', + vote_average: 7.4, + vote_count: 12, + genres: [{ id: 1, name: 'Drama' }, { id: 2, name: 'Comedy' }], + }; + + let isEnabled: jest.Mock; + let enrichMovie: jest.Mock; + let enrichTv: jest.Mock; + + function createService(): DashboardHeroTmdbService { + TestBed.configureTestingModule({ + providers: [ + { + provide: TmdbEnrichmentService, + useValue: { isEnabled, enrichMovie, enrichTv }, + }, + ], + }); + return TestBed.inject(DashboardHeroTmdbService); + } + + beforeEach(() => { + isEnabled = jest.fn().mockReturnValue(true); + enrichMovie = jest.fn().mockResolvedValue(null); + enrichTv = jest.fn().mockResolvedValue(tvDetails); + }); + + it('returns null when TMDB is disabled or the item is live', async () => { + isEnabled.mockReturnValue(false); + const service = createService(); + await expect( + service.getExtras({ title: 'X', type: 'movie' }) + ).resolves.toBeNull(); + + isEnabled.mockReturnValue(true); + await expect( + service.getExtras({ title: 'X', type: 'live' }) + ).resolves.toBeNull(); + expect(enrichMovie).not.toHaveBeenCalled(); + }); + + it('falls back to a TV lookup for movie-typed items without a movie match', async () => { + // Stalker embedded-series items are typed 'movie' in activity rows + const service = createService(); + + const extras = await service.getExtras({ + title: 'Между нами химия (8 серий)', + type: 'movie', + }); + + expect(enrichMovie).toHaveBeenCalledTimes(1); + expect(enrichTv).toHaveBeenCalledTimes(1); + expect(extras?.backdropUrl).toContain('/serial.jpg'); + expect(extras?.rating).toBe('7.4'); + expect(extras?.genres).toEqual(['Drama', 'Comedy']); + }); + + it('does not retry as TV when the movie lookup succeeds', async () => { + enrichMovie.mockResolvedValue({ ...tvDetails, id: 200 }); + const service = createService(); + + await service.getExtras({ title: 'Heat', type: 'movie' }); + + expect(enrichTv).not.toHaveBeenCalled(); + }); + + it('memoizes results per title and type', async () => { + const service = createService(); + + await service.getExtras({ title: 'The Boys', type: 'series' }); + await service.getExtras({ title: 'The Boys', type: 'series' }); + + expect(enrichTv).toHaveBeenCalledTimes(1); + }); +}); diff --git a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.ts b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.ts index cbdf91b18..be33eb0c6 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.ts +++ b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-hero-tmdb.service.ts @@ -58,10 +58,18 @@ export class DashboardHeroTmdbService { title: item.title, year: extractYear(null, item.title), }; - const details = + let details = item.type === 'movie' ? await this.enrichment.enrichMovie(query) : await this.enrichment.enrichTv(query); + if (!details && item.type === 'movie') { + // Stalker embedded-series items (vclub) are typed 'movie' + // in activity rows but are TV shows on TMDB — the detail + // view resolves them via is_series, so the TV lookup is + // usually already cached. Misses are negative-cached, so + // the retry never turns into repeated network traffic. + details = await this.enrichment.enrichTv(query); + } if (!details) { return null; }