Two HTTP client tests asserted AUX (id=10001 / sourceproviderid=9) was
present in /streaming/account/{a}/full and /streaming/account/{a}/sources.
After 2b40481 drops AUX from those cloud responses (matching real Bose
behaviour; see pkg/service/marge/marge.go getAccountSources), both
tests fail. Updates them to:
- Expect 5 sources in /full (down from 6) — INTERNET_RADIO,
LOCAL_INTERNET_RADIO, TUNEIN, RADIO_BROWSER, Spotify.
- Expect ids 10002/10003/10004 (not 10001/...) in /sources.
- Add explicit negative assertions that sourceproviderid=9 / id=10001
is *not* present, so a regression that re-introduces AUX in cloud
responses fails loud.
Verified via `make test-http-client`: 49 requests, 0 failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds rename_device.http between get_group.http and unregister_device.http
in the make test-http-client sequence. The new test fires the PUT
the speaker emits after a rename and asserts:
- 200 OK, content type vnd.bose.streaming-v1.2+xml
- the response carries the renamed value
- createdOn matches the value captured during register_device.http
(cross-request global), locking in the "first-paired" semantics
- ipaddress is preserved from the prior power_on, not reset by the
rename body's empty IP field
- a mismatched body deviceid is rejected with 400
register_device.http captures the initial createdOn into a global so
the rename test can assert equality rather than a flakier
updatedOn != createdOn heuristic. The variant POST's stale
updatedOn === createdOn assertion is replaced with an upsert-aware
equality against the same captured global.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an end-to-end IntelliJ HTTP Client test that replays the exact
request shape a SoundTouch 10 master sends to its configured Marge
server during stereo-pair formation (captured live in issue #252):
POST /streaming/account/{accountId}/group/
Authorization: Bearer <token>
Content-Type: application/vnd.bose.streaming-v1.2+xml
<group>
<masterDeviceId>...</masterDeviceId>
<name>TEST</name>
<roles>
<groupRole><deviceId>...</deviceId><role>LEFT</role></groupRole>
<groupRole><deviceId>...</deviceId><role>RIGHT</role></groupRole>
</roles>
</group>
Assertions cover the wire contract that fails loudly if regressed:
trailing-slash URL is matched, response is 201 Created with the vendor
media type, Location header references the new group under the
account, and the body echoes masterDeviceId, name, and both groupRole
entries.
Wired into the make test-http-client target, sequenced before
get_group.http so the GET runs against the post-create state.
get_group.http's assertion only checks for the presence of a <group>
element, so adding a populated group beforehand is compatible.
Refs #252
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add cmd/mock-amazon/main.go (mirrors mock-spotify, uses testutils/amazon)
- Add amazon-mock service to docker-compose.yml (port 8082)
- Add AMAZON_CLIENT_ID/SECRET/TOKEN_URL/PROFILE_URL to docker-compose.ci.yml
- Add amazon_registration.http: registers account via /mgmt/amazon/callback
before the token-refresh test runs (mirrors spotify_registration.http)
- Update {{amazonRefreshToken}} in env to match mock response (Atzr|amazon-refresh-token)
- Log amazon-mock output on test failure in Makefile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add GetAccountByRefreshToken to amazon.Service — the speaker sends
the bare Atzr| refresh token (extracted from AmazonSecret JSON), not
a surrogate, so lookup must match against Account.RefreshToken
- Add amazonService field, SetAmazonService and IsAmazonConfigured to
Server (step 5 essentials required by the handler)
- Replace HandleBoseAmazonToken 501 stub with full implementation:
lookup by refresh token → RefreshAccessToken; fallback to
GetFreshToken; fallback to HandleBoseProxy if no service configured;
scope intentionally omitted from response
- Add handler tests covering the by-refresh-token path (mock LWA
server), the default-account path, and the no-service fallback
- Unlock assertions in post_oauth_token_amazon.http integration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Recognize Amazon Music in learned sources (classifyAsAmazon) and
AddSource dispatch, using CredentialTypeToken (cs1) not cs3
- Exclude Amazon from default sources: an empty-credential Amazon entry
triggers the speaker's AmazonController to fail JSON parsing with
MUSIC_SERVICE_ACCOUNT_LOGIN_FAILED; Amazon must only appear once a
real OAuth token is present
- Merge missing defaults into stored sources at request time so devices
with older Sources.xml still receive all current defaults
- Fix source providers ETag: was time.Now().UnixMilli() (always new),
now a content hash so If-None-Match/304 works correctly
- Include default sources fingerprint in GetETagForAccount so adding a
new default invalidates cached /full responses on speakers
- Refactor createLearnedSource into classifyLearnedSource +
classifyAsX helpers to reduce cyclomatic complexity below linter limit
- Add regression test for two-device scenario matching production setup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: improve Bose SoundTouch parity, Spotify integration, and data
reliability
- Update XML marshaling for ServicePreset and ServiceRecent to match
Bose parity requirements.
- Add support for adding music sources via
`/streaming/account/{account}/source`.
- Implement HandleBoseAccountToken for Spotify OAuth code exchange and
token persistence.
- Implement atomic file writes in the datastore to prevent data
corruption.
- Add startup logic to initialize default sources for existing devices.
- Expand test coverage with new parity regression and Spotify
integration tests.
---------
Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>