refactor(service): stub the unused /accounts/* mirror with a 501 "report it" handler (refs #451)

Shrink the route surface the #451 refactor must preserve by retiring the
/accounts/{account}/* compatibility mirror. Across the full recording corpus
(all _/backup/*, _/mitm, _/i195, _/issue-94, captures + data/ + tests/, 139k+
.http files) no speaker or app uses the /accounts prefix, and every operation it
offered is served by the /streaming/account/* paths real clients actually use.

- New HandleUnsupported: returns 501 and logs the full request + client IP + a
  "please report this" message, so any real-world use surfaces instead of being
  silently dropped, and the prefix becomes a clean removal candidate.
- Re-point every /accounts/* route to it. The frozen /streaming/* contract is
  left entirely on its real handlers (those stay even where our corpus didn't
  exercise them — absence of capture is not proof of disuse).
- Migrate the integration tests off the /accounts mirror onto their recorded
  /streaming/account/* equivalents (register/unregister/spotify_full_flow), then
  pin the mirror's 501 contract in unsupported_routes.http.
- Router + frozen-route-coverage golden files updated accordingly.

make test-http-client: 91 requests, 0 failed. go test + golangci-lint clean.

Note for release time: call out the intentional /accounts/* 501 breakage in the
release notes' Noteworthy section (use /streaming/account/* instead).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-06-06 19:11:24 +02:00
co-authored by Claude Opus 4.8
parent 44f3ccfc18
commit ea6ee3e097
9 changed files with 263 additions and 69 deletions
@@ -1,5 +1,5 @@
### POST /{{accountId}}/devices (Register Device)
POST {{host}}/accounts/{{accountId}}/devices
### POST /streaming/account/{{accountId}}/device/ (Register Device)
POST {{host}}/streaming/account/{{accountId}}/device/
Content-Type: application/vnd.bose.streaming-v1.2+xml
Authorization: Bearer {{token}}
@@ -40,7 +40,7 @@ Authorization: Basic admin change_me!
# 4. Verify Marge Source Registration and extract Spotify Source ID
# @name Get Marge Sources
GET {{host}}/accounts/{{accountId}}/sources
GET {{host}}/streaming/account/{{accountId}}/sources
> {%
client.test("Spotify source registered in Marge", function() {
@@ -94,7 +94,7 @@ GET {{host}}/accounts/{{accountId}}/sources
# 5. Add a Spotify Preset
# @name Update Preset
POST {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/presets/1
PUT {{host}}/streaming/account/{{accountId}}/device/{{deviceId}}/preset/1
Content-Type: application/xml
<preset id="1">
@@ -114,7 +114,7 @@ Content-Type: application/xml
# 6. Verify Preset in Marge
# @name Get Presets
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/presets
GET {{host}}/streaming/account/{{accountId}}/device/{{deviceId}}/presets
> {%
client.test("Verify preset content", function() {
@@ -1,5 +1,5 @@
### DELETE /{{accountId}}/devices/{{deviceId}} (Unregister Device)
DELETE {{host}}/accounts/{{accountId}}/devices/{{deviceId}}
### DELETE /streaming/account/{{accountId}}/device/{{deviceId}} (Unregister Device)
DELETE {{host}}/streaming/account/{{accountId}}/device/{{deviceId}}
Authorization: Bearer {{token}}
> {%
@@ -7,13 +7,3 @@ Authorization: Bearer {{token}}
client.assert(response.status === 200, "Response status is not 200");
});
%}
### DELETE /streaming/account/{{accountId}}/device/{{deviceId}} (Unregister Device Variant)
DELETE {{host}}/streaming/account/{{accountId}}/device/{{deviceId}}
Authorization: Bearer {{token}}
> {%
client.test("Device unregistered successfully (variant)", function() {
client.assert(response.status === 200, "Response status is not 200");
});
%}
@@ -0,0 +1,147 @@
### Unsupported (zero-usage) /accounts/* mirror -> 501
###
### The /accounts/* group mirrored /streaming/account/* for compatibility, but no
### speaker or app was ever observed using this prefix across the full recording
### corpus, and every operation it offers is provably served via the
### /streaming/account/* paths that real clients DO use (the integration tests
### were migrated onto those). The whole mirror is therefore wired to
### HandleUnsupported, which logs the full request + client IP + a "please report
### this" message and returns 501, so any real-world use surfaces instead of
### being silently broken and the prefix becomes a clean removal candidate for
### the #451 refactor. These tests pin that 501 contract.
###
### NOTE: the real /streaming/account/* (and /streaming/...) routes are NOT
### stubbed — those are the frozen Bose contract and stay on their real handlers
### even where our corpus didn't exercise them.
### GET /accounts/{a}/full
GET {{host}}/accounts/{{accountId}}/full
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/sources (real clients use /streaming/account/{a}/sources)
GET {{host}}/accounts/{{accountId}}/sources
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices
GET {{host}}/accounts/{{accountId}}/devices
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### POST /accounts/{a}/devices (real clients use POST /streaming/account/{a}/device/)
POST {{host}}/accounts/{{accountId}}/devices
User-Agent: Bose_Lisa/27.0.6
Content-Type: application/xml
<device/>
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### DELETE /accounts/{a}/devices/{deviceId} (real clients use DELETE /streaming/account/{a}/device/{d})
DELETE {{host}}/accounts/{{accountId}}/devices/{{deviceId}}
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices/{deviceId}/group
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/group
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices/{deviceId}/group/
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/group/
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices/{deviceId}/group/server
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/group/server
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices/{deviceId}/group/member
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/group/member
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices/{deviceId}/presets (real clients use /streaming/account/{a}/device/{d}/presets)
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/presets
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### GET /accounts/{a}/devices/{deviceId}/recents
GET {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/recents
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### POST /accounts/{a}/devices/{deviceId}/recents
POST {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/recents
User-Agent: Bose_Lisa/27.0.6
Content-Type: application/xml
<recent/>
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### POST /accounts/{a}/devices/{deviceId}/presets/{n} (real clients use PUT /streaming/account/{a}/device/{d}/preset/{n})
POST {{host}}/accounts/{{accountId}}/devices/{{deviceId}}/presets/1
User-Agent: Bose_Lisa/27.0.6
Content-Type: application/xml
<preset/>
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### POST /accounts/{a}/group
POST {{host}}/accounts/{{accountId}}/group
User-Agent: Bose_Lisa/27.0.6
Content-Type: application/xml
<group/>
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### POST /accounts/{a}/group/
POST {{host}}/accounts/{{accountId}}/group/
User-Agent: Bose_Lisa/27.0.6
Content-Type: application/xml
<group/>
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### POST /accounts/{a}/group/{groupId}
POST {{host}}/accounts/{{accountId}}/group/1
User-Agent: Bose_Lisa/27.0.6
Content-Type: application/xml
<group/>
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### DELETE /accounts/{a}/group
DELETE {{host}}/accounts/{{accountId}}/group
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### DELETE /accounts/{a}/group/
DELETE {{host}}/accounts/{{accountId}}/group/
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}
### DELETE /accounts/{a}/group/{groupId}
DELETE {{host}}/accounts/{{accountId}}/group/1
User-Agent: Bose_Lisa/27.0.6
> {% client.test("501", function () { client.assert(response.status === 501, "got " + response.status); }); %}