Files
summercms/modules/tide/manifest_contract_test.go
Jakub Zych 5e50b166ef refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
2026-09-28 02:21:02 +02:00

117 lines
3.6 KiB
Go

package tide
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
)
func TestManifestContract(t *testing.T) {
ctx := context.Background()
fixtures := t.TempDir()
if err := os.MkdirAll(filepath.Join(fixtures, "routes"), 0o755); err != nil {
t.Fatal(err)
}
var hits atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hits.Add(1)
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case "/pass":
_, _ = w.Write([]byte(`{"v":1}`))
case "/fail":
_, _ = w.Write([]byte(`{"v":2}`))
default:
_, _ = w.Write([]byte(`{"v":0}`))
}
}))
t.Cleanup(srv.Close)
writeRouteFixture(t, srv.URL, fixtures, "pass", "/pass", `{"v":1}`)
writeRouteFixture(t, srv.URL, fixtures, "fail", "/fail", `{"v":1}`)
m := Manifest{
Version: 1,
AuthGroups: []string{"public"},
Routes: []Route{
routeEntry("GET /pass public", "/pass", StatusPorted, "routes/pass.yaml"),
routeEntry("GET /fail public", "/fail", StatusPorted, "routes/fail.yaml"),
{ID: "GET /none public", Method: http.MethodGet, Path: "/none", AuthGroup: "public", Status: StatusPending},
},
}
before := hits.Load()
cov, err := ReplayManifest(ctx, m, ManifestConfig{Target: srv.URL, Fixtures: fixtures, Mode: ModeRequireRecorded})
if err == nil {
t.Fatal("ported fail plus unrecorded required must be nonzero")
}
if cov.Passing != 1 || cov.Failing != 1 || cov.Unrecorded != 1 || cov.Recorded != 2 {
t.Fatalf("coverage %+v", cov)
}
if cov.Pending != 1 || cov.Ported != 2 {
t.Fatalf("pending/ported counts %+v", cov)
}
if !strings.Contains(cov.SummaryLine(), "recorded 2/3") {
t.Fatalf("summary %s", cov.SummaryLine())
}
if hits.Load()-before != 2 {
t.Fatalf("comparison failure must still replay later recorded flows, extra hits=%d", hits.Load()-before)
}
foundFailPath := false
for _, d := range cov.Diffs {
if strings.Contains(d, "GET /fail public") && (strings.Contains(d, "$.v") || strings.Contains(d, ".v")) {
foundFailPath = true
}
}
if !foundFailPath {
t.Fatalf("coverage diffs missing path for failing route: %v", cov.Diffs)
}
t.Run("pending mismatch is measured not fatal", func(t *testing.T) {
pending := Manifest{
Version: 1,
AuthGroups: []string{"public"},
Routes: []Route{routeEntry("GET /fail public", "/fail", StatusPending, "routes/fail.yaml")},
}
cov, err := ReplayManifest(ctx, pending, ManifestConfig{Target: srv.URL, Fixtures: fixtures})
if err != nil {
t.Fatalf("pending mismatch must not fail: %v", err)
}
if cov.Failing != 1 || cov.Passing != 0 {
t.Fatalf("pending failing %+v", cov)
}
if _, err := ReplayManifest(ctx, pending, ManifestConfig{Target: srv.URL, Fixtures: fixtures, SelfCheck: true}); err == nil {
t.Fatal("self-check must fail pending mismatches")
}
})
t.Run("require-recorded reports unrecorded", func(t *testing.T) {
empty := Manifest{
Version: 1,
AuthGroups: []string{"public"},
Routes: []Route{{
ID: "GET /ghost public", Method: http.MethodGet, Path: "/ghost",
AuthGroup: "public", Status: StatusPending,
}},
}
if err := ValidateManifest(empty, fixtures, ModeAllowIncomplete); err != nil {
t.Fatal(err)
}
if err := ValidateManifest(empty, fixtures, ModeRequireRecorded); err == nil {
t.Fatal("missing cases must fail require-recorded")
}
cov, err := ReplayManifest(ctx, empty, ManifestConfig{Target: srv.URL, Fixtures: fixtures, Mode: ModeRequireRecorded})
if err == nil {
t.Fatal("unrecorded required must error")
}
if cov.Unrecorded != 1 || cov.Passing != 0 {
t.Fatalf("unrecorded coverage %+v", cov)
}
})
}