Fix Content-Type and status code in Marge AddRecent handler

- Reorder header setting and WriteHeader calls in HandleMargeAddRecent to ensure Content-Type is correctly sent.
- Update HandleMargeAddRecent to explicitly use 201 Created status code.
- Improve parity mismatch logging to correctly capture headers from local handlers.
- Enhance mirroring logic to support local testing of upstream parity.
This commit is contained in:
Tobias Gesellchen
2026-02-26 21:08:14 +01:00
parent f4268f3111
commit 5d22a53c8b
4 changed files with 76 additions and 11 deletions
+1
View File
@@ -300,6 +300,7 @@ func (s *Server) HandleMargeAddRecent(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
w.WriteHeader(http.StatusCreated)
_, _ = w.Write(data)
}
+4 -4
View File
@@ -320,8 +320,8 @@ func TestMargeAddRecentRoute(t *testing.T) {
defer func() { _ = res.Body.Close() }()
if res.StatusCode != http.StatusOK {
t.Errorf("Expected status OK, got %v", res.Status)
if res.StatusCode != http.StatusCreated {
t.Errorf("Expected status Created, got %v", res.Status)
}
// Verify file was saved
@@ -389,9 +389,9 @@ func TestMargeNativeStreamingRoutes(t *testing.T) {
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
if res.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(res.Body)
t.Errorf("Expected status OK, got %v: %s", res.Status, string(body))
t.Errorf("Expected status Created, got %v: %s", res.Status, string(body))
}
if ct := res.Header.Get("Content-Type"); ct != "application/vnd.bose.streaming-v1.2+xml" {
+24 -6
View File
@@ -116,26 +116,42 @@ type parityResponseWriter struct {
}
func (p *parityResponseWriter) Header() http.Header {
return p.ResponseWriter.Header()
return p.recorder.Header()
}
func (p *parityResponseWriter) Write(b []byte) (int, error) {
if p.recorder.status == 0 {
p.WriteHeader(http.StatusOK)
}
p.recorder.body.Write(b)
return p.ResponseWriter.Write(b)
}
func (p *parityResponseWriter) WriteHeader(statusCode int) {
p.recorder.status = statusCode
// Copy headers to the real response writer before writing the header
for k, vv := range p.recorder.headers {
for _, v := range vv {
p.ResponseWriter.Header().Add(k, v)
}
}
p.ResponseWriter.WriteHeader(statusCode)
}
func (s *Server) performMirror(r *http.Request) *mirrorResponseRecorder {
host := r.Host
if host == "" || host == "localhost" || strings.HasPrefix(host, "127.0.0.1") {
if host == "" || host == "localhost" {
host = "streaming.bose.com"
}
scheme := "https"
if strings.HasPrefix(host, "127.0.0.1") || strings.HasPrefix(host, "localhost") {
scheme = "http"
}
targetURL := scheme + "://" + host
target, err := url.Parse(targetURL)
@@ -231,12 +247,14 @@ func (s *Server) saveParityMismatch(req *http.Request, local, upstream *mirrorRe
"path": req.URL.Path,
"reasons": reasons,
"local": map[string]interface{}{
"status": local.status,
"body": local.body.String(),
"status": local.status,
"headers": local.headers,
"body": local.body.String(),
},
"upstream": map[string]interface{}{
"status": upstream.status,
"body": upstream.body.String(),
"status": upstream.status,
"headers": upstream.headers,
"body": upstream.body.String(),
},
}
+47 -1
View File
@@ -1,6 +1,7 @@
package handlers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
@@ -57,7 +58,8 @@ func TestMirroring(t *testing.T) {
_ = os.WriteFile(filepath.Join(deviceDir, "Sources.xml"), []byte("<sources/>"), 0644)
t.Run("Mirrored Endpoint", func(t *testing.T) {
req, _ := http.NewRequest("GET", ts.URL+"/streaming/account/"+account+"/device/"+deviceID+"/recent", nil)
path := "/streaming/account/" + account + "/device/" + deviceID + "/recent"
req, _ := http.NewRequest("GET", ts.URL+path, nil)
// We set the host to our mock upstream so performMirror finds it
req.Host = strings.TrimPrefix(boseUpstream.URL, "http://")
@@ -91,6 +93,50 @@ func TestMirroring(t *testing.T) {
t.Errorf("Expected to find mirrored interaction in logs (category: mirror). Found: %v", files)
}
})
t.Run("Parity Mismatch Header Capture", func(t *testing.T) {
// The previous test already triggered a mismatch because the bodies and content-types differ
// local: <recents/> (from file), content-type: text/xml (default)
// upstream: <bose-response/>, content-type: application/vnd.bose.streaming-v1.2+xml
matchesMismatch, _ := filepath.Glob(filepath.Join(tempDir, "parity_mismatches", "*.json"))
if len(matchesMismatch) == 0 {
t.Fatal("Expected to find parity mismatch JSON file")
}
data, err := os.ReadFile(matchesMismatch[0])
if err != nil {
t.Fatalf("Failed to read mismatch file: %v", err)
}
var record struct {
Local struct {
Headers http.Header `json:"headers"`
} `json:"local"`
Upstream struct {
Headers http.Header `json:"headers"`
} `json:"upstream"`
}
if err := json.Unmarshal(data, &record); err != nil {
t.Fatalf("Failed to unmarshal mismatch record: %v", err)
}
if len(record.Local.Headers) == 0 {
t.Error("Expected local headers in parity mismatch, got none")
}
if len(record.Upstream.Headers) == 0 {
t.Error("Expected upstream headers in parity mismatch, got none")
}
// Check specifically for Content-Type
if ct := record.Local.Headers.Get("Content-Type"); ct == "" {
t.Error("Expected Content-Type in local headers")
}
if ct := record.Upstream.Headers.Get("Content-Type"); ct != "application/vnd.bose.streaming-v1.2+xml" {
t.Errorf("Expected Upstream Content-Type application/vnd.bose.streaming-v1.2+xml, got %s", ct)
}
})
}
// SetRecordEnabled is a helper for testing