mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
Major Features: • Implement complete /requestToken API endpoint for bearer token generation • Fix clock time parsing and display with comprehensive time information • Add comprehensive API documentation for 103 discovered endpoints /requestToken Implementation: • Add BearerToken model with full XML marshaling support • Add RequestToken() client method with proper error handling • Add 'soundtouch-cli token request' CLI command with security features • Token validation, formatting, and secure display (truncated for security) • Comprehensive unit tests and integration tests • Support for Authorization header formatting and raw token extraction Clock Time Fixes: • Fix ClockTime model to match actual device XML response structure • Add LocalTime component for nested time details • Support for utcTime, timeFormat, brightness, clockError attributes • Enhanced CLI display with comprehensive time information • Fixed month conversion (device uses 0-11, Go uses 1-12) Documentation Enhancements: • Add comprehensive /supportedURLs endpoint analysis (103 endpoints discovered) • Create detailed unimplemented endpoints documentation with examples • Update API coverage from 34% implemented to full endpoint catalog • Add SoundTouch End of Life notice with May 6, 2026 details • Enhanced endpoint descriptions with real device response examples Security: • All tests use generic token examples (no real tokens exposed) • Integration tests validate token properties without exposing values • Secure token display with truncation in CLI and string representations • Environment variable based testing for real devices Testing: • 15+ new test functions with comprehensive coverage • Real device validation on 192.168.178.28 and 192.168.178.35 • Mock server tests and XML marshaling validation • Integration tests with SOUNDTOUCH_TEST_HOST environment variable CLI Enhancements: • Enhanced clock time display with local time details and device settings • New token management commands with usage instructions • Improved error handling and user-friendly output formatting
305 lines
7.2 KiB
Go
305 lines
7.2 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"testing"
|
|
)
|
|
|
|
func TestBearerToken_GetToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token BearerToken
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Valid token",
|
|
token: BearerToken{Value: "Bearer abc123def456"},
|
|
expected: "Bearer abc123def456",
|
|
},
|
|
{
|
|
name: "Empty token",
|
|
token: BearerToken{Value: ""},
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Token without Bearer prefix",
|
|
token: BearerToken{Value: "abc123def456"},
|
|
expected: "abc123def456",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.token.GetToken()
|
|
if result != tt.expected {
|
|
t.Errorf("GetToken() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBearerToken_GetTokenWithoutPrefix(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token BearerToken
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Valid token with Bearer prefix",
|
|
token: BearerToken{Value: "Bearer abc123def456"},
|
|
expected: "abc123def456",
|
|
},
|
|
{
|
|
name: "Token without Bearer prefix",
|
|
token: BearerToken{Value: "abc123def456"},
|
|
expected: "abc123def456",
|
|
},
|
|
{
|
|
name: "Token with Bearer and extra spaces",
|
|
token: BearerToken{Value: "Bearer abc123def456 "},
|
|
expected: " abc123def456 ",
|
|
},
|
|
{
|
|
name: "Empty token",
|
|
token: BearerToken{Value: ""},
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.token.GetTokenWithoutPrefix()
|
|
if result != tt.expected {
|
|
t.Errorf("GetTokenWithoutPrefix() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBearerToken_IsValid(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token BearerToken
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Valid token",
|
|
token: BearerToken{Value: "Bearer abc123def456"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Empty token",
|
|
token: BearerToken{Value: ""},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Token without Bearer prefix",
|
|
token: BearerToken{Value: "abc123def456"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Just Bearer",
|
|
token: BearerToken{Value: "Bearer"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Bearer with space but no token",
|
|
token: BearerToken{Value: "Bearer "},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.token.IsValid()
|
|
if result != tt.expected {
|
|
t.Errorf("IsValid() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBearerToken_String(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token BearerToken
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Valid long token",
|
|
token: BearerToken{Value: "Bearer abcdefghij1234567890klmnopqrstuvwxyz"},
|
|
expected: "Bearer abcdefghij...qrstuvwxyz",
|
|
},
|
|
{
|
|
name: "Valid short token",
|
|
token: BearerToken{Value: "Bearer abc123"},
|
|
expected: "Bearer abc123",
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
token: BearerToken{Value: "abc123"},
|
|
expected: "Invalid bearer token",
|
|
},
|
|
{
|
|
name: "Empty token",
|
|
token: BearerToken{Value: ""},
|
|
expected: "Invalid bearer token",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.token.String()
|
|
if result != tt.expected {
|
|
t.Errorf("String() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBearerToken_GetAuthHeader(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token BearerToken
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Valid token",
|
|
token: BearerToken{Value: "Bearer abc123def456"},
|
|
expected: "Bearer abc123def456",
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
token: BearerToken{Value: "abc123"},
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Empty token",
|
|
token: BearerToken{Value: ""},
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.token.GetAuthHeader()
|
|
if result != tt.expected {
|
|
t.Errorf("GetAuthHeader() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewBearerToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected BearerToken
|
|
}{
|
|
{
|
|
name: "Token with Bearer prefix",
|
|
input: "Bearer abc123def456",
|
|
expected: BearerToken{Value: "Bearer abc123def456"},
|
|
},
|
|
{
|
|
name: "Token without Bearer prefix",
|
|
input: "abc123def456",
|
|
expected: BearerToken{Value: "Bearer abc123def456"},
|
|
},
|
|
{
|
|
name: "Empty token",
|
|
input: "",
|
|
expected: BearerToken{Value: "Bearer "},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := NewBearerToken(tt.input)
|
|
if result.Value != tt.expected.Value {
|
|
t.Errorf("NewBearerToken() = %v, want %v", result.Value, tt.expected.Value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBearerToken_XMLMarshaling(t *testing.T) {
|
|
// Test unmarshaling from XML (device response)
|
|
xmlData := `<?xml version="1.0" encoding="UTF-8" ?><bearertoken value="Bearer abc123def456xyz789" />`
|
|
|
|
var token BearerToken
|
|
|
|
err := xml.Unmarshal([]byte(xmlData), &token)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal XML: %v", err)
|
|
}
|
|
|
|
if token.Value != "Bearer abc123def456xyz789" {
|
|
t.Errorf("Unmarshaled token value = %v, want %v", token.Value, "Bearer abc123def456xyz789")
|
|
}
|
|
|
|
if !token.IsValid() {
|
|
t.Error("Unmarshaled token should be valid")
|
|
}
|
|
|
|
// Test marshaling to XML
|
|
token2 := BearerToken{Value: "Bearer test123token456"}
|
|
|
|
data, err := xml.Marshal(token2)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal to XML: %v", err)
|
|
}
|
|
|
|
expected := `<bearertoken value="Bearer test123token456"></bearertoken>`
|
|
if string(data) != expected {
|
|
t.Errorf("Marshaled XML = %v, want %v", string(data), expected)
|
|
}
|
|
}
|
|
|
|
func TestBearerToken_RealDeviceExample(t *testing.T) {
|
|
// Test with example device response format (generic token)
|
|
xmlData := `<?xml version="1.0" encoding="UTF-8" ?><bearertoken value="Bearer vUApzBVT6Lh0nw1xVu/plr1UDRNdMYMEpe0cStm4wCH5mWSjrrtORnGGirMn3pspkJ8mNR1MFh/J4OcsbEikMplcDGJVeuZOnDPAskQALvDBCF0PW74qXRms2k1AfLJ/" />`
|
|
|
|
var token BearerToken
|
|
|
|
err := xml.Unmarshal([]byte(xmlData), &token)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal device XML: %v", err)
|
|
}
|
|
|
|
// Verify it's valid
|
|
if !token.IsValid() {
|
|
t.Error("Device token should be valid")
|
|
}
|
|
|
|
// Verify auth header
|
|
authHeader := token.GetAuthHeader()
|
|
if authHeader != token.Value {
|
|
t.Errorf("Auth header = %v, want %v", authHeader, token.Value)
|
|
}
|
|
|
|
// Verify string representation shows truncated version for long tokens
|
|
stringRepr := token.String()
|
|
if stringRepr == token.Value {
|
|
t.Error("String representation should be truncated for long tokens")
|
|
}
|
|
|
|
// Should contain "Bearer" and "..." for long tokens
|
|
if len(stringRepr) >= len(token.Value) {
|
|
t.Error("String representation should be shorter than full token")
|
|
}
|
|
|
|
// Verify raw token extraction
|
|
rawToken := token.GetTokenWithoutPrefix()
|
|
|
|
expectedRawToken := "vUApzBVT6Lh0nw1xVu/plr1UDRNdMYMEpe0cStm4wCH5mWSjrrtORnGGirMn3pspkJ8mNR1MFh/J4OcsbEikMplcDGJVeuZOnDPAskQALvDBCF0PW74qXRms2k1AfLJ/"
|
|
if rawToken != expectedRawToken {
|
|
t.Errorf("Raw token = %v, want %v", rawToken, expectedRawToken)
|
|
}
|
|
|
|
// Verify token length is reasonable (typical bearer tokens are long)
|
|
if len(rawToken) < 50 {
|
|
t.Errorf("Token seems too short: %d characters", len(rawToken))
|
|
}
|
|
}
|