mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-24 14:47:23 +00:00
242 lines
7.0 KiB
Go
242 lines
7.0 KiB
Go
package marge
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
"github.com/gesellix/bose-soundtouch/pkg/service/datastore"
|
|
)
|
|
|
|
func TestMargeXML(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "marge-test-*")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := datastore.NewDataStore(tempDir)
|
|
account := "123"
|
|
device := "ABC"
|
|
|
|
// Setup initial data
|
|
info := &models.ServiceDeviceInfo{
|
|
DeviceID: device,
|
|
Name: "Living Room",
|
|
}
|
|
_ = ds.SaveDeviceInfo(account, device, info)
|
|
|
|
// Save empty presets/recents to avoid index out of range when stripping header
|
|
_ = ds.SavePresets(account, device, []models.ServicePreset{})
|
|
_ = ds.SaveRecents(account, device, []models.ServiceRecent{})
|
|
|
|
// Test SourceProvidersToXML
|
|
xmlData, err := SourceProvidersToXML()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !strings.Contains(string(xmlData), "<sourceProviders>") {
|
|
t.Errorf("Expected <sourceProviders>, got %s", string(xmlData))
|
|
}
|
|
|
|
// Test AccountFullToXML
|
|
fullXML, err := AccountFullToXML(ds, account)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !strings.Contains(string(fullXML), `id="123"`) {
|
|
t.Errorf("Expected account id 123, got %s", string(fullXML))
|
|
}
|
|
|
|
if !strings.Contains(string(fullXML), "Living Room") {
|
|
t.Errorf("Expected device name Living Room, got %s", string(fullXML))
|
|
}
|
|
|
|
// Test SoftwareUpdateToXML
|
|
swXML := SoftwareUpdateToXML()
|
|
if !strings.Contains(swXML, "<software_update>") {
|
|
t.Errorf("Expected <software_update>, got %s", swXML)
|
|
}
|
|
}
|
|
|
|
func TestEscapeXML(t *testing.T) {
|
|
input := "Antenne Chillout & Other"
|
|
expected := "Antenne Chillout & Other"
|
|
actual := EscapeXML(input)
|
|
if actual != expected {
|
|
t.Errorf("Expected %s, got %s", expected, actual)
|
|
}
|
|
|
|
inputWithAll := "< > & ' \""
|
|
expectedWithAll := "< > & ' ""
|
|
actualWithAll := EscapeXML(inputWithAll)
|
|
if actualWithAll != expectedWithAll {
|
|
t.Errorf("Expected %s, got %s", expectedWithAll, actualWithAll)
|
|
}
|
|
}
|
|
|
|
func TestRecentsXML_EmptyIDFix(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "marge-test-*")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
}
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := datastore.NewDataStore(tempDir)
|
|
account := "test-acc"
|
|
device := "test-dev"
|
|
|
|
deviceDir := ds.AccountDeviceDir(account, device)
|
|
_ = os.MkdirAll(deviceDir, 0755)
|
|
|
|
// Create a Recents.xml with empty ID
|
|
recentsXML := []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
|
<recents>
|
|
<recent id="" deviceID="test-dev" utcTime="1708896000">
|
|
<contentItem source="SPOTIFY" type="tracklisturl" location="/test" sourceAccount="user" isPresetable="true">
|
|
<itemName>Test Item</itemName>
|
|
</contentItem>
|
|
</recent>
|
|
</recents>`)
|
|
_ = os.WriteFile(filepath.Join(deviceDir, "Recents.xml"), recentsXML, 0644)
|
|
_ = os.WriteFile(filepath.Join(deviceDir, "Sources.xml"), []byte("<sources/>"), 0644)
|
|
|
|
// Fetching should fix the empty ID
|
|
recents, err := ds.GetRecents(account, device)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get recents: %v", err)
|
|
}
|
|
|
|
if len(recents) != 1 {
|
|
t.Fatalf("Expected 1 recent, got %d", len(recents))
|
|
}
|
|
|
|
if recents[0].ID == "" {
|
|
t.Errorf("Expected non-empty ID for recent")
|
|
}
|
|
|
|
if _, err := strconv.Atoi(recents[0].ID); err != nil {
|
|
t.Errorf("Expected numeric ID, got %s", recents[0].ID)
|
|
}
|
|
|
|
// Verify the XML output also has the non-empty ID
|
|
xmlData, err := RecentsToXML(ds, account, device)
|
|
if err != nil {
|
|
t.Fatalf("RecentsToXML failed: %v", err)
|
|
}
|
|
|
|
if strings.Contains(string(xmlData), `recent id=""`) {
|
|
t.Errorf("XML should not contain empty recent ID: %s", string(xmlData))
|
|
}
|
|
|
|
if !strings.Contains(string(xmlData), `recent id="1"`) {
|
|
t.Errorf("XML should contain fixed numeric ID: %s", string(xmlData))
|
|
}
|
|
}
|
|
|
|
func TestGetConfiguredSourceXML_Escaping(t *testing.T) {
|
|
src := models.ConfiguredSource{
|
|
ID: "101&202",
|
|
DisplayName: "Test & Source",
|
|
Secret: "key&value",
|
|
}
|
|
src.SourceKeyAccount = "user&name"
|
|
|
|
xml := GetConfiguredSourceXML(src)
|
|
if !strings.Contains(xml, "id=\"101&202\"") {
|
|
t.Errorf("ID not escaped in attribute: %s", xml)
|
|
}
|
|
if strings.Contains(xml, "<sourceid>101&202</sourceid>") {
|
|
t.Errorf("ID should not be escaped in sourceid tag inside source tag anymore: %s", xml)
|
|
}
|
|
if !strings.Contains(xml, "<sourcename>Test & Source</sourcename>") {
|
|
t.Errorf("DisplayName not escaped: %s", xml)
|
|
}
|
|
if !strings.Contains(xml, ">key&value</credential>") {
|
|
t.Errorf("Secret not escaped: %s", xml)
|
|
}
|
|
}
|
|
|
|
func TestAddRecent_TimestampPreservation(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "marge-test-*")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := datastore.NewDataStore(tempDir)
|
|
account := "test-acc"
|
|
device := "test-dev"
|
|
|
|
// 1. Setup configured sources
|
|
// We need a Sources.xml file in the account directory
|
|
deviceDir := ds.AccountDeviceDir(account, device)
|
|
_ = os.MkdirAll(deviceDir, 0755)
|
|
src := models.ConfiguredSource{
|
|
ID: "101",
|
|
DisplayName: "Test Source",
|
|
SecretType: "Audio",
|
|
}
|
|
src.SourceKey.Type = "TUNEIN"
|
|
src.SourceKey.Account = "test-user"
|
|
src.SourceKeyType = "TUNEIN"
|
|
src.SourceKeyAccount = "test-user"
|
|
|
|
_ = ds.SaveConfiguredSources(account, device, []models.ConfiguredSource{src})
|
|
_ = ds.SaveRecents(account, device, []models.ServiceRecent{})
|
|
|
|
// 2. Add an initial recent
|
|
sourceXML := []byte(`
|
|
<recent>
|
|
<name>Initial Station</name>
|
|
<sourceid>101</sourceid>
|
|
<location>station-1</location>
|
|
<contentItemType>station</contentItemType>
|
|
</recent>`)
|
|
|
|
_, err = AddRecent(ds, account, device, sourceXML)
|
|
if err != nil {
|
|
t.Fatalf("AddRecent failed: %v", err)
|
|
}
|
|
|
|
recents, _ := ds.GetRecents(account, device)
|
|
if len(recents) != 1 {
|
|
t.Fatalf("Expected 1 recent, got %d", len(recents))
|
|
}
|
|
|
|
// 3. Add the same recent again (it should move to front and preserve createdOn)
|
|
// We'll wait a second to ensure time.Now() would be different if it were used for createdOn
|
|
time.Sleep(1 * time.Second)
|
|
|
|
respXML, err := AddRecent(ds, account, device, sourceXML)
|
|
if err != nil {
|
|
t.Fatalf("AddRecent second time failed: %v", err)
|
|
}
|
|
|
|
if !strings.Contains(string(respXML), "2012-09-19T12:43:00.000+00:00") {
|
|
// Our DateStr is 2012-09-19T12:43:00.000+00:00
|
|
t.Errorf("Expected preserved DateStr in createdOn, got XML: %s", string(respXML))
|
|
}
|
|
|
|
recents, _ = ds.GetRecents(account, device)
|
|
if len(recents) != 1 {
|
|
t.Errorf("Expected still 1 recent, got %d", len(recents))
|
|
}
|
|
|
|
// Verify that sourceid is present in recent response and is a sibling to source tag
|
|
if !strings.Contains(string(respXML), "<sourceid>101</sourceid>") {
|
|
t.Errorf("Expected sourceid in recent response: %s", string(respXML))
|
|
}
|
|
if strings.Contains(string(respXML), "<source id=\"101\" type=\"Audio\"><createdOn>2012-09-19T12:43:00.000+00:00</createdOn><credential type=\"token\">key&value</credential><name>test-user</name><sourceid>101</sourceid>") {
|
|
t.Errorf("sourceid should not be inside source tag: %s", string(respXML))
|
|
}
|
|
}
|