Skip to content

test(validation): pin the DecodedBody envelope unwrap on the strict-required path#252

Merged
wadakatu merged 3 commits into
mainfrom
test/strict-required-decoded-body-regression-issue-249
May 18, 2026
Merged

test(validation): pin the DecodedBody envelope unwrap on the strict-required path#252
wadakatu merged 3 commits into
mainfrom
test/strict-required-decoded-body-regression-issue-249

Conversation

@wadakatu
Copy link
Copy Markdown
Collaborator

@wadakatu wadakatu commented May 18, 2026

Summary

OpenApiResponseValidator の strict-required 記録経路に、DecodedBody envelope を unwrap してから walker へ渡す挙動を固定する回帰テストを追加します。

Why

PR #247 は、strict-required walker が present-literal-null マーカーではなく decoded body の実値を観測するよう unwrap を1行追加しました。#248 でそのマーカーが DecodedBody envelope に置き換わったため、現在の unwrap は OpenApiResponseValidator::maybeRecordStrictRequired() に渡す $body->value です。この経路を直接固定するテストが無いと #247 の pr-test レビューで指摘されていました(severity 6)。

Fixes #249

Verification

tests/Unit/Validation/Strict/StrictRequiredValidatorIntegrationTest.php に回帰テストを4本追加:

  • present_literal_null_body_records_no_strict_required_pointer — literal JSON null body(DecodedBody::present(null))を OAS 3.0 nullable: true schema で検証し、validation success かつ strict-required observation 無記録(walker が実 null を観測)であることを assert。literal-null Success 経路の characterization テスト。
  • present_literal_null_body_on_oas31_nullable_schema_records_no_pointer — 同じ挙動を OAS 3.1 type: ["object","null"] schema で固定(OpenApiSchemaConverter は 3.0 / 3.1 を別経路で処理するため、3.1 type 配列の converter 回帰を捕捉)。
  • absent_decoded_body_against_json_schema_fails_and_records_nothing — アダプタが直接生成する DecodedBody::absent() を JSON schema エンドポイントへ渡すと conformance 失敗し observation 無記録であることを assert(present(null) は success / absent() は failure という envelope の本質的区別を直接経路で固定)。
  • decoded_body_envelope_is_unwrapped_before_strict_required_walk — present な object body を DecodedBody envelope として渡し、walker が envelope ではなく内側の配列を観測すること(/ ポインタにキーが記録される)を assert。$body->value$body に変えると collectPointers()DecodedBody を受け取り空マップを返すため、このテストが落ちる = Issue の「ゴール」を満たす teeth テスト。

fixture追加:

  • under-described.json に OAS 3.0 nullable: true/nullable-object エンドポイント
  • under-described-3.1.json(新規)に OAS 3.1 type: ["object","null"]/nullable-object エンドポイント

ゴール検証として src/OpenApiResponseValidator.php$body->value を一時的に $body へ書き換え、teeth テストが fail することを確認済み(変更は revert 済み)。

  • composer test passes(1785 tests, 4192 assertions)
  • composer stan passes(PHPStan level 6, No errors)
  • composer cs-check passes

Notes for reviewers

wadakatu added 3 commits May 18, 2026 15:04
…equired path (#249)

PR #247 added an unwrap step so the strict-required walker observes the
decoded body value rather than the present-literal-null marker. #248
replaced that marker with the DecodedBody envelope, so the unwrap is now
`$body->value` in OpenApiResponseValidator::maybeRecordStrictRequired().
That path had no direct regression test — the pr-test review flagged it.

Add two regression tests to StrictRequiredValidatorIntegrationTest:

- present_literal_null_body_records_no_strict_required_pointer: a literal
  JSON null body (DecodedBody::present(null)) against a nullable object
  schema reaches the Success path and records no pointer — documenting
  that the walker observes a real null, not the envelope.
- decoded_body_envelope_is_unwrapped_before_strict_required_walk: a
  present object body passed as a DecodedBody envelope must be unwrapped
  before walking. Fails the moment `$body->value` becomes `$body`, since
  collectPointers() would receive a DecodedBody and return an empty map.

Add a `/nullable-object` endpoint to the under-described fixture so the
literal-null body validates successfully.
Address pr-review-toolkit feedback on the #249 regression tests:

- Drop the inaccurate #246 citation: the DecodedBody::present(null)
  representation is #248's work, not #246 (which only added the
  superseded marker enum).
- Reframe the literal-null test docstring as a characterization test
  for the Success path — it does not pin the unwrap (a leaked envelope
  also yields an empty pointer map), which the sibling test guards.
- Clarify that a dropped unwrap skips the observation entirely rather
  than recording an empty one, and note the teeth test's expected keys
  come from the input body, not the spec schema.
- Remove the inert `required: ["id"]` from the /nullable-object fixture;
  the literal-null body never exercises a required-key comparison.
…path

Close the two coverage gaps the pr-test review flagged on the #249
regression tests:

- absent_decoded_body_against_json_schema_fails_and_records_nothing:
  the framework adapters build the envelope directly, so the production
  path for a missing body is an explicit DecodedBody::absent() — not a
  bare null through fromLegacy(). Passing absent() against a JSON-schema
  endpoint must fail conformance and record nothing, mirroring the
  present-literal-null case (present(null) validates, absent() does not).
- present_literal_null_body_on_oas31_nullable_schema_records_no_pointer:
  the literal-null Success path is version-specific. The existing test
  pins OAS 3.0 `nullable: true`; this one pins OAS 3.1
  `type: ["object", "null"]` via a new under-described-3.1 fixture, so a
  converter regression on 3.1 type arrays surfaces here.
@wadakatu wadakatu merged commit eb8aeee into main May 18, 2026
17 checks passed
@wadakatu wadakatu deleted the test/strict-required-decoded-body-regression-issue-249 branch May 18, 2026 06:13
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.

tech-debt(test) — OpenApiResponseValidator の PresentJsonNull unwrap 経路に回帰テストが無い

1 participant