Skip to content

tests: an in-process JUnit slice runs an intent application's custom/ Java against H2 in milliseconds (#7643) - #7694

Open
NicoleNG18 wants to merge 1 commit into
eclipse-dirigible:masterfrom
NicoleNG18:issue-7643-intent-slice
Open

NicoleNG18 wants to merge 1 commit into
eclipse-dirigible:masterfrom
NicoleNG18:issue-7643-intent-slice

Conversation

@NicoleNG18

Copy link
Copy Markdown
Contributor

Cause

A generated application keeps its hand-written logic (calculated-field actions, delegates, check predicates) in custom/*.java. The only way to run that code was to boot the whole platform. The money paths it carries therefore had no unit tests at all.

There were two obstacles. First, everything the generated code reaches goes through the static BeanProvider/Beans lookup: the entity store, the client bean container, numbering. Second, the platform compiled every .java in a project, so a custom/test/*Test.java importing JUnit ended up in the live client codebase as a page of javac errors. You can see this on master by publishing the sample below: JavaSourceCompiler - javac error at /custom/test/LineVatActionTest.java ....

Change

dirigible-sdk-test (tests/tests-sdk): @IntentSlice, a JUnit 5 extension.

  • Runtime. The project's gen/ + custom/ classes run on a private in-memory H2 behind the platform's own code: JavaEntityManager/JavaEntityStore (generated repositories with their calculated fields, validations, checks, UnitOfWork and outbox), engine-java's ComponentContainer, DocumentNumberService (series from the project's .numbers files) and a single-tenant context. All of it sits in a small context behind BeanProvider. There is no web server, synchronizer, broker or IDE.
  • Classes come from the test classpath, not from an in-process compile. The module compiles the project folder as a test source root, so gen/, custom/ and custom/test/ build together and the test and the slice share one Class per type. A slice compiling into its own ClientClassLoader would hand the test a different InvoiceLineEntity from the one it imports.
  • Fixtures:
    • slice.given(Entity.class).with("Field", value).saved(); values convert, e.g. "2.90" becomes a BigDecimal.
    • .entity() gives the record unsaved, to call an action directly.
    • Slice.expectRefused(() -> ...) asserts the refusal and returns it with its message.
    • slice.sent() / slice.sentTo(topic) list the events a write published. A stand-in producer records them, so nothing is logged as an undelivered message.
    • Any application component can be a test parameter.
  • Lifecycle. The application starts once per JVM and project (about 1.3 s). Before each test the tables are emptied, identities and number series restart, and the recorded messages are cleared.
  • Package-private platform constructors (outbox, numbering, the messaging facade, batch entity registration) are reached through four tiny same-package helpers in the jar. They need no new production API, and a signature change breaks this module's compile rather than a downstream run.

engine-java: JavaSynchronizer.isAccepted skips custom/test/**. The rule lives in one place, ClientTestSources.isTestSource, which the slice uses too.

Reference sample sample-intent-money:

  • InvoiceLine.vatAmount is computed by custom/LineVatAction in BigDecimal.
  • custom/test/LineVatActionTest is the slice's test suite.
  • Its gen/ Java is checked in, regenerated on this base. A nested .gitignore un-ignores it against the repo-wide **/gen rule.

IntentSliceSampleIT (HTTP-level, untagged) publishes the sample. It asserts that only the four application sources are registered and the VAT over REST is 0.15.

Docs: tests/tests-sdk/README.md (setup, API, what is not covered), the sample's README, the AOT recipe in engine-java/README.md, and .claude/docs/client-java.md.

Verified

  • LineVatActionTest: 6 green in 1.6 s with mvn test in tests/tests-sdk.
  • The issue's proof. I swapped LineVatAction for a double computation and ran the suite: roundsTheVatHalfUpToTheCent failed with expected: <0.15> but was: <0.14>, and so did the event test. With BigDecimal it is green.
  • A standalone project module outside the reactor, with the pom shown in the README (project root as the test source root, dirigible-parent imported for versions), ran the sample's suite: 6 green.
  • IntentSliceSampleIT green (19 s). With the isAccepted change reverted it fails, the registered artefacts then including custom/test/LineVatActionTest.java.
  • engine-java unit suite green (159, including the new JavaSynchronizerTestSourcesTest).
  • mvn -T 1C formatter:validate after wiping the formatter cache: BUILD SUCCESS.
  • Release-profile javadoc build on engine-java and tests-sdk: BUILD SUCCESS.
  • After rebasing onto current master: reactor rebuilt, gen/ regenerated, and the slice suite and IT re-run green.

Not verified

  • PostgreSQL. The slice is H2-only by design; the IT ran on H2.
  • Windows. Only the paths matter there: Path iteration and surefire's workingDirectory.
  • license:check. It cannot run in a git worktree ("Bare Repository has neither a working tree, nor an index"), so I checked the EPL headers on every new Java file by hand.

Not covered (each is its own piece of work)

  • Event reactions, notify and a MailAsserter. The slice records an event and stops; dispatching to the generated listeners is the next step.
  • A fake clock. Generated rules read LocalDate.now()/Instant.now() directly in many emitted places, so a clock the slice could fix needs a generator-wide seam first.
  • The AOT packaging gate ("refuse custom/*.java without custom/test unless aot.tests: none"). This repo has no packaging step to put it in: AOT modules are built by the README recipe. That recipe now points at the module's mvn test.
  • _HISTORY/_LANG side tables from the generated .schema are not created. In the slice, a write to a history: true entity logs that the trail could not be recorded, and a read of a multilingual: true entity warns.

Fixes #7643

🤖 Generated with Claude Code

… Java against H2 in milliseconds (eclipse-dirigible#7643)

custom/*.java of a generated application could only be exercised by booting
the whole platform, so the money paths it carries had no unit tests at all.

dirigible-sdk-test (tests/tests-sdk) adds @IntentSlice: the project's gen/
and custom/ classes, taken from the test classpath, run on a private
in-memory H2 behind the platform's own entity store, bean container and
numbering, with no web server, synchronizer, broker or IDE. Fixtures:
given(Entity).with(field, value).saved(), Slice.expectRefused(...), the
messages a write sent, and any application component as a test parameter.
The application starts once per JVM and is reset before each test.

The tests live in custom/test/, which the module's build compiles together
with gen/ and custom/ as one test source root - so the test and the slice
share one Class per type. JavaSynchronizer now skips custom/test/**: the
platform has no JUnit to compile those sources against.

sample-intent-money is the reference: a VAT computed by a custom/ action,
its unit tests (red at 0.14 with double rounding, green at 0.15 with
BigDecimal) as this module's test suite, and IntentSliceSampleIT publishing
it to assert the platform leaves custom/test/ out.

Not covered: event reactions/notify (events are recorded, not delivered),
a fake clock (generated rules read the system clock directly), and the AOT
packaging gate (no packaging step exists in this repository).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
if (entity.Description == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The 'Description' property is required");
}
if (entity.Description != null && entity.Description.length() > 200) {

This branch has not been deployed

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

Labels

None yet

Projects

None yet

2 participants