156 lines
4.7 KiB
Go
156 lines
4.7 KiB
Go
package recognize
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.vakhrushev.me/av/jellybit/internal/metadata"
|
|
)
|
|
|
|
type fakeProvider struct {
|
|
name string
|
|
candidates []metadata.Candidate
|
|
counts map[int]int
|
|
searchErr error
|
|
searched int
|
|
}
|
|
|
|
func (f *fakeProvider) Name() string {
|
|
if f.name == "" {
|
|
return "tmdb"
|
|
}
|
|
return f.name
|
|
}
|
|
func (f *fakeProvider) Search(_ context.Context, _ metadata.Query) ([]metadata.Candidate, error) {
|
|
f.searched++
|
|
return f.candidates, f.searchErr
|
|
}
|
|
func (f *fakeProvider) SeasonEpisodeCounts(_ context.Context, _ string) (map[int]int, error) {
|
|
return f.counts, nil
|
|
}
|
|
|
|
func recognizerWith(p metadata.Provider) *Recognizer {
|
|
var providers []metadata.Provider
|
|
if p != nil {
|
|
providers = []metadata.Provider{p}
|
|
}
|
|
return New(&fakeLLM{}, providers, Config{}, testLogger())
|
|
}
|
|
|
|
func TestMatchMetadata_SingleStrong(t *testing.T) {
|
|
p := &fakeProvider{candidates: []metadata.Candidate{
|
|
{Provider: "tmdb", ID: "603", Title: "The Matrix", Year: 1999},
|
|
{Provider: "tmdb", ID: "604", Title: "The Matrix Reloaded", Year: 2003},
|
|
}}
|
|
r := recognizerWith(p)
|
|
m := r.matchMetadata(context.Background(),
|
|
Plan{Type: MediaMovie, Title: "The Matrix", Year: 1999})
|
|
if m == nil {
|
|
t.Fatal("expected match")
|
|
}
|
|
if m.ProviderID != "603" || m.Provider != "tmdb" {
|
|
t.Errorf("match = %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestMatchMetadata_AmbiguousNoMatch(t *testing.T) {
|
|
// Два кандидата с тем же названием и годом — неоднозначно.
|
|
p := &fakeProvider{candidates: []metadata.Candidate{
|
|
{ID: "1", Title: "Fargo", Year: 2014},
|
|
{ID: "2", Title: "Fargo", Year: 2014},
|
|
}}
|
|
r := recognizerWith(p)
|
|
if m := r.matchMetadata(context.Background(),
|
|
Plan{Type: MediaSeries, Title: "Fargo", Year: 2014}); m != nil {
|
|
t.Errorf("ambiguous must not match, got %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestMatchMetadata_YearMismatch(t *testing.T) {
|
|
p := &fakeProvider{candidates: []metadata.Candidate{{ID: "1", Title: "X", Year: 1990}}}
|
|
r := recognizerWith(p)
|
|
if m := r.matchMetadata(context.Background(),
|
|
Plan{Type: MediaMovie, Title: "X", Year: 2020}); m != nil {
|
|
t.Errorf("year mismatch must not match, got %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestMatchMetadata_OriginalTitle(t *testing.T) {
|
|
p := &fakeProvider{candidates: []metadata.Candidate{
|
|
{ID: "1", Title: "Leon", OriginalTitle: "Léon", Year: 1994},
|
|
}}
|
|
r := recognizerWith(p)
|
|
m := r.matchMetadata(context.Background(),
|
|
Plan{Type: MediaMovie, Title: "Léon", Year: 1994})
|
|
if m == nil || m.ProviderID != "1" {
|
|
t.Errorf("should match by original title, got %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestMatchMetadata_SeriesFetchesCounts(t *testing.T) {
|
|
p := &fakeProvider{
|
|
candidates: []metadata.Candidate{{ID: "60622", Title: "Fargo", Year: 2014}},
|
|
counts: map[int]int{1: 10, 2: 10},
|
|
}
|
|
r := recognizerWith(p)
|
|
m := r.matchMetadata(context.Background(),
|
|
Plan{Type: MediaSeries, Title: "Fargo", Year: 2014})
|
|
if m == nil || m.SeasonEpisodeCounts[1] != 10 {
|
|
t.Errorf("counts not fetched: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestMatchMetadata_ProviderErrorNoMatch(t *testing.T) {
|
|
p := &fakeProvider{searchErr: errors.New("upstream down")}
|
|
r := recognizerWith(p)
|
|
if m := r.matchMetadata(context.Background(),
|
|
Plan{Type: MediaMovie, Title: "X", Year: 2000}); m != nil {
|
|
t.Errorf("provider error must yield no match, got %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestMatchMetadata_Disabled(t *testing.T) {
|
|
r := recognizerWith(nil)
|
|
if m := r.matchMetadata(context.Background(), Plan{Type: MediaMovie, Title: "X"}); m != nil {
|
|
t.Errorf("no providers → no match, got %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestNormalize(t *testing.T) {
|
|
cases := map[string]string{
|
|
"The Matrix": "the matrix",
|
|
"Léon: The Pro!": "léon the pro",
|
|
" A B ": "a b",
|
|
"Привет, Мир": "привет мир",
|
|
}
|
|
for in, want := range cases {
|
|
if got := normalize(in); got != want {
|
|
t.Errorf("normalize(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Сквозной авто: LLM-план + матч в базе + чистая валидация → Decision.Auto.
|
|
func TestRecognize_AutoWithMatch(t *testing.T) {
|
|
in := Input{Name: "The.Matrix.1999", Files: []File{{Path: "m/film.mkv", Size: 1}}}
|
|
resp := `{"type":"movie","title":"The Matrix","year":1999,"confidence":0.95,
|
|
"provider_hint":"The Matrix","files":[{"src":"m/film.mkv","role":"main"}]}`
|
|
llmFake := &fakeLLM{responses: []string{resp}}
|
|
p := &fakeProvider{candidates: []metadata.Candidate{
|
|
{Provider: "tmdb", ID: "603", Title: "The Matrix", Year: 1999},
|
|
}}
|
|
r := New(llmFake, []metadata.Provider{p}, Config{}, testLogger())
|
|
|
|
res, err := r.Recognize(context.Background(), in)
|
|
if err != nil {
|
|
t.Fatalf("Recognize: %v", err)
|
|
}
|
|
if !res.Decision.Auto {
|
|
t.Errorf("expected auto, reasons: %v", res.Decision.Reasons)
|
|
}
|
|
if res.Match == nil || res.Match.ProviderID != "603" {
|
|
t.Errorf("match = %+v", res.Match)
|
|
}
|
|
}
|