From 70d05554a0920b59b097e7f22dc1a3f02672c536 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Wed, 29 Apr 2026 10:52:40 +0200 Subject: [PATCH] feat: add Amazon LWA mock server (testutils + integration mocks) Mirror the Spotify equivalents: pkg/testutils/amazon/handlers.go provides HandleToken and HandleProfile for use in unit tests; tests/integration/mocks/amazon.go wraps them in an AmazonMock with TokenURL() and ProfileURL() accessors. Co-Authored-By: Claude Sonnet 4.6 --- pkg/testutils/amazon/handlers.go | 93 +++++++++++++++++++++++++++++++ tests/integration/mocks/amazon.go | 39 +++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 pkg/testutils/amazon/handlers.go create mode 100644 tests/integration/mocks/amazon.go diff --git a/pkg/testutils/amazon/handlers.go b/pkg/testutils/amazon/handlers.go new file mode 100644 index 0000000..2539e8f --- /dev/null +++ b/pkg/testutils/amazon/handlers.go @@ -0,0 +1,93 @@ +// Package amazon provides shared handlers for mocking the Amazon LWA API. +package amazon + +import ( + "encoding/json" + "log" + "net/http" +) + +// NewAmazonHandler returns a new http.Handler configured with Amazon LWA mock endpoints. +func NewAmazonHandler() http.Handler { + mux := http.NewServeMux() + + // LWA Token Endpoint (POST body credentials, not Basic Auth) + mux.HandleFunc("/auth/o2/token", HandleToken) + + // LWA User Profile Endpoint + mux.HandleFunc("/user/profile", HandleProfile) + + return mux +} + +// HandleToken simulates the Amazon LWA token endpoint. +// Amazon requires client_id and client_secret as POST body fields, not HTTP Basic Auth. +func HandleToken(w http.ResponseWriter, r *http.Request) { + log.Printf("[Amazon Mock] Token request: %s", r.Method) + + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + if err := r.ParseForm(); err != nil { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + + grantType := r.FormValue("grant_type") + log.Printf("[Amazon Mock] Grant type: %s", grantType) + + resp := map[string]interface{}{ + "access_token": "Atza|amazon-access-token", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "Atzr|amazon-refresh-token", + } + + switch grantType { + case "authorization_code": + if r.FormValue("code") == "" { + http.Error(w, `{"error":"invalid_grant"}`, http.StatusBadRequest) + return + } + case "refresh_token": + if r.FormValue("refresh_token") == "" { + http.Error(w, `{"error":"invalid_grant"}`, http.StatusBadRequest) + return + } + default: + http.Error(w, `{"error":"unsupported_grant_type"}`, http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Error encoding token response: %v", err) + } +} + +// HandleProfile simulates the Amazon LWA user profile endpoint. +// LWA returns "user_id" and "name" (not "id" / "display_name" like Spotify). +func HandleProfile(w http.ResponseWriter, r *http.Request) { + log.Printf("[Amazon Mock] Profile request: %s", r.Method) + + auth := r.Header.Get("Authorization") + if auth != "Bearer Atza|amazon-access-token" { + http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized) + return + } + + resp := map[string]interface{}{ + "user_id": "amzn1.account.TESTUSER", + "name": "Amazon Test User", + "email": "amazon-test@example.com", + } + + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Error encoding profile response: %v", err) + } +} diff --git a/tests/integration/mocks/amazon.go b/tests/integration/mocks/amazon.go new file mode 100644 index 0000000..ec0ddee --- /dev/null +++ b/tests/integration/mocks/amazon.go @@ -0,0 +1,39 @@ +package mocks + +import ( + "net/http/httptest" + + "github.com/gesellix/bose-soundtouch/pkg/testutils/amazon" +) + +// AmazonMock simulates Amazon LWA API responses for OAuth and profile interactions. +type AmazonMock struct { + server *httptest.Server +} + +// NewAmazonMock creates and starts a new Amazon LWA mock server. +func NewAmazonMock() *AmazonMock { + return &AmazonMock{ + server: httptest.NewServer(amazon.NewAmazonHandler()), + } +} + +// URL returns the base URL of the mock server. +func (m *AmazonMock) URL() string { + return m.server.URL +} + +// TokenURL returns the LWA token endpoint URL. +func (m *AmazonMock) TokenURL() string { + return m.server.URL + "/auth/o2/token" +} + +// ProfileURL returns the LWA user profile endpoint URL. +func (m *AmazonMock) ProfileURL() string { + return m.server.URL + "/user/profile" +} + +// Close stops the mock server. +func (m *AmazonMock) Close() { + m.server.Close() +}