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:
@@ -136,7 +136,7 @@ func degradedMessageFor(st BackupTargetState) string {
|
||||
return backupTargetDegradedText
|
||||
}
|
||||
|
||||
// handleBackupTargetState serves GET /api/backup-target — the dashboard's source for the degraded
|
||||
// handleBackupTargetState serves GET /api/storage/backup-target — the dashboard's source for the degraded
|
||||
// banner and the offer.
|
||||
func (s *Server) handleBackupTargetState(w http.ResponseWriter, r *http.Request) {
|
||||
st := s.resolveBackupTargetState(r.Context())
|
||||
@@ -157,7 +157,7 @@ func (s *Server) handleBackupTargetState(w http.ResponseWriter, r *http.Request)
|
||||
writeDiskJSON(w, http.StatusOK, true, "", out)
|
||||
}
|
||||
|
||||
// handleBackupTargetAssign is the ACCEPTANCE of the offer — POST /api/backup-target/assign.
|
||||
// handleBackupTargetAssign is the ACCEPTANCE of the offer — POST /api/storage/backup-target/assign.
|
||||
//
|
||||
// NOTHING calls this except an explicit customer action. Registration does not, the drive-gate does
|
||||
// not, and no scheduler does (E-2 §3: a drive never acquires a role by appearing). Declining is
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -356,9 +356,9 @@ func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStorageRegister(w, r)
|
||||
// E-2 Parts 3+4. State drives the degraded banner and the OFFER; assign is the offer's
|
||||
// ACCEPTANCE and the ONLY writer of the role — registration above deliberately does not set it.
|
||||
case r.URL.Path == "/api/backup-target" && r.Method == http.MethodGet:
|
||||
case r.URL.Path == "/api/storage/backup-target" && r.Method == http.MethodGet:
|
||||
s.handleBackupTargetState(w, r)
|
||||
case r.URL.Path == "/api/backup-target/assign" && r.Method == http.MethodPost:
|
||||
case r.URL.Path == "/api/storage/backup-target/assign" && r.Method == http.MethodPost:
|
||||
s.handleBackupTargetAssign(w, r)
|
||||
case r.URL.Path == "/api/storage/migrate" && r.Method == http.MethodPost:
|
||||
s.handleStorageMigrate(w, r)
|
||||
|
||||
Reference in New Issue
Block a user