Repository navigation
AsyncAPI 3.x: an E2E test, NCS driven through Kafka - #1810
Draft
LautaroPetaccio wants to merge 6 commits into
Draft
LautaroPetaccio wants to merge 6 commits into
LautaroPetaccio wants to merge 6 commits into
Conversation
LautaroPetaccio
added this pull request to stack #1812
September 30, 2026 17:25
LautaroPetaccio
force-pushed
the
feature/asyncapi-e2e-kafka
branch
3 times, most recently
from
October 7, 2026 01:31
032b47f to
c0a749c
Compare
The first search against a real broker. The SUT is the NCS case study re-expressed as a Kafka service: six request topics answered on six reply topics with a result or an error, as ncs-kafka.yaml describes. It speaks only Kafka; there is no HTTP endpoint. Its routines are ported from EMB's NCS and cited as such. The driver owns the broker, a testcontainers apache/kafka, and is the reference implementation of SutController.executeAsyncApiAction: publish with the correlation id where the document says it goes, wait for the reply that carries it back, report and do not judge. kafka-clients and testcontainers-kafka are used only here, in the test tree, and are managed in the root pom like everything else. The test asserts that every operation answers and that expint and gammq each reach both their declared replies. Those two are the operations whose rejected inputs the schema does not rule out; bessj's and remainder's bounds are in the schema, so their errors need invalid data, which the search does not publish yet. 662 targets in 26 seconds, on two runs.
Neither side of the experimental oracle gate was exercised against a live broker. A second search now runs with the oracles on and a reply deadline no round trip can meet, so every message goes unanswered and the fault is reported, while the existing search asserts that a service which answers correctly has nothing reported against it. Nothing in the service is broken on purpose for this. The driver ignores replies whose correlation id does not match the request it is waiting on, so the late answers piling up on the reply topics cannot be taken for fresh ones.
The search was covered by a run against a real broker; what the run wrote was not. Both output languages are now generated, compiled and executed against the same broker: Kotlin in the existing run, Java in a second one, because a Java half that never compiles is invisible to a Kotlin-only check. The service gains one deliberate defect, marked as such where it is written: a non-positive triangle edge is answered with a payload the document declares nowhere for that reply. It correlates, it arrives, and a client written against the contract still cannot read it, so it is the one fault only the reply classifier finds. The suite is expected to report it. The two replies whose errors no conforming client can reach say so in the document, beside the reply itself, so the next reader does not take them for a gap in the search. The driver compared the correlation location against the two string constants that have since become an enum, and the shared compile step assumed Kotlin sources.
The module starts a broker in a container, so it wants Docker and it is slower than the rest. It gets a profile of its own and a flag every other profile turns off, which is how the database E2Es are kept out of the runs that have no business paying for them, and CI gains the job that turns it on.
bessj's error reply is reachable after all. Its n is fenced exactly, which is what the comment saw, but its x carries no bound at all, and the routine returns a value that is not a number for a very small one, which the service answers with an error. Checked by running the routine: n=3 with x=1e-100 is one of many. remainder's identical claim does hold -- integer arithmetic, no finiteness check -- so only bessj's is corrected, here and in the E2E that repeats it. fisher was described as stating its constraint in prose; it states none. The driver kept its reply consumers for the whole class, while every test restarts the search under the same seed and so mints the same correlation ids again. A reply left on a topic by one test could then answer a request of the next. They are dropped when the state is reset, and the next one needed is created at the end of its topic. A broker that failed to start was never stopped: isSutRunning answers on the Spring context, which is assigned last, so nothing downstream would have called stopSut. And the address it answers with was guarded on a field that is final and never null, so asking before the container was up threw where the contract says to return null. A reply the service could not publish was dropped in silence, which the run then reports as a reply that never came. Said out loud instead. The triangle reply channel now declares the error message the other five do. The service answers a rejected request the same way everywhere, so leaving it out made an ordinary error a second undeclared payload on that topic, indistinguishable from the deliberate one. A second on a loaded CI machine is not a generous reply deadline when the test asserts that nothing went unanswered, so it is three. The module stops reusing forks, as every other container-backed E2E does, and drops two dependencies nothing in it uses. The ported NCS routines are listed on the re-used code page, which is where the policy says they belong.
hamcrest was dropped as unused, which it is -- by this module's own sources. The suites EvoMaster generates import org.hamcrest.Matchers, and they are compiled against this module's classpath, so without it the E2E fails at "unresolved reference 'Matchers'" the moment it tries to compile what the run wrote. Said in the pom, so the next reader does not drop it again.
LautaroPetaccio
force-pushed
the
feature/asyncapi-e2e-kafka
branch
from
October 7, 2026 01:35
c0a749c to
ac3b5fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Last of the AsyncAPI 3.x series, on top of #1809. Replaces #1760, which could not be retargeted in place.
The SUT — NCS over Kafka
The NCS case study re-expressed as a message-driven service: six request topics (
ncs.<op>.request), each answered on a reply topic with a result message or, for the inputs the REST version answers with a 400, an error message — exactly whatncs-kafka.yaml(already in core's test resources) describes. The routines are ported from EMB'sorg.restncs.impand cited at the top of each file; the rejection rules mirrorNcsRest. A Spring Boot application with no web layer: it speaks only Kafka, which is the point.One deliberate deviation: a result that is
NaNor infinite is answered as an error. JSON has no such numbers, and a string where the contract promises a number would be a false fault.And one deliberate defect, marked as such in
NcsServicewhere it is written: a triangle with a non-positive edge is answered with{"classification": ...}, a shape the document declares nowhere for that reply. The original NCS routine classifies it as0and answers with the declared result. It is here because the reply classifier's fault needs a service that commits it: the message arrives, it correlates, nothing times out, and a client written against the contract still cannot read it. Nothing else in the run notices, and the generated suite is expected to report it. Worth knowing before reading the fixture as a faithful port — it is faithful everywhere else.The driver — the reference
executeAsyncApiActionNcsKafkaControllerowns the broker (testcontainers,apache/kafka:3.8.0, KRaft), creates the twelve topics up front, and implements the hook exactly as #1738's javadoc sketches it:replyTimeoutMsruns out; a reply carrying someone else's id is skipped, one carrying none is taken and reported ascorrelationMatched = false;AsyncApiReplyDtoand judge nothing.It also answers
getAsyncApiServerAddress, so a generated test reaches the broker this run actually started rather than the address the document declares.kafka-clientsandtestcontainers-kafkaappear only in this test module, managed in the rootpom.xmlas the doc requires. Nothing is added to any shipped jar.The search
AsyncApiTestBasejoins the other bases ine2e-tests-utils:initAndRun, and helpers that readAsyncApiCallResultoff the solution.NcsKafkaEMTestruns 300 action evaluations and asserts:checkTriangle,bessjandremainderreached their result reply;expintandgammqreached both their result and their error — the two operations whose rejected inputs (x < 0;a ≤ 0 || x < 0) the schema does not rule out.bessj's order andremainder's operands are bounded in the schema, so their error replies need invalid data, which the search does not publish yet. Both documents now say so beside the reply, so the next reader does not take it for a gap;checkTriangle, and nothing is reported against the operations that answer correctly;NO_REPLY, noPUBLISH_FAILED.A second search runs with the oracles on and a reply deadline no round trip can meet, so every message goes unanswered and the no-reply fault is reported. Neither side of that gate had been exercised against a live broker before.
Because the driver is embedded, the SUT is instrumented, so the search covers lines and branches of a message-driven service as well: 662 targets in 26 seconds, identical on two runs.
The generated suite
The run now also writes the suite, compiles it and runs it against the same broker, so #1809 is covered by the same E2E. Both output languages are generated: Kotlin in the first run, Java in a second one, because a Java half that never compiles is invisible to a Kotlin-only check. The shared compile step learned to tell
.javasources from.ktones to make that possible.CI
The module starts a broker in a container, so it wants Docker and is slower than the rest. It gets an
AsyncApiTestsprofile and askipAsyncApiflag every other profile turns off, which is how the database E2Es are kept out of runs that have no business paying for them, and CI gains the matching job.