package api import ( "encoding/json" "strings" "testing" ) // The two lists Emby answers with can name the same file, and the television renders the // result into a keyed grid — which throws on a repeated key rather than merely looking // wrong. First-seen order is kept, so the trailer stays at the front where the join put it. func TestDedupeExtrasKeepsFirstOfEachID(t *testing.T) { items := []json.RawMessage{ json.RawMessage(`{"Id":"trailer-1","Name":"Official Trailer","Type":"Trailer"}`), json.RawMessage(`{"Id":"feature-1","Name":"Four Winters","Type":"Featurette"}`), json.RawMessage(`{"Id":"trailer-1","Name":"Official Trailer","Type":"Trailer"}`), json.RawMessage(`{"Id":"feature-2","Name":"Deleted: The Crossing","Type":"DeletedScene"}`), } got := dedupeExtras(items) if len(got) != 3 { t.Fatalf("expected 3 extras after deduplication, got %d", len(got)) } ids := extraIDs(t, got) want := []string{"trailer-1", "feature-1", "feature-2"} for index, id := range want { if ids[index] != id { t.Fatalf("extra %d was %q, want %q (order: %v)", index, ids[index], id, ids) } } } // An entry the television could not key is dropped rather than passed on. Nothing // downstream can tell one blank id from another. func TestDedupeExtrasDropsUnidentifiableEntries(t *testing.T) { items := []json.RawMessage{ json.RawMessage(`{"Name":"Nameless","Type":"Clip"}`), json.RawMessage(`{"Id":"","Name":"Blank","Type":"Clip"}`), json.RawMessage(`not json at all`), json.RawMessage(`{"Id":"clip-1","Name":"A Clip","Type":"Clip"}`), } got := dedupeExtras(items) if len(got) != 1 { t.Fatalf("expected only the identified extra to survive, got %d", len(got)) } if ids := extraIDs(t, got); ids[0] != "clip-1" { t.Fatalf("survivor was %q, want clip-1", ids[0]) } } func TestDedupeExtrasOnNothing(t *testing.T) { if got := dedupeExtras(nil); len(got) != 0 { t.Fatalf("expected no extras, got %d", len(got)) } } // The Details tab is the only reader of these, and every one of them is absent from Emby's // response unless it is asked for by name — so a field quietly dropped from the query is a // row that silently stops appearing rather than anything that fails. func TestDetailFieldsCarryTheDetailsTabVocabulary(t *testing.T) { fields := make(map[string]bool) for _, field := range strings.Split(fieldsDetail, ",") { fields[field] = true } for _, required := range []string{ "Studios", "Taglines", "PremiereDate", "OriginalTitle", "ProductionLocations", } { if !fields[required] { t.Errorf("detail responses must request %q for the Details tab", required) } } } func extraIDs(t *testing.T, items []json.RawMessage) []string { t.Helper() ids := make([]string, 0, len(items)) for _, raw := range items { var identified struct { ID string `json:"Id"` } if err := json.Unmarshal(raw, &identified); err != nil { t.Fatalf("could not read back an extra: %v", err) } ids = append(ids, identified.ID) } return ids }