5.7 KiB
tide
HTTP parity toolkit that records request and response fixtures from a reference backend, replays them against a new one and reports normalized differences.
import "git.golem15.com/golem15/summercms/modules/tide"
Overview
tide is the acceptance-test engine for porting an existing WinterCMS or other PHP backend to SummerCMS: the reference backend's real responses define the contract, and the Go port must reproduce them. It records YAML fixtures (flows of request and response steps) either by driving a spec against a target or by sitting as a loopback reverse proxy in front of the reference backend while a real client uses it, then replays those fixtures against the port and diffs the responses after masking values that legitimately differ, such as IDs and timestamps. The summer CLI's parity:proxy, parity:record and parity:replay commands are thin wrappers around this package. It has no WinterCMS counterpart.
Features
- Flow fixtures:
tide.Flowis a versioned, ordered list oftide.Stepvalues, loaded strictly (unknown fields rejected) withtide.LoadFlowandtide.ParseFlow, and written atomically withtide.SaveFlowortide.SaveFlowExclusive. Response bodies can live in sidecar files, confined to the fixture directory and checked against an optional SHA-256 digest. - Recording:
tide.RecordFlowexecutes a spec flow against a target URL and fills in the responses, with a bounded body size (tide.DefaultMaxBody, 8 MiB). - Recording proxy:
tide.NewProxybuilds a reverse proxy that only binds to and forwards to loopback addresses, groups traffic into named sessions (from thetide.SessionHeaderrequest header or a default session) and writes one fixture per complete session ontide.Proxy.Flush. - Capture rules:
tide.Rules(loaded withtide.LoadRules) decide which request and response headers are kept per route and which values are captured into variables, from response JSON paths, headers, redirect query strings or form fields. - Variables:
tide.Storeholds captured values such as tokens and IDs in a mode-0600 file,tide.Store.Expandsubstitutes{{name}}placeholders before a request is sent, andtide.ScrubStepputs placeholders back into fixtures. Scrubbing fails when a step still holds an unclassified token- or password-shaped value, so credentials do not leak into committed fixtures. - Replay and diff:
tide.ReplayFlowre-sends each step, compares status, a fixed set of contract headers and the body, and returnstide.Resultwith per-steptide.Diffentries. JSON bodies are compared structurally after maskingid,*_idand*_idsvalues and*_attimestamps; other bodies are compared byte for byte. - Manifests:
tide.Manifestlists routes with auth groups, a pending or ported status, cases and fixture paths;tide.RecordManifestrecords missing cases in batches of at mosttide.MaxBatch, andtide.ReplayManifestreplays every recorded case into atide.Coveragetable.
Usage
Record a flow once against the reference backend, then replay it against the port:
spec, err := tide.LoadFlow("testdata/parity/posts.spec.yaml")
if err != nil {
return err
}
flow, err := tide.RecordFlow(ctx, spec, tide.RecordConfig{Target: "http://127.0.0.1:8000"})
if err != nil {
return err
}
if err := tide.SaveFlow("testdata/parity/posts.yaml", flow); err != nil {
return err
}
res, err := tide.ReplayFlow(ctx, flow, tide.ReplayConfig{
Target: "http://127.0.0.1:8080",
BaseDir: "testdata/parity",
})
if err != nil {
return err
}
for _, step := range res.Steps {
for _, d := range step.Diffs {
fmt.Printf("%s %s: want %s, got %s\n", step.ID, d.Path, d.Expected, d.Actual)
}
}
API reference
| Identifier | Description |
|---|---|
tide.Flow |
Versioned, ordered list of request and response steps; the fixture format. |
tide.Step |
One request and response pair, with capture and normalizer overrides. |
tide.LoadFlow |
Reads and validates a flow file. |
tide.SaveFlow |
Writes a validated flow atomically. |
tide.RecordFlow |
Executes a spec flow against a target and returns the recorded flow. |
tide.ReplayFlow |
Replays a recorded flow against a target and diffs every step. |
tide.Result |
Replay outcome: overall status and per-step tide.StepResult values. |
tide.Diff |
One structural JSON or byte-level mismatch. |
tide.MismatchError |
Error that carries a failing tide.Result. |
tide.NewProxy |
Builds the loopback recording reverse proxy from a tide.ProxyConfig. |
tide.Proxy |
The recording proxy: tide.Proxy.Handler, tide.Proxy.ListenAndServe, tide.Proxy.Flush. |
tide.Rules |
Header keep lists and capture rules for proxy sessions. |
tide.Store |
Named capture variables, optionally persisted to a private file. |
tide.OpenStore |
Opens a file-backed store, or a memory-only store for an empty path. |
tide.Manifest |
Route list with auth groups, status, cases and fixture paths. |
tide.ValidateManifest |
Checks a manifest in tide.ModeAllowIncomplete or tide.ModeRequireRecorded mode. |
tide.RecordManifest |
Records a manifest's seed flow and missing route cases in batches. |
tide.ReplayManifest |
Replays every recorded route case and builds a coverage table. |
tide.Coverage |
Recorded, passing, failing and unrecorded counts, with table rows and a summary line. |
Dependencies
- SummerCMS modules: none.
- Third-party:
github.com/goccy/go-yaml(fixture, rules and manifest parsing). - Standard library:
net/http,net/http/httputil,encoding/json,crypto/sha256, among others.
Testing
go test ./modules/tide/...
The tests run recording, the proxy and replay against local net/http/httptest servers and use the sample spec in modules/tide/testdata/; they need no external services.