package discovery
import (
"testing"
)
// TestMediaServerFromDescription_WithCDS verifies that a description that
// includes a ContentDirectory service produces a valid MediaServer (ok=true)
// with all fields populated.
func TestMediaServerFromDescription_WithCDS(t *testing.T) {
// Use the canned XML defined in ssdp_test.go (same package).
location := "http://192.0.2.1:49000/rootDesc.xml"
desc, err := parseDescription([]byte(cannedDescriptionXML), location)
if err != nil {
t.Fatalf("parseDescription: %v", err)
}
srv, ok := mediaServerFromDescription(desc)
if !ok {
t.Fatal("mediaServerFromDescription: ok=false, want true")
}
if srv.UDN == "" {
t.Error("UDN is empty")
}
if srv.FriendlyName == "" {
t.Error("FriendlyName is empty")
}
if srv.CDSControlURL == "" {
t.Error("CDSControlURL is empty")
}
// Control URL must be absolute.
if !isAbsoluteURL(srv.CDSControlURL) {
t.Errorf("CDSControlURL %q is not absolute", srv.CDSControlURL)
}
// Icon must be resolved.
if srv.IconURL == "" {
t.Error("IconURL is empty")
}
if !isAbsoluteURL(srv.IconURL) {
t.Errorf("IconURL %q is not absolute", srv.IconURL)
}
// Address must be the host:port from the CDS control URL.
// cannedDescriptionXML has URLBase http://192.0.2.1:49000 and CDS
// controlURL /ctl/ContentDir, so Address = "192.0.2.1:49000".
if srv.Address == "" {
t.Error("Address is empty")
}
if srv.Address != "192.0.2.1:49000" {
t.Errorf("Address = %q, want %q", srv.Address, "192.0.2.1:49000")
}
t.Logf("MediaServer: UDN=%q FriendlyName=%q CDSControlURL=%q IconURL=%q Address=%q",
srv.UDN, srv.FriendlyName, srv.CDSControlURL, srv.IconURL, srv.Address)
}
// TestMediaServerFromDescription_WithoutCDS verifies that a description
// without a ContentDirectory service returns ok=false.
func TestMediaServerFromDescription_WithoutCDS(t *testing.T) {
const xmlNoCDS = `
urn:schemas-upnp-org:device:MediaRenderer:1
SoundTouch 20
Bose
SoundTouch 20
uuid:bose-st20-0001
urn:schemas-upnp-org:service:AVTransport:1
/ctl/AVTransport
`
desc, err := parseDescription([]byte(xmlNoCDS), "http://192.0.2.10:8200/desc.xml")
if err != nil {
t.Fatalf("parseDescription: %v", err)
}
_, ok := mediaServerFromDescription(desc)
if ok {
t.Error("mediaServerFromDescription: ok=true, want false (no ContentDirectory)")
}
}
// TestMediaServerFromDescription_Nil ensures a nil Description returns ok=false
// without panicking.
func TestMediaServerFromDescription_Nil(t *testing.T) {
_, ok := mediaServerFromDescription(nil)
if ok {
t.Error("mediaServerFromDescription(nil): ok=true, want false")
}
}
// TestMediaServerFromDescription_FlatServer verifies a flat description (no
// sub-devices, CDS in root) maps correctly.
func TestMediaServerFromDescription_FlatServer(t *testing.T) {
const xmlFlat = `
http://198.51.100.20:8200
urn:schemas-upnp-org:device:MediaServer:1
MiniDLNA
Justin Maggard
MiniDLNA
uuid:minidlna-0001
image/png
48
48
/icons/sm.png
urn:schemas-upnp-org:service:ContentDirectory:1
/ctl/ContentDir
`
desc, err := parseDescription([]byte(xmlFlat), "http://198.51.100.20:8200/rootDesc.xml")
if err != nil {
t.Fatalf("parseDescription: %v", err)
}
srv, ok := mediaServerFromDescription(desc)
if !ok {
t.Fatal("mediaServerFromDescription: ok=false, want true")
}
if srv.FriendlyName != "MiniDLNA" {
t.Errorf("FriendlyName = %q, want %q", srv.FriendlyName, "MiniDLNA")
}
if srv.UDN != "uuid:minidlna-0001" {
t.Errorf("UDN = %q, want %q", srv.UDN, "uuid:minidlna-0001")
}
wantCDS := "http://198.51.100.20:8200/ctl/ContentDir"
if srv.CDSControlURL != wantCDS {
t.Errorf("CDSControlURL = %q, want %q", srv.CDSControlURL, wantCDS)
}
wantIcon := "http://198.51.100.20:8200/icons/sm.png"
if srv.IconURL != wantIcon {
t.Errorf("IconURL = %q, want %q", srv.IconURL, wantIcon)
}
// Address must reflect the host:port of the CDS control URL.
// URLBase is http://198.51.100.20:8200 and CDS controlURL is /ctl/ContentDir.
if srv.Address != "198.51.100.20:8200" {
t.Errorf("Address = %q, want %q", srv.Address, "198.51.100.20:8200")
}
}
// isAbsoluteURL returns true when s starts with "http://" or "https://".
func isAbsoluteURL(s string) bool {
return len(s) > 7 && (s[:7] == "http://" || (len(s) > 8 && s[:8] == "https://"))
}