- attach.PublicURL and (*File).URL build Winter File::getPath() URLs; the thumbnailer decodes webp via golang.org/x/image v0.46.0 and checks the image size from the header before decoding - tide requests carry multipart parts (files beside the fixture pinned by sha256) encoded with the fixed MultipartBoundary, so PHP and Go receive byte-identical bodies - tide masks the random partition, disk name and file id of url/thumb_url upload URLs while still diffing prefix, size, mode and extension, and NormalizePublications masks Carbon dates in the published album
113 lines
3.3 KiB
Go
113 lines
3.3 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
|
|
}
|
|
req, err = prepareRequest(req, cfg.BaseDir)
|
|
if err != nil {
|
|
return result, fmt.Errorf("tide: replay step %s: %w", step.ID, err)
|
|
}
|
|
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, compareStepWith(want, live.Response, maskOptions{uploadPrefix: cfg.UploadPrefix})...)
|
|
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 {
|
|
return compareStepWith(want, got, maskOptions{})
|
|
}
|
|
|
|
func compareStepWith(want Step, got Response, opts maskOptions) []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, compareBodiesWith(want.Response, got, want, opts)...)
|
|
return diffs
|
|
}
|