diff --git a/pkg/client/bass_integration_test.go b/pkg/client/bass_integration_test.go index 94edd94..2b231f6 100644 --- a/pkg/client/bass_integration_test.go +++ b/pkg/client/bass_integration_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gesellix/bose-soundtouch/pkg/models" + "github.com/gesellix/bose-soundtouch/pkg/speaker" ) // Integration tests for bass control functionality @@ -426,7 +427,7 @@ func BenchmarkClient_Bass_Integration(b *testing.B) { // This is a simple version for test use func parseBassHostPort(hostPort string) (string, int) { if !containsSubstring(hostPort, ":") { - return hostPort, defaultSoundTouchPort + return hostPort, speaker.HTTPPort } // Simple parsing - in real use, we'd use net.SplitHostPort @@ -448,7 +449,7 @@ func parseBassHostPort(hostPort string) (string, int) { if len(parts) == 2 { // Try to parse port - port := defaultSoundTouchPort + port := speaker.HTTPPort portStr := parts[1] portInt := 0 @@ -468,5 +469,5 @@ func parseBassHostPort(hostPort string) (string, int) { return parts[0], port } - return hostPort, defaultSoundTouchPort + return hostPort, speaker.HTTPPort } diff --git a/pkg/client/source_selection_integration_test.go b/pkg/client/source_selection_integration_test.go index de7b6b5..77d6a25 100644 --- a/pkg/client/source_selection_integration_test.go +++ b/pkg/client/source_selection_integration_test.go @@ -4,6 +4,8 @@ import ( "os" "testing" "time" + + "github.com/gesellix/bose-soundtouch/pkg/speaker" ) // Integration tests for source selection functionality @@ -386,7 +388,7 @@ func BenchmarkClient_SelectSource_Integration(b *testing.B) { // This is a simple version for test use func parseHostPort(hostPort string) (string, int) { if !containsSubstring(hostPort, ":") { - return hostPort, defaultSoundTouchPort + return hostPort, speaker.HTTPPort } // Simple parsing - in real use, we'd use net.SplitHostPort @@ -408,7 +410,7 @@ func parseHostPort(hostPort string) (string, int) { if len(parts) == 2 { // Try to parse port - port := defaultSoundTouchPort + port := speaker.HTTPPort portStr := parts[1] portInt := 0 @@ -428,5 +430,5 @@ func parseHostPort(hostPort string) (string, int) { return parts[0], port } - return hostPort, defaultSoundTouchPort + return hostPort, speaker.HTTPPort } diff --git a/pkg/service/constants/constants.go b/pkg/service/constants/constants.go index 7bf1df4..ca52eb4 100644 --- a/pkg/service/constants/constants.go +++ b/pkg/service/constants/constants.go @@ -299,11 +299,10 @@ const ( RecentsFile = "Recents.xml" SourcesFile = "Sources.xml" - SpeakerHTTPPort = 8090 - SpeakerDeviceInfoPath = "/info" - SpeakerRecentsPath = "/recents" - SpeakerPresetsPath = "/presets" - SpeakerSourcesFileLocation = "/mnt/nv/BoseApp-Persistence/1/Sources.xml" + // Speaker-protocol constants (HTTP port, paths, on-device file + // locations) moved to github.com/gesellix/bose-soundtouch/pkg/speaker + // so the client library and CLI can share them without depending on + // the service package. // DateStr is the hardcoded date used in many Bose XML responses DateStr = "2012-09-19T12:43:00.000+00:00" diff --git a/pkg/service/constants/constants_test.go b/pkg/service/constants/constants_test.go index 2b0e8e2..dd552fb 100644 --- a/pkg/service/constants/constants_test.go +++ b/pkg/service/constants/constants_test.go @@ -9,10 +9,6 @@ func TestConstants(t *testing.T) { t.Error("DateStr should not be empty") } - if SpeakerHTTPPort != 8090 { - t.Errorf("Expected SpeakerHTTPPort 8090, got %d", SpeakerHTTPPort) - } - if len(GetProviders()) == 0 { t.Error("Providers should not be empty") } diff --git a/pkg/speaker/speaker.go b/pkg/speaker/speaker.go new file mode 100644 index 0000000..5fbfd56 --- /dev/null +++ b/pkg/speaker/speaker.go @@ -0,0 +1,29 @@ +// Package speaker holds protocol-level constants for the Bose SoundTouch +// speaker's local API surface: the well-known HTTP port, the request paths +// exposed by every device, and the on-device file locations the migration +// flow needs to know about. +// +// This package is intentionally a leaf with no internal dependencies, so +// any layer can import it (client library, service, CLI, tests) without +// introducing a cycle or a cross-topic edge. Anything speaker-shaped that +// would otherwise be duplicated between packages belongs here. +package speaker + +// HTTPPort is the well-known port the SoundTouch device exposes its local +// API on (e.g. /info, /presets, /group). +const HTTPPort = 8090 + +// Well-known HTTP paths the SoundTouch device serves on HTTPPort. +const ( + DeviceInfoPath = "/info" + PresetsPath = "/presets" + RecentsPath = "/recents" +) + +// On-device filesystem paths that the migration/sync flow needs to read or +// write over SSH. These live in the device's persistence area and are not +// part of the HTTP surface. +const ( + SourcesFileLocation = "/mnt/nv/BoseApp-Persistence/1/Sources.xml" + GroupServiceFileLocation = "/mnt/nv/BoseApp-Persistence/1/GroupService.xml" +) diff --git a/pkg/speaker/speaker_test.go b/pkg/speaker/speaker_test.go new file mode 100644 index 0000000..0606597 --- /dev/null +++ b/pkg/speaker/speaker_test.go @@ -0,0 +1,25 @@ +package speaker + +import "testing" + +// Sanity-check the well-known values — a wrong number here would silently +// break every transport and is cheap to guard against. +func TestSpeakerConstants(t *testing.T) { + if HTTPPort != 8090 { + t.Errorf("HTTPPort = %d, want 8090", HTTPPort) + } + + cases := map[string]string{ + "DeviceInfoPath": DeviceInfoPath, + "PresetsPath": PresetsPath, + "RecentsPath": RecentsPath, + "SourcesFileLocation": SourcesFileLocation, + "GroupServiceFileLocation": GroupServiceFileLocation, + } + + for name, val := range cases { + if val == "" { + t.Errorf("%s is empty", name) + } + } +}