From 1885d197d64dd43f905a19a3ced73db9a707af74 Mon Sep 17 00:00:00 2001 From: bvandeusen Date: Sun, 19 Apr 2026 17:39:03 +0000 Subject: [PATCH] test(subsonic): envelope JSON/XML/JSONP + WriteFail --- internal/subsonic/envelope_test.go | 101 +++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 internal/subsonic/envelope_test.go diff --git a/internal/subsonic/envelope_test.go b/internal/subsonic/envelope_test.go new file mode 100644 index 00000000..89883834 --- /dev/null +++ b/internal/subsonic/envelope_test.go @@ -0,0 +1,101 @@ +package subsonic + +import ( + "encoding/json" + "encoding/xml" + "io" + "net/http/httptest" + "strings" + "testing" +) + +func TestWriteJSONEnvelope(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/rest/ping.view", nil) + Write(rec, req, PingResponse{Envelope: NewEnvelope("ok")}) + + if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { + t.Errorf("content-type = %q", ct) + } + var payload map[string]map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { + t.Fatalf("unmarshal: %v — body=%s", err, rec.Body) + } + inner, ok := payload["subsonic-response"] + if !ok { + t.Fatalf("missing subsonic-response key: %s", rec.Body) + } + if inner["status"] != "ok" || inner["version"] != APIVersion || inner["type"] != ServerType { + t.Errorf("envelope fields = %+v", inner) + } + if inner["openSubsonic"] != true { + t.Errorf("openSubsonic flag missing: %+v", inner) + } +} + +func TestWriteXMLEnvelope(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/rest/getLicense.view?f=xml", nil) + Write(rec, req, LicenseResponse{ + Envelope: NewEnvelope("ok"), + License: License{Valid: true}, + }) + + if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/xml") { + t.Errorf("content-type = %q", ct) + } + body, _ := io.ReadAll(rec.Body) + var decoded struct { + XMLName xml.Name `xml:"subsonic-response"` + Status string `xml:"status,attr"` + Version string `xml:"version,attr"` + License struct { + Valid bool `xml:"valid,attr"` + } `xml:"license"` + } + if err := xml.Unmarshal(body, &decoded); err != nil { + t.Fatalf("xml decode: %v — body=%s", err, body) + } + if decoded.Status != "ok" || decoded.Version != APIVersion { + t.Errorf("envelope attrs = %+v", decoded) + } + if !decoded.License.Valid { + t.Errorf("license.valid attr not parsed: %+v", decoded) + } +} + +func TestWriteFailEnvelopeJSON(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/rest/ping.view", nil) + WriteFail(rec, req, ErrWrongCredentials, "nope") + + var payload map[string]map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { + t.Fatalf("unmarshal: %v", err) + } + inner := payload["subsonic-response"] + if inner["status"] != "failed" { + t.Errorf("status = %v, want failed", inner["status"]) + } + errMap, ok := inner["error"].(map[string]any) + if !ok { + t.Fatalf("error field missing/not object: %+v", inner) + } + if int(errMap["code"].(float64)) != ErrWrongCredentials { + t.Errorf("error.code = %v, want %d", errMap["code"], ErrWrongCredentials) + } + if errMap["message"] != "nope" { + t.Errorf("error.message = %v", errMap["message"]) + } +} + +func TestWriteJSONPWrapsCallback(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/rest/ping.view?f=jsonp&callback=cb", nil) + Write(rec, req, PingResponse{Envelope: NewEnvelope("ok")}) + + body := rec.Body.String() + if !strings.HasPrefix(body, "cb(") || !strings.HasSuffix(strings.TrimSpace(body), ")") { + t.Errorf("jsonp wrapper missing: %q", body) + } +}