Files

259 lines
9.1 KiB
Go
Raw Permalink Normal View History

package metadata
import (
"context"
"fmt"
"testing"
"github.com/Silo-Server/silo-server/internal/models"
)
func seedRelanguageItem(t *testing.T, h *testHarness, lockedFields ...MetadataField) {
t.Helper()
locked := make([]int, 0, len(lockedFields))
for _, field := range lockedFields {
locked = append(locked, int(field))
}
if err := h.itemRepo.Upsert(context.Background(), &models.MediaItem{
ContentID: "existing-1",
Type: "movie",
Title: "Gammel Kinesisk Titel",
Overview: "Old-language overview",
Year: 2020,
Status: "matched",
DefaultMetadataLanguage: "zh",
LockedFields: locked,
Studios: []string{},
Networks: []string{},
Countries: []string{},
Genres: []string{},
}); err != nil {
t.Fatalf("upsert existing item: %v", err)
}
}
func newRelanguageProvider() *capturingMetadataProvider {
return &capturingMetadataProvider{
response: &MetadataResult{
HasMetadata: true,
Title: "Ny Dansk Titel",
Overview: "Dansk oversigt",
ProviderIDs: map[string]string{"tmdb": "42"},
},
}
}
// TestProcess_AdoptLanguageRewritesBaseRowAndRestamps covers the core of
// issue #211: a refresh carrying AdoptLanguage must treat req.Language as the
// item's new canonical metadata language — replacing the base-row title and
// overview and restamping default_metadata_language — instead of pinning to
// the language stamped at first match.
func TestProcess_AdoptLanguageRewritesBaseRowAndRestamps(t *testing.T) {
h := newTestHarness()
ctx := context.Background()
seedRelanguageItem(t, h)
provider := newRelanguageProvider()
result, err := h.service.ProcessWithProviders(ctx, ProcessRequest{
ContentID: "existing-1",
Language: "da",
Mode: ModeManualRefresh,
AdoptLanguage: true,
}, []Provider{provider})
if err != nil {
t.Fatalf("ProcessWithProviders: %v", err)
}
if result == nil || !result.Updated {
t.Fatalf("result = %#v, want Updated=true", result)
}
item, err := h.itemRepo.GetByID(ctx, "existing-1")
if err != nil {
t.Fatalf("get item: %v", err)
}
if item.Title != "Ny Dansk Titel" {
t.Errorf("title = %q, want base row rewritten to %q", item.Title, "Ny Dansk Titel")
}
if item.Overview != "Dansk oversigt" {
t.Errorf("overview = %q, want base row rewritten", item.Overview)
}
if item.DefaultMetadataLanguage != "da" {
t.Errorf("default_metadata_language = %q, want restamped to da", item.DefaultMetadataLanguage)
}
}
// TestProcess_WithoutAdoptLanguageKeepsCanonicalPin pins the pre-existing
// behavior: a refresh in a non-canonical language without AdoptLanguage must
// NOT rewrite the base row or restamp the item (the fetch routes to the
// localization tables instead).
func TestProcess_WithoutAdoptLanguageKeepsCanonicalPin(t *testing.T) {
h := newTestHarness()
ctx := context.Background()
seedRelanguageItem(t, h)
provider := newRelanguageProvider()
result, err := h.service.ProcessWithProviders(ctx, ProcessRequest{
ContentID: "existing-1",
Language: "da",
Mode: ModeManualRefresh,
}, []Provider{provider})
if err != nil {
t.Fatalf("ProcessWithProviders: %v", err)
}
if result == nil || !result.Updated {
t.Fatalf("result = %#v, want Updated=true", result)
}
item, err := h.itemRepo.GetByID(ctx, "existing-1")
if err != nil {
t.Fatalf("get item: %v", err)
}
if item.Title != "Gammel Kinesisk Titel" {
t.Errorf("title = %q, want base row untouched", item.Title)
}
if item.DefaultMetadataLanguage != "zh" {
t.Errorf("default_metadata_language = %q, want zh (unchanged)", item.DefaultMetadataLanguage)
}
}
// TestAdoptableFolderLanguage covers the decision for when a folder-scoped
// refresh should adopt the folder's language as the item's new canonical
// language (issue #211). Only manual-refresh passes adopt: scheduled
// refreshes merge fill-empty, which would restamp the language without
// rewriting the text.
func TestAdoptableFolderLanguage(t *testing.T) {
cases := []struct {
name string
mode RefreshMode
stamp string
language string
want bool
}{
{"manual refresh with mismatch adopts", ModeManualRefresh, "zh", "da", true},
{"manual refresh case-insensitive match does not adopt", ModeManualRefresh, "DA", "da", false},
{"scheduled refresh never adopts", ModeScheduledRefresh, "zh", "da", false},
{"identify never adopts", ModeIdentify, "zh", "da", false},
{"initial match never adopts", ModeInitialMatch, "zh", "da", false},
{"unstamped item does not adopt", ModeManualRefresh, "", "da", false},
{"empty target language does not adopt", ModeManualRefresh, "zh", "", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := adoptableFolderLanguage(tc.mode, tc.stamp, tc.language); got != tc.want {
t.Fatalf("adoptableFolderLanguage(%v, %q, %q) = %v, want %v", tc.mode, tc.stamp, tc.language, got, tc.want)
}
})
}
}
// TestProcess_AdoptLanguageSkippedWhenBothLanguageFieldsLocked: when both
// language-bearing fields (title, overview) are locked, adoption must not
// restamp default_metadata_language — nothing would actually be rewritten,
// so the restamp would permanently mislabel the old-language text and the
// quick-refresh language-mismatch predicate would never flag the item again.
// The refresh behaves like a non-adopting refresh in that language instead.
func TestProcess_AdoptLanguageSkippedWhenBothLanguageFieldsLocked(t *testing.T) {
h := newTestHarness()
ctx := context.Background()
seedRelanguageItem(t, h, FieldName, FieldOverview)
result, err := h.service.ProcessWithProviders(ctx, ProcessRequest{
ContentID: "existing-1",
Language: "da",
Mode: ModeManualRefresh,
AdoptLanguage: true,
}, []Provider{newRelanguageProvider()})
if err != nil {
t.Fatalf("ProcessWithProviders: %v", err)
}
if result == nil || !result.Updated {
t.Fatalf("result = %#v, want Updated=true", result)
}
item, err := h.itemRepo.GetByID(ctx, "existing-1")
if err != nil {
t.Fatalf("get item: %v", err)
}
if item.Title != "Gammel Kinesisk Titel" {
t.Errorf("title = %q, want locked title untouched", item.Title)
}
if item.Overview != "Old-language overview" {
t.Errorf("overview = %q, want locked overview untouched", item.Overview)
}
if item.DefaultMetadataLanguage != "zh" {
t.Errorf("default_metadata_language = %q, want zh (stamp kept when both language fields are locked)",
item.DefaultMetadataLanguage)
}
}
// TestProcess_AdoptLanguageWithOnlyTitleLocked: a single locked language
// field does not block adoption — the unlocked field is rewritten in the new
// language and the stamp is adopted, while the locked field keeps its text.
func TestProcess_AdoptLanguageWithOnlyTitleLocked(t *testing.T) {
h := newTestHarness()
ctx := context.Background()
seedRelanguageItem(t, h, FieldName)
result, err := h.service.ProcessWithProviders(ctx, ProcessRequest{
ContentID: "existing-1",
Language: "da",
Mode: ModeManualRefresh,
AdoptLanguage: true,
}, []Provider{newRelanguageProvider()})
if err != nil {
t.Fatalf("ProcessWithProviders: %v", err)
}
if result == nil || !result.Updated {
t.Fatalf("result = %#v, want Updated=true", result)
}
item, err := h.itemRepo.GetByID(ctx, "existing-1")
if err != nil {
t.Fatalf("get item: %v", err)
}
if item.Title != "Gammel Kinesisk Titel" {
t.Errorf("title = %q, want locked title untouched", item.Title)
}
if item.Overview != "Dansk oversigt" {
t.Errorf("overview = %q, want base row rewritten", item.Overview)
}
if item.DefaultMetadataLanguage != "da" {
t.Errorf("default_metadata_language = %q, want restamped to da (only one field locked)",
item.DefaultMetadataLanguage)
}
}
// TestItemLibrariesAgreeOnLanguage covers the multi-library adoption gate: an
// item that lives in several libraries (media_item_libraries) must only adopt
// a language every containing library agrees on — otherwise libraries with
// different languages would rewrite the canonical base row back and forth on
// every refresh.
func TestItemLibrariesAgreeOnLanguage(t *testing.T) {
ctx := context.Background()
cases := []struct {
name string
languages []string
lookupErr error
target string
want bool
}{
{"single library matching target agrees", []string{"da"}, nil, "da", true},
{"case-insensitive match agrees", []string{"DA"}, nil, "da", true},
{"disagreeing libraries block adoption", []string{"da", "en"}, nil, "da", false},
{"single library with other language blocks adoption", []string{"de"}, nil, "da", false},
{"no membership rows cannot disagree", nil, nil, "da", true},
{"lookup failure blocks adoption", nil, fmt.Errorf("boom"), "da", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
h := newTestHarness()
h.libraryRepo.setMetadataLanguages("existing-1", tc.languages, tc.lookupErr)
if got := h.service.itemLibrariesAgreeOnLanguage(ctx, "existing-1", tc.target); got != tc.want {
t.Fatalf("itemLibrariesAgreeOnLanguage(%v, %q) = %v, want %v", tc.languages, tc.target, got, tc.want)
}
})
}
}