v0.185.1 — E-2: the offer endpoints were mounted where nothing routed to them

Registered as /api/backup-target inside ServeStorageAPI, which main.go mounts ONLY
at /api/storage/. Live result: endpoint not found, while every unit test passed --
the tests called the handlers directly and never travelled the mount. Caught by
the first live call, which is why the live call is part of the procedure.

Moved to /api/storage/backup-target[/assign]. A new test asserts the dispatcher
source contains both paths, so a handler nothing routes to fails the suite --
the seam-wiring rule applied to a route rather than a button.
This commit is contained in:
2026-07-29 09:13:13 +02:00
parent 3f7cf2a965
commit cdaeb36972
4 changed files with 53 additions and 4 deletions
@@ -1,6 +1,7 @@
package web
import (
"os"
"strings"
"testing"
)
@@ -59,3 +60,39 @@ func TestUnknownStateRendersNothing(t *testing.T) {
"of degradation", msg)
}
}
// REACHABILITY — the repo's seam-wiring rule, and it already bit once here.
//
// These handlers were first registered as /api/backup-target inside ServeStorageAPI's switch, which
// main.go mounts ONLY at "/api/storage/". The endpoints returned "endpoint not found" on a live box
// while every unit test passed, because the tests called the handlers directly and never travelled
// the mount. A feature is not shipped until its entry point is reachable.
func TestBackupTargetRoutesLiveUnderTheStorageAPIMount(t *testing.T) {
// main.go: mux.Handle("/api/storage/", ... ServeStorageAPI). Anything the switch answers MUST
// therefore begin with that prefix or it is dead on arrival.
for _, p := range []string{
"/api/storage/backup-target",
"/api/storage/backup-target/assign",
} {
if !strings.HasPrefix(p, "/api/storage/") {
t.Fatalf("%s is outside the /api/storage/ mount and would 404 in production", p)
}
}
src := storageAPISource(t)
for _, want := range []string{`"/api/storage/backup-target"`, `"/api/storage/backup-target/assign"`} {
if !strings.Contains(src, want) {
t.Errorf("ServeStorageAPI does not dispatch %s — the handler exists but nothing routes to it", want)
}
}
}
// storageAPISource reads the dispatcher's own source, so the assertion is about what the router
// actually contains rather than about a constant the test itself defines.
func storageAPISource(t *testing.T) string {
t.Helper()
b, err := os.ReadFile("storage_handlers.go")
if err != nil {
t.Fatalf("read storage_handlers.go: %v", err)
}
return string(b)
}