package api import ( "testing" "github.com/ponzischeme89/memby/server/internal/bazarr" "github.com/ponzischeme89/memby/server/internal/opensubtitles" "github.com/ponzischeme89/memby/server/internal/store" ) // A row has to carry the backend it came from, because the two tokens are opaque in // different ways and handing one to the other is a mistake nothing downstream could catch. func TestCandidatesCarryTheirSource(t *testing.T) { fromBazarr := bazarrCandidates([]bazarr.Subtitle{{Language: "eng", Token: "opaque"}}) if len(fromBazarr) != 1 || fromBazarr[0].Source != store.SubtitleProviderBazarr { t.Fatalf("bazarr candidate = %+v", fromBazarr) } fromOpen := openSubtitlesCandidates([]opensubtitles.Subtitle{{FileID: 42, Language: "en"}}) if len(fromOpen) != 1 || fromOpen[0].Source != store.SubtitleProviderOpenSubtitles { t.Fatalf("opensubtitles candidate = %+v", fromOpen) } if fromOpen[0].Token != "42" { t.Fatalf("token = %q, want the file id", fromOpen[0].Token) } } // A machine translation is a real answer and sometimes the only one, so it is offered — // below everything a person wrote, whatever confidence the provider claims for it. func TestRankingSinksMachineTranslationsBelowHumanOnes(t *testing.T) { found := []subtitleCandidate{ {Source: "opensubtitles", Token: "robot", Language: "en", Score: 99, MachineOnly: true}, {Source: "bazarr", Token: "human", Language: "en", Score: 40}, } results := rankMergedCandidates(found, "en") if results[0].Token != "human" { t.Fatalf("order = %q, %q", results[0].Token, results[1].Token) } if want := "English · Machine translated · 99% match"; results[1].Label != want { t.Fatalf("label = %q, want %q", results[1].Label, want) } } // The viewer's language still outranks everything, across providers as it did within one. func TestRankingKeepsTheChosenLanguageFirstAcrossProviders(t *testing.T) { found := []subtitleCandidate{ {Source: "bazarr", Token: "en", Language: "en", Score: 99}, {Source: "opensubtitles", Token: "it", Language: "it", Score: 20}, } if got := rankMergedCandidates(found, "it"); got[0].Token != "it" { t.Fatalf("first row = %q, want the Italian one", got[0].Token) } } // A file nobody has rated is not a bad file, so it lands mid-scale rather than at the // bottom — the judgement heroUnratedScore already makes about an unrated title. func TestOpenSubtitlesScoreIsMidScaleWhenUnrated(t *testing.T) { unrated := openSubtitlesScore(opensubtitles.Subtitle{}) if unrated < 40 || unrated > 70 { t.Fatalf("unrated score = %d, want mid-scale", unrated) } if rated := openSubtitlesScore(opensubtitles.Subtitle{Rating: 9.4}); rated <= unrated { t.Fatalf("a well-rated file scored %d, below an unrated %d", rated, unrated) } if machine := openSubtitlesScore(opensubtitles.Subtitle{Rating: 9.4, MachineOnly: true}); machine >= 94 { t.Fatalf("a machine translation kept its full score (%d)", machine) } } // Two identically labelled rows in one drop-up is what makes a viewer distrust the whole // menu, so where both exist Emby's track wins: it is the one in the file. func TestMergeSubtitleTracksDropsWhatEmbyAlreadyHas(t *testing.T) { emby := []playableSubtitle{{ID: "3", Language: "eng", DeliveryMethod: "External"}} stored := []playableSubtitle{ {ID: "gw:1:en:plain", Language: "en", DeliveryMethod: "External"}, {ID: "gw:1:it:plain", Language: "it", DeliveryMethod: "External"}, } merged := mergeSubtitleTracks(emby, stored) if len(merged) != 2 { t.Fatalf("merged = %+v", merged) } if merged[1].ID != "gw:1:it:plain" { t.Fatalf("kept %q, want only the Italian one to survive", merged[1].ID) } } // A forced track is not the same track as a plain one in the same language, so it must not // be deduplicated away — that is exactly the subtitle somebody downloaded it for. func TestMergeSubtitleTracksKeepsADifferentVariant(t *testing.T) { emby := []playableSubtitle{{ID: "3", Language: "eng"}} stored := []playableSubtitle{{ID: "gw:1:en:forced", Language: "en", IsForced: true}} if merged := mergeSubtitleTracks(emby, stored); len(merged) != 2 { t.Fatalf("merged = %+v", merged) } } // The id is derived from what was asked for, so fetching the same language twice replaces // the file rather than growing a second track a viewer has to tell apart by guessing. func TestStoredSubtitleIDIsStablePerVariant(t *testing.T) { plain := storedSubtitleID("42", subtitleCandidate{Language: "it"}) again := storedSubtitleID("42", subtitleCandidate{Language: "it", Provider: "elsewhere"}) if plain != again { t.Fatalf("%q != %q for the same title and language", plain, again) } forced := storedSubtitleID("42", subtitleCandidate{Language: "it", Forced: true}) if forced == plain { t.Fatal("a forced track took the plain track's id") } if !isStoredSubtitleID(plain) { t.Fatalf("%q is not recognised as the gateway's own", plain) } // Emby's ids are stream indices, which are plain numbers. The two namespaces must not // be able to collide, because the player matches a track on the id it was handed. if isStoredSubtitleID("3") { t.Fatal("an Emby stream index was read as a gateway subtitle") } } // A search restricted to nothing returns every language on earth, which is unreadable on a // television; English rides along because a household that never set a language gets one. func TestOpenSubtitlesLanguagesAlwaysNameSomething(t *testing.T) { for _, language := range []string{"", "auto"} { if got := openSubtitlesLanguages(language); len(got) != 1 || got[0] != "en" { t.Fatalf("languages for %q = %v", language, got) } } if got := openSubtitlesLanguages("it"); len(got) != 2 || got[0] != "it" { t.Fatalf("languages for Italian = %v, want Italian first", got) } } // The allowance running out is the only failure where pressing the button again is // definitely not the answer, so it must not be flattened into "did not answer". func TestSubtitleFailureMessageSeparatesTheQuotaCase(t *testing.T) { if got := subtitleFailureMessage(nil); got != "No subtitles were found for this release." { t.Fatalf("no failures gave %q", got) } quota := subtitleFailureMessage([]error{&opensubtitles.QuotaError{}}) other := subtitleFailureMessage([]error{&opensubtitles.APIError{StatusCode: 500}}) if quota == other { t.Fatalf("an exhausted quota reads the same as any other failure: %q", quota) } }