feat(02-02): capture, scrub, and strictly diff stateful flows

Resolve named placeholders from a private variable store, mask
dates and ids after shape checks, and keep comparing independent steps.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-17 12:32:49 +02:00
parent bb6a5a91c9
commit aa165fe3d0
13 changed files with 1390 additions and 203 deletions

View File

@@ -5,21 +5,85 @@ import (
"encoding/json"
"fmt"
"mime"
"net/http"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
func compareBodies(want, got Response) []Diff {
func compareBodies(want, got Response, step Step) []Diff {
wantJSON := isJSONContentType(want.Headers)
gotJSON := isJSONContentType(got.Headers)
if wantJSON && gotJSON {
return diffJSON([]byte(want.Body), []byte(got.Body))
wantRaw, wantDiffs := normalizeJSON([]byte(want.Body), step)
gotRaw, gotDiffs := normalizeJSON([]byte(got.Body), step)
diffs := append([]Diff{}, wantDiffs...)
diffs = append(diffs, gotDiffs...)
diffs = append(diffs, diffJSON(wantRaw, gotRaw)...)
return diffs
}
return diffBytes([]byte(want.Body), []byte(got.Body))
}
var globalCompareHeaders = []string{
"Content-Type",
"X-Total-Count",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Credentials",
"Access-Control-Allow-Headers",
"Access-Control-Allow-Methods",
"Access-Control-Expose-Headers",
}
var extraCompareHeaders = []string{
"Cache-Control",
"Pragma",
"WWW-Authenticate",
"Content-Disposition",
"Link",
}
var neverCompareHeaders = []string{
"Date",
"Server",
"X-Request-Id",
"X-Request-ID",
"X-Correlation-Id",
}
func compareHeaders(want, got, extra map[string]string) []Diff {
allow := map[string]bool{}
for _, n := range globalCompareHeaders {
allow[http.CanonicalHeaderKey(n)] = true
}
for _, n := range extraCompareHeaders {
allow[http.CanonicalHeaderKey(n)] = true
}
for n := range extra {
allow[http.CanonicalHeaderKey(n)] = true
}
for _, n := range neverCompareHeaders {
delete(allow, http.CanonicalHeaderKey(n))
}
var diffs []Diff
for k, wv := range want {
ck := http.CanonicalHeaderKey(k)
if !allow[ck] {
continue
}
gv := headerValue(got, k)
if gv == wv {
continue
}
if gv == "" {
gv = "<missing>"
}
diffs = append(diffs, Diff{Path: "header." + ck, Expected: wv, Actual: gv})
}
return diffs
}
func isJSONContentType(headers map[string]string) bool {
ct := headerValue(headers, "Content-Type")
if ct == "" {