Skip to content

conformance: a path-less requestBody assertion pins the whole body - #854

Open
jeremy wants to merge 2 commits into
mainfrom
fix/conformance-whole-body-request-assertion
Open

conformance: a path-less requestBody assertion pins the whole body#854
jeremy wants to merge 2 commits into
mainfrom
fix/conformance-whole-body-request-assertion

Conversation

@jeremy

@jeremy jeremy commented Sep 9, 2026

Copy link
Copy Markdown
Member

Summary

conformance/schema.json declared path optional on a requestBody assertion, and no runner implemented the path-less form: each of the six dereferenced path unconditionally, so a schema-valid fixture produced a different crash or a misleading "key not present" per language.

This takes the first of the issue's two options. A requestBody assertion with no path now means the whole captured body equals expected, exactly — a strictly stronger pin than the per-key form, because a key the SDK adds on its own is a failure instead of an unnoticed extra. All six runners implement it, and conformance/schema.json plus SPEC §19 document both forms.

The other half of the class is closed by the schema rather than by six more branches: every other path-taking assertion type (errorField, headerAbsent, headerInjected, headerPresent, headerValue, requestBodyAbsent, responseBody, responseMeta) has no meaning without a path, so an allOf/if/then now requires it for those. A path-less fixture of that shape fails make conformance-fixtures-check with a message naming the field instead of reaching a runner. A census of conformance/tests/*.json shows the split is already clean: those eight always carry path today, and no other type ever does.

Red-proof

The fixture landed first. schedule_entries_write.json "replace-omission-clears" — whose description already says the raw replace sends exactly what the caller passed — gained a path-less requestBody pinning the whole three-key body. Against the unmodified runners:

Runner Failure
Go Expected request body field "" on request index 0, but it was absent
Python KeyError: 'path'
Ruby runner.rb:1311:in 'TestRunner#fetch_body_key': undefined method 'split' for nil (NoMethodError)
TypeScript TypeError: Cannot read properties of undefined (reading 'split') at lookupBodyPath
Kotlin / Swift navigateJsonPath(body, "") / body.navigate("") look up the empty key and report "key not present in request body"

With the change, all six pass it, and the existing per-key and requestBodyAbsent assertions on the same case stay as they were.

Runner semantics

Shared across the six: no path → whole-body exact equality; a request with no JSON body fails; the failure message names the full expected and actual bodies. Each runner compares with the same helper it already used for the per-key form (jsonEqual, toStrictEqual, ==, compareJsonValues, compareJSON), so number and nesting handling is unchanged.

Fixes #587


Summary by cubic

Fixes #587 by implementing the path-less requestBody assertion that conformance/schema.json allowed but no runner handled. A requestBody assertion without path now means the whole captured body must equal expected exactly, and the other eight path-taking assertion types now require path in the schema.

Details

  • Whole-body pinning is strictly stronger than the per-key form: a key the SDK adds on its own is a failure instead of an unnoticed extra.
  • All six runners compare with the same helper they already used for the per-key form, so number and nesting handling is unchanged; a request with no JSON body fails in either form.
  • The schema rejects a path-less fixture of the other types at make conformance-fixtures-check with a message naming the field, and a census of existing fixtures shows none omit path on those types.
  • The schema also rejects an empty path string: three runners treat "" as absent and three as a lookup of the empty key, so minLength: 1 keeps that disagreement out of any schema-valid fixture.
  • schedule_entries_write.json gained a path-less requestBody pinning the whole three-key body, which previously failed on all six runners with different errors.

Written for commit a2ae17f. Summary will update on new commits.

Review in cubic

conformance/schema.json declared path optional on requestBody while all six
runners dereferenced it, so a schema-valid fixture crashed or misreported
per language. The path-less form now means whole-body exact equality in
every runner, and the schema requires path on the other path-taking
assertion types, where no path-less form has a meaning.

Fixes #587
Copilot AI balanced review requested due to automatic review settings September 9, 2026 23:56
@jeremy jeremy added the bug Something isn't working label Sep 9, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T00:04:28.447085Z 32f6151 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

An empty path remains schema-valid but has conflicting semantics across the six runners.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Implements path-less requestBody assertions as exact whole-body comparisons across all six SDK conformance runners and tightens path requirements for other assertions.

Changes:

  • Adds whole-body request assertions to every runner.
  • Updates schema and SPEC semantics.
  • Adds a shared conformance fixture.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File summaries
File Description
SPEC.md Documents assertion semantics.
conformance/schema.json Adds conditional path requirements.
conformance/tests/schedule_entries_write.json Exercises whole-body equality.
conformance/runner/go/main.go Implements Go handling.
conformance/runner/python/runner.py Implements Python handling.
conformance/runner/ruby/runner.rb Implements Ruby handling.
conformance/runner/swift/Sources/ConformanceRunner/Assertions.swift Implements Swift handling.
conformance/runner/typescript/runner.test.ts Implements TypeScript handling.
kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt Implements Kotlin handling.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread conformance/schema.json Outdated
Comment on lines +158 to +160
"path": {
"type": "string",
"description": "JSONPath or dot-notation path for nested value assertions. For requestBody / requestBodyAbsent, the key (dot-notation) inside the captured request body."
"description": "JSONPath or dot-notation path for nested value assertions. For requestBody / requestBodyAbsent, the key (dot-notation) inside the captured request body. Optional for requestBody alone: omitted, `expected` is the WHOLE captured body and must equal it exactly, so a key the SDK added fails rather than passing unseen. Every other path-taking type requires it (the allOf below), so a path-less fixture is rejected here instead of reaching a runner that cannot execute it."

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a2ae17f: path carries minLength 1 in the schema, so an empty string is rejected at conformance-fixtures-check like a missing one, and the three-versus-three disagreement cannot reach a schema-valid fixture. The runners are unchanged; the schema is the layer that can see the value.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32f61514b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conformance/schema.json Outdated
Comment on lines +159 to +160
"type": "string",
"description": "JSONPath or dot-notation path for nested value assertions. For requestBody / requestBodyAbsent, the key (dot-notation) inside the captured request body."
"description": "JSONPath or dot-notation path for nested value assertions. For requestBody / requestBodyAbsent, the key (dot-notation) inside the captured request body. Optional for requestBody alone: omitted, `expected` is the WHOLE captured body and must equal it exactly, so a key the SDK added fails rather than passing unseen. Every other path-taking type requires it (the allOf below), so a path-less fixture is rejected here instead of reaching a runner that cannot execute it."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject empty strings as assertion paths

Because this schema still accepts "path": "", a valid shared fixture can now have different meanings across runners: Go, Kotlin, and Swift treat the empty string like an omitted path and compare the whole body, while TypeScript, Python, and Ruby pass it to their path walkers and look for a key named "". Require a non-empty string here so omission remains the only whole-body form and every schema-valid fixture has consistent semantics.

Useful? React with 👍 / 👎.

Comment on lines +1369 to +1371
elif body_path is None:
if request["body"] != expected:
failures.append(f"Expected request body on request index {idx} to equal {expected!r} exactly, got {request['body']!r}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare Python request bodies with JSON type awareness

For a path-less assertion containing a boolean, Python's native container equality can produce a false pass when the SDK emits a number: for example, {"highlighted": false} compares equal to {"highlighted": 0} because False == 0 in Python. The other runners reject that wire-type mismatch, so this new whole-body assertion is not exact or cross-SDK consistent unless the Python branch performs type-aware recursive JSON comparison.

Useful? React with 👍 / 👎.

Comment thread conformance/schema.json
}
}
},
"then": { "required": ["path"] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require expected for whole-body assertions

A path-less requestBody assertion also needs expected, but the new conditional only requires paths for the other assertion types, so {"type":"requestBody"} still passes conformance-fixtures-check. It then raises KeyError: 'expected' in Python while the other runners report differing assertion failures, leaving the same schema-valid, cross-runner failure class this change is intended to eliminate; require expected when the type is requestBody (and distinguish an omitted property from an explicit JSON null).

Useful? React with 👍 / 👎.

@jeremy

jeremy commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

Converged: CI green on a2ae17f (41 checks), the one review finding (empty path) fixed in the schema and answered, and no further review activity since. Ready for a human look and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working conformance Conformance test suite kotlin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conformance: schema.json declares requestBody.path optional, but all six runners require it

2 participants