Files
silo-server/internal/jellycompat/needs_detail_fields_test.go

139 lines
4.0 KiB
Go

package jellycompat
import "testing"
func TestRequestedFieldsNeedDetail_AllowsBrowseDerivableFields(t *testing.T) {
browseDerivable := []string{
"mediasourcecount",
"primaryimageaspectratio",
"genres",
"studios",
"taglines",
"providerids",
"basicsyncinfo",
"candelete",
"canresume",
"container",
"criticrating",
"displaypreferencesid",
"enddate",
"etag",
"itemcounts",
"originaltitle",
"overview",
"parentid",
"path",
"premieredate",
"prefix",
"productionlocations",
"productionyear",
"sortname",
"status",
"tags",
}
for _, f := range browseDerivable {
fields := map[string]bool{f: true}
if requestedFieldsNeedDetail(fields) {
t.Errorf("field %q must not trigger needsDetailFields", f)
}
}
}
func TestRequestedFieldsNeedDetail_RequiresDetailForRichFields(t *testing.T) {
requireDetail := []string{
"people",
"chapters",
"mediastreams", // requires file detail join we don't yet do
"mediasources", // pending LATERAL JOIN against media_files (plan §3.2 part b)
}
for _, f := range requireDetail {
fields := map[string]bool{f: true}
if !requestedFieldsNeedDetail(fields) {
t.Errorf("field %q should trigger needsDetailFields", f)
}
}
}
// Empty / nil field maps must never trigger detail.
func TestRequestedFieldsNeedDetail_EmptyDoesNotTriggerDetail(t *testing.T) {
if requestedFieldsNeedDetail(nil) {
t.Errorf("nil fields map must not trigger needsDetailFields")
}
if requestedFieldsNeedDetail(map[string]bool{}) {
t.Errorf("empty fields map must not trigger needsDetailFields")
}
}
// Unknown fields (not previously enumerated) must not trigger detail; the
// allowlist must be explicit on what requires detail, not the inverse.
func TestRequestedFieldsNeedDetail_UnknownFieldDoesNotTriggerDetail(t *testing.T) {
fields := map[string]bool{"someunknownjellyfinfield": true}
if requestedFieldsNeedDetail(fields) {
t.Errorf("unknown field must not trigger needsDetailFields; only fields in fieldsRequiringDetail should")
}
}
// Mixed input: a single detail-required field in a larger map must still
// trigger detail mode for the whole request.
func TestRequestedFieldsNeedDetail_MixedTriggersOnDetailField(t *testing.T) {
fields := map[string]bool{
"genres": true,
"studios": true,
"chapters": true, // forces detail
}
if !requestedFieldsNeedDetail(fields) {
t.Errorf("mixed fields containing chapters must trigger needsDetailFields")
}
}
// TestUnsatisfiedListFields_FlagsBrowseDroppedFields covers the diagnostic
// helper that surfaces silent client/server feature-gap drift. Fields that
// itemFromList does not populate AND that don't trigger a detail fetch are
// "unsatisfied" — the response will simply omit them.
func TestUnsatisfiedListFields_FlagsBrowseDroppedFields(t *testing.T) {
cases := []struct {
name string
fields map[string]bool
want []string
}{
{
name: "browse-served only",
fields: map[string]bool{"genres": true, "studios": true, "overview": true},
want: nil,
},
{
name: "detail-required short-circuits (no warning)",
fields: map[string]bool{"chapters": true, "remotetrailers": true, "externalurls": true},
want: nil, // detail path serves all fields
},
{
name: "list path with unserved fields",
fields: map[string]bool{"genres": true, "remotetrailers": true, "externalurls": true, "trickplay": true},
want: []string{"externalurls", "remotetrailers", "trickplay"},
},
{
name: "wildcard skipped",
fields: map[string]bool{"*": true, "remotetrailers": true},
want: []string{"remotetrailers"},
},
{
name: "empty input",
fields: nil,
want: nil,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := unsatisfiedListFields(tc.fields)
if len(got) != len(tc.want) {
t.Fatalf("unsatisfiedListFields(%v) = %v, want %v", tc.fields, got, tc.want)
}
for i := range got {
if got[i] != tc.want[i] {
t.Fatalf("unsatisfiedListFields(%v)[%d] = %q, want %q", tc.fields, i, got[i], tc.want[i])
}
}
})
}
}