mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
- New webhooks.test.ts: 34 tests covering all 3 payload parsers (Plex/Jellyfin/Emby), toOptionalInt, and processWebhook end-to-end (movie/episode watch logging, deduplication, resolution failures) - Expand credits.test.ts: 6 new tests for refreshCredits (movie/TV cast+crew upsert, person deduplication, cast limit, notable crew filter) - Expand image-cache.test.ts: 8 new tests for imageCacheEnabled, getLocalImagePath, and loadImageBuffer error handling - Expand metadata.test.ts: 4 new tests for ensureBrowseTitlesExist (shell title creation, deduplication, mixed existing/new) 304 → 345 tests passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
279 lines
7.9 KiB
TypeScript
279 lines
7.9 KiB
TypeScript
import { beforeEach, describe, expect, test } from "vitest";
|
|
|
|
import { titles } from "@sofa/db/schema";
|
|
import { clearAllTables, insertTitle, testDb } from "@sofa/test/db";
|
|
import type { TmdbMovieDetails, TmdbTvDetails, TmdbVideo } from "@sofa/tmdb/client";
|
|
|
|
import {
|
|
ensureBrowseTitlesExist,
|
|
extractMovieContentRating,
|
|
extractTvContentRating,
|
|
pickBestTrailer,
|
|
} from "../src/metadata";
|
|
|
|
beforeEach(() => {
|
|
clearAllTables();
|
|
});
|
|
|
|
function makeVideo(overrides: Partial<TmdbVideo> = {}): TmdbVideo {
|
|
return {
|
|
id: "v1",
|
|
key: "abc123",
|
|
name: "Trailer",
|
|
site: "YouTube",
|
|
type: "Trailer",
|
|
official: false,
|
|
published_at: "2024-01-01T00:00:00.000Z",
|
|
size: 1080,
|
|
iso_639_1: "en",
|
|
iso_3166_1: "US",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("pickBestTrailer", () => {
|
|
test("returns null for empty array", () => {
|
|
expect(pickBestTrailer([])).toBeNull();
|
|
});
|
|
|
|
test("returns null when no YouTube videos", () => {
|
|
const videos = [makeVideo({ site: "Vimeo", key: "vimeo1" })];
|
|
expect(pickBestTrailer(videos)).toBeNull();
|
|
});
|
|
|
|
test("prefers official trailers over unofficial", () => {
|
|
const videos = [
|
|
makeVideo({ key: "unofficial", official: false, type: "Trailer" }),
|
|
makeVideo({ key: "official", official: true, type: "Trailer" }),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBe("official");
|
|
});
|
|
|
|
test("prefers trailers over teasers", () => {
|
|
const videos = [
|
|
makeVideo({ key: "teaser", type: "Teaser" }),
|
|
makeVideo({ key: "trailer", type: "Trailer" }),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBe("trailer");
|
|
});
|
|
|
|
test("falls back to teaser when no trailers", () => {
|
|
const videos = [
|
|
makeVideo({ key: "teaser", type: "Teaser" }),
|
|
makeVideo({ key: "featurette", type: "Featurette" }),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBe("teaser");
|
|
});
|
|
|
|
test("returns null when only non-trailer/teaser types exist", () => {
|
|
const videos = [
|
|
makeVideo({ key: "feat", type: "Featurette" }),
|
|
makeVideo({ key: "clip", type: "Clip" }),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBeNull();
|
|
});
|
|
|
|
test("prefers English over other languages", () => {
|
|
const videos = [
|
|
makeVideo({ key: "french", iso_639_1: "fr", type: "Trailer" }),
|
|
makeVideo({ key: "english", iso_639_1: "en", type: "Trailer" }),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBe("english");
|
|
});
|
|
|
|
test("sorts by newest published_at", () => {
|
|
const videos = [
|
|
makeVideo({
|
|
key: "older",
|
|
type: "Trailer",
|
|
official: true,
|
|
published_at: "2023-01-01T00:00:00.000Z",
|
|
}),
|
|
makeVideo({
|
|
key: "newer",
|
|
type: "Trailer",
|
|
official: true,
|
|
published_at: "2024-06-01T00:00:00.000Z",
|
|
}),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBe("newer");
|
|
});
|
|
|
|
test("handles missing published_at", () => {
|
|
const videos = [
|
|
makeVideo({
|
|
key: "no-date",
|
|
type: "Trailer",
|
|
official: true,
|
|
published_at: undefined as unknown as string,
|
|
}),
|
|
makeVideo({
|
|
key: "with-date",
|
|
type: "Trailer",
|
|
official: true,
|
|
published_at: "2024-01-01T00:00:00.000Z",
|
|
}),
|
|
];
|
|
expect(pickBestTrailer(videos)).toBe("with-date");
|
|
});
|
|
});
|
|
|
|
describe("extractMovieContentRating", () => {
|
|
test("returns US certification", () => {
|
|
const movie = {
|
|
release_dates: {
|
|
results: [
|
|
{
|
|
iso_3166_1: "US",
|
|
release_dates: [{ certification: "PG-13", type: 3 }],
|
|
},
|
|
],
|
|
},
|
|
} as unknown as TmdbMovieDetails;
|
|
expect(extractMovieContentRating(movie)).toBe("PG-13");
|
|
});
|
|
|
|
test("returns first non-empty certification", () => {
|
|
const movie = {
|
|
release_dates: {
|
|
results: [
|
|
{
|
|
iso_3166_1: "US",
|
|
release_dates: [
|
|
{ certification: "", type: 1 },
|
|
{ certification: "R", type: 3 },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
} as unknown as TmdbMovieDetails;
|
|
expect(extractMovieContentRating(movie)).toBe("R");
|
|
});
|
|
|
|
test("returns null when no US entry", () => {
|
|
const movie = {
|
|
release_dates: {
|
|
results: [
|
|
{
|
|
iso_3166_1: "GB",
|
|
release_dates: [{ certification: "15", type: 3 }],
|
|
},
|
|
],
|
|
},
|
|
} as unknown as TmdbMovieDetails;
|
|
expect(extractMovieContentRating(movie)).toBeNull();
|
|
});
|
|
|
|
test("returns null when release_dates is undefined", () => {
|
|
const movie = {} as unknown as TmdbMovieDetails;
|
|
expect(extractMovieContentRating(movie)).toBeNull();
|
|
});
|
|
|
|
test("returns null when all US certifications are empty", () => {
|
|
const movie = {
|
|
release_dates: {
|
|
results: [
|
|
{
|
|
iso_3166_1: "US",
|
|
release_dates: [
|
|
{ certification: "", type: 1 },
|
|
{ certification: "", type: 3 },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
} as unknown as TmdbMovieDetails;
|
|
expect(extractMovieContentRating(movie)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("extractTvContentRating", () => {
|
|
test("returns US rating", () => {
|
|
const show = {
|
|
content_ratings: {
|
|
results: [{ iso_3166_1: "US", rating: "TV-MA" }],
|
|
},
|
|
} as unknown as TmdbTvDetails;
|
|
expect(extractTvContentRating(show)).toBe("TV-MA");
|
|
});
|
|
|
|
test("returns null when no US entry", () => {
|
|
const show = {
|
|
content_ratings: {
|
|
results: [{ iso_3166_1: "DE", rating: "16" }],
|
|
},
|
|
} as unknown as TmdbTvDetails;
|
|
expect(extractTvContentRating(show)).toBeNull();
|
|
});
|
|
|
|
test("returns null when content_ratings is undefined", () => {
|
|
const show = {} as unknown as TmdbTvDetails;
|
|
expect(extractTvContentRating(show)).toBeNull();
|
|
});
|
|
|
|
test("returns null when US rating is empty", () => {
|
|
const show = {
|
|
content_ratings: {
|
|
results: [{ iso_3166_1: "US", rating: "" }],
|
|
},
|
|
} as unknown as TmdbTvDetails;
|
|
expect(extractTvContentRating(show)).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── ensureBrowseTitlesExist ────────────────────────────────────────
|
|
|
|
describe("ensureBrowseTitlesExist", () => {
|
|
test("creates shell titles for new entries", () => {
|
|
const result = ensureBrowseTitlesExist([
|
|
{ tmdbId: 100, type: "movie", title: "Movie A", posterPath: "/a.jpg" },
|
|
{ tmdbId: 200, type: "tv", title: "Show B", posterPath: "/b.jpg" },
|
|
]);
|
|
|
|
expect(result.size).toBe(2);
|
|
expect(result.get("100-movie")).toBeDefined();
|
|
expect(result.get("200-tv")).toBeDefined();
|
|
|
|
const allTitles = testDb.select().from(titles).all();
|
|
expect(allTitles).toHaveLength(2);
|
|
// Shell titles have no lastFetchedAt
|
|
expect(allTitles[0].lastFetchedAt).toBeNull();
|
|
expect(allTitles[1].lastFetchedAt).toBeNull();
|
|
});
|
|
|
|
test("returns existing titles without creating duplicates", () => {
|
|
insertTitle({ id: "existing-1", tmdbId: 100, type: "movie", title: "Movie A" });
|
|
|
|
const result = ensureBrowseTitlesExist([
|
|
{ tmdbId: 100, type: "movie", title: "Movie A", posterPath: "/a.jpg" },
|
|
]);
|
|
|
|
expect(result.size).toBe(1);
|
|
expect(result.get("100-movie")?.id).toBe("existing-1");
|
|
|
|
const allTitles = testDb.select().from(titles).all();
|
|
expect(allTitles).toHaveLength(1);
|
|
});
|
|
|
|
test("handles mix of existing and new titles", () => {
|
|
insertTitle({ id: "existing-1", tmdbId: 100, type: "movie", title: "Movie A" });
|
|
|
|
const result = ensureBrowseTitlesExist([
|
|
{ tmdbId: 100, type: "movie", title: "Movie A", posterPath: "/a.jpg" },
|
|
{ tmdbId: 200, type: "tv", title: "Show B", posterPath: "/b.jpg" },
|
|
]);
|
|
|
|
expect(result.size).toBe(2);
|
|
expect(result.get("100-movie")?.id).toBe("existing-1");
|
|
expect(result.get("200-tv")).toBeDefined();
|
|
|
|
const allTitles = testDb.select().from(titles).all();
|
|
expect(allTitles).toHaveLength(2);
|
|
});
|
|
|
|
test("returns empty map for empty input", () => {
|
|
const result = ensureBrowseTitlesExist([]);
|
|
expect(result.size).toBe(0);
|
|
});
|
|
});
|