diff --git a/pkg/service/handlers/handlers_marge.go b/pkg/service/handlers/handlers_marge.go
index e1c9faa..1ff6a1b 100644
--- a/pkg/service/handlers/handlers_marge.go
+++ b/pkg/service/handlers/handlers_marge.go
@@ -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)
}
diff --git a/pkg/service/handlers/handlers_marge_test.go b/pkg/service/handlers/handlers_marge_test.go
index 2faef2d..18784d8 100644
--- a/pkg/service/handlers/handlers_marge_test.go
+++ b/pkg/service/handlers/handlers_marge_test.go
@@ -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" {
diff --git a/pkg/service/handlers/mirror_middleware.go b/pkg/service/handlers/mirror_middleware.go
index 749093c..3aa0e95 100644
--- a/pkg/service/handlers/mirror_middleware.go
+++ b/pkg/service/handlers/mirror_middleware.go
@@ -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(),
},
}
diff --git a/pkg/service/handlers/mirror_test.go b/pkg/service/handlers/mirror_test.go
index 8ce5149..f179df0 100644
--- a/pkg/service/handlers/mirror_test.go
+++ b/pkg/service/handlers/mirror_test.go
@@ -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(""), 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: (from file), content-type: text/xml (default)
+ // upstream: , 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