ed085a5e17
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
194 lines
5.7 KiB
TypeScript
194 lines
5.7 KiB
TypeScript
import { Database } from "bun:sqlite";
|
|
import { describe, expect, test, beforeEach } from "bun:test";
|
|
import { SCHEMA } from "../../db/schema";
|
|
import { resolveLanguage, type LanguageResolverConfig } from "../language-resolver";
|
|
import type { RadarrLibrary } from "../radarr";
|
|
import type { SonarrLibrary } from "../sonarr";
|
|
|
|
let db: Database;
|
|
|
|
function initDb(): Database {
|
|
const d = new Database(":memory:");
|
|
for (const stmt of SCHEMA.split(";").filter((s) => s.trim())) {
|
|
d.run(`${stmt};`);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
function insertItem(db: Database, overrides: Record<string, unknown> = {}): number {
|
|
const defaults = {
|
|
jellyfin_id: "jf-1",
|
|
type: "Movie",
|
|
name: "Test Movie",
|
|
file_path: "/movies/test.mkv",
|
|
original_language: "deu",
|
|
orig_lang_source: "jellyfin",
|
|
needs_review: 0,
|
|
tmdb_id: "12345",
|
|
imdb_id: null,
|
|
tvdb_id: null,
|
|
series_jellyfin_id: null,
|
|
};
|
|
const row = { ...defaults, ...overrides };
|
|
db.prepare(
|
|
`INSERT INTO media_items (jellyfin_id, type, name, file_path, original_language, orig_lang_source, needs_review, tmdb_id, imdb_id, tvdb_id, series_jellyfin_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
).run(
|
|
row.jellyfin_id,
|
|
row.type,
|
|
row.name,
|
|
row.file_path,
|
|
row.original_language,
|
|
row.orig_lang_source,
|
|
row.needs_review,
|
|
row.tmdb_id,
|
|
row.imdb_id,
|
|
row.tvdb_id,
|
|
row.series_jellyfin_id,
|
|
);
|
|
return (db.prepare("SELECT id FROM media_items WHERE jellyfin_id = ?").get(row.jellyfin_id) as { id: number }).id;
|
|
}
|
|
|
|
function makeRadarrLibrary(entries: Array<{ tmdbId?: string; imdbId?: string; lang: string }>): RadarrLibrary {
|
|
const byTmdbId = new Map<string, { originalLanguage?: { name: string } }>();
|
|
const byImdbId = new Map<string, { originalLanguage?: { name: string } }>();
|
|
for (const e of entries) {
|
|
const movie = { originalLanguage: { name: e.lang } };
|
|
if (e.tmdbId) byTmdbId.set(e.tmdbId, movie);
|
|
if (e.imdbId) byImdbId.set(e.imdbId, movie);
|
|
}
|
|
return { byTmdbId, byImdbId };
|
|
}
|
|
|
|
function makeSonarrLibrary(entries: Array<{ tvdbId: string; lang: string }>): SonarrLibrary {
|
|
const byTvdbId = new Map<string, { originalLanguage?: { name: string } }>();
|
|
for (const e of entries) {
|
|
byTvdbId.set(e.tvdbId, { originalLanguage: { name: e.lang } });
|
|
}
|
|
return { byTvdbId };
|
|
}
|
|
|
|
function baseCfg(overrides: Partial<LanguageResolverConfig> = {}): LanguageResolverConfig {
|
|
return {
|
|
radarr: null,
|
|
sonarr: null,
|
|
radarrLibrary: null,
|
|
sonarrLibrary: null,
|
|
resolveSeriesTvdb: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
db = initDb();
|
|
});
|
|
|
|
describe("resolveLanguage", () => {
|
|
test("radarr hit overrides jellyfin guess for movies", async () => {
|
|
const id = insertItem(db, {
|
|
type: "Movie",
|
|
tmdb_id: "12345",
|
|
original_language: "deu",
|
|
orig_lang_source: "jellyfin",
|
|
});
|
|
const cfg = baseCfg({
|
|
radarr: { url: "http://radarr:7878", apiKey: "key" },
|
|
radarrLibrary: makeRadarrLibrary([{ tmdbId: "12345", lang: "English" }]),
|
|
});
|
|
|
|
const result = await resolveLanguage(db, id, cfg);
|
|
|
|
expect(result.origLang).toBe("eng");
|
|
expect(result.origLangSource).toBe("radarr");
|
|
expect(result.needsReview).toBe(0);
|
|
expect(result.externalRaw).toBeDefined();
|
|
});
|
|
|
|
test("sonarr hit overrides jellyfin guess for episodes", async () => {
|
|
const id = insertItem(db, {
|
|
jellyfin_id: "jf-ep1",
|
|
type: "Episode",
|
|
name: "Test Episode",
|
|
tmdb_id: null,
|
|
tvdb_id: "99999",
|
|
original_language: "deu",
|
|
orig_lang_source: "jellyfin",
|
|
});
|
|
const cfg = baseCfg({
|
|
sonarr: { url: "http://sonarr:8989", apiKey: "key" },
|
|
sonarrLibrary: makeSonarrLibrary([{ tvdbId: "99999", lang: "Japanese" }]),
|
|
});
|
|
|
|
const result = await resolveLanguage(db, id, cfg);
|
|
|
|
expect(result.origLang).toBe("jpn");
|
|
expect(result.origLangSource).toBe("sonarr");
|
|
expect(result.needsReview).toBe(0);
|
|
});
|
|
|
|
test("resolveSeriesTvdb fixes episode with episode-level tvdbId", async () => {
|
|
const id = insertItem(db, {
|
|
jellyfin_id: "jf-ep2",
|
|
type: "Episode",
|
|
name: "Arrow S01E01",
|
|
tmdb_id: null,
|
|
tvdb_id: "episode-tvdb-123",
|
|
series_jellyfin_id: "series-jf-1",
|
|
original_language: "deu",
|
|
orig_lang_source: "jellyfin",
|
|
});
|
|
// Sonarr library has the series under a different tvdbId
|
|
const cfg = baseCfg({
|
|
sonarr: { url: "http://sonarr:8989", apiKey: "key" },
|
|
sonarrLibrary: makeSonarrLibrary([{ tvdbId: "series-tvdb-456", lang: "English" }]),
|
|
resolveSeriesTvdb: async (_seriesJellyfinId: string) => "series-tvdb-456",
|
|
});
|
|
|
|
const result = await resolveLanguage(db, id, cfg);
|
|
|
|
expect(result.origLang).toBe("eng");
|
|
expect(result.origLangSource).toBe("sonarr");
|
|
expect(result.needsReview).toBe(0);
|
|
});
|
|
|
|
test("skips resolution when orig_lang_source is manual", async () => {
|
|
const id = insertItem(db, {
|
|
type: "Movie",
|
|
tmdb_id: "12345",
|
|
original_language: "fra",
|
|
orig_lang_source: "manual",
|
|
});
|
|
const cfg = baseCfg({
|
|
radarr: { url: "http://radarr:7878", apiKey: "key" },
|
|
radarrLibrary: makeRadarrLibrary([{ tmdbId: "12345", lang: "English" }]),
|
|
});
|
|
|
|
const result = await resolveLanguage(db, id, cfg);
|
|
|
|
expect(result.origLang).toBe("fra");
|
|
expect(result.origLangSource).toBe("manual");
|
|
expect(result.needsReview).toBe(0);
|
|
});
|
|
|
|
test("returns jellyfin guess unchanged when no external source matches", async () => {
|
|
const id = insertItem(db, {
|
|
type: "Movie",
|
|
tmdb_id: "99999",
|
|
original_language: "deu",
|
|
orig_lang_source: "jellyfin",
|
|
needs_review: 0,
|
|
});
|
|
// Radarr library has no entry for tmdb 99999
|
|
const cfg = baseCfg({
|
|
radarr: { url: "http://radarr:7878", apiKey: "key" },
|
|
radarrLibrary: makeRadarrLibrary([{ tmdbId: "11111", lang: "English" }]),
|
|
});
|
|
|
|
const result = await resolveLanguage(db, id, cfg);
|
|
|
|
expect(result.origLang).toBe("deu");
|
|
expect(result.origLangSource).toBe("jellyfin");
|
|
expect(result.needsReview).toBe(1);
|
|
expect(result.externalRaw).toBeNull();
|
|
});
|
|
});
|