Files
summercms/tide/replay.go
Jakub Zych 24c1f43e84 feat(02-02): record manifest route cases and report coverage
Drive ordered route cases through RecordFlow, resume in batches of
15, and print recorded/passing/failing/unrecorded coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 12:36:48 +02:00

94 lines
2.6 KiB
Go

package tide
import (
"context"
"fmt"
"strings"
)
// ReplayFlow executes each recorded step against target and diffs responses.
func ReplayFlow(ctx context.Context, flow Flow, cfg ReplayConfig) (Result, error) {
if err := validateFlow(flow); err != nil {
return Result{}, err
}
if strings.TrimSpace(cfg.Target) == "" {
return Result{}, fmt.Errorf("tide: replay target is required")
}
client := cfg.Client
if client == nil {
client = defaultClient()
}
limit := maxBody(cfg.MaxBody)
store := cfg.Store
if store == nil {
store = mustMemoryStore()
}
result := Result{OK: true, Steps: make([]StepResult, 0, len(flow.Steps))}
skipRest := false
for _, step := range flow.Steps {
if skipRest {
result.Steps = append(result.Steps, StepResult{ID: step.ID, Skipped: true})
continue
}
sr := StepResult{ID: step.ID, OK: true}
want := step
if err := materializeSidecar(cfg.BaseDir, &want.Response); err != nil {
return result, fmt.Errorf("tide: replay step %s: %w", step.ID, err)
}
req, err := expandRequest(step.Request, store)
if err != nil {
sr.OK = false
result.OK = false
sr.Diffs = append(sr.Diffs, Diff{Path: "request", Expected: "resolved placeholders", Actual: err.Error()})
result.Steps = append(result.Steps, sr)
skipRest = true
continue
}
got, err := doStep(ctx, client, cfg.Target, req, limit)
if err != nil {
return result, fmt.Errorf("tide: replay step %s: %w", step.ID, err)
}
live := step
live.Request = req
live.Response = got
if err := CaptureStep(store, &live); err != nil {
sr.OK = false
result.OK = false
sr.Diffs = append(sr.Diffs, Diff{Path: "capture", Expected: "captured value", Actual: err.Error()})
result.Steps = append(result.Steps, sr)
skipRest = true
continue
}
_ = ScrubStep(store, &live)
sr.Diffs = append(sr.Diffs, compareStep(want, live.Response)...)
if len(sr.Diffs) > 0 {
sr.OK = false
result.OK = false
}
result.Steps = append(result.Steps, sr)
}
if !result.OK {
return result, &MismatchError{Result: result}
}
return result, nil
}
func mustMemoryStore() *Store {
s, _ := OpenStore("")
return s
}
func compareStep(want Step, got Response) []Diff {
var diffs []Diff
if want.Response.Status != 0 && got.Status != want.Response.Status {
diffs = append(diffs, Diff{
Path: "status",
Expected: fmt.Sprintf("%d", want.Response.Status),
Actual: fmt.Sprintf("%d", got.Status),
})
}
diffs = append(diffs, compareHeaders(want.Response.Headers, got.Headers, want.Headers)...)
diffs = append(diffs, compareBodies(want.Response, got, want)...)
return diffs
}