mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Adds the CLI-first surface for the DLNA feature (https://github.com/gesellix/Bose-SoundTouch/discussions/213), so the discovery/browse/play plumbing can be exercised against a real media server and speaker without the web build loop. - soundtouch-cli library servers: app-side SSDP sweep (discovery.DiscoverMediaServers); --via-speaker queries the speaker's own /listMediaServers instead, for an A/B of the two views. - soundtouch-cli library browse --udn <id> [--object --start --count]: dlna.Browse of a discovered server's ContentDirectory. - soundtouch-cli library play --url <streamURL> --mode <...>: plays a track URL on a speaker; --mode selects the playback path (local-internet-radio, local-music, stored-music, content-item) so the best one can be found empirically on hardware. - pkg/client.ListMediaServers() + models.ListMediaServersResponse for the speaker-native (Option 2) path; an empty <ListMediaServersResponse /> parses to an empty slice. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
2.7 KiB
Go
90 lines
2.7 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"testing"
|
|
)
|
|
|
|
func TestListMediaServersResponse_Populated(t *testing.T) {
|
|
raw := `<ListMediaServersResponse>` +
|
|
`<media_server id="uuid:1234-5678" mac="AA:BB:CC:DD:EE:FF" ip="192.0.2.5"` +
|
|
` manufacturer="ExampleCorp" model_name="NAS-3000" friendly_name="My NAS"` +
|
|
` model_description="Home NAS" location="http://192.0.2.5:8200/rootDesc.xml" />` +
|
|
`<media_server id="uuid:AAAA-BBBB" mac="11:22:33:44:55:66" ip="192.0.2.6"` +
|
|
` manufacturer="OtherCorp" model_name="Media-1" friendly_name="Living Room NAS"` +
|
|
` location="http://192.0.2.6:8200/rootDesc.xml" />` +
|
|
`</ListMediaServersResponse>`
|
|
|
|
var resp ListMediaServersResponse
|
|
|
|
if err := xml.Unmarshal([]byte(raw), &resp); err != nil {
|
|
t.Fatalf("unmarshal failed: %v", err)
|
|
}
|
|
|
|
if len(resp.MediaServers) != 2 {
|
|
t.Fatalf("expected 2 servers, got %d", len(resp.MediaServers))
|
|
}
|
|
|
|
first := resp.MediaServers[0]
|
|
|
|
if first.ID != "uuid:1234-5678" {
|
|
t.Errorf("server[0].ID = %q; want %q", first.ID, "uuid:1234-5678")
|
|
}
|
|
|
|
if first.MAC != "AA:BB:CC:DD:EE:FF" {
|
|
t.Errorf("server[0].MAC = %q; want %q", first.MAC, "AA:BB:CC:DD:EE:FF")
|
|
}
|
|
|
|
if first.IP != "192.0.2.5" {
|
|
t.Errorf("server[0].IP = %q; want %q", first.IP, "192.0.2.5")
|
|
}
|
|
|
|
if first.FriendlyName != "My NAS" {
|
|
t.Errorf("server[0].FriendlyName = %q; want %q", first.FriendlyName, "My NAS")
|
|
}
|
|
|
|
if first.Manufacturer != "ExampleCorp" {
|
|
t.Errorf("server[0].Manufacturer = %q; want %q", first.Manufacturer, "ExampleCorp")
|
|
}
|
|
|
|
if first.ModelName != "NAS-3000" {
|
|
t.Errorf("server[0].ModelName = %q; want %q", first.ModelName, "NAS-3000")
|
|
}
|
|
|
|
if first.ModelDescription != "Home NAS" {
|
|
t.Errorf("server[0].ModelDescription = %q; want %q", first.ModelDescription, "Home NAS")
|
|
}
|
|
|
|
if first.Location != "http://192.0.2.5:8200/rootDesc.xml" {
|
|
t.Errorf("server[0].Location = %q; want %q", first.Location, "http://192.0.2.5:8200/rootDesc.xml")
|
|
}
|
|
|
|
second := resp.MediaServers[1]
|
|
|
|
if second.ID != "uuid:AAAA-BBBB" {
|
|
t.Errorf("server[1].ID = %q; want %q", second.ID, "uuid:AAAA-BBBB")
|
|
}
|
|
|
|
if second.ModelDescription != "" {
|
|
t.Errorf("server[1].ModelDescription should be empty for omitted attr, got %q", second.ModelDescription)
|
|
}
|
|
}
|
|
|
|
func TestListMediaServersResponse_Empty(t *testing.T) {
|
|
// Speakers can return a self-closing element when no servers are visible.
|
|
for _, raw := range []string{
|
|
`<ListMediaServersResponse />`,
|
|
`<ListMediaServersResponse></ListMediaServersResponse>`,
|
|
} {
|
|
var resp ListMediaServersResponse
|
|
|
|
if err := xml.Unmarshal([]byte(raw), &resp); err != nil {
|
|
t.Fatalf("unmarshal %q failed: %v", raw, err)
|
|
}
|
|
|
|
if len(resp.MediaServers) != 0 {
|
|
t.Errorf("expected 0 servers for %q, got %d", raw, len(resp.MediaServers))
|
|
}
|
|
}
|
|
}
|