Files
netfelix-audio-fix/server/services/__tests__/language-resolver.test.ts
T
felixfoertsch 6022ed09b2 simplify language-resolver: drop jellyfin resolveSeriesTvdb callback
remove resolveSeriesTvdb from LanguageResolverConfig, rename jellyfinFallback
to probeFallback, replace Jellyfin-based TVDB resolution with series_name
title search over Sonarr library, update tests accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:54 +02:00

192 lines
5.6 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 = {
type: "Movie",
name: "Test Movie",
file_path: "/movies/test.mkv",
original_language: "deu",
orig_lang_source: "probe",
needs_review: 0,
tmdb_id: "12345",
imdb_id: null,
tvdb_id: null,
series_name: null,
series_key: null,
};
const row = { ...defaults, ...overrides };
db.prepare(
`INSERT INTO media_items (type, name, file_path, original_language, orig_lang_source, needs_review, tmdb_id, imdb_id, tvdb_id, series_name, series_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
).run(
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_name,
row.series_key,
);
return (db.prepare("SELECT id FROM media_items WHERE file_path = ?").get(row.file_path) 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; title?: string }>): SonarrLibrary {
const byTvdbId = new Map<string, { originalLanguage?: { name: string }; title?: string }>();
for (const e of entries) {
byTvdbId.set(e.tvdbId, { originalLanguage: { name: e.lang }, title: e.title });
}
return { byTvdbId };
}
function baseCfg(overrides: Partial<LanguageResolverConfig> = {}): LanguageResolverConfig {
return {
radarr: null,
sonarr: null,
radarrLibrary: null,
sonarrLibrary: null,
...overrides,
};
}
beforeEach(() => {
db = initDb();
});
describe("resolveLanguage", () => {
test("radarr hit overrides probe guess for movies", async () => {
const id = insertItem(db, {
type: "Movie",
tmdb_id: "12345",
original_language: "deu",
orig_lang_source: "probe",
});
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 probe guess for episodes via tvdb_id", async () => {
const id = insertItem(db, {
type: "Episode",
file_path: "/series/ep1.mkv",
name: "Test Episode",
tmdb_id: null,
tvdb_id: "99999",
original_language: "deu",
orig_lang_source: "probe",
});
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("sonarr hit via series_name when tvdb_id not in library", async () => {
const id = insertItem(db, {
type: "Episode",
file_path: "/series/arrow-ep1.mkv",
name: "Arrow S01E01",
tmdb_id: null,
tvdb_id: null,
series_name: "Arrow",
original_language: "deu",
orig_lang_source: "probe",
});
// Sonarr library has the series under a title match
const cfg = baseCfg({
sonarr: { url: "http://sonarr:8989", apiKey: "key" },
sonarrLibrary: makeSonarrLibrary([{ tvdbId: "series-tvdb-456", lang: "English", title: "Arrow" }]),
});
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 probe guess unchanged when no external source matches", async () => {
const id = insertItem(db, {
type: "Movie",
tmdb_id: "99999",
original_language: "deu",
orig_lang_source: "probe",
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("probe");
expect(result.needsReview).toBe(1);
expect(result.externalRaw).toBeNull();
});
});