Skip to content

The live IT compiles against a client generated from either contract - #25

Merged
gsavastano merged 1 commit into
mainfrom
gsavastano/live-it-spec-tolerant
Sep 10, 2026
Merged

gsavastano merged 1 commit into
mainfrom
gsavastano/live-it-spec-tolerant

Conversation

@gsavastano

Copy link
Copy Markdown
Contributor

What

Makes OpenDppLiveIT compile against a generated client from either contract, by not naming the two
things that differ by contract at compile time.

  • resolve(api, name, key) finds a resolver by NAME and passes null for every parameter after the key.
    Those are all optional on the wire — a grant token, and since contract 1.16.0 the representation flag
    — so the request is exactly the one the two-argument form made: anonymous tier, default compressed
    document. InvocationTargetException is unwrapped, so the 404 case still asserts on a typed
    ApiException rather than a reflection wrapper.
  • The getMetadata() assertion is split. What it actually tested — untyped JSON surviving Jackson — is
    now asserted through @context, which every contract declares as a relaxed Object, by requiring a
    structured value rather than a flattened string. The non-empty map check is kept opportunistically via
    optionalMap, so contracts up to 1.15.0 still prove the body is non-empty; from 1.16.0 the property is
    gone and the body's elements are root members with no generated getter, so there is nothing to read.

Test-only. No regenerated client, no version bump, no vendored spec change — those stay the mirror sync's
to write.

Why

This file is hand-written against the generated client, and two lanes generate that client from two
different specs: this repo's CI from the vendored openapi.json, and opendpp-node's SDK regen gate from
its live contract. They can sit a contract apart, so any hand-written reference to a generated signature
or getter compiles in one lane and breaks the other.

Contract 1.16.0 did exactly that. It added representation to both public resolvers and moved the
passport body out of a metadata object onto the document root. This file kept compiling here against
the vendored 1.15.0 and stopped compiling in node's gate: four call sites on argument count, one on the
missing getter. That gate feeds the required rollup on node's release/train-*, so a contract change was
blocked by a test in another repository that could not have been green in both places at once.

The header now states the constraint, so the next contract change does not rediscover it: anything
asserted unconditionally has to be true of both contracts.

Verification

Both directions, using node's own gate script (scripts/sdk-regen-verify.ts <checkout> --only=java),
which is what its CI runs:

  • Against a client generated from the live 1.16.0 contract: failed on exactly the five reported
    errors before this change; after it, regenerates, builds and tests clean at 1.16.0.
  • Against the vendored 1.15.0 in this tree: ./gradlew build passes, unchanged.

The live IT itself stays opt-in behind OPENDPP_LIVE_TEST=1, so neither run executed it against the
hosted node.

Note for the merge

node's SDK regen job checks this repository out at ref: main, so the gate only clears once this is on
main.

… so a node-side spec change cannot block its own release

The Java live IT is hand-written against the GENERATED client, and two lanes generate that client from
two different specs: this repository's CI from the vendored `openapi.json`, opendpp-node's "SDK regen"
gate from its live contract. They can sit a contract apart, so any hand-written reference to a generated
signature or getter compiles in one lane and breaks the other.

That is what happened. Contract 1.16.0 added the `representation` query parameter to both public
resolvers and moved the passport body out of a `metadata` object onto the document root. This file kept
compiling here, against the vendored 1.15.0, and stopped compiling in node's gate — four call sites on
argument count, one on a getter that no longer exists. Since that gate feeds the required rollup on
`release/train-*`, a contract change was blocked by a test in another repository that could not have
been green in both places.

So the two things that differ by contract are no longer named at compile time:

- `resolve(api, name, key)` finds the resolver by NAME and passes `null` for every parameter after the
  key. Those are all optional on the wire — a grant token, and since 1.16.0 the representation flag —
  so the request is the same one the two-argument form made: anonymous tier, default compressed
  document. It unwraps `InvocationTargetException` so the 404 case still asserts on a typed
  `ApiException` rather than a reflection wrapper.
- The old `getMetadata()` assertion is split. What it was really testing — that untyped JSON survives
  Jackson — is now asserted through `@context`, which both contracts declare as a relaxed Object, by
  requiring a structured value rather than a flattened string. The map check itself is kept
  opportunistically through `optionalMap`, so contracts up to 1.15.0 still prove the body is non-empty;
  on 1.16.0 the property is gone and the body's elements are root members with no generated getter,
  so there is nothing there to read.

Everything asserted unconditionally is now true of both contracts, and the file header says so, so the
next contract change does not rediscover this.

Verified both directions with node's own gate script (`scripts/sdk-regen-verify.ts … --only=java`),
which is what CI runs: against a client generated from the live 1.16.0 contract it failed on exactly the
five reported errors before this change and passes after, regenerating, building and testing at 1.16.0.
Against the vendored 1.15.0 in this tree, `./gradlew build` passes unchanged. Only the test file is
touched — no regenerated client and no version bump, which stay the mirror sync's to write.
@gsavastano
gsavastano merged commit bb212b6 into main Sep 10, 2026
4 checks passed
@gsavastano
gsavastano deleted the gsavastano/live-it-spec-tolerant branch September 10, 2026 08:44
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.

2 participants