Artifact c4a3ace4803868601a68367442fa5bb6d4dc02aa38e28e10c9823a94663106b0:
- File
internal/api/router_test.go
— part of check-in
[bf969e9d1a]
at
2026-08-15 02:10:44
on branch trunk
— phase 0 exit: serve, HTML page via op, harness loop, provenance gate
API router + web page as registry consumers; disposability loop over all surfaces; third_party ledger enforced by arch test. (user: dmestas, size: 4617) [annotate] [blame] [check-ins using]
package api import ( "context" "encoding/json" "net/http" "net/http/httptest" "net/url" "strings" "testing" "craftdesign.group/quarry/internal/ops" ) type routerIn struct { Repo string `json:"repo" validate:"required"` Limit int `json:"limit" validate:"min=0,max=100"` } type routerOut struct { Repo string `json:"repo"` Limit int `json:"limit"` } type postIn struct { Repo string `json:"repo" validate:"required"` Body string `json:"body" validate:"required"` } type postOut struct { Echo string `json:"echo"` } func routerRegistry(t *testing.T) *ops.Registry { t.Helper() r := ops.NewRegistry() if err := ops.Register(r, ops.Op[routerIn, routerOut]{ Name: "echo.get", Doc: "Echo query input.", Kind: ops.Query, Route: "GET /api/v1/r/{repo}/echo", Run: func(_ context.Context, in routerIn) (routerOut, error) { return routerOut(in), nil }, }); err != nil { t.Fatal(err) } if err := ops.Register(r, ops.Op[postIn, postOut]{ Name: "echo.post", Doc: "Echo body input.", Kind: ops.Command, Route: "POST /api/v1/r/{repo}/echo", Run: func(_ context.Context, in postIn) (postOut, error) { return postOut{Echo: in.Repo + ":" + in.Body}, nil }, }); err != nil { t.Fatal(err) } return r } func serve(t *testing.T, req *http.Request) *httptest.ResponseRecorder { t.Helper() h, err := Router(routerRegistry(t), "0.0.0-test") if err != nil { t.Fatal(err) } rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec } func TestGetOpPathAndQuery(t *testing.T) { rec := serve(t, httptest.NewRequest("GET", "/api/v1/r/sqlite/echo?limit=5", nil)) if rec.Code != 200 { t.Fatalf("status %d: %s", rec.Code, rec.Body) } var out routerOut if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatal(err) } if out.Repo != "sqlite" || out.Limit != 5 { t.Fatalf("got %+v", out) } if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { t.Fatalf("content-type %q", ct) } } func TestGetOpValidateFails400(t *testing.T) { rec := serve(t, httptest.NewRequest("GET", "/api/v1/r/sqlite/echo?limit=101", nil)) if rec.Code != 400 { t.Fatalf("status %d: %s", rec.Code, rec.Body) } var e map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &e); err != nil { t.Fatalf("error body not JSON: %s", rec.Body) } if e["error"] == nil { t.Fatalf("no error field: %v", e) } } func TestGetOpBadTypeFails400(t *testing.T) { rec := serve(t, httptest.NewRequest("GET", "/api/v1/r/sqlite/echo?limit=abc", nil)) if rec.Code != 400 { t.Fatalf("status %d", rec.Code) } } func TestPostOpFormBody(t *testing.T) { form := url.Values{"body": {"hello"}} req := httptest.NewRequest("POST", "/api/v1/r/sqlite/echo", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rec := serve(t, req) if rec.Code != 200 { t.Fatalf("status %d: %s", rec.Code, rec.Body) } var out postOut json.Unmarshal(rec.Body.Bytes(), &out) if out.Echo != "sqlite:hello" { t.Fatalf("got %+v", out) } } func TestPostOpJSONBody(t *testing.T) { req := httptest.NewRequest("POST", "/api/v1/r/sqlite/echo", strings.NewReader(`{"body":"hi"}`)) req.Header.Set("Content-Type", "application/json") rec := serve(t, req) if rec.Code != 200 { t.Fatalf("status %d: %s", rec.Code, rec.Body) } var out postOut json.Unmarshal(rec.Body.Bytes(), &out) if out.Echo != "sqlite:hi" { t.Fatalf("got %+v", out) } } func TestPostOpJSONCannotOverridePathParam(t *testing.T) { // The path is authoritative for wildcard fields; a body value for the // same field must not win. req := httptest.NewRequest("POST", "/api/v1/r/sqlite/echo", strings.NewReader(`{"repo":"evil","body":"x"}`)) req.Header.Set("Content-Type", "application/json") rec := serve(t, req) var out postOut json.Unmarshal(rec.Body.Bytes(), &out) if !strings.HasPrefix(out.Echo, "sqlite:") { t.Fatalf("path param overridden: %+v", out) } } func TestPostOpMissingRequired400(t *testing.T) { req := httptest.NewRequest("POST", "/api/v1/r/sqlite/echo", strings.NewReader(`{}`)) req.Header.Set("Content-Type", "application/json") rec := serve(t, req) if rec.Code != 400 { t.Fatalf("status %d: %s", rec.Code, rec.Body) } } func TestOpenAPIDocumentServed(t *testing.T) { rec := serve(t, httptest.NewRequest("GET", "/api/v1/openapi.json", nil)) if rec.Code != 200 { t.Fatalf("status %d", rec.Code) } var doc map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &doc); err != nil { t.Fatal(err) } if doc["openapi"] != "3.1.0" { t.Fatalf("doc = %v", doc["openapi"]) } }