Skip to content

AsyncAPI 3.x: the fitness, and --problemType ASYNCAPI runs - #1753

Draft
LautaroPetaccio wants to merge 4 commits into
feature/asyncapi-samplerfrom
feature/asyncapi-fitness
Draft

AsyncAPI 3.x: the fitness, and --problemType ASYNCAPI runs#1753
LautaroPetaccio wants to merge 4 commits into
feature/asyncapi-samplerfrom
feature/asyncapi-fitness

Conversation

@LautaroPetaccio

@LautaroPetaccio LautaroPetaccio commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Twelfth in the AsyncAPI stack, on top of #1752. With this one --problemType ASYNCAPI runs a whole search, everything but writing the tests.

What a search over an AsyncAPI service looks for

REST covers (status × endpoint). There are no status codes here, so the targets are:

  • (outcome × operation) — what publishing to an operation was seen to do: PUBLISHED (fire-and-forget), REPLIED, or NO_REPLY.
  • (declared reply × operation) — when a reply came back, which of the messages the contract declares for that reply it was. A reply channel that lists a result and an error gives the search two things to reach, and telling them apart is what makes an asynchronous service observable from outside.

Two things are faults (both @Experimental categories): a promised reply that never arrives, and a reply that matches none of the declared messages. A message the driver could not publish is neither — it is a broken setup, no target is registered for it, and the test stops there.

Recognising a reply

AsyncApiReplyClassifier matches the observed payload against each declared payload schema. It is deliberately a classifier, not a validator: it reads the parts of JSON Schema that tell one message from another — type (including type lists), required, const/enum, allOf/anyOf/oneOf, items, $ref to component schemas — and gives the benefit of the doubt on anything it does not understand (formats, bounds, patterns). When several declared messages fit, the most specific wins: the one pinning down the most top-level fields. No new dependency.

The fitness

AsyncApiBlackBoxFitness resolves everything the driver needs from the document — address, content type, correlation location and pointer, reply address, timeout — so the driver never reads the document. The correlation id is runId-counter, with runId drawn from Randomness, so a seeded run is reproducible and ids never repeat within a run. Headers travel as a map by way of the gene's own JSON printing, which is what knows which optional headers are on.

Not supported yet, and said so with a one-time warning: a reply address announced inside the request (reply.address.location) rather than fixed by the contract. Such an operation is published without waiting.

Plumbing

  • RemoteController.executeNewAsyncApiActionAndGetReply, mirroring the RPC call. It has a default that throws, so the four other controllers (three of them test fakes) need not each say they cannot publish.
  • EMConfig.asyncApiReplyTimeoutMs, @Experimental, default 5 s.
  • A constraint: --createTests false is required for AsyncAPI until the test writer exists. Failing at start-up beats searching for an hour and failing at the end. TestCaseWriter is bound to the existing NoTestCaseWriter.
  • AsyncApiModule, one for both modes, binding the driver unconditionally. It does not call super.configure(), like the RPC, GraphQL and Web modules: what EnterpriseModule binds there needs FitnessFunction<RestIndividual>.
  • AsyncApiStructureMutator: one message more or one fewer. Unlike RPC's, it states its two bounds outright — a test keeps at least one message, and can grow to exactly maxTestSize — because mirroring RPC's arithmetic emptied a one-message test when maxTestSize was 2, and never let a test reach the maximum.
  • Seeding test cases is refused at start-up too, for the same reason as writing them.
  • Main's ASYNCAPI branch binds the module instead of throwing.

Testing

Three suites build the injector Main would, with the driver replaced by a fake, through one shared AsyncApiTestInjector.

  • AsyncApiReplyClassifierTest (16): result vs error behind $refs, undeclared payloads, non-JSON, 3.0 is an integer, most-specific-wins, type lists; and, on a document written for the purpose, const through a chain of references, enum, oneOf/allOf/anyOf, array items, booleans vs their spelling, an unknown type name, and a reference into the middle of a schema being given the benefit of the doubt.
  • AsyncApiBlackBoxFitnessTest (13): every DTO field the driver is told; unique correlation ids across evaluations; result and error as distinct targets; undeclared reply and silence as the two faults; fire-and-forget covered by PUBLISHED; headers as a map without the stamped one, structured headers as JSON; a correlation id located in the payload; a reply address announced at run time being published without waiting; a driver that could not publish, with and without saying why; a reply without its correlation id recorded rather than judged.
  • AsyncApiStructureMutatorTest (4): the two bounds above, the maximum being reachable, added messages coming from the document, nothing happening when only one message is allowed. The first fails against RPC's arithmetic.
  • AsyncApiModuleTest (1): a whole MIO search, 100 action evaluations, black-box, through the module Main binds, against a stand-in that answers like the NCS service. Asserts that every operation was answered, that some operation reached both its declared replies, that nothing was a fault, and that no correlation id repeated.

All AsyncAPI suites plus the config ones: 193 tests, 0 failures. jacoco over the AsyncAPI package: every new class above 90% line coverage.

@LautaroPetaccio
LautaroPetaccio added this pull request to stack #1712 September 13, 2026 19:50
RemoteController.executeNewAsyncApiActionAndGetReply PUTs the action to the
driver and reads back an AsyncApiReplyDto, as its RPC counterpart does. It
has a default that throws, so the controllers that never publish need not
say so one by one.

EMConfig gains asyncApiReplyTimeoutMs, experimental, and a constraint:
there is no test writer for AsyncAPI yet, so a run must say
--createTests false rather than search for an hour and fail at the end.
The two tests that already parsed --problemType ASYNCAPI say so now.

Two experimental fault categories: a promised reply that never arrives,
and a reply matching none of the messages the contract declares.
AsyncApiBlackBoxFitness publishes each message of a test through the
driver and turns what comes back into targets: for every operation, what
publishing to it was seen to do, and, when a reply came back, which of the
messages the contract declares for the reply it was. A contract listing a
result and an error thus gives the search two things to reach, which is
the AsyncAPI analogue of REST's (status x endpoint).

AsyncApiReplyClassifier is what recognises a reply: a structural match
against each declared payload schema, reading the parts of JSON Schema
that tell one message from another and giving the benefit of the doubt on
the rest. The most specific match wins.

AsyncApiModule binds it all, with the driver bound unconditionally, and
Main uses it in place of the message it showed until now. One test runs a
whole MIO search against a stand-in for the NCS service and sees both
declared replies of an operation covered.
Review: with room for exactly two messages, a one-message test was
mutated by removal, leaving a test that publishes nothing; and a test
could never grow to the maximum the user allowed, only to one less. Both
bounds are now stated as such, and AsyncApiStructureMutatorTest holds
them.

Seeding test cases is refused at start-up, like writing them, instead of
failing inside the sampler. The whole-search suite is named after the
class it exercises, AsyncApiModule, and the three suites that build the
injector share how they do it.
Review: the ':' joining a target id and the '-' inside a correlation id
were spelled out where used; they are constants now, and the two kinds of
target id are built in one place each. The remote controller named its
queryFromDatabase parameter four times over, once in the new call and
three in the ones it mirrors; it is a constant now. The fake driver's one
field comes before its companion.
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.

1 participant