Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ gen/
*.sqlite-shm
*.sqlite-wal
.vscode/

**/gen/
**/edmx/
**/target/
.flattened-pom.xml
schema*.sql
*.log*
21 changes: 14 additions & 7 deletions lib/cds-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,21 @@ class Test extends require('./axios') {
default: this.in(folder_or_cmd); args.push('--in-memory?')
}
const {cds} = this
const self = this

// launch cds server...
before (async ()=>{
before (async function () {
process.env.cds_test_temp = cds.utils.path.resolve(cds.root,'_out',''+process.pid)
if (!args.includes('--port')) args.push('--port', '0')
if (cds.env.profiles.indexOf('java') > -1 && cds.env.profiles.indexOf('node') < 0) {
this.timeout?.(30000)
cds.exec
cds.exec = require('./java').bind(self)
}
let { server, url } = await cds.exec(...args)
this.server = server
this.url = url
})
self.server = server
self.url = url
}, 30000)

// gracefully shutdown cds server...
after (async ()=>{
Expand Down Expand Up @@ -208,16 +214,17 @@ let _expect = undefined

} else if (is_jest) { // it's jest

global.before = (msg,fn) => { return global.beforeAll(fn||msg) }
global.after = (msg,fn) => global.afterAll(fn||msg)
global.before = (msg,fn,time) => global.beforeAll(typeof fn === 'function' ? fn : msg,time||fn)
global.after = (msg,fn,time) => global.afterAll(typeof fn === 'function' ? fn : msg,time||fn)

} else { // it's node --test

const { describe, suite, test, before, after, beforeEach, afterEach } = require('node:test')
describe.each = test.each = describe.skip.each = test.skip.each = each
global.describe = describe; global.xdescribe = describe.skip
global.test = global.it = test; global.xtest = test.skip
global.beforeAll = global.before = (msg,fn=msg) => {
global.beforeAll = global.before = (msg,fn) => {
if (typeof msg === 'function') fn = msg
if (fn.length > 0) { const f = fn; fn = (_,done)=>f(done) }
return before(fn) // doesn't work for some reason
}
Expand Down
42 changes: 42 additions & 0 deletions lib/java-hcql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const cds = require('@sap/cds')

const InsertResults = require('@cap-js/db-service/lib/InsertResults')

module.exports = class extends cds.Service {
init() {
this.on('*', async req => {
const { axios } = this.options

// REVISIT: make draft and text direct access work
if (
req.target.isDraft ||
req.target.includes?.includes('sap.common.TextsAspect')
) return []

if (req.query.INSERT?.rows) {
req.query.INSERT.entries = req.query.INSERT?.rows
.map(r => req.query.INSERT.columns.reduce((l, c, i) => {
l[c] = r[i]
return l
}, {}))
req.query.INSERT.rows = undefined
}

const service = cds.model.services[req.path.split('.')[0]]
if (!service) {
const sub = req.query[req.query.kind]
const ref = sub.from || sub.into || sub.entity
if (!ref) { debugger }
if (ref.ref[0].id) ref.ref[0].id = 'db.' + ref.ref[0].id
else ref.ref[0] = 'db.' + ref.ref[0]
}
const res = await axios.post('/hcql/' + (service?.['@path'] ?? 'db'), req.query, { headers: { 'content-type': 'application/json' } })

// Convert HCQL result format to @cap-js/db-service compliant results
if (req.query.SELECT) return req.query.SELECT?.one ? res.data.data[0] : res.data.data
if (req.query.INSERT) return new InsertResults(req.query, res.data.data)
return res.data.rowCounts.reduce((l, c) => l + c)
})
}
url4() { return 'Java Proxy' }
}
82 changes: 82 additions & 0 deletions lib/java.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const childProcess = require('child_process')
const { setTimeout } = require('node:timers/promises')

module.exports = async function java(...args) {
const { cds } = this
const { fs: { promises: fs }, path } = cds.utils
const srv = path.resolve(cds.root, cds.env.folders.srv)

// forces java to respond @odata.context and @odata.count just like the node runtime
this.axios.defaults.headers.common['Odata-Version'] = '4.0'

cds.env.requires.db = { impl: require.resolve('./java-hcql.js'), axios: this.axios }

const [_, options] = require('@sap/cds/bin/args')(require('@sap/cds/bin/serve'), args)

// load application model
const from = [...(options.from?.split(',') ?? ['*'])]
const model = await cds.load(from)
if (model.definitions.db) {
// link test enviroment with application linked model
cds.model = cds.linked(model)
} else {
// enhance java model with database hcql service
const db = { ...model, definitions: { db: { kind: 'service', '@path': 'db', '@protocol': ['hcql'], '@requires': 'any' } } }
const services = []
for (const name in model.definitions) {
const def = model.definitions[name]
if (def.kind === 'service') services.push(name)
if (def.kind !== 'entity') continue
if (services.find(s => name.startsWith(s))) continue
db.definitions['db.' + name] = { "kind": "entity", "projection": { "from": { "ref": [name] } } }
}
await Promise.all([
fs.writeFile(path.resolve(srv, 'db.cds'), `using from './db.json';`),
fs.writeFile(path.resolve(srv, 'db.json'), JSON.stringify(db)),
])

// link test enviroment with application linked model
cds.model = cds.linked(await cds.load([...from, path.resolve(srv, 'db.cds')]))
}

let res, rej
const ready = new Promise((resolve, reject) => {
res = resolve
rej = reject
})

const p = await port()
const url = `http://localhost:${p}`
const app = childProcess.spawn('mvn', ['spring-boot:run', `-Dspring-boot.run.arguments=--server.port=${p}`], { cwd: cds.root, stdio: 'inherit', env: process.env })
app.on('error', rej)
app.on('exit', () => rej(new Error('Application failed to start.')))
cds.shutdown = () => Promise.all([
app.kill(),
fs.rm(path.resolve(srv, 'db.cds')),
fs.rm(path.resolve(srv, 'db.json')),
])

// REVISIT: make it call an actual /health check
// ping the server until it responds
const ping = () => cds.test.axios.get(url).catch(() => ping())
ping().then(res)
await ready

// connect to primary database hcql proxy service
await cds.connect()

return { server: { address: () => { return p } }, url }
}

function port() {
return new Promise((resolve, reject) => {
const net = require('net')
const server = net.createServer()
server.on('error', reject)

server.listen(() => {
const { port } = server.address()
server.close(() => resolve(port))
})
})
}
136 changes: 136 additions & 0 deletions test/app/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>customer</groupId>
<artifactId>app-parent</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>

<name>app parent</name>

<properties>
<!-- OUR VERSION -->
<revision>1.0.0-SNAPSHOT</revision>

<!-- DEPENDENCIES VERSION -->
<jdk.version>21</jdk.version>
<cds.services.version>3.2.0</cds.services.version>
<spring.boot.version>3.3.3</spring.boot.version>
<cds.install-cdsdk.version>8.1.2</cds.install-cdsdk.version>

<cds.install-node.downloadUrl>https://nodejs.org/dist/</cds.install-node.downloadUrl>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<modules>
<module>srv</module>
</modules>

<dependencyManagement>
<dependencies>
<!-- CDS SERVICES -->
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-services-bom</artifactId>
<version>${cds.services.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- SPRING BOOT -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<!-- JAVA VERSION -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${jdk.version}</release>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<!-- MAKE SPRING BOOT PLUGIN RUNNABLE FROM ROOT -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>

<!-- SUREFIRE VERSION -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.1</version>
</plugin>

<!-- POM FLATTENING FOR CI FRIENDLY VERSIONS -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- PROJECT STRUCTURE CHECKS -->
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>Project Structure Checks</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>3.6.3</version>
</requireMavenVersion>
<requireJavaVersion>
<version>${jdk.version}</version>
</requireJavaVersion>
<reactorModuleConvergence />
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2 changes: 1 addition & 1 deletion test/app/srv/admin-service.cds
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using { sap.capire.bookshop as my } from '../db/schema';

@path: '/admin'
@path: 'admin'
service AdminService {
entity Books as projection on my.Books;
entity Authors as projection on my.Authors;
Expand Down
2 changes: 2 additions & 0 deletions test/app/srv/cat-service.cds
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using { sap.capire.bookshop as my } from '../db/schema';

@path: 'catalog'
service CatalogService {

/** For displaying lists of Books */
Expand Down
2 changes: 1 addition & 1 deletion test/app/srv/draft-service.cds
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using { sap.capire.bookshop as my } from '../db/schema';

@path: '/draft'
@path: 'draft'
service DraftService {
@odata.draft.enabled
entity Books as projection on my.Books;
Expand Down
Loading