Skip to content

feat: run and test against java#85

Open
PDT42 wants to merge 181 commits into
mainfrom
feat/java
Open

feat: run and test against java#85
PDT42 wants to merge 181 commits into
mainfrom
feat/java

Conversation

@PDT42
Copy link
Copy Markdown

@PDT42 PDT42 commented Apr 28, 2026

Based on original changes from @BobdenOs fork: https://github.com/BobdenOs/cds-test/tree/feat/java


Description

This PR adds the "run tests against a CAP Java app" feature, based on the changes from the fork linked above.

Requirements

Running cds-test tests against a Java app requires:

  1. Managing the Lifecycle of the Java app-runtime from within the test-runtime
  2. Exposing the app-runtime's database to the test-runtime (e.g. to make cds.ql work)
  3. Exposing the app-runtime's runtime model to the test-runtime (e.g. to make cds.entities(...) work)

The goal must be to create a developer experience, where there is no distinguishable difference between writing tests for a node app and writing tests for a java app - as long as the two apps share the same .cds model. Additionally, the test setup must be easy to comprehend, robust and reliable. Developers must be able to trust their test setup explicitly; i.e. it must not introduce unnecessary obfuscation; it must be easy to prove that the test setup it-self isn't the root cause of test failures.

Solution

This PR introduces a Java "run-mode". This mode will be selected in environments where cds.env.profiles.includes('java'). Selecting the java runner will cause cds.exec to be overridden for the cds.test(...) run.

The overridden exec will:

  • Locate the Java app to test against
    • ... by lookup relative to cds.root based on artifactid from pom.xml
  • Construct and inject a @hcql based dbProxy service into the Java app-runtime model
    • ... exposes the tables of those entities from the cds.linked model that will become part of schema-h2.sql
  • Override the registered cds.env.requires.db in the test-runtime ...
    • ... specifying a custom service implementation (java-hcql.js) that will intercept all queries sent to db during cds.test(...) and route them to the dbProxy we injected into the Java app-runtime
    • ... extend and override the cds.model of the test runtime, to include .drafts and make them accessible via cds.entities(db.schema.namespace.activeEntity.drafts)
sequenceDiagram
    participant W as test-worker
    participant J as java.js
    participant Lock as .mvn-build.lock
    participant H as java-hcql.js
    participant App as CAP Java App
    participant DB as Java H2

    Note over W,App: Setup — serialized across parallel workers

    W->>J: cds.exec()
    J->>Lock: open('wx') [exclusive create — polls every 500ms until free]
    Lock-->>J: acquired

    J->>J: buildDatabaseProxy() → dbProxy CSN
    alt model changed or JAR absent
        J->>App: mvn package -DskipTests
        App-->>J: JAR ready
    end

    J->>Lock: unlink() [finally — always released]
    J->>App: java -jar app-exec.jar --server.port=N
    App-->>J: HTTP 200 (startup ping)
    J->>J: cds.env.requires.db = java-hcql.js
    J->>H: cds.connect.to('db')
    J-->>W: { server, url }

    Note over W,DB: Test execution
    W->>H: req.query (original entity names)
    H->>H: JSON-serialize → regex-rewrite all entity name positions via proxyMap
    H->>H: normalize null comparisons
    H->>App: POST /hcql/dbProxy
    App->>DB: SQL
    DB-->>App: rows
    App-->>H: { data: [...] }
    H-->>W: result

    Note over W,App: Cleanup — cds.shutdown() in after() hook
    W->>W: delete cds.services.db + cds.db
    W->>App: app.kill()
    App-->>W: exit [awaited unless CDS_ENV_TEST_SKIP_AWAIT_SHUTDOWN]
Loading

I updated the directory structure of test to separate java, node and cds-test tests cleanly.

Implementation Details

Updated test directory structure

test
├── lib
│   └── <feature>.test.js                      ← unit tests for cds-test
└── runtime
    ├── java
    │   ├── app                                ← java test app (schema, services, ...)
    │   │   ├── test
    │   │   │   └── sample.test.js             ← test realistic usage of cds-test in a java app
    │   │   └── ...
    │   └── <feature>.test.js                  ← cds-test feature tests in a java app
    └── node
        ├── app                                ← node test app (schema, services, ...)
        │   ├── test
        │   │   └── sample.test.js             ← test realistic usage of cds-test in a node app
        │   └── ...
        └── <feature>.test.js                  ← cds-test feature tests in a node app

Specific changes compared to https://github.com/BobdenOs/cds-test/tree/feat/java

  • Renamed the proxy service we inject into the app-runtime: db -> dbProxy

    Calling the service db is neat for the test-runtime, because we physically place a db in the runtime model, that could be auto discovered. However, the real injection is done via cds.env manipulation every time. Using the pre-occupied db prefix introduces complexity in the app-runtime. IMO it is cleaner to make the proxy nature of the service explicit in the runtime model and the db spoofing + intercept -> re-route obvious, yet transparent in the test-runtime.

  • Resolved the .drafts, .texts gap

    The implementation from the fork used the definitions that are easily available from cds.load, cds.linked and the various cds.compile.for.xyz transformations. I didn't find a way to naturally get access to .drafts and .texts (which we know will be part of schema-h2.sql) by any combination and order of existing transformations - and eventually stopped trying. The main problem seems to be that compile.for.java does not use compile.for.lean_drafts and running compile.for.java early does not get us anything use-full, since we need to construct and inject a proxy service .csn that will and can pass through compile.for.java again, without raising reference issues ... I found that it is easier to construct relevant proxy entities without using existing utilities. Similar reasoning holds for .texts. The solution I ended up with could be seen as a kind of compile.for.hcql_db_proxy ...

  • Changed java.js to re-compile only when a model change is detected; will not switch to spring-boot

    .jar and db-proxy.json freshness are guaranteed by a flock inspired .mvn-build.lock preventing concurrent builds by parallel workers and using db-proxy.json as a hash equivalent to establish whether or not re-compilation is required, after acquiring the lock.

  • Changed entity name rewriting to cover all CQN name positions

    The fork rewrites only the top-level query target by mutating the CQN object directly. This misses entity names in deeper positions — ref arrays, "id": navigation steps within infix filters, and column refs with entity-qualified paths. The PR serializes the full CQN to JSON and rewrites all occurrences via a regex covering subqueries, joins and any nesting depth. Rewriting is purely data-driven via proxyMap, with no dependency on req.path resolution.

  • Added null comparison normalization before forwarding CQN to Java HCQL

    Java HCQL rejects = {"val":null} silently — queries testing for null return no results instead of an error. The PR rewrites = {"val":null} → "is","null" and != {"val":null} → "is not","null" at the JSON level before sending, covering all nesting depths in a single pass.

  • Manually injecting a proxy entity for DRAFT.DraftAdministrativeData when required to enable INSERT.info(Entity.drafts)

    No model that is shared between the test- und the java-runtime includes this, easily accessible.

  • Introduced an up_ filter for children of managed compositions in data.reset.

    These would usually only be added during cds.linked, but for flows they are added earlier and make it into the db model. They will always be deleted transitively, when deleting the parent. Java does not allow targeting them with directly; i.e.: fails when sending DELETE via @hcql. This workaround prevents the erroneous request.

Notes

  • Complexity introduced by compile/for/lean_drafts vs cds-compiler/lib/transform/draft/odata
  • Could consider trying to enable cds.ql: SELECT.localized
  • Java GET /health afterEach?

@PDT42 PDT42 changed the title Feat/java feat: run and test against java Apr 28, 2026
# Conflicts:
#	lib/cds-test.js
PDT42 added 19 commits April 28, 2026 17:21
Split test/app into test/node-app (pure Node, no pom.xml) and
test/java-app (minimal Spring Boot app for java.js integration tests).

The pom.xml presence in test/app was causing CDS's project-nature
detection to activate the java profile for all tests, replacing
cds.exec with the Java launcher even in Node-only CI environments.

- git mv test/app → test/node-app; Java files → test/java-app
- Add test/java-app with minimal bookshop model and sample-java.it.js
- Add .github/workflows/java.yml (SAP Machine JDK 21, Maven cache)
- sample-java.it.js uses .it.js suffix to stay out of chest discovery
@PDT42 PDT42 marked this pull request as ready for review May 12, 2026 21:19
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