Files
memby/server/internal/api/subtitle_providers_test.go
T

143 lines
6.2 KiB
Go
Raw Normal View History

2026-08-09 08:25:50 +12:00
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)
}
}