89 lines
5.7 KiB
Markdown
89 lines
5.7 KiB
Markdown
# 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.Flow` is a versioned, ordered list of `tide.Step` values, loaded strictly (unknown fields rejected) with `tide.LoadFlow` and `tide.ParseFlow`, and written atomically with `tide.SaveFlow` or `tide.SaveFlowExclusive`. Response bodies can live in sidecar files, confined to the fixture directory and checked against an optional SHA-256 digest.
|
|
- Recording: `tide.RecordFlow` executes a spec flow against a target URL and fills in the responses, with a bounded body size (`tide.DefaultMaxBody`, 8 MiB).
|
|
- Recording proxy: `tide.NewProxy` builds a reverse proxy that only binds to and forwards to loopback addresses, groups traffic into named sessions (from the `tide.SessionHeader` request header or a default session) and writes one fixture per complete session on `tide.Proxy.Flush`.
|
|
- Capture rules: `tide.Rules` (loaded with `tide.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.Store` holds captured values such as tokens and IDs in a mode-0600 file, `tide.Store.Expand` substitutes `{{name}}` placeholders before a request is sent, and `tide.ScrubStep` puts 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.ReplayFlow` re-sends each step, compares status, a fixed set of contract headers and the body, and returns `tide.Result` with per-step `tide.Diff` entries. JSON bodies are compared structurally after masking `id`, `*_id` and `*_ids` values and `*_at` timestamps; other bodies are compared byte for byte.
|
|
- Manifests: `tide.Manifest` lists routes with auth groups, a pending or ported status, cases and fixture paths; `tide.RecordManifest` records missing cases in batches of at most `tide.MaxBatch`, and `tide.ReplayManifest` replays every recorded case into a `tide.Coverage` table.
|
|
|
|
## Usage
|
|
|
|
Record a flow once against the reference backend, then replay it against the port:
|
|
|
|
```go
|
|
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
|
|
|
|
```sh
|
|
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.
|