test(http): the unknown-field rows now hold the contract, not the gap #317

Closed
coilyco-ops wants to merge 1 commit from fix/flip-unknown-field-rows into main
Member

main is red, and this is my file doing its job. 36415ac delivered #173 and my characterization rows fired with the flip instruction they carry. Flipping them.

They are no longer characterization

The rows now assert the delivered behaviour: every unknown field is refused with 400, and the refusal names the field.

That second half is the acceptance criterion I measured as failing under a bare DisallowUnknownFields(). The fix carries the decoder's own error through, so the field name comes from the struct rather than from a list that would drift from it. That is a better answer than the one I would have written.

Measured after the fix

unknown field   400 "request body carries an unknown field: user_id"
malformed json  400 "request body must be a JSON object"
oversized body  400 "request body must be a JSON object"

Criterion 3 holds for the unknown-field case — it is distinguishable now. Malformed and oversized are still byte-identical to a caller, which is #157 and still open, so TestTurnRejectionsAreNotDistinguishableToACaller stays exactly as it is and keeps describing what ships.

Also verified, since it fixes an issue I filed

3b93ef4 installs the pre-commit hook from ward-command.sh. I checked it end to end rather than reading it:

hook absent, routed verb run   -> installed
routed verb run again          -> unchanged, idempotent
commit with a planted typo     -> blocked

It works. The hook fired on this very commit. That closes the cheap half of #307 — the half I said I would do first — and it is the reason this red was caught by a test rather than by a fourth round of duplicated diagnosis.

One file, tests only.

**main is red, and this is my file doing its job.** 36415ac delivered https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/173 and my characterization rows fired with the flip instruction they carry. Flipping them. ## They are no longer characterization The rows now assert the delivered behaviour: every unknown field is refused with 400, **and the refusal names the field.** That second half is the acceptance criterion I measured as failing under a bare `DisallowUnknownFields()`. The fix carries the decoder's own error through, so the field name comes from the struct rather than from a list that would drift from it. That is a better answer than the one I would have written. ## Measured after the fix ``` unknown field 400 "request body carries an unknown field: user_id" malformed json 400 "request body must be a JSON object" oversized body 400 "request body must be a JSON object" ``` Criterion 3 holds for the unknown-field case — it is distinguishable now. Malformed and oversized are still byte-identical to a caller, which is https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/157 and still open, so `TestTurnRejectionsAreNotDistinguishableToACaller` stays exactly as it is and keeps describing what ships. ## Also verified, since it fixes an issue I filed 3b93ef4 installs the pre-commit hook from `ward-command.sh`. I checked it end to end rather than reading it: ``` hook absent, routed verb run -> installed routed verb run again -> unchanged, idempotent commit with a planted typo -> blocked ``` It works. The hook fired on this very commit. That closes the cheap half of https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/307 — the half I said I would do first — and it is the reason this red was caught by a test rather than by a fourth round of duplicated diagnosis. One file, tests only.
test(http): the unknown-field rows now hold the contract, not the gap
All checks were successful
ci / test (pull_request) Successful in 29s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
ci / image-build (pull_request) Successful in 17s
f93c453b92
36415ac delivered issue 173 and main went red on my characterization
rows, which is what they are for. Flipping them.

They are no longer characterization. The rows assert the delivered
behaviour: every unknown field is refused with 400, and the refusal names
the field. That second half was the acceptance criterion I measured as
failing under a bare DisallowUnknownFields, and the fix carries the
decoder's own error through, so the name comes from the struct rather
than a list that would drift from it.

Measured after the fix:

  unknown field   400 "request body carries an unknown field: user_id"
  malformed json  400 "request body must be a JSON object"
  oversized body  400 "request body must be a JSON object"

So criterion three holds for the unknown-field case: it is now
distinguishable. Malformed and oversized are still identical to a caller,
which is issue 157 and still open, so
TestTurnRejectionsAreNotDistinguishableToACaller stays as it is and keeps
describing what ships.

Refs #173

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Author
Member

Closing. This is redundant and I checked rather than assumed.

36415ac flipped my rows as part of the fix, following the instruction the failure message carries. That is the pattern working as designed — the "pinned defect fixed underneath its pin" problem this repository keeps hitting does not happen when the pin says what to do.

It also added TestHTTPTurnUnknownFieldNamesTheField, which covers the criterion I thought was unguarded. I mutation-tested that claim instead of reading it — replacing the refusal with a message that drops the field name:

main            --- FAIL: TestHTTPTurnUnknownFieldNamesTheField
                --- FAIL: TestHTTPTurnRejectsUnknownJSONFields
this branch     --- FAIL: TestTurnRefusesUnknownFieldsAndNamesThem

Both catch it. Mine is a second test for a property already held, and two tests asserting one thing is worse than one.

One small residue, not worth a PR. The rows on main now read rejectedNow: true, shouldReject: true, issue: "" — both columns agree and the issue field is empty, so the characterization harness no longer characterizes anything. A reader meeting that struct will reasonably assume it is tracking an open gap. Whoever next touches the file might collapse it to a plain table; I am not going to churn the file for that alone.

Closing. This is redundant and I checked rather than assumed. 36415ac **flipped my rows as part of the fix**, following the instruction the failure message carries. That is the pattern working as designed — the "pinned defect fixed underneath its pin" problem this repository keeps hitting does not happen when the pin says what to do. It also added `TestHTTPTurnUnknownFieldNamesTheField`, which covers the criterion I thought was unguarded. I mutation-tested that claim instead of reading it — replacing the refusal with a message that drops the field name: ``` main --- FAIL: TestHTTPTurnUnknownFieldNamesTheField --- FAIL: TestHTTPTurnRejectsUnknownJSONFields this branch --- FAIL: TestTurnRefusesUnknownFieldsAndNamesThem ``` Both catch it. Mine is a second test for a property already held, and two tests asserting one thing is worse than one. **One small residue, not worth a PR.** The rows on main now read `rejectedNow: true, shouldReject: true, issue: ""` — both columns agree and the issue field is empty, so the characterization harness no longer characterizes anything. A reader meeting that struct will reasonably assume it is tracking an open gap. Whoever next touches the file might collapse it to a plain table; I am not going to churn the file for that alone.
coilyco-ops closed this pull request 2026-08-13 09:10:22 +00:00
All checks were successful
ci / test (pull_request) Successful in 29s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
ci / image-build (pull_request) Successful in 17s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!317
No description provided.