- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
105 lines
2.9 KiB
Go
105 lines
2.9 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
|
|
}
|
|
if err := store.Save(); err != nil {
|
|
return result, fmt.Errorf("tide: replay step %s: %w", step.ID, err)
|
|
}
|
|
want.Response, err = expandResponse(want.Response, store)
|
|
if err != nil {
|
|
sr.OK = false
|
|
result.OK = false
|
|
sr.Diffs = append(sr.Diffs, Diff{Path: "expected", Expected: "resolved placeholders", Actual: err.Error()})
|
|
result.Steps = append(result.Steps, sr)
|
|
skipRest = true
|
|
continue
|
|
}
|
|
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
|
|
}
|