diff --git a/cmd/soundtouch-service/main.go b/cmd/soundtouch-service/main.go
index 75fab3e..8219652 100644
--- a/cmd/soundtouch-service/main.go
+++ b/cmd/soundtouch-service/main.go
@@ -1046,7 +1046,6 @@ func setupRouter(server *handlers.Server, stockholmHandler *stockholm.Handler) *
r.Post("/account/{account}/music/musicprovider/{sourceID}/token/cs", server.HandleBoseAccountToken)
r.Post("/device/{deviceID}/music/musicprovider/{sourceID}/token/cs1", server.HandleBoseToken)
r.Post("/device/{deviceID}/music/musicprovider/{sourceID}/token/cs3", server.HandleBoseToken)
- r.HandleFunc("/*", server.HandleBoseProxy)
})
r.Route("/v1", func(r chi.Router) {
@@ -1098,8 +1097,6 @@ func setupRouter(server *handlers.Server, stockholmHandler *stockholm.Handler) *
})
})
- r.Get("/proxy/*", server.HandleProxyRequest)
-
r.Route("/setup", func(r chi.Router) {
r.Get("/devices", server.HandleListDiscoveredDevices)
r.Post("/devices", server.HandleAddManualDevice)
diff --git a/cmd/soundtouch-service/testdata/router_routes.txt b/cmd/soundtouch-service/testdata/router_routes.txt
index 68baf17..3e499b3 100644
--- a/cmd/soundtouch-service/testdata/router_routes.txt
+++ b/cmd/soundtouch-service/testdata/router_routes.txt
@@ -1,12 +1,10 @@
CONNECT /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
CONNECT /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-CONNECT /oauth/* handlers.(*Server).HandleBoseProxy-fm
DELETE /accounts/{account}/devices/{device} handlers.(*Server).HandleMargeRemoveDevice-fm
DELETE /accounts/{account}/group/{groupId} handlers.(*Server).HandleMargeDeleteGroup-fm
DELETE /bmx/tunein/v1/favorite/{stationID} handlers.(*Server).HandleTuneInDeleteFavorite-fm
DELETE /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
DELETE /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-DELETE /oauth/* handlers.(*Server).HandleBoseProxy-fm
DELETE /setup/devices/{deviceId} handlers.(*Server).HandleRemoveDevice-fm
DELETE /setup/dns-discoveries handlers.(*Server).HandleClearDNSDiscoveries-fm
DELETE /setup/interactions/sessions handlers.(*Server).HandleCleanupSessions-fm
@@ -53,8 +51,6 @@ GET /mgmt/devices/{deviceId}/events handlers.(
GET /mgmt/spotify/accounts handlers.(*Server).HandleMgmtSpotifyAccounts-fm
GET /mgmt/spotify/callback handlers.(*Server).HandleMgmtSpotifyCallback-fm
GET /mgmt/spotify/token handlers.(*Server).HandleMgmtSpotifyToken-fm
-GET /oauth/* handlers.(*Server).HandleBoseProxy-fm
-GET /proxy/* handlers.(*Server).HandleProxyRequest-fm
GET /setup/account-id-suggestions/{deviceId} handlers.(*Server).HandleAccountIDSuggestions-fm
GET /setup/ca.crt handlers.(*Server).HandleGetCACert-fm
GET /setup/devices handlers.(*Server).HandleListDiscoveredDevices-fm
@@ -95,13 +91,10 @@ GET /v1/blacklist/{deviceId} setupRoute
GET /web/* setupRouter.(*Server).HandleWeb
HEAD /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
HEAD /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-HEAD /oauth/* handlers.(*Server).HandleBoseProxy-fm
OPTIONS /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
OPTIONS /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-OPTIONS /oauth/* handlers.(*Server).HandleBoseProxy-fm
PATCH /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
PATCH /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-PATCH /oauth/* handlers.(*Server).HandleBoseProxy-fm
POST /accounts/{account}/devices handlers.(*Server).HandleMargeAddDevice-fm
POST /accounts/{account}/devices/{device}/presets/{presetNumber} handlers.(*Server).HandleMargeUpdatePreset-fm
POST /accounts/{account}/devices/{device}/recents handlers.(*Server).HandleMargeAddRecent-fm
@@ -126,7 +119,6 @@ POST /mgmt/spotify/confirm handlers.(
POST /mgmt/spotify/entity handlers.(*Server).HandleMgmtSpotifyEntity-fm
POST /mgmt/spotify/init handlers.(*Server).HandleMgmtSpotifyInit-fm
POST /mgmt/spotify/prime handlers.(*Server).HandleMgmtPrimeDevice-fm
-POST /oauth/* handlers.(*Server).HandleBoseProxy-fm
POST /oauth/account/{account}/music/musicprovider/{sourceID}/token/cs handlers.(*Server).HandleBoseAccountToken-fm
POST /oauth/device/{deviceID}/music/musicprovider/{sourceID}/token handlers.(*Server).HandleBoseLegacyToken-fm
POST /oauth/device/{deviceID}/music/musicprovider/{sourceID}/token/cs1 handlers.(*Server).HandleBoseToken-fm
@@ -169,9 +161,7 @@ POST /v1/scmudc/{deviceId} handlers.(
POST /v1/stapp/{deviceId} handlers.(*Server).HandleAppEvents-fm
PUT /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
PUT /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-PUT /oauth/* handlers.(*Server).HandleBoseProxy-fm
PUT /streaming/account/{account}/device/{device} handlers.(*Server).HandleMargeUpdateDevice-fm
PUT /streaming/account/{account}/device/{device}/preset/{presetNumber} handlers.(*Server).HandleMargeUpdatePreset-fm
TRACE /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter handlers.(*Server).HandleSiriusXMLiveAdapter-fm
TRACE /core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/* handlers.(*Server).HandleSiriusXMLiveAdapterSubpath-fm
-TRACE /oauth/* handlers.(*Server).HandleBoseProxy-fm
diff --git a/pkg/service/datastore/datastore.go b/pkg/service/datastore/datastore.go
index ddb87a3..6c29f27 100644
--- a/pkg/service/datastore/datastore.go
+++ b/pkg/service/datastore/datastore.go
@@ -2102,12 +2102,6 @@ type Settings struct {
AmazonClientSecret string `json:"amazon_client_secret,omitempty"`
AmazonRedirectURI string `json:"amazon_redirect_uri,omitempty"`
- // AllowInsecureUpstreamTLS, when true, disables TLS certificate verification
- // for the upstream Bose-cloud proxy traffic. The default (false)
- // keeps verification on; opt in only when the upstream certificate chain is
- // broken (post end-of-service) and a temporary unblock is required.
- AllowInsecureUpstreamTLS bool `json:"allow_insecure_upstream_tls,omitempty"`
-
// TrustForwardedHeaders enables proxy-aware client IP resolution: when the
// immediate TCP peer is one of the TrustedProxyCIDRs, the X-Real-IP /
// X-Forwarded-For / True-Client-IP headers are honoured and replace
diff --git a/pkg/service/handlers/handlers_oauth.go b/pkg/service/handlers/handlers_oauth.go
index 71133ed..e6180dc 100644
--- a/pkg/service/handlers/handlers_oauth.go
+++ b/pkg/service/handlers/handlers_oauth.go
@@ -35,7 +35,8 @@ func (s *Server) HandleBoseToken(w http.ResponseWriter, r *http.Request) {
}
}
- s.HandleBoseProxy(w, r)
+ log.Printf("[OAuth] Unknown music provider: %s", sourceID)
+ http.Error(w, "Unknown music provider", http.StatusNotFound)
}
// HandleBoseLegacyToken handles the Bose-specific token refresh request (legacy or variant).
@@ -111,16 +112,16 @@ func (s *Server) HandleBoseAmazonToken(w http.ResponseWriter, r *http.Request) {
s.mu.RUnlock()
if svc == nil {
- log.Printf("[Amazon] Amazon service not configured, falling back to upstream")
- s.HandleBoseProxy(w, r)
+ log.Printf("[Amazon] Amazon service not configured, returning 503")
+ http.Error(w, "Amazon service not configured", http.StatusServiceUnavailable)
return
}
accounts := svc.GetAccounts()
if len(accounts) == 0 {
- log.Printf("[Amazon] No Amazon accounts linked, falling back to upstream")
- s.HandleBoseProxy(w, r)
+ log.Printf("[Amazon] No Amazon accounts linked, returning 503")
+ http.Error(w, "No linked Amazon accounts", http.StatusServiceUnavailable)
return
}
@@ -157,8 +158,8 @@ func (s *Server) HandleBoseAmazonToken(w http.ResponseWriter, r *http.Request) {
if account != nil {
if err := svc.RefreshAccessToken(account); err != nil {
- log.Printf("[Amazon] Failed to refresh token for %s: %v. Falling back to upstream", account.UserID, err)
- s.HandleBoseProxy(w, r)
+ log.Printf("[Amazon] Failed to refresh token for %s: %v. Returning 502", account.UserID, err)
+ http.Error(w, "Token refresh failed", http.StatusBadGateway)
return
}
@@ -169,8 +170,8 @@ func (s *Server) HandleBoseAmazonToken(w http.ResponseWriter, r *http.Request) {
accessToken, userID, err = svc.GetFreshToken()
if err != nil {
- log.Printf("[Amazon] Failed to get fresh token: %v. Falling back to upstream", err)
- s.HandleBoseProxy(w, r)
+ log.Printf("[Amazon] Failed to get fresh token: %v. Returning 502", err)
+ http.Error(w, "Failed to get fresh token", http.StatusBadGateway)
return
}
@@ -206,16 +207,16 @@ func (s *Server) HandleBoseSpotifyToken(w http.ResponseWriter, r *http.Request)
s.mu.RUnlock()
if svc == nil {
- log.Printf("[Spotify Proxy] Spotify service not configured, falling back to upstream")
- s.HandleBoseProxy(w, r)
+ log.Printf("[Spotify Proxy] Spotify service not configured, returning 503")
+ http.Error(w, "Spotify service not configured", http.StatusServiceUnavailable)
return
}
accounts := svc.GetAccounts()
if len(accounts) == 0 {
- log.Printf("[Spotify Proxy] No Spotify accounts linked, falling back to upstream")
- s.HandleBoseProxy(w, r)
+ log.Printf("[Spotify Proxy] No Spotify accounts linked, returning 503")
+ http.Error(w, "No linked Spotify accounts", http.StatusServiceUnavailable)
return
}
@@ -256,8 +257,8 @@ func (s *Server) HandleBoseSpotifyToken(w http.ResponseWriter, r *http.Request)
if account != nil {
if err := svc.RefreshAccessToken(account); err != nil {
- log.Printf("[Spotify Proxy] Failed to refresh token for %s: %v. Falling back to upstream", account.UserID, err)
- s.HandleBoseProxy(w, r)
+ log.Printf("[Spotify Proxy] Failed to refresh token for %s: %v. Returning 502", account.UserID, err)
+ http.Error(w, "Token refresh failed", http.StatusBadGateway)
return
}
@@ -269,8 +270,8 @@ func (s *Server) HandleBoseSpotifyToken(w http.ResponseWriter, r *http.Request)
accessToken, userID, err = svc.GetFreshToken()
if err != nil {
- log.Printf("[Spotify Proxy] Failed to get fresh token: %v. Falling back to upstream", err)
- s.HandleBoseProxy(w, r)
+ log.Printf("[Spotify Proxy] Failed to get fresh token: %v. Returning 502", err)
+ http.Error(w, "Failed to get fresh token", http.StatusBadGateway)
return
}
diff --git a/pkg/service/handlers/handlers_oauth_test.go b/pkg/service/handlers/handlers_oauth_test.go
index a5bcd7c..77ec4fe 100644
--- a/pkg/service/handlers/handlers_oauth_test.go
+++ b/pkg/service/handlers/handlers_oauth_test.go
@@ -84,33 +84,17 @@ func TestHandleBoseSpotifyToken_FallbackToProxy(t *testing.T) {
ds := datastore.NewDataStore(tmpDir)
server := NewServer(ds, nil, "http://localhost", false, false, false)
- // chi.URLParam works when using chi router
r := chi.NewRouter()
r.Post("/oauth/device/{deviceID}/music/musicprovider/{sourceID}/token/cs3", server.HandleBoseToken)
- // Since there's no Spotify service, it should fall back to HandleBoseProxy.
- // HandleBoseProxy will try to contact streaming.bose.com.
- // We can check if it returns a 502 or 404 (since we are not actually proxying to real Bose).
-
+ // Without a configured Spotify service, the handler returns 503.
req := httptest.NewRequest("POST", "/oauth/device/DEVICE123/music/musicprovider/15/token/cs3", nil)
- req.Host = "localhost" // use localhost to avoid real network call
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
- // If it fell back to proxy, it should NOT have X-Proxy-Origin: self
- if w.Header().Get("X-Proxy-Origin") == "self" {
- t.Errorf("Expected fallback to proxy, but got X-Proxy-Origin: self")
- }
-
- // HandleBoseProxy sets X-Proxy-Origin: upstream
- if w.Header().Get("X-Proxy-Origin") != "upstream" {
- // It might fail before setting the header if the target host is invalid,
- // but our HandleBoseProxy sets it in ModifyResponse.
- // If it fails to connect, it might return 502 without the header.
- if w.Code != http.StatusBadGateway && w.Code != http.StatusNotFound {
- t.Errorf("Expected fallback to proxy (upstream), got status %d and origin %s", w.Code, w.Header().Get("X-Proxy-Origin"))
- }
+ if w.Code != http.StatusServiceUnavailable {
+ t.Errorf("expected 503 without Spotify service, got %d", w.Code)
}
}
@@ -260,14 +244,14 @@ func TestHandleBoseAmazonToken_FallbackToProxy(t *testing.T) {
r := chi.NewRouter()
r.Post("/oauth/device/{deviceID}/music/musicprovider/{sourceID}/token/cs1", server.HandleBoseToken)
+ // Without a configured Amazon service, the handler returns 503.
req := httptest.NewRequest("POST", "/oauth/device/DEVICE123/music/musicprovider/20/token/cs1", nil)
- req.Host = "localhost"
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
- if w.Header().Get("X-Proxy-Origin") == "self" {
- t.Error("Expected fallback to proxy, but got X-Proxy-Origin: self")
+ if w.Code != http.StatusServiceUnavailable {
+ t.Errorf("expected 503 without Amazon service, got %d", w.Code)
}
}
diff --git a/pkg/service/handlers/handlers_proxy.go b/pkg/service/handlers/handlers_proxy.go
index ce03d6f..0ff00ba 100644
--- a/pkg/service/handlers/handlers_proxy.go
+++ b/pkg/service/handlers/handlers_proxy.go
@@ -2,114 +2,11 @@ package handlers
import (
"bytes"
- "crypto/tls"
"io"
"log"
"net/http"
- "net/http/httputil"
- "net/url"
- "strings"
-
- "github.com/gesellix/bose-soundtouch/pkg/service/proxy"
)
-// HandleProxyRequest handles requests to the logging proxy.
-func (s *Server) HandleProxyRequest(w http.ResponseWriter, r *http.Request) {
- if r.Header.Get("X-Bose-Proxy-Hop") != "" {
- log.Printf("[PROXY_LOOP] Loop detected for %s %s, breaking loop", r.Method, r.URL.Path)
- http.Error(w, "Loop detected", http.StatusNotFound)
-
- return
- }
-
- targetURLStr := strings.TrimPrefix(r.URL.Path, "/proxy/")
- if targetURLStr == "" {
- http.Error(w, "Target URL is required", http.StatusBadRequest)
- return
- }
-
- // Reconstruct original URL (it might have lost its double slashes in the path)
- if !strings.HasPrefix(targetURLStr, "http://") && !strings.HasPrefix(targetURLStr, "https://") {
- // Try to fix it if it looks like http:/...
- if strings.HasPrefix(targetURLStr, "http:/") {
- targetURLStr = "http://" + strings.TrimPrefix(targetURLStr, "http:/")
- } else if strings.HasPrefix(targetURLStr, "https:/") {
- targetURLStr = "https://" + strings.TrimPrefix(targetURLStr, "https:/")
- }
- }
-
- target, err := url.Parse(targetURLStr)
- if err != nil {
- http.Error(w, "Invalid target URL: "+err.Error(), http.StatusBadRequest)
- return
- }
-
- s.ServeProxy(target)(w, r)
-}
-
-// ServeProxy returns a handler that proxies to the given target.
-func (s *Server) ServeProxy(target *url.URL) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- lp := proxy.NewLoggingProxy(target.String(), s.proxyRedact)
- lp.LogBody = s.proxyLogBody
- lp.RecordEnabled = s.recordEnabled
- lp.SetRecorder(s.recorder)
-
- // Capture request body for recording, as it will be consumed by the proxy
- var reqBody []byte
- if r.Body != nil {
- reqBody, _ = io.ReadAll(r.Body)
- r.Body = io.NopCloser(bytes.NewBuffer(reqBody))
- }
-
- // AllowInsecureUpstreamTLS is opt-in via settings.json — defaults to
- // false so the upstream certificate chain is verified normally. The
- // opt-in exists for deployments stuck behind a broken Bose-cloud
- // chain post end-of-service.
- settings, _ := s.ds.GetSettings()
- insecure := settings.AllowInsecureUpstreamTLS
-
- rp := &httputil.ReverseProxy{
- Rewrite: func(pr *httputil.ProxyRequest) {
- pr.SetURL(target)
- pr.Out.Host = target.Host
- // If target has a path, we should probably append or replace.
- // For Bose upstream, it's usually just the domain.
- if target.Path != "" && target.Path != "/" {
- pr.Out.URL.Path = target.Path
- }
-
- pr.Out.Header.Set("X-Bose-Proxy-Hop", "1")
-
- lp.LogRequest(pr.Out)
- },
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
- },
- }
-
- rp.ModifyResponse = func(res *http.Response) error {
- res.Header.Set("X-Proxy-Origin", "upstream")
- // Generic Header Preservation
- if etags, ok := res.Header["Etag"]; ok {
- delete(res.Header, "Etag")
- res.Header["ETag"] = etags
- }
-
- // Restore captured request body for the recorder
- if reqBody != nil {
- res.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
- }
-
- lp.LogResponse(res)
-
- return nil
- }
-
- rp.ServeHTTP(w, r)
- }
-}
-
// HandleNotFound handles requests that don't match any route.
// It always logs [UNHANDLED] so unimplemented endpoints are visible in plain output.
// When proxyLogBody is enabled it also logs the request body (truncated to 512 bytes).
@@ -131,38 +28,5 @@ func (s *Server) HandleNotFound(w http.ResponseWriter, r *http.Request) {
log.Printf("[UNHANDLED] %s %s", r.Method, r.URL.Path)
}
- s.HandleBoseProxy(w, r)
-}
-
-// HandleBoseProxy proxies the request to the Bose upstream.
-func (s *Server) HandleBoseProxy(w http.ResponseWriter, r *http.Request) {
- if r.Header.Get("X-Bose-Proxy-Hop") != "" {
- log.Printf("[PROXY_LOOP] Loop detected for %s %s, breaking loop", r.Method, r.URL.Path)
- http.Error(w, "Loop detected", http.StatusNotFound)
-
- return
- }
-
- host := r.Host
- if host == "" {
- host = "streaming.bose.com"
- }
-
- // Default to HTTPS for Bose services
- scheme := "https"
- if strings.HasPrefix(host, "localhost") || strings.HasPrefix(host, "127.0.0.1") || strings.HasPrefix(host, "::1") {
- scheme = "http"
- }
-
- targetURL := scheme + "://" + host
-
- target, err := url.Parse(targetURL)
- if err != nil {
- log.Printf("[PROXY_ERR] Failed to parse target URL %s: %v", targetURL, err)
- http.Error(w, "Invalid upstream host", http.StatusBadGateway)
-
- return
- }
-
- s.ServeProxy(target)(w, r)
+ http.NotFound(w, r)
}
diff --git a/pkg/service/handlers/handlers_proxy_test.go b/pkg/service/handlers/handlers_proxy_test.go
index 00f94ac..8d1015b 100644
--- a/pkg/service/handlers/handlers_proxy_test.go
+++ b/pkg/service/handlers/handlers_proxy_test.go
@@ -2,28 +2,17 @@ package handlers
import (
"bytes"
- "io"
"log"
"net/http"
"net/http/httptest"
"os"
- "path/filepath"
"strings"
"testing"
"github.com/gesellix/bose-soundtouch/pkg/service/datastore"
- "github.com/gesellix/bose-soundtouch/pkg/service/proxy"
)
func TestHandleNotFound_UnhandledLogging(t *testing.T) {
- // backend absorbs proxied requests so the test doesn't hit the real Bose upstream
- backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
- w.WriteHeader(http.StatusOK)
- }))
- defer backend.Close()
-
- backendHost := strings.TrimPrefix(backend.URL, "http://")
-
captureLog := func(fn func()) string {
var buf bytes.Buffer
log.SetOutput(&buf)
@@ -37,7 +26,6 @@ func TestHandleNotFound_UnhandledLogging(t *testing.T) {
server := NewServer(ds, nil, "http://localhost", false, false, false)
req := httptest.NewRequest("GET", "/some/unknown/path", nil)
- req.Host = backendHost
logged := captureLog(func() {
server.HandleNotFound(httptest.NewRecorder(), req)
@@ -56,7 +44,6 @@ func TestHandleNotFound_UnhandledLogging(t *testing.T) {
server := NewServer(ds, nil, "http://localhost", false, true, false)
req := httptest.NewRequest("POST", "/marge/unknown", bytes.NewBufferString(""))
- req.Host = backendHost
logged := captureLog(func() {
server.HandleNotFound(httptest.NewRecorder(), req)
@@ -72,7 +59,6 @@ func TestHandleNotFound_UnhandledLogging(t *testing.T) {
server := NewServer(ds, nil, "http://localhost", false, false, false)
req := httptest.NewRequest("POST", "/marge/unknown", bytes.NewBufferString(""))
- req.Host = backendHost
logged := captureLog(func() {
server.HandleNotFound(httptest.NewRecorder(), req)
@@ -86,108 +72,17 @@ func TestHandleNotFound_UnhandledLogging(t *testing.T) {
}
})
- t.Run("body is still forwarded to proxy after being read for logging", func(t *testing.T) {
- var receivedBody string
- forwardCheck := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- b, _ := io.ReadAll(r.Body)
- receivedBody = string(b)
- w.WriteHeader(http.StatusOK)
- }))
- defer forwardCheck.Close()
-
+ t.Run("returns 404 for unmatched routes", func(t *testing.T) {
ds := datastore.NewDataStore(t.TempDir())
- server := NewServer(ds, nil, "http://localhost", false, true, false)
+ server := NewServer(ds, nil, "http://localhost", false, false, false)
- req := httptest.NewRequest("POST", "/marge/unknown", bytes.NewBufferString(""))
- req.Host = strings.TrimPrefix(forwardCheck.URL, "http://")
+ req := httptest.NewRequest("GET", "/some/unknown/path", nil)
+ w := httptest.NewRecorder()
- captureLog(func() {
- server.HandleNotFound(httptest.NewRecorder(), req)
- })
+ server.HandleNotFound(w, req)
- if receivedBody != "" {
- t.Errorf("expected body forwarded to proxy, got: %q", receivedBody)
+ if w.Code != http.StatusNotFound {
+ t.Errorf("expected 404 for unmatched route, got %d", w.Code)
}
})
}
-
-func TestHandleProxyRequest_RequestBodyRecording(t *testing.T) {
- t.Setenv("RECORDER_ASYNC", "false")
- tmpDir, err := os.MkdirTemp("", "proxy-request-body-test")
- if err != nil {
- t.Fatalf("failed to create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
-
- // Start a backend server to receive the proxied request
- backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Read the body to ensure it's consumed
- _, _ = io.ReadAll(r.Body)
- w.Header().Set("Content-Type", "application/xml")
- w.WriteHeader(http.StatusOK)
- w.Write([]byte("ok"))
- }))
- defer backend.Close()
-
- ds := datastore.NewDataStore(filepath.Join(tmpDir, "test.db"))
- server := NewServer(ds, nil, "http://localhost", false, false, false)
- server.recordEnabled = true
- server.proxyLogBody = true
- recorder := proxy.NewRecorder(tmpDir)
- server.SetRecorder(recorder)
-
- // Create a proxy request to the backend
- requestBody := "data"
- targetURL := backend.URL
- proxyPath := "/proxy/" + targetURL
- req := httptest.NewRequest("POST", proxyPath, bytes.NewBufferString(requestBody))
- req.Header.Set("Content-Type", "application/xml")
-
- w := httptest.NewRecorder()
- server.HandleProxyRequest(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- // Verify that the interaction was recorded and contains the request body
- sessionID := recorder.SessionID
-
- // The recorder uses sanitized segments for the directory.
- // Since the target URL is http://127.0.0.1:PORT, the path is empty,
- // so it should be in the "root" directory under the category.
-
- // We'll search recursively to be sure
- foundBody := false
- err = filepath.Walk(filepath.Join(tmpDir, "interactions", sessionID), func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if !info.IsDir() && strings.HasSuffix(path, ".http") {
- content, err := os.ReadFile(path)
- if err != nil {
- return err
- }
- if strings.Contains(string(content), requestBody) {
- foundBody = true
- }
- }
- return nil
- })
-
- if err != nil {
- t.Fatalf("failed to walk interactions dir: %v", err)
- }
-
- if !foundBody {
- t.Errorf("request body %q not found in any recorded interaction file", requestBody)
- // List all files found for debugging
- _ = filepath.Walk(filepath.Join(tmpDir, "interactions", sessionID), func(path string, info os.FileInfo, err error) error {
- if !info.IsDir() {
- content, _ := os.ReadFile(path)
- t.Logf("Found file %s with content:\n%s", path, string(content))
- }
- return nil
- })
- }
-}
diff --git a/pkg/service/handlers/loop_prevention_test.go b/pkg/service/handlers/loop_prevention_test.go
deleted file mode 100644
index c92be62..0000000
--- a/pkg/service/handlers/loop_prevention_test.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package handlers
-
-import (
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/gesellix/bose-soundtouch/pkg/service/datastore"
-)
-
-func TestHandleBoseProxy_LoopPrevention(t *testing.T) {
- tmpDir, err := os.MkdirTemp("", "proxy-loop-test")
- if err != nil {
- t.Fatalf("failed to create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
-
- ds := datastore.NewDataStore(filepath.Join(tmpDir, "test.db"))
- server := NewServer(ds, nil, "http://localhost", false, false, false)
-
- t.Run("first hop should be allowed", func(t *testing.T) {
- req := httptest.NewRequest("GET", "/unknown-endpoint", nil)
- req.Host = "localhost"
- w := httptest.NewRecorder()
- server.HandleBoseProxy(w, req)
- if w.Code == http.StatusNotFound {
- t.Errorf("Expected first hop to be allowed (even if it fails later), but got 404")
- }
- })
-
- t.Run("second hop should be blocked", func(t *testing.T) {
- req := httptest.NewRequest("GET", "/unknown-endpoint", nil)
- req.Host = "localhost"
- req.Header.Set("X-Bose-Proxy-Hop", "1")
- w := httptest.NewRecorder()
- server.HandleBoseProxy(w, req)
- if w.Code != http.StatusNotFound {
- t.Errorf("Expected second hop to be blocked with 404, but got %d", w.Code)
- }
- })
-
- t.Run("HandleProxyRequest loop detection", func(t *testing.T) {
- req := httptest.NewRequest("GET", "/proxy/http://example.com", nil)
- req.Header.Set("X-Bose-Proxy-Hop", "1")
- w := httptest.NewRecorder()
- server.HandleProxyRequest(w, req)
- if w.Code != http.StatusNotFound {
- t.Errorf("Expected HandleProxyRequest loop to be blocked with 404, but got %d", w.Code)
- }
- })
-}
diff --git a/pkg/service/handlers/server.go b/pkg/service/handlers/server.go
index a42190f..be9a3ed 100644
--- a/pkg/service/handlers/server.go
+++ b/pkg/service/handlers/server.go
@@ -47,7 +47,6 @@ type Server struct {
shortcuts map[string]int
recorder *proxy.Recorder
dnsDiscovery *discovery.DNSDiscovery
- UpstreamProxy http.Handler
Version string
Commit string
Date string