Files
iptvnator/libs/portal/xtream/data-access/src/lib/xtream-content-metadata.util.spec.ts
T
4gray 61fca6f016 fix(dashboard): reuse the detail view's TMDB identity for activity rows (#1423)
An Xtream activity row is built from its `content` row, and the catalog
endpoints that create those rows carry only a title and a poster. So the
dashboard hero and the recommendations rail rebuilt their TMDB query from the
display title alone, while the detail view had searched with the original
title, the release date and often a TMDB id. Without a year
`pickConfidentMatch` requires a globally unique exact title, which common
titles never satisfy — "Inside Out" matches several films and resolves to
nothing, every time.

Three `content` columns close that gap next to the existing `backdrop_url`:
`tmdb_id`, `release_year`, `original_title`. The detail views back-fill them
from what is on screen, the activity SELECTs project them, and
`buildDashboardTmdbAttempts` reads them back. Stalker keeps stating the same
facts through its stored entry, and rows with neither keep the title-only
fallback.

Measured against a real profile before building: of 58 distinct Xtream
movie/series activity rows, 16 (28%) produce a year-less key — the cohort
where a miss is guaranteed rather than likely.

Contracts worth preserving:

- Per-column, never overwrite. Enrichment supplies the pieces at different
  times, so a row-level guard would let the first arrival block every later
  one forever.
- `release_year` is the year the PROVIDER stated. The TMDB merge fills the
  date field when the provider left it empty, so it marks its own
  substitution with `tmdb_supplied_release_date` and the extractor skips
  those — making contamination structurally impossible rather than avoided.
- The id is stored unvetted: every consumer re-gates it through
  `assessProviderId`, which re-decides per lookup where a write-time verdict
  would be permanent.
- No media-type column — for Xtream the catalog files movies and series
  apart, so `content.type` already is the media type.

Worker requests now await `getDatabase()` before dispatching. The renderer
loads before `initDatabase()` and the worker opens the database file without
running migrations, so a query issued during startup on an upgraded install
could otherwise hit a schema whose new columns do not exist yet.

Not covered: the PWA, whose catalog cache is rebuilt from the API on every
load, so a stored id would never outlive the detail view that resolved it.
2026-08-13 18:51:26 +02:00

126 lines
4.4 KiB
TypeScript

import {
xtreamContentMetadataKey,
xtreamDetailContentMetadata,
} from './xtream-content-metadata.util';
describe('xtreamDetailContentMetadata', () => {
it('reads a VOD detail response', () => {
expect(
xtreamDetailContentMetadata({
backdrop_path: ['https://example.com/b.jpg'],
tmdb_id: '603',
releasedate: '1999-03-31',
o_name: 'The Matrix',
})
).toEqual({
backdropUrl: 'https://example.com/b.jpg',
tmdbId: 603,
releaseYear: 1999,
originalTitle: 'The Matrix',
});
});
it('reads a series detail response, which spells the date differently', () => {
expect(
xtreamDetailContentMetadata({
releaseDate: '2019-11-12',
tmdb_id: 82856,
})
).toEqual({ tmdbId: 82856, releaseYear: 2019 });
});
it('does not store a date TMDB supplied when the provider had none', () => {
// `mergeVodInfoWithTmdb` fills `releasedate` from `details.release_date`
// whenever the provider left it empty, and this runs against the
// merged object. Recording that as a provider fact is unfixable
// afterwards: the column is never overwritten, so a real provider date
// arriving later cannot correct it.
expect(
xtreamDetailContentMetadata({
releasedate: '2015-06-19',
tmdb_supplied_release_date: true,
tmdb_id: 150540,
})
).toEqual({ tmdbId: 150540 });
});
it('stores the date when the provider stated it, enrichment or not', () => {
// The merge keeps the provider's own value and leaves the marker off,
// so an enriched item with a real provider date still records it.
expect(
xtreamDetailContentMetadata({
releasedate: '1999-03-31',
tmdb_id: 603,
})
).toEqual({ tmdbId: 603, releaseYear: 1999 });
});
it('applies the same rule to a series releaseDate', () => {
expect(
xtreamDetailContentMetadata({
releaseDate: '2019-11-12',
tmdb_supplied_release_date: true,
})
).toBeNull();
});
it('never stores a year found only in the title', () => {
// The provider stated no date. Readers apply their own title-derived
// fallback; recording one here would turn a guess into a fact, and
// "2001: A Space Odyssey" is not a 2001 film.
expect(
xtreamDetailContentMetadata({
tmdb_id: 62,
releasedate: '',
})
).toEqual({ tmdbId: 62 });
});
it.each([
['no info at all', null],
['an empty response', {}],
['only empty values', { backdrop_path: [], releasedate: '', o_name: '' }],
])('reports %s as nothing to persist', (_label, info) => {
expect(xtreamDetailContentMetadata(info)).toBeNull();
});
it('takes only the first backdrop', () => {
expect(
xtreamDetailContentMetadata({
backdrop_path: ['https://a.example/1.jpg', 'https://a.example/2.jpg'],
})
).toEqual({ backdropUrl: 'https://a.example/1.jpg' });
});
});
describe('xtreamContentMetadataKey', () => {
it('changes when enrichment adds the id to an already-backfilled patch', () => {
// The detail effect skips a repeat write by comparing this key. Keyed
// on the backdrop alone, it would suppress the write that carries the
// id arriving moments later.
const beforeEnrichment = xtreamDetailContentMetadata({
backdrop_path: ['https://example.com/b.jpg'],
});
const afterEnrichment = xtreamDetailContentMetadata({
backdrop_path: ['https://example.com/b.jpg'],
tmdb_id: 603,
});
expect(xtreamContentMetadataKey(beforeEnrichment)).not.toEqual(
xtreamContentMetadataKey(afterEnrichment)
);
});
it('is stable for an unchanged patch', () => {
const patch = { backdropUrl: 'https://example.com/b.jpg', tmdbId: 603 };
expect(xtreamContentMetadataKey(patch)).toEqual(
xtreamContentMetadataKey({ ...patch })
);
});
it('is empty for no patch', () => {
expect(xtreamContentMetadataKey(null)).toBe('');
});
});