Skip to content

Commit 702e08a

Browse files
committed
test(server): divergence guard for the deployed route set
Add a parity test that walks the mounted chi router and fails if /v1/answer or /v1/answer/pageindex (or the /v1/query mount) is missing. The deployed cmd/server and standalone cmd/engine binaries serve overlapping APIs from two separate routers and have silently diverged before — this is the regression that left the PageIndex answer endpoints unreachable in production. The guard makes any future drop of a required route fail loudly instead of shipping a half-wired binary.
1 parent f30f9f9 commit 702e08a

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package handler
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/go-chi/chi/v5"
8+
)
9+
10+
// TestRouterParity is a divergence guard. The deployed cmd/server
11+
// binary and the standalone cmd/engine binary serve overlapping route
12+
// sets from two different routers (internal/handler vs internal/api).
13+
// They have silently diverged before — the PageIndex redesign landed
14+
// only on cmd/engine, leaving /v1/answer and /v1/answer/pageindex
15+
// unreachable in production.
16+
//
17+
// This test walks the mounted chi router and asserts that the routes
18+
// the deployed binary is contractually required to expose are present.
19+
// If a future refactor drops one of the answer endpoints (or the
20+
// per-request strategy override's /v1/query mount), this fails loudly
21+
// instead of shipping a binary that's missing half the API.
22+
func TestRouterParity(t *testing.T) {
23+
t.Parallel()
24+
25+
// A zero Deps is enough to construct the router: handlers store
26+
// their dependencies and only dereference them at request time, so
27+
// Walk sees the full route table without any live backend.
28+
h := Router(Deps{})
29+
30+
routes, ok := h.(chi.Routes)
31+
if !ok {
32+
t.Fatalf("Router did not return a chi.Routes (got %T); cannot walk routes", h)
33+
}
34+
35+
got := map[string]bool{}
36+
walk := func(method, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
37+
got[method+" "+route] = true
38+
return nil
39+
}
40+
if err := chi.Walk(routes, walk); err != nil {
41+
t.Fatalf("chi.Walk: %v", err)
42+
}
43+
44+
// The route set the deployed binary MUST expose. Anything added
45+
// here becomes a hard contract; drop a route and this test fails.
46+
want := []string{
47+
"POST /v1/query/",
48+
"POST /v1/answer",
49+
"POST /v1/answer/pageindex",
50+
}
51+
for _, route := range want {
52+
if !got[route] {
53+
t.Errorf("router is missing required route %q\nmounted routes: %v", route, sortedKeys(got))
54+
}
55+
}
56+
}
57+
58+
// TestRouterMountsAnswerEndpoints is the focused assertion the task
59+
// calls out explicitly: /v1/answer and /v1/answer/pageindex must be
60+
// mounted on the deployed router. Kept separate from the broad parity
61+
// set so a failure points straight at the answer-endpoint regression.
62+
func TestRouterMountsAnswerEndpoints(t *testing.T) {
63+
t.Parallel()
64+
65+
routes, ok := Router(Deps{}).(chi.Routes)
66+
if !ok {
67+
t.Fatal("Router did not return a chi.Routes")
68+
}
69+
70+
found := map[string]bool{}
71+
_ = chi.Walk(routes, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
72+
found[route] = true
73+
return nil
74+
})
75+
76+
for _, route := range []string{"/v1/answer", "/v1/answer/pageindex"} {
77+
if !found[route] {
78+
t.Errorf("deployed router must mount %q but does not", route)
79+
}
80+
}
81+
}
82+
83+
func sortedKeys(m map[string]bool) []string {
84+
out := make([]string, 0, len(m))
85+
for k := range m {
86+
out = append(out, k)
87+
}
88+
// Simple insertion sort to avoid pulling in sort just for a test
89+
// diagnostic; the route set is tiny.
90+
for i := 1; i < len(out); i++ {
91+
for j := i; j > 0 && out[j-1] > out[j]; j-- {
92+
out[j-1], out[j] = out[j], out[j-1]
93+
}
94+
}
95+
return out
96+
}

0 commit comments

Comments
 (0)