Conversation
# Conflicts: # lib/cds-test.js
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
| <executions> | ||
| <execution> | ||
| <id>cds.npm-ci</id> | ||
| <goals> | ||
| <goal>npm</goal> | ||
| </goals> | ||
| <configuration> | ||
| <arguments>ci</arguments> | ||
| </configuration> | ||
| </execution> | ||
| <execution> | ||
| <id>cds.clean</id> | ||
| <goals> | ||
| <goal>clean</goal> | ||
| </goals> | ||
| </execution> | ||
|
|
||
| <execution> | ||
| <id>cds.resolve</id> | ||
| <goals> | ||
| <goal>resolve</goal> | ||
| </goals> | ||
| </execution> | ||
|
|
||
| <execution> | ||
| <id>cds.build</id> | ||
| <goals> | ||
| <goal>cds</goal> | ||
| </goals> | ||
| <configuration> | ||
| <commands> | ||
| <command>build --for java</command> | ||
| <command>deploy --to h2 --with-mocks --dry --out "${project.basedir}/src/main/resources/schema-h2.sql"</command> | ||
| </commands> | ||
| </configuration> | ||
| </execution> |
There was a problem hiding this comment.
It should be possible to delete all these steps. It should also fix the setup step Warm npm cache for Java app as it would remove the npm ci execution on the mvn build. The build --for java step can be run once outside of the mvn build.
| // Java HCQL rejects `= {"val":null}`: | ||
| // -> globally convert to `is "null"` | ||
| json = json.replace(/"=",{"val":null}/g, '"is","null"') | ||
| json = json.replace(/"!=",{"val":null}/g, '"is not","null"') |
There was a problem hiding this comment.
Should be followed up as part of HCQL specification.
| // CQN JSON can include (fully qualified) db-entity-names in only two places: | ||
| // As the first element of a `ref` array, always preceeded by '[' | ||
| // > e.g.: ["db.bookshop.Books"...] | ||
| // As an id in a navigation segment, always preceeded by '"id":' | ||
| // > e.g.: {"id":"bookshop.Books",...} | ||
| // -> Use replace instead of parsing the CQN, to inject proxy-entity-refs | ||
| for (const [from, to] of Object.entries(proxyMap)) | ||
| json = json.replace(new RegExp(`(?<=\\[|"id":)"${from.replace(/\./g, '\\.')}"`, 'g'), `"${to}"`) |
There was a problem hiding this comment.
With HCQL@2 of java the service should be local name aware. Which might enable the removal of this fully qualified name conversion.
| // Normalize INSERT values (single-row) → entries | ||
| if (query.INSERT?.values && query.INSERT.columns) { | ||
| query.INSERT.entries = [ | ||
| query.INSERT.columns.reduce((entry, column, index) => { | ||
| entry[column] = query.INSERT.values[index] | ||
| return entry | ||
| }, {}) | ||
| ] | ||
| delete query.INSERT.values | ||
| } |
There was a problem hiding this comment.
Is the same as rows shouldn't duplicate it.
| } | ||
|
|
||
| const id = cds.utils.uuid() | ||
| inFlight.set(id, query) |
There was a problem hiding this comment.
I wouldn't do this. All the requests should be failing and reject their promises already. So collecting them and having them throw all the other active requests doesn't really help as it will fill the logs with duplicate information. Just having a clean error message that explains that the app crashed should be enough.
| if (def['@cds.persistence.skip'] === true) return false | ||
| if (def['@cds.persistence.exists']) return false | ||
|
|
||
| // Exclude service projections — only DB-layer views get a proxy entity |
There was a problem hiding this comment.
This sounds like it will prevent certain things from working.
| - name: Install Java app npm dependencies | ||
| run: npm ci | ||
| working-directory: test/runtime/java/app |
There was a problem hiding this comment.
This step looks like it shouldn't be necessary. Did you make sure to add the test app into the workspace configuration ?
I don't want to replicate cds.resolve;
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-testtests against a Java app requires:cds.qlwork)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
.cdsmodel. 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 causecds.execto be overridden for thecds.test(...)run.The overridden
execwill:cds.rootbased onartifactidfrompom.xml@hcqlbaseddbProxyservice into the Java app-runtime modelcds.linkedmodel that will become part ofschema-h2.sqlcds.env.requires.dbin the test-runtime ...java-hcql.js) that will intercept all queries sent todbduringcds.test(...)and route them to thedbProxywe injected into the Java app-runtimecds.modelof the test runtime, to include.draftsand make them accessible viacds.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]I updated the directory structure of
testto separatejava,nodeandcds-testtests cleanly.Implementation Details
Updated
testdirectory structureSpecific changes compared to https://github.com/BobdenOs/cds-test/tree/feat/java
db->dbProxy.drafts,.textsgapjava.jsto re-compile only when a model change is detected; will not switch to spring-bootDRAFT.DraftAdministrativeDatawhen required to enableINSERT.info(Entity.drafts)up_filter for children of managed compositions indata.reset.Notes
compile/for/lean_draftsvscds-compiler/lib/transform/draft/odatacds.ql:SELECT.localized/healthafterEach?