feat(12-01): record multipart uploads and match Winter upload URLs
- 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
This commit is contained in:
@@ -77,6 +77,27 @@ for _, target := range []string{port.URL, broken.URL} {
|
||||
|
||||
The first port returns other IDs and timestamps and passes; the second changed a title and fails with the JSON path of the difference. A difference makes `tide.ReplayFlow` return a `tide.MismatchError` that carries the full `tide.Result`.
|
||||
|
||||
## Uploads
|
||||
|
||||
A step that uploads a file describes its multipart body as `parts` instead of a `body`. Each `tide.Part` is a text field with a `value`, or a file field whose bytes live in a file beside the fixture:
|
||||
|
||||
```yaml
|
||||
request:
|
||||
method: POST
|
||||
path: /api/blog/posts/{{id:post}}/photos
|
||||
parts:
|
||||
- name: caption
|
||||
value: Cover
|
||||
- name: file
|
||||
file: files/cover.png
|
||||
content_type: image/png
|
||||
sha256: <sha256 of files/cover.png>
|
||||
```
|
||||
|
||||
Recording and replaying encode the parts in order with one fixed boundary, `tide.MultipartBoundary`, so the reference backend and the port receive the same bytes. Set `BaseDir` on `tide.RecordConfig` and `tide.ReplayConfig` to the fixture directory the files are read from. `tide.LoadFlow` refuses a fixture whose part file is missing or no longer matches its `sha256`, and the file bytes are never copied into the YAML.
|
||||
|
||||
Upload responses carry URLs with random parts: the partition and disk name of the original, and the file id in a thumbnail name. Under `url` and `thumb_url` keys, the normalizer checks the WinterCMS shape below the uploads prefix (`tide.ReplayConfig` `UploadPrefix`, `tide.DefaultUploadPrefix` by default) and masks only the random parts. The prefix, the thumbnail size and mode and the extension are still compared, so a port that serves `/storage/uploads/...` or makes 100 by 100 thumbnails instead of 200 by 200 fails the diff.
|
||||
|
||||
## The parity commands
|
||||
|
||||
The `summer` CLI wraps tide. Run the reference backend and the port on loopback addresses:
|
||||
@@ -100,6 +121,6 @@ Realtime side effects are part of the contract too. `summer parity:broadcasts` r
|
||||
summer parity:broadcasts --flow testdata/broadcasts/flows/post-lifecycle.yaml --step delete --name deleted --target http://127.0.0.1:8000 --vars /tmp/parity/vars.yaml --out testdata/broadcasts/deleted.yaml
|
||||
```
|
||||
|
||||
Point the reference backend's Centrifugo API URL at the recorder (`127.0.0.1:8424` by default). `--step` keeps only the publications of one step, running the earlier steps as setup. Timestamps, the actor and captured IDs are masked (`tide.NormalizePublications`), so the Go port's publications, recorded the same way, compare with `tide.DiffPublications`. A golden with `--pending` set is recorded but not yet asserted.
|
||||
Point the reference backend's Centrifugo API URL at the recorder (`127.0.0.1:8424` by default). `--step` keeps only the publications of one step, running the earlier steps as setup. Timestamps, the actor, the `*_at` dates inside a published album and captured IDs are masked (`tide.NormalizePublications`), so the Go port's publications, recorded the same way, compare with `tide.DiffPublications`. A golden with `--pending` set is recorded but not yet asserted.
|
||||
|
||||
On the Go side, the memory realtime driver records publications the same way in tests; see [Realtime](realtime.md).
|
||||
|
||||
Reference in New Issue
Block a user