Compare commits

..

11 Commits

Author SHA1 Message Date
Christian Georgi
ce3aaa778f Enforce eslint 9, fix config 2024-04-15 14:33:27 +02:00
Mara Kiefer
2861c80e16 Update eslint.config.js 2024-04-15 14:24:28 +02:00
Mara Kiefer
b2c26278b5 Update package.json 2024-04-15 14:21:13 +02:00
Mara Kiefer
11b43a924d Update eslint.config.js 2024-04-15 08:55:06 +02:00
Mara Kiefer
48173c75ed Update package.json 2024-04-15 08:24:42 +02:00
Mara Kiefer
b59997aa47 Update package.json 2024-04-15 08:22:48 +02:00
Mara Kiefer
a30cfefd88 Update eslint.config.js 2024-04-12 08:45:51 +02:00
Mara Kiefer
8887c614d8 Update eslint.config.js 2024-04-12 08:42:25 +02:00
Mara Kiefer
3f78e8249f Update eslint.config.js 2024-04-12 08:41:07 +02:00
Mara Kiefer
b584de02a3 Update eslint.config.js 2024-04-12 08:38:39 +02:00
Mara Kiefer
6a6b498d9a Create eslint.config.js 2024-04-11 13:35:42 +02:00
22 changed files with 671 additions and 608 deletions

31
.eslintrc Normal file
View File

@@ -0,0 +1,31 @@
{
"extends": [
"plugin:@sap/cds/recommended",
"eslint:recommended"
],
"env": {
"browser": true,
"es2022": true,
"node": true,
"jest": true,
"mocha": true
},
"globals": {
"SELECT": true,
"INSERT": true,
"UPSERT": true,
"UPDATE": true,
"DELETE": true,
"CREATE": true,
"DROP": true,
"CDL": true,
"CQL": true,
"cds": true
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off",
"require-await":"warn",
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
}
}

View File

@@ -15,6 +15,3 @@ updates:
- dependency-name: "chai" - dependency-name: "chai"
# chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test # chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test
versions: ["5.x"] versions: ["5.x"]
- dependency-name: "chai-as-promised"
# chai-as-promised 8 doesn't work atm w/ cds.test, TODO fix that in cds.test
versions: ["8.x"]

View File

@@ -11,6 +11,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@@ -18,21 +19,10 @@ jobs:
node-version: [20.x, 18.x] node-version: [20.x, 18.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm ci - run: npm ci
- run: npm test - run: npm test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: npm ci
- run: npm run lint

View File

@@ -13,7 +13,7 @@
"@cap-js/sqlite": "*" "@cap-js/sqlite": "*"
}, },
"dependencies": { "dependencies": {
"@sap/cds": ">=7", "@sap/cds": "^7",
"express": "^4.17.1" "express": "^4.17.1"
}, },
"scripts": { "scripts": {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
module.exports = class AdminService extends cds.ApplicationService { init(){ module.exports = class AdminService extends cds.ApplicationService { init(){
this.before ('NEW','Authors', genid) this.before ('NEW','Authors', genid)

View File

@@ -1,4 +1,3 @@
const cds = require('@sap/cds')
module.exports = class CatalogService extends cds.ApplicationService { init() { module.exports = class CatalogService extends cds.ApplicationService { init() {
const { Books } = cds.entities('sap.capire.bookshop') const { Books } = cds.entities('sap.capire.bookshop')

View File

@@ -1,36 +1,27 @@
const cds = require('@sap/eslint-plugin-cds') const globals = require("globals");
const globals = require('globals') const js = require('@eslint/js');
const js = require('@eslint/js') const cds = require('@sap/eslint-plugin-cds');
module.exports = [ module.exports = [
cds.configs.recommended, cds.configs.recommended,
js.configs.recommended,
{ {
languageOptions: { "files": ["**/*.js"],
globals: { ...js.configs.recommended,
es2022: true, "languageOptions": {
"globals": {
...globals.browser, ...globals.browser,
...globals.node, ...globals.node,
...globals.jest, ...globals.jest,
...globals.mocha, ...globals.mocha,
cds: true, "es2022": true
sap: true, },
CDL: true,
CQL: true,
CREATE: true,
DELETE: true,
DROP: true,
INSERT: true,
SELECT: true,
UPDATE: true,
UPSERT: true
}
}, },
rules: { "rules": {
'no-console': 'off', ...js.configs.recommended.rules,
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], "no-console": "off",
'require-atomic-updates': 'off', "require-atomic-updates": "off",
'require-await': 'warn' "require-await": "warn",
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
} }
} }
] ]

View File

@@ -1,7 +1,7 @@
<!doctype html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -10,22 +10,21 @@
<script> <script>
window["sap-ushell-config"] = { window["sap-ushell-config"] = {
defaultRenderer: "fiori2", defaultRenderer: "fiori2",
applications: {}, applications: {}
}; };
</script> </script>
<script id="sap-ushell-bootstrap" src="https://ui5.sap.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script> <script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js" <script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge" data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-theme="sap_horizon"></script> data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_horizon"
data-sap-ui-frameOptions="allow"
></script>
<script> <script>
sap.ui.getCore().attachInit(() => sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
sap.ushell.Container.createRenderer().placeAt("content")
);
</script> </script>
</head> </head>
<body class="sapUiBody" id="content"></body>
<body class="sapUiBody" id="content">
</body>
</html> </html>

8
fiori/server.js Normal file
View File

@@ -0,0 +1,8 @@
// install OData v2 adapter
const cds = require("@sap/cds")
const proxy = require('@cap-js-community/odata-v2-adapter')
const opts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
cds.on('bootstrap', app => app.use(proxy(opts))) // install proxy
// cds.log('cov2ap','silent') // suppress anoying log outpout, e.g. for `npm run mocha -- --reporter nyan`
module.exports = require('@capire/bookstore/server.js')

View File

@@ -1,10 +1,13 @@
const cds = require ('@sap/cds') const cds = require ('@sap/cds')
describe('Hello world!', () => { describe('Hello world!', () => {
beforeAll (()=> process.env.CDS_TYPESCRIPT = true)
afterAll (()=> delete process.env.CDS_TYPESCRIPT)
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds') const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds')
it('should say hello with class impl', async () => { it('should say hello with class impl', async () => {
const {data} = await GET`/say/hello(to='world')` const {data} = await GET`/say/hello(to='world')`
expect(data.value).to.match(/Hello world.*typescript.*/i) expect(data.value).toMatch(/Hello world.*typescript.*/i)
}) })
}) })

View File

@@ -1,4 +1,4 @@
const cds = require ('@sap/cds') const cds = require ('@sap/cds/lib')
const LOG = cds.log('cds.log') const LOG = cds.log('cds.log')
module.exports = class LogService extends cds.Service { module.exports = class LogService extends cds.Service {

834
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,57 @@
{ {
"name": "@capire/samples", "name": "@capire/samples",
"version": "2.0.0", "version": "2.0.0",
"description": "A monorepo with several samples for CAP.", "description": "A monorepo with several samples for CAP.",
"repository": "https://github.com/sap-samples/cloud-cap-samples.git", "repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com", "author": "daniel.hutzel@sap.com",
"dependencies": { "dependencies": {
"@sap/cds": ">=7" "@sap/cds": ">=7"
}, },
"workspaces": [ "workspaces": [
"./bookshop", "./bookshop",
"./bookstore", "./bookstore",
"./common", "./common",
"./data-viewer", "./data-viewer",
"./fiori", "./fiori",
"./hello", "./hello",
"./media", "./media",
"./orders", "./orders",
"./loggers", "./loggers",
"./reviews" "./reviews"
], ],
"devDependencies": { "devDependencies": {
"@cap-js/sqlite": "^1", "@cap-js/sqlite": "^1",
"@sap/eslint-plugin-cds": "^3", "@sap/eslint-plugin-cds": "^3",
"axios": "^1", "axios": "^1",
"chai": "^4.3.4", "chai": "^4.3.4",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0", "chai-subset": "^1.6.0",
"eslint": "^9", "eslint": "^9",
"semver": "^7" "semver": "^7"
}, },
"scripts": { "scripts": {
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules", "cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
"bookshop": "cds watch bookshop", "bookshop": "cds watch bookshop",
"fiori": "cds watch fiori", "fiori": "cds watch fiori",
"hello": "cds watch hello", "hello": "cds watch hello",
"media": "cds watch media", "media": "cds watch media",
"mocha": "CDS_TEST_SILENT=y npx mocha", "mocha": "CDS_TEST_SILENT=y npx mocha",
"jest": "npx jest --silent", "jest": "npx jest --silent",
"start": "cds watch fiori", "start": "cds watch fiori",
"test": "npm run jest -- --silent", "test": "npm run jest -- --silent",
"test:hello": "cd hello && npm test", "test:hello": "cd hello && npm test"
"lint": "eslint ." },
}, "jest": {
"jest": { "testTimeout": 20000,
"testTimeout": 20000, "testMatch": [
"testMatch": [ "**/*.test.js"
"**/*.test.js" ]
] },
}, "mocha": {
"mocha": { "recursive": true,
"recursive": true, "parallel": true,
"parallel": true, "timeout": 6666
"timeout": 6666 },
}, "license": "SEE LICENSE IN LICENSE",
"license": "SEE LICENSE IN LICENSE", "private": true
"private": true }
}

View File

@@ -1,8 +1,7 @@
const cds = require('@sap/cds')
const { expect } = cds.test
describe('cds.ql → cqn', () => { describe('cds.ql → cqn', () => {
const cds = require('@sap/cds/lib')
const { expect } = cds.test
const Foo = { name: 'Foo' } const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' } const Books = { name: 'capire.bookshop.Books' }

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Consuming Services locally', () => { describe('cap/samples - Consuming Services locally', () => {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Custom Handlers', () => { describe('cap/samples - Custom Handlers', () => {
@@ -8,10 +8,9 @@ describe('cap/samples - Custom Handlers', () => {
}) })
it('should reject out-of-stock orders', async () => { it('should reject out-of-stock orders', async () => {
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith( await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/)
/409 - 5 exceeds stock for book #201/)
const { data } = await GET`/admin/Books/201/stock/$value` const { data } = await GET`/admin/Books/201/stock/$value`
expect(data).to.equal(2) expect(data).to.equal(2)
}) })

View File

@@ -1,8 +1,4 @@
// Quick hack: suppress deprecation warnings w/ Node22 caused by http-proxy (used by OData v2 proxy) const cds = require('@sap/cds/lib')
// See also: https://github.com/http-party/node-http-proxy/pull/1666
require('util')._extend = Object.assign
const cds = require('@sap/cds')
describe('cap/samples - Fiori APIs - v2', function() { describe('cap/samples - Fiori APIs - v2', function() {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Hello world!', () => { describe('cap/samples - Hello world!', () => {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
const { expect } = cds.test.in(__dirname,'..') const { expect } = cds.test.in(__dirname,'..')
describe('cap/samples - Hierarchical Data', ()=>{ describe('cap/samples - Hierarchical Data', ()=>{
@@ -77,45 +77,49 @@ describe('cap/samples - Hierarchical Data', ()=>{
) )
}) })
it ('supports nested reads', ()=> expect ( it ('supports nested reads', async()=>{
SELECT.one.from (Cats, c=>{ expect (await
c.ID, c.name.as('parent'), c.children (c=>{ SELECT.one.from (Cats, c=>{
c.name.as('child') c.ID, c.name.as('parent'), c.children (c=>{
}) c.name.as('child')
}) .where ({name:'Cat'}) })
) .to.eventually.eql ( }) .where ({name:'Cat'})
{ ID:101, parent:'Cat', children:[ ) .to.eql (
{ child:'Kitty' }, { ID:101, parent:'Cat', children:[
{ child:'Catwoman' }, { child:'Kitty' },
]} { child:'Catwoman' },
)) ]}
)
})
it ('supports deeply nested reads', ()=> expect ( it ('supports deeply nested reads', async()=>{
SELECT.one.from (Cats, c=>{ expect (await SELECT.one.from (Cats, c=>{
c.ID, c.name, c.children ( c.ID, c.name, c.children (
c => { c.name }, c => { c.name },
{levels:3} {levels:3}
) )
}) .where ({name:'Cat'}) }) .where ({name:'Cat'})
) .to.eventually.eql ( ) .to.eql (
{ ID:101, name:'Cat', children:[ { ID:101, name:'Cat', children:[
{ name:'Kitty', children:[ { name:'Kitty', children:[
{ name:'Kitty Cat', children:[ { name:'Kitty Cat', children:[
{ name:'Aristocat' }, ]}, // level 3 { name:'Aristocat' }, ]}, // level 3
{ name:'Kitty Bat', children:[] }, ]}, { name:'Kitty Bat', children:[] }, ]},
{ name:'Catwoman', children:[ { name:'Catwoman', children:[
{ name:'Catalina', children:[] } ]}, { name:'Catalina', children:[] } ]},
]} ]}
)) )
})
it ('supports cascaded deletes', async()=>{ it ('supports cascaded deletes', async()=>{
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]}) const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
expect (affectedRows) .to.be.greaterThan (0) expect (affectedRows) .to.be.greaterThan (0)
await expect (SELECT`ID,name`.from(Cats) ).to.eventually.eql ([ const expected = [
{ ID:100, name:'Some Cats...' }, { ID:100, name:'Some Cats...' },
{ ID:101, name:'Cat' }, { ID:101, name:'Cat' },
{ ID:108, name:'Catweazle' } { ID:108, name:'Catweazle' }
]) ]
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
}) })
}) })

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Localized Data', () => { describe('cap/samples - Localized Data', () => {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Messaging', ()=>{ describe('cap/samples - Messaging', ()=>{

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Bookshop APIs', () => { describe('cap/samples - Bookshop APIs', () => {
const { GET, expect, axios } = cds.test ('@capire/bookshop') const { GET, expect, axios } = cds.test ('@capire/bookshop')
@@ -8,10 +8,9 @@ describe('cap/samples - Bookshop APIs', () => {
const { headers, status, data } = await GET `/browse/$metadata` const { headers, status, data } = await GET `/browse/$metadata`
expect(status).to.equal(200) expect(status).to.equal(200)
expect(headers).to.contain({ expect(headers).to.contain({
// 'content-type': 'application/xml', //> fails with 'application/xml;charset=utf-8', which is set by express 'content-type': 'application/xml',
'odata-version': '4.0', 'odata-version': '4.0',
}) })
expect(headers['content-type']).to.match(/application\/xml/)
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">') expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">')
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>') expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
}) })
@@ -29,66 +28,63 @@ describe('cap/samples - Bookshop APIs', () => {
]) ])
}) })
describe('query options...', () => { it('supports $search in multiple fields', async () => {
const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` },
}}`
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
])
})
it('supports $search in multiple fields', async () => { it('supports $select', async () => {
const { data } = await GET `/browse/Books ${{ const { data } = await GET(`/browse/Books`, {
params: { $search: 'Po', $select: `title,author` }, params: { $select: `ID,title` },
}}`
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
])
}) })
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights' },
{ ID: 207, title: 'Jane Eyre' },
{ ID: 251, title: 'The Raven' },
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
})
it('supports $select', async () => { it('supports $expand', async () => {
const { data } = await GET(`/browse/Books`, { const { data } = await GET(`/admin/Authors`, {
params: { $select: `ID,title` }, params: {
}) $select: `name`,
expect(data.value).to.containSubset([ $expand: `books($select=title)`,
{ ID: 201, title: 'Wuthering Heights' }, },
{ ID: 207, title: 'Jane Eyre' },
{ ID: 251, title: 'The Raven' },
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
}) })
expect(data.value).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('supports $expand', async () => { it('supports $value requests', async () => {
const { data } = await GET(`/admin/Authors`, { const { data } = await GET`/admin/Books/201/stock/$value`
params: { expect(data).to.equal(12)
$select: `name`, })
$expand: `books($select=title)`,
},
})
expect(data.value).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('supports $value requests', async () => { it('supports $top/$skip paging', async () => {
const { data } = await GET`/admin/Books/201/stock/$value` const { data: p1 } = await GET`/browse/Books?$select=title&$top=3`
expect(data).to.equal(12) expect(p1.value).to.containSubset([
}) { ID: 201, title: 'Wuthering Heights' },
{ ID: 207, title: 'Jane Eyre' },
it('supports $top/$skip paging', async () => { { ID: 251, title: 'The Raven' },
const { data: p1 } = await GET`/browse/Books?$select=title&$top=3` ])
expect(p1.value).to.containSubset([ const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
{ ID: 201, title: 'Wuthering Heights' }, expect(p2.value).to.containSubset([
{ ID: 207, title: 'Jane Eyre' }, { ID: 252, title: 'Eleonora' },
{ ID: 251, title: 'The Raven' }, { ID: 271, title: 'Catweazle' },
]) ])
const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
expect(p2.value).to.containSubset([
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
})
}) })
it('serves user info', async () => { it('serves user info', async () => {