Skip to content

feat: an acceptance test replays a request the API took - #119

Merged
ShocOne merged 1 commit into
mainfrom
feat/an-acceptance-test-replays-a-request-the-api-took
Aug 26, 2026
Merged

feat: an acceptance test replays a request the API took#119
ShocOne merged 1 commit into
mainfrom
feat/an-acceptance-test-replays-a-request-the-api-took

Conversation

@ShocOne

@ShocOne ShocOne commented Aug 26, 2026

Copy link
Copy Markdown
Member

Every generated acceptance test failed at its first step. The reason was the same one every time, in a different field: the configuration was derived from the document rather than taken from the API.

The probe already gets creates accepted. It then threw the body away — accepted bodies lived in memory only long enough to diff variants — and generation went back to the document and invented values again. So a fixture sent tfpfgen-test-icon where ThousandEyes only takes LABEL, and each such field had to be found and fixed one at a time.

What changed

The probe records what it got accepted, request and response, to audit/bodies/<entity>.bodies.json:

{ "entity": "tag",
  "minimal": { "status": 201,
    "request": {"accessType":"all","key":"branch","objectType":"test","type":"static","value":"sfo"} } }

Acceptance configurations are built from that. A configuration is now a request the API demonstrably took, rather than a reconstruction resembling one. Unit configurations stay derived — their mocks are built from the same derivation, and the two must not disagree.

Two searches bracket the create. Additive for the minimal: the document's required is only a hypothesis, so a refused create adds one field at a time until the API answers 2xx. This is the first thing that makes an entity declaring nothing required creatable at all. Subtractive for the maximal: what the API objects to is dropped until it takes the rest. Both bounded per entity; a refused create makes no object, so a search costs requests rather than debris.

The response decides what a configuration may hold. A property the API accepts and never returns cannot live in one — terraform reads a value that never comes back as the provider losing it — so it is dropped and the reason written into the file:

# (every optional attribute) skipped: no create larger than the minimal one was accepted,
# so this configuration is the minimal one

Live names carry a per-run suffix from a random_string the configuration declares, so a re-run never collides with what the last one left behind. Only invented names take it: a document-supplied value is one the API accepts, and appending to it could make it invalid.

Two workarounds removed

Both were mine, from chasing these failures field by field:

  • fixtures were sending x-tfpfgen-server-default as an input value. A server default is what you get back when you omit a field; sending it back is a category error that happened to work for icon: LABEL.
  • the state mapper had been changed to never null an optional attribute. That fixed one symptom and cost the provider any ability to notice a server clearing a field.

The four test assertions rewritten for that mapper change are restored to their original form.

Measured

tags/v1/tag passes all three steps of the acceptance suite — create minimal, import verify, update to maximal — on a replayed body. make check green at 90.7%; both pilot trees regenerate and pass postcheck.

Known, not fixed here

No maximal is recorded yet for any entity. runCreateMinimal leaves its object alive, then runCreateMaximal tries to create a second one and the API refuses a duplicate. The reduction runs correctly — it strips back to the protected minimal — but cannot reach a 2xx while the first object exists. That ordering problem is pre-existing and is the next branch.

🤖 Generated with Claude Code

Every generated acceptance test failed at its first step, and the reason was
the same one every time in a different field: the configuration was derived
from the document rather than taken from the API.

The probe already gets creates accepted. It then threw the body away — the
accepted bodies lived in memory only long enough to diff variants — and
generation went back to the document and invented values again. So a fixture
sent tfpfgen-test-icon where the API only takes LABEL, and each such field
had to be found and fixed one at a time.

The probe now records what it got accepted, request and response, to
audit/bodies/<entity>.bodies.json, and the acceptance configurations are
built from that. A configuration is now a request the API demonstrably took
rather than a reconstruction resembling one. The unit configurations stay
derived, because their mocks are built from the same derivation and the two
must not disagree.

Bracketing the create are two searches. Additive for the minimal: the
document's required fields are only a hypothesis, so a refused create adds
one field at a time until the API answers 2xx, which is the first thing that
makes an entity declaring nothing required creatable at all. Subtractive for
the maximal: what the API objects to is dropped until it takes the rest.
Both bounded per entity; a refused create makes no object, so a search costs
requests rather than debris.

The response decides what a configuration may hold. A property the API
accepts and never returns cannot live in one — terraform reads a value that
never comes back as the provider losing it — so it is dropped and the reason
written into the file.

Names that reach a live API carry a per-run suffix from a random_string the
configuration declares, so a re-run never collides with what the last one
left behind. Only invented names take it: a value the document supplied is
one the API accepts, and appending to it could make it invalid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ShocOne
ShocOne merged commit d01837e into main Aug 26, 2026
1 of 2 checks passed
@ShocOne
ShocOne deleted the feat/an-acceptance-test-replays-a-request-the-api-took branch August 26, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant