Skip to content

test(agent-bff): drive a generated client through list, count, form and execute - #1828

Open
nbouliol wants to merge 3 commits into
mainfrom
feature/prd-687-verify-a-generated-client-calls-list-count-form-and-execute
Open

test(agent-bff): drive a generated client through list, count, form and execute#1828
nbouliol wants to merge 3 commits into
mainfrom
feature/prd-687-verify-a-generated-client-calls-list-count-form-and-execute

Conversation

@nbouliol

@nbouliol nbouliol commented Aug 14, 2026

Copy link
Copy Markdown
Member

What

Proves the dynamic OpenAPI document is correct enough for codegen, not just schema-valid: a client generated from it can actually call list, count, action form and action execute.

The test:

  1. emits the document through the CLI command (dispatchCli(['openapi', '--output', …]), the body of forest-bff openapi) from a fixture schema and fixture capabilities;
  2. runs @hey-api/openapi-ts on that file, as a consumer would from the command line;
  3. drives the generated client over HTTP against the real data and action middlewares (agent clients faked through the existing createClient seam), asserting a 2xx and the exact response body for each of the six operations.

One fixture feeds both the document and the runtime read-model. Two sources would let the test go green while the document described a surface the runtime does not have.

Why the operator check has teeth

The filter operator is read from the emitted enum (components.schemas.FilterLeaf_users-1.properties.operator.enum) and injected into the call, instead of being hardcoded — so the assertion follows the document rather than a human's memory of it.

A negative control sends an operator the document leaves out and asserts 400 invalid_filter_operator. Without it, a permissive stub (or a regression that stopped validating) would keep the check green. The fixture reports a single capability operator (equalEqual) so the out-of-set operator lands unambiguously on 400 rather than on 422 field_not_filterable or 500 mapping_error.

What it found

Nothing broken. Worth recording, since these were the risks:

  • the percent-encoded action segments survive codegen and the round trip, including a non-ASCII name (Générer la factureG%C3%A9n%C3%A9rer%20la%20facture);
  • the recursive anyOf filter tree becomes a clean recursive TS union, and the operator enum reaches the generated types as a string literal;
  • not is dropped by the codegen — a tooling quirk, not a document defect: the union already encodes the leaf/branch exclusivity structurally, and the runtime 400 stays documented in prose. The document is unchanged.

Notes

  • @hey-api/openapi-ts over openapi-generator-cli: the document is OpenAPI 3.1, and openapi-generator converts 3.1 → 3.0 internally, which would make "document defect vs tooling quirk" undecidable on exactly the constructs at risk. It also avoids a Java toolchain and a jar download inside a CI test.
  • Auth stays out of scope, as the ticket states — but the generated client does send the documented X-Forest-Bff-Key scheme, and the test asserts the credential arrives under that header.
  • The generated client lands in a gitignored test/openapi/.generated/, wiped before and after the run. ESLint ignores dot-directories by default, so no lint config change was needed.
  • Runtime: the whole file takes ~2.5 s; the package suite stays at ~10 s (1093 tests green).

Fixes PRD-687

🤖 Generated with Claude Code

Note

Add generated-client test suite for agent-bff OpenAPI operations

  • Adds a Jest test in openapi-generated-client.test.ts that runs the CLI to emit an OpenAPI doc, invokes @hey-api/openapi-ts to generate a TypeScript client, then drives the client against an in-memory Koa server through list, count, form, and execute flows.
  • Verifies the generated client exports documented operationIds, propagates security and timezone headers, accepts documented filter operators (Equal), and rejects undocumented ones (GreaterThan) with a 400.
  • Adds @hey-api/openapi-ts 0.99.0 to package.json and ignores the .generated output directory in .gitignore.

Changes since #1828 opened

  • Added validation for OpenAPI client generation with action names containing path separators [1bf8e77]
  • Modified the beforeAll hook in the OpenAPI generated client test to capture stderr before spy restoration and clear inherited Node options during codegen [1bf8e77]
  • Moved the ignore rule for test/openapi/.generated from root .gitignore to packages/agent-bff/.gitignore [1bf8e77]

Macroscope summarized 90b37ef.

@linear-code

linear-code Bot commented Aug 14, 2026

Copy link
Copy Markdown

PRD-687

@qltysh

qltysh Bot commented Aug 14, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts Outdated
Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts
Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts
Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts Outdated
@nbouliol
nbouliol force-pushed the feature/prd-687-verify-a-generated-client-calls-list-count-form-and-execute branch from c01807b to e242dc6 Compare August 19, 2026 10:03
nbouliol and others added 2 commits August 21, 2026 12:08
…nd execute

Emits the document through the CLI command, runs @hey-api/openapi-ts on it, and
drives the generated client against the real data and action middlewares over
HTTP, so a 2xx proves the documented paths, fields and operator enums are
sufficient to construct the call.

The filter operator is read from the emitted enum rather than hardcoded, and a
negative control asserts an operator the document leaves out is rejected with
400 - without it a permissive stub would keep the check green.

Fixes PRD-687

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing it

A failing step now throws with what the tool said: the emission carries the CLI
stderr it silenced, and the codegen carries its own output plus the artifact it
never wrote. The exit status alone was not enough - this codegen reports a
missing input while exiting 0.

Also bounds the codegen child, since spawnSync blocks the event loop and the
beforeAll deadline could never fire, and awaits the server close so the port
cannot leak into the next suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nbouliol
nbouliol force-pushed the feature/prd-687-verify-a-generated-client-calls-list-count-form-and-execute branch from e242dc6 to 90b37ef Compare August 21, 2026 10:08

@Tonours Tonours left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Second pass on the branch head. Five findings: one blocking (the emission-failure message is always empty), two tests that cannot fail, one latent coupling and one gitignore placement.

Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts Outdated
Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts
Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts Outdated
Comment thread packages/agent-bff/test/openapi/openapi-generated-client.test.ts Outdated
Comment thread .gitignore Outdated
Both were vacuous. The encoding one passed on a raw document because undici
percent-encodes client-side, so the fixture now carries an action named
"Facture 50/50": raw, its path splits into segments no route matches. The enum
one compared the document against itself, so it now asserts the literal before
filtering the generated types.

Also captures the CLI stderr before restoring the spy, since mockRestore clears
mock.calls and the failure message was always empty, and clears NODE_OPTIONS for
the codegen child so an inherited Node warning cannot fail an assertion about
the document.

The ignored path for the generated client moves to the package, next to how
datasource-sql and agent-testing ignore their own test artifacts.

@Tonours Tonours left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All five findings are addressed. I verified the two decorative tests now bite, by mutation rather than by reading the diff:

  • segment returning name unchanged: should reach an action whose name carries the path separator goes red (14 passed, 1 failed). The Facture 50/50 fixture is what gives it teeth.
  • operator: { type: 'string', enum: operators } down to a bare { type: 'string' }: should carry the documented operator set into the generated types now goes red too, along with the two operator tests (12 passed, 3 failed). It stayed green before the literal assertion.

The other three read clean: written is captured before mockRestore(), NODE_OPTIONS: '' isolates the child's output from the runner's flags, and the ignore rule moved into packages/agent-bff/.gitignore with the root entry gone.

Package suite 1099/1099 green locally, CI green on 1bf8e77.

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