mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
refactor(tunein): route HandleTuneInNavigate through stations.Navigate
handlers_bmx_tunein.go's parseTuneInNavigatePath and stations.go's navigateTuneIn were byte-for-byte identical (confirmed both already independently implemented the same "profiles" parsing before PR #677; that fix was applied consistently to both copies rather than introducing new duplication). Removed the duplicate: HandleTuneInNavigate now delegates to stations.Navigate(stations.ProviderTuneIn, wildcard), the same path stations' own callers already use. Added TestNavigateTuneIn_ProfilesPathDispatch, covering the single- segment (current), legacy multi-segment, and empty-URI shapes -- this package (and the handler package's now-removed copy) had zero test coverage for this logic before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c56fc6189c
commit
7b2c10507f
@@ -9,11 +9,11 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gesellix/bose-soundtouch/pkg/service/bmx"
|
||||
"github.com/gesellix/bose-soundtouch/pkg/service/datastore"
|
||||
"github.com/gesellix/bose-soundtouch/pkg/service/stations"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
@@ -236,7 +236,7 @@ func (s *Server) HandleTuneInNavigate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
wildcard := chi.URLParam(r, "*")
|
||||
|
||||
resp, err := parseTuneInNavigatePath(wildcard)
|
||||
resp, err := stations.Navigate(stations.ProviderTuneIn, wildcard)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -249,57 +249,6 @@ func (s *Server) HandleTuneInNavigate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseTuneInNavigatePath(wildcard string) (interface{}, error) {
|
||||
if wildcard == "" {
|
||||
return bmx.TuneInNavigate("", nil)
|
||||
}
|
||||
|
||||
firstSlash := strings.Index(wildcard, "/")
|
||||
if firstSlash == -1 {
|
||||
return bmx.TuneInNavigate(wildcard, nil)
|
||||
}
|
||||
|
||||
prefix := wildcard[:firstSlash]
|
||||
rest := wildcard[firstSlash+1:]
|
||||
|
||||
switch prefix {
|
||||
case "sub":
|
||||
secondSlash := strings.Index(rest, "/")
|
||||
if secondSlash == -1 {
|
||||
return bmx.TuneInNavigate(rest, nil)
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(rest[:secondSlash])
|
||||
if err != nil {
|
||||
return bmx.TuneInNavigate(wildcard, nil)
|
||||
}
|
||||
|
||||
return bmx.TuneInNavigate(rest[secondSlash+1:], &n)
|
||||
|
||||
case "profiles":
|
||||
// Hrefs are generated as a single path segment:
|
||||
// /v1/navigate/profiles/{encodedURI} (see tuneInSearchProfile in
|
||||
// pkg/service/bmx/tunein.go). Requiring a profiles/{type}/{id}/{encodedURI}
|
||||
// shape here caused every profile link to fall through to
|
||||
// bmx.TuneInNavigate with the literal "profiles/..." prefix still
|
||||
// attached, which is not valid base64 and produced a 500 ("illegal
|
||||
// base64 data..."). Take the last path segment as the encoded URI so
|
||||
// both the current single-segment hrefs and any legacy
|
||||
// multi-segment ones decode correctly.
|
||||
parts := strings.Split(rest, "/")
|
||||
|
||||
encodedURI := parts[len(parts)-1]
|
||||
if encodedURI == "" {
|
||||
return bmx.TuneInNavigate(wildcard, nil)
|
||||
}
|
||||
|
||||
return bmx.TuneInNavigateProfile(encodedURI)
|
||||
|
||||
default:
|
||||
return bmx.TuneInNavigate(wildcard, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleTuneInSearch returns live TuneIn search results for the given query.
|
||||
func (s *Server) HandleTuneInSearch(w http.ResponseWriter, r *http.Request) {
|
||||
// Authorization gate temporarily disabled (was: 401 if header missing).
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package stations
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -176,3 +178,50 @@ func TestNavigate_UnknownProvider(t *testing.T) {
|
||||
t.Error("expected error for unknown provider")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNavigateTuneIn_ProfilesPathDispatch covers navigateTuneIn's "profiles"
|
||||
// case, the only TuneIn dispatch this package previously had zero coverage
|
||||
// for (this is also the sole implementation now: handlers_bmx_tunein.go's
|
||||
// HandleTuneInNavigate delegates here instead of carrying its own,
|
||||
// previously byte-for-byte identical, copy of this parsing logic).
|
||||
//
|
||||
// Each case encodes the same fake, deliberately-unreachable host so a
|
||||
// "URL host not in allowed list: <the encoded URL, verbatim>" error proves
|
||||
// the encoded URI was extracted and decoded correctly, independent of any
|
||||
// real network access.
|
||||
func TestNavigateTuneIn_ProfilesPathDispatch(t *testing.T) {
|
||||
const fakeURL = "https://not-a-real-tunein-host.invalid/profiles/p123"
|
||||
|
||||
encoded := base64.RawURLEncoding.EncodeToString([]byte(fakeURL))
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
wildcard string
|
||||
wantErrHas string
|
||||
}{
|
||||
{
|
||||
name: "single-segment href (current shape)",
|
||||
wildcard: "profiles/" + encoded,
|
||||
wantErrHas: fakeURL,
|
||||
},
|
||||
{
|
||||
name: "legacy multi-segment href extracts the last segment",
|
||||
wildcard: "profiles/type/id/" + encoded,
|
||||
wantErrHas: fakeURL,
|
||||
},
|
||||
{
|
||||
name: "empty encoded URI falls back instead of erroring on empty input",
|
||||
wildcard: "profiles/",
|
||||
wantErrHas: "illegal base64 data",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := navigateTuneIn(tc.wildcard)
|
||||
if err == nil || !strings.Contains(err.Error(), tc.wantErrHas) {
|
||||
t.Fatalf("navigateTuneIn(%q) error = %v, want containing %q", tc.wildcard, err, tc.wantErrHas)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user