mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
360 lines
8.1 KiB
Go
360 lines
8.1 KiB
Go
package datastore
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
)
|
|
|
|
func TestDataStore(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "soundcork-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
account := "test-account"
|
|
device := "test-device"
|
|
|
|
// Test Save/Get DeviceInfo
|
|
info := &models.ServiceDeviceInfo{
|
|
DeviceID: device,
|
|
Name: "Test Speaker",
|
|
}
|
|
|
|
err = ds.SaveDeviceInfo(account, device, info)
|
|
if err != nil {
|
|
t.Errorf("SaveDeviceInfo failed: %v", err)
|
|
}
|
|
|
|
loadedInfo, err := ds.GetDeviceInfo(account, device)
|
|
if err != nil {
|
|
t.Errorf("GetDeviceInfo failed: %v", err)
|
|
}
|
|
|
|
if loadedInfo.Name != info.Name {
|
|
t.Errorf("Expected name %s, got %s", info.Name, loadedInfo.Name)
|
|
}
|
|
|
|
// Test Presets
|
|
presets := []models.ServicePreset{
|
|
{
|
|
ServiceContentItem: models.ServiceContentItem{
|
|
Name: "Preset 1",
|
|
},
|
|
},
|
|
}
|
|
|
|
err = ds.SavePresets(account, presets)
|
|
if err != nil {
|
|
t.Errorf("SavePresets failed: %v", err)
|
|
}
|
|
|
|
loadedPresets, err := ds.GetPresets(account)
|
|
if err != nil {
|
|
t.Errorf("GetPresets failed: %v", err)
|
|
}
|
|
|
|
if len(loadedPresets) != 1 || loadedPresets[0].Name != "Preset 1" {
|
|
t.Errorf("Unexpected presets: %+v", loadedPresets)
|
|
}
|
|
|
|
// Test Recents
|
|
recents := []models.ServiceRecent{
|
|
{
|
|
ServiceContentItem: models.ServiceContentItem{
|
|
Name: "Recent 1",
|
|
},
|
|
},
|
|
}
|
|
|
|
err = ds.SaveRecents(account, recents)
|
|
if err != nil {
|
|
t.Errorf("SaveRecents failed: %v", err)
|
|
}
|
|
|
|
loadedRecents, err := ds.GetRecents(account)
|
|
if err != nil {
|
|
t.Errorf("GetRecents failed: %v", err)
|
|
}
|
|
|
|
if len(loadedRecents) != 1 || loadedRecents[0].Name != "Recent 1" {
|
|
t.Errorf("Unexpected recents: %+v", loadedRecents)
|
|
}
|
|
|
|
// Test path helpers
|
|
expectedAccountDir := filepath.Join(tempDir, account)
|
|
if ds.AccountDir(account) != expectedAccountDir {
|
|
t.Errorf("Expected account dir %s, got %s", expectedAccountDir, ds.AccountDir(account))
|
|
}
|
|
}
|
|
|
|
func TestListAllDevices_Empty(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "soundcork-empty-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
|
|
// Case 1: DataDir does not exist
|
|
_ = os.RemoveAll(tempDir)
|
|
|
|
devices, err := ds.ListAllDevices()
|
|
if err != nil {
|
|
t.Errorf("ListAllDevices should not return error when DataDir does not exist, got %v", err)
|
|
}
|
|
|
|
if devices == nil || len(devices) != 0 {
|
|
t.Errorf("Expected empty slice when DataDir does not exist, got %+v", devices)
|
|
}
|
|
|
|
// Case 2: DataDir is empty
|
|
_ = os.MkdirAll(tempDir, 0755)
|
|
|
|
devices, err = ds.ListAllDevices()
|
|
if err != nil {
|
|
t.Errorf("ListAllDevices failed on empty dir: %v", err)
|
|
}
|
|
|
|
if devices == nil {
|
|
t.Errorf("Expected empty slice (not nil) when no devices exist")
|
|
}
|
|
|
|
if len(devices) != 0 {
|
|
t.Errorf("Expected 0 devices, got %d", len(devices))
|
|
}
|
|
}
|
|
|
|
func TestListAllDevices(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "soundcork-list-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
account := "default"
|
|
deviceID := "BO5EBO5E-F00D-F00D-FEED-08DF1F0BA325"
|
|
|
|
info := &models.ServiceDeviceInfo{
|
|
DeviceID: deviceID,
|
|
Name: "Test Speaker",
|
|
IPAddress: "192.168.178.28",
|
|
DeviceSerialNumber: deviceID,
|
|
ProductCode: "SoundTouch 10",
|
|
FirmwareVersion: "1.2.3",
|
|
}
|
|
|
|
err = ds.SaveDeviceInfo(account, deviceID, info)
|
|
if err != nil {
|
|
t.Fatalf("SaveDeviceInfo failed: %v", err)
|
|
}
|
|
|
|
devices, err := ds.ListAllDevices()
|
|
if err != nil {
|
|
t.Fatalf("ListAllDevices failed: %v", err)
|
|
}
|
|
|
|
if len(devices) != 1 {
|
|
t.Fatalf("Expected 1 device, got %d", len(devices))
|
|
}
|
|
|
|
if devices[0].DeviceID != deviceID {
|
|
t.Errorf("Expected DeviceID %s, got %s", deviceID, devices[0].DeviceID)
|
|
}
|
|
}
|
|
|
|
func TestListAllDevices_EmptyDeviceID(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "soundcork-empty-id-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
account := "default"
|
|
deviceID := ""
|
|
|
|
info := &models.ServiceDeviceInfo{
|
|
DeviceID: deviceID,
|
|
Name: "Empty ID Speaker",
|
|
}
|
|
|
|
// Use IP as fallback for device ID if it is empty
|
|
key := deviceID
|
|
if key == "" {
|
|
key = "127.0.0.1"
|
|
}
|
|
|
|
err = ds.SaveDeviceInfo(account, key, info)
|
|
if err != nil {
|
|
t.Fatalf("SaveDeviceInfo failed: %v", err)
|
|
}
|
|
|
|
devices, err := ds.ListAllDevices()
|
|
if err != nil {
|
|
t.Fatalf("ListAllDevices failed: %v", err)
|
|
}
|
|
|
|
if len(devices) != 1 {
|
|
t.Fatalf("Expected 1 device, got %d", len(devices))
|
|
}
|
|
|
|
if devices[0].Name != "Empty ID Speaker" {
|
|
t.Errorf("Expected Name 'Empty ID Speaker', got %s", devices[0].Name)
|
|
}
|
|
}
|
|
|
|
func TestListAllDevices_MultipleEmptyIDs(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "soundcork-multi-empty-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
account := "default"
|
|
|
|
// Save two devices with empty ID but different IPs
|
|
info1 := &models.ServiceDeviceInfo{
|
|
DeviceID: "",
|
|
Name: "Speaker 1",
|
|
IPAddress: "192.168.1.1",
|
|
}
|
|
info2 := &models.ServiceDeviceInfo{
|
|
DeviceID: "",
|
|
Name: "Speaker 2",
|
|
IPAddress: "192.168.1.2",
|
|
}
|
|
|
|
// We use the same logic as in main.go: use IP as fallback for directory name
|
|
err = ds.SaveDeviceInfo(account, info1.IPAddress, info1)
|
|
if err != nil {
|
|
t.Fatalf("SaveDeviceInfo 1 failed: %v", err)
|
|
}
|
|
|
|
err = ds.SaveDeviceInfo(account, info2.IPAddress, info2)
|
|
if err != nil {
|
|
t.Fatalf("SaveDeviceInfo 2 failed: %v", err)
|
|
}
|
|
|
|
devices, err := ds.ListAllDevices()
|
|
if err != nil {
|
|
t.Fatalf("ListAllDevices failed: %v", err)
|
|
}
|
|
|
|
if len(devices) != 2 {
|
|
t.Fatalf("Expected 2 devices, got %d", len(devices))
|
|
}
|
|
}
|
|
|
|
func TestListAllDevices_MalformedXML(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "soundcork-malformed-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
account := "default"
|
|
deviceID := "malformed-device"
|
|
|
|
dir := ds.AccountDeviceDir(account, deviceID)
|
|
_ = os.MkdirAll(dir, 0755)
|
|
_ = os.WriteFile(filepath.Join(dir, "DeviceInfo.xml"), []byte("<info>not even closed"), 0644)
|
|
|
|
devices, err := ds.ListAllDevices()
|
|
if err != nil {
|
|
t.Fatalf("ListAllDevices should not return error on malformed XML: %v", err)
|
|
}
|
|
|
|
if len(devices) != 0 {
|
|
t.Errorf("Expected 0 devices, got %d", len(devices))
|
|
}
|
|
}
|
|
|
|
func TestConfiguredSources(t *testing.T) {
|
|
tempDir, _ := os.MkdirTemp("", "datastore-sources-*")
|
|
|
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
|
|
ds := NewDataStore(tempDir)
|
|
account := "test-acc"
|
|
|
|
sources := []models.ConfiguredSource{
|
|
{
|
|
DisplayName: "Source 1",
|
|
ID: "101",
|
|
Secret: "secret1",
|
|
SecretType: "type1",
|
|
SourceKeyType: "TUNEIN",
|
|
SourceKeyAccount: "user1",
|
|
},
|
|
{
|
|
DisplayName: "Source 2",
|
|
ID: "102",
|
|
Secret: "secret2",
|
|
SecretType: "type2",
|
|
SourceKeyType: "PANDORA",
|
|
SourceKeyAccount: "user2",
|
|
},
|
|
}
|
|
|
|
err := ds.SaveConfiguredSources(account, sources)
|
|
if err != nil {
|
|
t.Fatalf("SaveConfiguredSources failed: %v", err)
|
|
}
|
|
|
|
loadedSources, err := ds.GetConfiguredSources(account)
|
|
if err != nil {
|
|
t.Fatalf("GetConfiguredSources failed: %v", err)
|
|
}
|
|
|
|
if len(loadedSources) != len(sources) {
|
|
t.Fatalf("Expected %d sources, got %d", len(sources), len(loadedSources))
|
|
}
|
|
|
|
for i, s := range sources {
|
|
ls := loadedSources[i]
|
|
if ls.DisplayName != s.DisplayName || ls.ID != s.ID || ls.Secret != s.Secret ||
|
|
ls.SecretType != s.SecretType || ls.SourceKeyType != s.SourceKeyType ||
|
|
ls.SourceKeyAccount != s.SourceKeyAccount {
|
|
t.Errorf("Source %d mismatch. Expected %+v, got %+v", i, s, ls)
|
|
}
|
|
}
|
|
|
|
// Test with missing ID (GetConfiguredSources should auto-assign)
|
|
sources2 := []models.ConfiguredSource{
|
|
{
|
|
DisplayName: "Source No ID",
|
|
SourceKeyType: "LOCAL",
|
|
SourceKeyAccount: "user3",
|
|
},
|
|
}
|
|
|
|
err = ds.SaveConfiguredSources(account, sources2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
loadedSources2, err := ds.GetConfiguredSources(account)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if loadedSources2[0].ID == "" {
|
|
t.Error("Expected auto-assigned ID for source with empty ID")
|
|
}
|
|
}
|