mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Add `soundtouch-cli setup` subcommand group covering the full reset →
re-provision → pair lifecycle as a scriptable alternative to the web UI:
inspect, verify, plan, factory-reset, wait-ap, wifi-push, wait-online,
ssh-check, install-ca, migrate, reboot, pair (bare | full state machine)
Supporting library code lives in pkg/service/setup: factory_reset.go,
wifi_provision.go, inspect.go, init_plan.go, setup_session.go.
Confirmed against ST10 firmware 27.0.6 that bare setMargeAccount over
WebSocket — no SETUP_START/SETUP_ENTER/SETUP_LEAVE bracket — is
sufficient to pair a factory-reset speaker; the firmware materializes
SystemConfigurationDB.xml and Sources.xml itself and the pairing
survives reboot. Result and field-by-field SystemConfigurationDB
comparison documented in docs/analysis/SETUP-WEBSOCKET-EXPERIMENT.md.
Captures the device's pre-reset DELETE-to-marge plus its LAN peer
notification flow in docs/analysis/FACTORY-RESET-PROTOCOL.md.
Perf: batch GetMigrationSummary's SSH probes into one Run() call via
ssh_probe.go / ssh_probe_apply.go — was ~8 sequential dials at
500-1000 ms each on FW 27 crypto, now one round-trip. Same data shape,
same MigrationSummary fields populated.
Fixes /clockTime and /clockDisplay wire formats — firmware 27 rejects
the legacy flat XML ("Error parsing request"). ClockTimeRequest now
uses utcTime attribute; ClockDisplayRequest emits the nested
<clockConfig> envelope with timezoneInfo/timeFormat/brightnessLevel.
Removes cmd/example-init-speaker (superseded by setup pair).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
360 lines
7.8 KiB
Go
360 lines
7.8 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestClockTime_UnmarshalXML(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
xmlData string
|
|
expected ClockTime
|
|
}{
|
|
{
|
|
name: "Basic clock time with UTC",
|
|
xmlData: `<clockTime zone="UTC" utc="1609459200">2021-01-01 00:00:00</clockTime>`,
|
|
expected: ClockTime{
|
|
Zone: "UTC",
|
|
UTC: 1609459200,
|
|
Value: "2021-01-01 00:00:00",
|
|
},
|
|
},
|
|
{
|
|
name: "Clock time with timezone",
|
|
xmlData: `<clockTime zone="America/New_York" utc="1609459200">2021-01-01 00:00:00</clockTime>`,
|
|
expected: ClockTime{
|
|
Zone: "America/New_York",
|
|
UTC: 1609459200,
|
|
Value: "2021-01-01 00:00:00",
|
|
},
|
|
},
|
|
{
|
|
name: "Clock time without UTC",
|
|
xmlData: `<clockTime zone="UTC">2021-01-01 12:30:45</clockTime>`,
|
|
expected: ClockTime{
|
|
Zone: "UTC",
|
|
Value: "2021-01-01 12:30:45",
|
|
},
|
|
},
|
|
{
|
|
name: "Empty clock time",
|
|
xmlData: `<clockTime></clockTime>`,
|
|
expected: ClockTime{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var clockTime ClockTime
|
|
|
|
err := xml.Unmarshal([]byte(tt.xmlData), &clockTime)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal XML: %v", err)
|
|
}
|
|
|
|
if clockTime.Zone != tt.expected.Zone {
|
|
t.Errorf("Expected Zone %q, got %q", tt.expected.Zone, clockTime.Zone)
|
|
}
|
|
|
|
if clockTime.UTC != tt.expected.UTC {
|
|
t.Errorf("Expected UTC %d, got %d", tt.expected.UTC, clockTime.UTC)
|
|
}
|
|
|
|
if clockTime.Value != tt.expected.Value {
|
|
t.Errorf("Expected Value %q, got %q", tt.expected.Value, clockTime.Value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClockTime_GetTime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
clockTime ClockTime
|
|
wantTime time.Time
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "UTC timestamp",
|
|
clockTime: ClockTime{
|
|
UTC: 1609459200, // 2021-01-01 00:00:00 UTC
|
|
},
|
|
wantTime: time.Unix(1609459200, 0),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "RFC3339 format",
|
|
clockTime: ClockTime{
|
|
Value: "2021-01-01T12:00:00Z",
|
|
},
|
|
wantTime: time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Standard format",
|
|
clockTime: ClockTime{
|
|
Value: "2021-01-01 12:00:00",
|
|
},
|
|
wantTime: time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Time only format",
|
|
clockTime: ClockTime{
|
|
Value: "15:30:45",
|
|
},
|
|
wantTime: time.Date(0, 1, 1, 15, 30, 45, 0, time.UTC),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Invalid format",
|
|
clockTime: ClockTime{
|
|
Value: "invalid-time",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Empty clock time",
|
|
clockTime: ClockTime{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotTime, err := tt.clockTime.GetTime()
|
|
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("Expected error, got none")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
return
|
|
}
|
|
|
|
if !gotTime.Equal(tt.wantTime) {
|
|
t.Errorf("Expected time %v, got %v", tt.wantTime, gotTime)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClockTime_GetUTC(t *testing.T) {
|
|
clockTime := ClockTime{UTC: 1609459200}
|
|
|
|
if got := clockTime.GetUTC(); got != 1609459200 {
|
|
t.Errorf("Expected UTC %d, got %d", 1609459200, got)
|
|
}
|
|
}
|
|
|
|
func TestClockTime_GetZone(t *testing.T) {
|
|
clockTime := ClockTime{Zone: "America/New_York"}
|
|
|
|
if got := clockTime.GetZone(); got != "America/New_York" {
|
|
t.Errorf("Expected Zone %q, got %q", "America/New_York", got)
|
|
}
|
|
}
|
|
|
|
func TestClockTime_GetTimeString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
clockTime ClockTime
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Valid UTC time",
|
|
clockTime: ClockTime{
|
|
UTC: 1609459200, // 2021-01-01 00:00:00 UTC
|
|
},
|
|
expected: time.Unix(1609459200, 0).UTC().Format("2006-01-02 15:04:05"),
|
|
},
|
|
{
|
|
name: "Value fallback",
|
|
clockTime: ClockTime{
|
|
Value: "2021-12-25 15:30:00",
|
|
},
|
|
expected: "2021-12-25 15:30:00",
|
|
},
|
|
{
|
|
name: "Invalid time returns value",
|
|
clockTime: ClockTime{
|
|
Value: "invalid-time",
|
|
},
|
|
expected: "invalid-time",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := tt.clockTime.GetTimeString(); got != tt.expected {
|
|
t.Errorf("Expected time string %q, got %q", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClockTime_IsEmpty(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
clockTime ClockTime
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Empty clock time",
|
|
clockTime: ClockTime{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Clock time with UTC",
|
|
clockTime: ClockTime{
|
|
UTC: 1609459200,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Clock time with value",
|
|
clockTime: ClockTime{
|
|
Value: "2021-01-01 00:00:00",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Clock time with both",
|
|
clockTime: ClockTime{
|
|
UTC: 1609459200,
|
|
Value: "2021-01-01 00:00:00",
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := tt.clockTime.IsEmpty(); got != tt.expected {
|
|
t.Errorf("Expected IsEmpty() %v, got %v", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClockTime_SetTime(t *testing.T) {
|
|
clockTime := ClockTime{}
|
|
testTime := time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC)
|
|
|
|
clockTime.SetTime(testTime)
|
|
|
|
if clockTime.UTC != testTime.Unix() {
|
|
t.Errorf("Expected UTC %d, got %d", testTime.Unix(), clockTime.UTC)
|
|
}
|
|
|
|
if clockTime.Value != "2021-01-01 12:00:00" {
|
|
t.Errorf("Expected Value %q, got %q", "2021-01-01 12:00:00", clockTime.Value)
|
|
}
|
|
|
|
if clockTime.Zone != "UTC" {
|
|
t.Errorf("Expected Zone %q, got %q", "UTC", clockTime.Zone)
|
|
}
|
|
}
|
|
|
|
func TestClockTime_SetUTC(t *testing.T) {
|
|
clockTime := ClockTime{}
|
|
utcTimestamp := int64(1609459200) // 2021-01-01 00:00:00 UTC
|
|
|
|
clockTime.SetUTC(utcTimestamp)
|
|
|
|
if clockTime.UTC != utcTimestamp {
|
|
t.Errorf("Expected UTC %d, got %d", utcTimestamp, clockTime.UTC)
|
|
}
|
|
|
|
expectedValue := time.Unix(utcTimestamp, 0).UTC().Format("2006-01-02 15:04:05")
|
|
if clockTime.Value != expectedValue {
|
|
t.Errorf("Expected Value %q, got %q", expectedValue, clockTime.Value)
|
|
}
|
|
}
|
|
|
|
func TestNewClockTimeRequest(t *testing.T) {
|
|
testTime := time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC)
|
|
|
|
request := NewClockTimeRequest(testTime)
|
|
|
|
if request.UTCTime != testTime.Unix() {
|
|
t.Errorf("Expected UTCTime %d, got %d", testTime.Unix(), request.UTCTime)
|
|
}
|
|
}
|
|
|
|
func TestNewClockTimeRequestUTC(t *testing.T) {
|
|
utcTimestamp := int64(1609459200) // 2021-01-01 00:00:00 UTC
|
|
|
|
request := NewClockTimeRequestUTC(utcTimestamp)
|
|
|
|
if request.UTCTime != utcTimestamp {
|
|
t.Errorf("Expected UTCTime %d, got %d", utcTimestamp, request.UTCTime)
|
|
}
|
|
}
|
|
|
|
func TestClockTimeRequest_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
request ClockTimeRequest
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Valid UTC request",
|
|
request: ClockTimeRequest{UTCTime: 1609459200},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Empty request",
|
|
request: ClockTimeRequest{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "UTC too old",
|
|
request: ClockTimeRequest{UTCTime: 946684799}, // Before year 2000
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "UTC too far in future",
|
|
request: ClockTimeRequest{UTCTime: 4102444801}, // After year 2100
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.request.Validate()
|
|
|
|
if tt.wantErr && err == nil {
|
|
t.Error("Expected error, got none")
|
|
}
|
|
|
|
if !tt.wantErr && err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClockTimeRequest_MarshalXML(t *testing.T) {
|
|
request := ClockTimeRequest{UTCTime: 1609459200}
|
|
|
|
data, err := xml.Marshal(request)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal XML: %v", err)
|
|
}
|
|
|
|
// Must match the device's GET /clockTime response attribute name
|
|
// — firmware 27 rejects `utc=` (no Time suffix) with "Error parsing request".
|
|
expected := `<clockTime utcTime="1609459200"></clockTime>`
|
|
if string(data) != expected {
|
|
t.Errorf("Expected XML %q, got %q", expected, string(data))
|
|
}
|
|
}
|