* fix(jellycompat): parse repeated Fields query params
parseItemsQuery read the Fields parameter via q.Get("Fields"), which
returns only the first value when a client sends Fields as repeated
query params (Fields=A&Fields=B&...) instead of comma-separated in a
single param (Fields=A,B,C).
The jellyfin-sdk-kotlin (used by Wholphin) sends repeated params. When
such a request listed a detail-only field like MediaSources after other
fields — e.g. the episode-playlist request
/Shows/{id}/Episodes?Fields=PrimaryImageAspectRatio&...&Fields=MediaSources&...
silo saw only the first value (PrimaryImageAspectRatio), so
needsDetailFields stayed false, the request took the list path, and the
response came back without MediaSources. Clients then could not start
playback of the returned episodes ("no media sources").
Join all repeated Fields values before splitting on commas so field
order and delimiter style no longer matter. Comma-separated single-param
clients (e.g. VidHub) are unaffected.
* fix(jellycompat): stop advertising CanDownload and stub ThemeSongs
Wholphin (jellyfin-sdk-kotlin) audit surfaced two reachable gaps:
- mapping.go set CanDownload=true on every playable item while no
/Items/{id}/Download route exists, sending clients that honor the flag
(e.g. Wholphin's screensaver/slideshow) into 404s. Advertise false until
a download route exists.
- GET /Items/{id}/ThemeSongs 404'd, so enabling theme songs in Wholphin
silently failed on every detail page. Stub it with an empty
ThemeMediaResult. This cannot reuse the generic item stub: the SDK
models OwnerId as non-nullable, so the response must include it even
when empty.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(architecture): add Wholphin endpoint coverage audit
Cross-references every Jellyfin endpoint the Wholphin client can call
against the routes jellycompat serves, with gating evidence for each
missing-but-unreachable endpoint and prioritized recommendations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
53 lines
2.2 KiB
Go
53 lines
2.2 KiB
Go
package jellycompat
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// TestRepeatedFieldsParamTriggersDetail covers clients (e.g. the
|
|
// jellyfin-sdk-kotlin / Wholphin) that send Fields as repeated query params
|
|
// (Fields=A&Fields=B&...) rather than comma-separated in a single param.
|
|
// q.Get("Fields") returns only the first repeated value, which silently
|
|
// dropped every field after the first — so a request whose first Fields entry
|
|
// is a list-servable field but which also asks for a detail-only field (e.g.
|
|
// MediaSources) later never triggered the detail path and came back without
|
|
// MediaSources. parseItemsQuery must join all repeated values before splitting.
|
|
func TestRepeatedFieldsParamTriggersDetail(t *testing.T) {
|
|
url := "/Shows/abc/Episodes?Fields=PrimaryImageAspectRatio&Fields=SeasonUserData" +
|
|
"&Fields=ChildCount&Fields=Overview&Fields=Trickplay&Fields=SortName" +
|
|
"&Fields=Chapters&Fields=MediaSources&Fields=MediaSourceCount" +
|
|
"&Fields=ParentId&Fields=CanDelete&startItemId=x&limit=100"
|
|
req := httptest.NewRequest("GET", url, nil)
|
|
query := parseItemsQuery(req, NewResourceIDCodec())
|
|
|
|
if !query.needsDetailFields {
|
|
t.Fatal("repeated Fields params with a detail-only field not-first must set needsDetailFields=true")
|
|
}
|
|
if !query.requestedFields["mediasources"] {
|
|
t.Error("mediasources must be parsed from repeated Fields params")
|
|
}
|
|
if !query.requestedFields["chapters"] {
|
|
t.Error("chapters must be parsed from repeated Fields params")
|
|
}
|
|
if !query.fieldsExplicit {
|
|
t.Error("fieldsExplicit must be true when repeated Fields params are present")
|
|
}
|
|
}
|
|
|
|
// TestCommaSeparatedFieldsStillParsed guards the original single-param,
|
|
// comma-separated form (e.g. VidHub: Fields=A,B,C) which must keep working.
|
|
func TestCommaSeparatedFieldsStillParsed(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/Items?Fields=Overview,MediaSources,People", nil)
|
|
query := parseItemsQuery(req, NewResourceIDCodec())
|
|
|
|
if !query.needsDetailFields {
|
|
t.Fatal("comma-separated Fields with MediaSources must set needsDetailFields=true")
|
|
}
|
|
for _, f := range []string{"overview", "mediasources", "people"} {
|
|
if !query.requestedFields[f] {
|
|
t.Errorf("field %q must be parsed from comma-separated Fields", f)
|
|
}
|
|
}
|
|
}
|