- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
117 lines
3.6 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|