Compare commits

..

98 Commits

Author SHA1 Message Date
Christian Georgi
11f3b357e4 Data load 2022-07-07 12:52:46 +02:00
Christian Georgi
5f89334403 Remove refs to 'master' branch 2021-10-12 18:46:21 +02:00
Christian Georgi
e612fa97ea Merge pull request #277 from SAP-samples/updated-package.json
Updated package.json
2021-10-08 11:16:10 +02:00
Christian Georgi
da2ea39466 Do not fail on absent cds-swagger-ui-express 2021-10-08 10:23:12 +02:00
Daniel
117000df71 Revert "temporarily using sqlite3 until next release to fix vulnerabilities"
This reverts commit f3ffb69d3a.
2021-10-08 07:50:04 +02:00
Daniel
f3ffb69d3a temporarily using sqlite3 until next release to fix vulnerabilities 2021-10-08 07:41:18 +02:00
Daniel
f908484973 Updated package.json 2021-10-08 06:42:50 +02:00
Christian Georgi
c4529f3cd7 Increase test timeout
At least on slower machines or when file system caches are not filled,
the default 5000 ms are not enough for the tests to finish.
2021-09-29 13:51:49 +02:00
Christian Georgi
0220400484 Avoid mixed spaces/tabs 2021-09-20 15:56:53 +02:00
dependabot[bot]
c1911b6e96 Bump axios from 0.21.1 to 0.21.4
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-10 15:42:25 +02:00
Daniel
19083d156e Rename amount -> quantity 2021-09-10 15:39:30 +02:00
Christian Georgi
48d547e6cd Make jest really silent 2021-09-10 15:35:36 +02:00
Daniel
bae491a832 one more more more 2021-08-31 20:36:36 +02:00
Daniel
ca41a2141c one more 2021-08-31 19:30:09 +02:00
Daniel Hutzel
08f409af73 more (#267) 2021-08-31 18:05:22 +02:00
Daniel Hutzel
efa60550fb fix cds.ql (#266) 2021-08-31 13:29:05 +02:00
Daniel Hutzel
f599206bf4 Fixed cds.ql in latest release (#232)
* Fixed cds.ql in latest release

* Requires @sap/cds ^5.1.5

* fixed fixes

* More cdr tests

Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2021-08-31 09:59:06 +02:00
Daniel
2f5d159428 using cds.test 2021-08-30 16:08:25 +02:00
Daniel
2be3d50389 More cds.ql cleanup 2021-08-30 11:09:42 +02:00
Daniel
46b58f1b5c skipping ts test 2021-08-27 14:19:17 +02:00
Daniel
e87527cbcd fixed package-lock 2021-08-27 14:19:17 +02:00
Daniel
091844219b Upgrade to jest 27 2021-08-27 14:19:17 +02:00
Daniel
de796e5a89 Fixed '*' in upcomming release 2021-08-27 10:46:53 +02:00
Daniel
f988088412 '*' is still different 2021-08-26 06:50:13 +02:00
Daniel
c4a51ab719 fixed test 2021-08-26 06:50:13 +02:00
Daniel
f58376607a Prep for cleaning up cds.ql 2021-08-26 06:50:13 +02:00
Dr. David A. Kunz
839048d87c Merge pull request #260 from SAP-samples/fixing-test
Cascade delete test: Yes it should have been deleted
2021-08-19 12:57:56 +02:00
D065023
3b3463f889 skipped 2021-08-19 12:55:48 +02:00
D065023
d3396304ec yes it should be deleted 2021-08-19 12:45:03 +02:00
Pierre Fritsch
ae09caf7ad Enable cds watch hello
by moving the `hello` implementation into the subfolder `srv`
2021-08-03 16:01:10 +02:00
Daniel Hutzel
e1052c209b Removing workaround for glitch in drafts (#254)
* Removing workaround for glitch in drafts

* cosmetics
2021-08-02 17:20:46 +02:00
Christian Georgi
cd3aad59e1 Fix UI warnings
These were caused by the UI check not being aware of the parallel
`common.cds` file.
2021-07-14 10:11:21 +02:00
Christian Georgi
bcbf65f95e Draft for localized: expose Books.texts well
`Books.texts` must be explicitly exposed in order to be drafted
2021-07-13 16:23:44 +02:00
Christian Georgi
1189374860 Update to cds 5.3.1 2021-07-13 15:16:27 +02:00
Christian Georgi
9f2eb8fa83 Merge pull request #248 from SAP-samples/ts-node_support
Ts node support + tests
2021-07-13 14:32:49 +02:00
Christian Georgi
7b18e15e5b Jest config in package.json again 2021-07-13 13:55:05 +02:00
Christian Georgi
00bfc1507e Isolate all TS stuff in hello package 2021-07-13 13:24:29 +02:00
Christian Georgi
47a58e7393 Moved typescript dependencies to hello package
This allows TS specifics to stay local in just one package
2021-07-13 13:09:54 +02:00
Christian Georgi
c04b972d27 Cosmetics 2021-07-13 13:01:50 +02:00
Christian Georgi
85b9b9d3a3 Clear process variable after test 2021-07-13 13:01:50 +02:00
Tobias Steckenborn
3655eef971 fix: 🚑 Add testEnvironment again 2021-07-13 13:01:50 +02:00
Tobias Steckenborn
87dd356204 refactor: 🔥 Add ts-jest preset, remove manual transformation 2021-07-13 13:01:50 +02:00
Christian Georgi
a89ec34f7c Move jest config out of the way 2021-07-13 13:01:50 +02:00
Christian Georgi
444dcacb14 Add typescript package 2021-07-13 13:01:50 +02:00
Christian Georgi
2011a1ca7a Use transform option to make ts test work 2021-07-13 13:01:50 +02:00
Christian Georgi
7e0c36ede5 use cds-ts binary for starting cds with ts-node runtime 2021-07-13 13:01:50 +02:00
Christian Georgi
e8d08d039e Add example for starting cds with ts-node 2021-07-13 13:01:46 +02:00
Christian Georgi
5e3258913e Merge pull request #247 from SAP-samples/openapi
OpenAPI and Swagger UI
2021-07-12 14:04:30 +02:00
Christian Georgi
44e14926e4 Use latest Jest 2021-07-12 14:02:42 +02:00
Christian Georgi
592e945c28 Update package-lock 2021-07-12 14:02:21 +02:00
Christian Georgi
5d5c787365 Merge remote-tracking branch 'origin/master' into openapi 2021-07-05 17:59:00 +02:00
Christian Georgi
bb79def1d9 Merge pull request #239 from SAP-samples/using-package-lock.json
Using package-lock.json
2021-07-05 17:58:25 +02:00
Christian Georgi
bd4a38bf54 Update package-lock 2021-07-05 17:55:10 +02:00
Christian Georgi
abf18d74db Use npm ci instead of npm install 2021-07-05 17:55:10 +02:00
Christian Georgi
f144354229 Merge branch 'origin/master' into using-package-lock.json 2021-07-05 17:50:39 +02:00
Christian Georgi
97670bbc84 Swagger UI with cds-swagger-ui-express
Moved from `bookshop` to `fiori`, as that one already has a `server.js`
2021-07-05 17:46:24 +02:00
Christian Georgi
82d6f54337 Merge remote-tracking branch 'origin/master' into openapi 2021-07-05 17:39:06 +02:00
Heiko Witteborg
a4baee6e3b Merge pull request #243 from MikhailGoncharov/not-simple-args-fluent-api-v2
Sync with cds-runtime
2021-06-10 08:37:14 +02:00
Mikhail Goncharov
95b01e7d7a Do not trim quotes 2021-06-09 13:39:12 +02:00
Mikhail Goncharov
c2437fc419 Handle cds versions 2021-06-09 12:49:39 +02:00
Mikhail Goncharov
9381459458 Sync with cds-runtime 2021-06-09 10:43:10 +02:00
Daniel Hutzel
28a7c16895 Merge branch 'master' into using-package-lock.json 2021-06-09 07:38:42 +02:00
Christian Georgi
c1141e6c87 Remove trailing comma 2021-06-08 16:19:34 +02:00
Daniel
6e686c6c8b adding top-level dependencies to @sap/cds 2021-06-04 11:52:49 +02:00
Daniel
1ccddb2155 . 2021-06-04 11:44:21 +02:00
Daniel
7c8e52d5e9 Using package-lock.json 2021-06-04 11:43:59 +02:00
Christian Georgi
0dc1dc198d Merge pull request #227 from gregorwolf/fiori-currency-format
Fiori currency format
2021-05-28 15:08:15 +02:00
Christian Georgi
20bfb5c5f7 Merge branch 'master' into fiori-currency-format 2021-05-28 15:03:54 +02:00
Pierre Fritsch
be87f5617a Fix typo orderd -> ordered 2021-05-28 14:58:51 +02:00
Christian Georgi
3117df1282 Test with Jest 26
`cds.test` seems to have issues w/ Jest 27.

To be investigated...
2021-05-28 14:56:25 +02:00
Heiko Witteborg
be685437aa Merge pull request #231 from SAP-samples/windows-test-fix
Avoid race condition in customer-handlers.test
2021-05-14 13:41:17 +02:00
d049904
ff3801be71 Avoid race condition 2021-05-12 12:41:31 +02:00
Christian Georgi
702c3245bd Fix broken translation UI
Use `Books.texts` instead of `_texts`, requiring sap/cds 5
2021-05-10 10:33:39 +02:00
Daniel Hutzel
14002300c5 Rename index.cds to services.cds 2021-05-03 21:07:04 +02:00
Gregor Wolf
ad417ec061 fix test for Yen 2021-05-03 09:33:05 +02:00
Gregor Wolf
a6ec296129 add annotation for ISOCurrency 2021-05-03 09:04:38 +02:00
Gregor Wolf
1b10bbbfe4 switch currency of a book 2021-05-03 09:04:13 +02:00
Gregor Wolf
aaa1b2d6c7 add translation for Japanese Yen 2021-05-03 09:03:48 +02:00
Daniel
1cb169e886 fixed indentation 2021-04-27 16:06:59 +02:00
Daniel
9b9bb4c114 No process.chdir 2021-04-24 17:16:02 +02:00
Daniel
2536f36596 Adjust to latest capire docs 2021-04-13 09:55:27 +02:00
Daniel
5f160c0927 Added debug conf 2021-04-10 13:06:15 +02:00
Daniel
0c4658a3ef Fixed and adjusted to cds5 2021-04-10 13:06:15 +02:00
Christian Georgi
33cd70065a Use cds.test 2021-04-06 16:27:36 +02:00
Christian Georgi
450577d5a7 Refer to fiori app instead of bookshop 2021-04-06 16:11:24 +02:00
Daniel
5a23ba0f76 Prepare cds5 2021-04-01 16:47:45 +02:00
Daniel
064ce32b8a npm script cleanup 2021-03-27 19:39:48 +01:00
Daniel
2c064d0200 Adhere to public recommendations 2021-03-27 19:39:24 +01:00
Daniel
51ee6d167c added comment 2021-03-25 01:08:14 +01:00
Christian Georgi
5e829ae7bb Merge branch 'origin/master' into openapi 2021-02-17 09:50:04 +01:00
Christian Georgi
d265a385f8 Some comments 2020-11-17 11:12:32 +01:00
Christian Georgi
80f469b5b6 Remove need for host to swagger UI 2020-11-17 11:06:04 +01:00
Christian Georgi
8e8ae949df Allow swagger.io to call us 2020-11-16 18:21:06 +01:00
Christian Georgi
3d24dc491d Create files for all services 2020-11-16 12:06:29 +01:00
Christian Georgi
f593265687 Cosmetics 2020-11-14 21:56:08 +01:00
Christian Georgi
6d488b042c Create diagram for Open API spec 2020-11-14 21:38:18 +01:00
Christian Georgi
d5ef630743 Link in index.html. Better server URL 2020-11-14 21:18:50 +01:00
Christian Georgi
b47c9d75df Embed Swagger UI 2020-11-13 16:10:07 +01:00
55 changed files with 3240 additions and 420 deletions

View File

@@ -5,9 +5,9 @@ name: CI
on: on:
push: push:
branches: [ master ] branches: [ main ]
pull_request: pull_request:
branches: [ master ] branches: [ main ]
jobs: jobs:
build: build:
@@ -24,5 +24,5 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm install - run: npm ci
- run: npm test - run: npm test

2
.gitignore vendored
View File

@@ -15,3 +15,5 @@ default-env.json
packages/messageBox packages/messageBox
reviews/msg-box reviews/msg-box
reviews/db/test.db reviews/db/test.db
*.openapi3.json

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
@sap:registry=https://registry.npmjs.org/
registry=https://registry.npmjs.org/

View File

@@ -5,7 +5,7 @@ const app = express()
const { PORT=4444 } = process.env const { PORT=4444 } = process.env
const [,,port=PORT] = process.argv const [,,port=PORT] = process.argv
process.chdir(__dirname) const cwd = __dirname
app.use('/-/:tarball', (req,res,next) => { app.use('/-/:tarball', (req,res,next) => {
console.debug ('GET', req.params) console.debug ('GET', req.params)
@@ -13,7 +13,7 @@ app.use('/-/:tarball', (req,res,next) => {
const { tarball } = req.params const { tarball } = req.params
const [, pkg ] = /^capire-(\w+)/.exec(tarball) const [, pkg ] = /^capire-(\w+)/.exec(tarball)
fs.lstat(tarball,(err => { fs.lstat(tarball,(err => {
if (err) exec(`npm pack ../${pkg}`,next) if (err) exec(`npm pack ../${pkg}`,{cwd},next)
else next() else next()
})) }))
} catch (e) { } catch (e) {

View File

@@ -105,5 +105,5 @@
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA." "description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
} }
], ],
"ref": "master" "ref": "main"
} }

37
.vscode/launch.json vendored
View File

@@ -4,28 +4,31 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{ {
"name": "bookshop", "name": "bookshop",
"command": "cds watch bookshop", "command": "npx cds watch bookshop",
"request": "launch",
"type": "node-terminal", "type": "node-terminal",
"skipFiles": ["<node_internals>/**"] "request": "launch",
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/cds/lib/lazy.js",
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
}, },
{ {
"name": "Fiori app", "name": "Fiori App",
"command": "cds watch fiori", "command": "npx cds watch fiori",
"request": "launch",
"type": "node-terminal", "type": "node-terminal",
"skipFiles": ["<node_internals>/**"] "request": "launch",
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/cds/lib/lazy.js",
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
} }
], ],
"inputs": [ "inputs": [
@@ -33,7 +36,7 @@
"type": "pickString", "type": "pickString",
"id": "sample", "id": "sample",
"description": "Which sample do you want to start?", "description": "Which sample do you want to start?",
"options": ["bookshop", "fiori", "reviews", "reviews/test/bookshop"], "options": [ "bookshop", "fiori", "reviews", "reviews" ],
"default": "bookshop" "default": "bookshop"
} }
] ]

View File

@@ -4,5 +4,14 @@
"**/.gitignore": true, "**/.gitignore": true,
"**/.vscode": true, "**/.vscode": true,
"LICENSES/**": true "LICENSES/**": true
},
"debug.javascript.terminalOptions": {
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/cds/lib/lazy.js",
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
} }
} }

View File

@@ -17,7 +17,7 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
### Download ### Download
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip). If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/main.zip).
```sh ```sh
git clone https://github.com/sap-samples/cloud-cap-samples samples git clone https://github.com/sap-samples/cloud-cap-samples samples
@@ -83,4 +83,4 @@ In case you've a question, find a bug, or otherwise need support, use our [commu
## License ## License
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt) file. Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE.txt) file.

View File

@@ -10,7 +10,7 @@ const books = new Vue ({
data: { data: {
list: [], list: [],
book: undefined, book: undefined,
order: { amount:1, succeeded:'', failed:'' } order: { quantity:1, succeeded:'', failed:'' }
}, },
methods: { methods: {
@@ -26,18 +26,18 @@ const books = new Vue ({
const book = books.book = books.list [eve.currentTarget.rowIndex-1] const book = books.book = books.list [eve.currentTarget.rowIndex-1]
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`) const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
Object.assign (book, res.data) Object.assign (book, res.data)
books.order = { amount:1 } books.order = { quantity:1 }
setTimeout (()=> $('form > input').focus(), 111) setTimeout (()=> $('form > input').focus(), 111)
}, },
async submitOrder () { async submitOrder () {
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict const {book,order} = books, quantity = parseInt (order.quantity) || 1 // REVISIT: Okra should be less strict
try { try {
const res = await POST(`/submitOrder`, { amount, book: book.ID }) const res = await POST(`/submitOrder`, { quantity, book: book.ID })
book.stock = res.data.stock book.stock = res.data.stock
books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` } books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
} catch (e) { } catch (e) {
books.order = { amount, failed: e.response.data.error.message } books.order = { quantity, failed: e.response.data.error.message }
} }
} }

View File

@@ -48,7 +48,7 @@
&nbsp;&nbsp; {{ book.stock }} in stock &nbsp;&nbsp; {{ book.stock }} in stock
</label> </label>
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse"> <form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
<input type="number" v-model="order.amount" v-bind:class="{ failed: order.failed }" style="width:5em"> <input type="number" v-model="order.quantity" v-bind:class="{ failed: order.failed }" style="width:5em">
<input type="submit" value="Order:" class="muted-button"> <input type="submit" value="Order:" class="muted-button">
</form> </form>
<h4> {{ book.title }} </h4> <h4> {{ book.title }} </h4>

View File

@@ -3,4 +3,4 @@ ID;title;descr;author_ID;stock;price;currency_code;genre_ID
207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11 207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11
251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16 251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16
252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16 252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16
271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;15;EUR;13 271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;150;JPY;13
1 ID title descr author_ID stock price currency_code genre_ID
3 207 Jane Eyre Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name "Currer Bell", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism. 107 11 12.34 GBP 11
4 251 The Raven "The Raven" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word "Nevermore". The poem makes use of folk, mythological, religious, and classical references. 150 333 13.13 USD 16
5 252 Eleonora "Eleonora" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively "happy" ending. 150 555 14 USD 16
6 271 Catweazle Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts. 170 22 15 150 EUR JPY 13

View File

@@ -4,7 +4,7 @@
"description": "A simple self-contained bookshop service.", "description": "A simple self-contained bookshop service.",
"dependencies": { "dependencies": {
"@capire/common": "*", "@capire/common": "*",
"@sap/cds": ">=4", "@sap/cds": "^5.0.4",
"express": "^4.17.1", "express": "^4.17.1",
"passport": "0.4.1" "passport": "0.4.1"
}, },
@@ -16,8 +16,7 @@
"cds": { "cds": {
"requires": { "requires": {
"db": { "db": {
"kind": "sqlite", "kind": "sql"
"model": "*"
} }
} }
} }

Binary file not shown.

View File

@@ -1,14 +1,16 @@
using { sap.capire.bookshop as my } from '../db/schema'; using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') { service CatalogService @(path:'/browse') {
@readonly entity Books as SELECT from my.Books { *, /** For displaying lists of Books */
@readonly entity ListOfBooks as projection on Books
excluding { descr };
/** For display in details pages */
@readonly entity Books as projection on my.Books { *,
author.name as author author.name as author
} excluding { createdBy, modifiedBy }; } excluding { createdBy, modifiedBy };
@readonly entity ListOfBooks as SELECT from Books
excluding { descr };
@requires: 'authenticated-user' @requires: 'authenticated-user'
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer }; action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String }; event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
} }

View File

@@ -5,21 +5,19 @@ class CatalogService extends cds.ApplicationService { init(){
// Reduce stock of ordered books if available stock suffices // Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => { this.on ('submitOrder', async req => {
const {book,amount} = req.data, tx = cds.tx(req) const {book,quantity} = req.data
let {stock} = await tx.read('stock').from(Books,book) let {stock} = await SELECT `stock` .from (Books,book)
if (stock >= amount) { if (stock >= quantity) {
await tx.update (Books,book).with ({ stock: stock -= amount }) await UPDATE (Books,book) .with (`stock -=`, quantity)
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id }) await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
return { stock } return { stock }
} }
else return req.error (409,`${amount} exceeds stock for book #${book}`) else return req.error (409,`${quantity} exceeds stock for book #${book}`)
}) })
// Add some discount for overstocked books // Add some discount for overstocked books
this.after ('READ','Books', each => { this.after ('READ','ListOfBooks', each => {
if (each.stock > 111) { if (each.stock > 111) each.title += ` -- 11% discount!`
each.title += ` -- 11% discount!`
}
}) })
return super.init() return super.init()

View File

@@ -1,19 +0,0 @@
const cds = require ('@sap/cds')
cds.on('listening', async ()=>{ try { //> just to see the output at the end; will later on be in db service impl
console.log ('\nFilling database with initial data...')
const m = cds.model.minified() //> we likely should do that by default
const tx = cds.tx()
tx.run ([ //> this will be replaced by
DELETE.from ('sap.capire.bookshop.Authors'),
DELETE.from ('sap.capire.bookshop.Books'),
DELETE.from ('sap.capire.bookshop.Books.texts'),
DELETE.from ('sap.capire.bookshop.Genres'),
DELETE.from ('sap.common.Currencies'),
DELETE.from ('sap.common.Currencies.texts'),
])
await cds.deploy(m).to(tx,{ddl:false})
await tx.commit()
} catch(e) { console.error(e) }})
module.exports = cds.server

View File

@@ -71,7 +71,7 @@ POST {{server}}/browse/submitOrder
Content-Type: application/json Content-Type: application/json
{{me}} {{me}}
{ "book":201, "amount":5 } { "book":201, "quantity":5 }
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------

View File

@@ -5,9 +5,11 @@ CAD;de;Kanadischer Dollar;Kanadischer Dollar
AUD;de;Australischer Dollar;Australischer Dollar AUD;de;Australischer Dollar;Australischer Dollar
GBP;de;Pfund;Britische Pfund GBP;de;Pfund;Britische Pfund
ILS;de;Schekel;Israelische Schekel ILS;de;Schekel;Israelische Schekel
JPY;de;Yen;Japanische Yen
EUR;fr;euro;de la Zone euro EUR;fr;euro;de la Zone euro
USD;fr;dollar;dollar des États-Unis USD;fr;dollar;dollar des États-Unis
CAD;fr;dollar canadien;dollar canadien CAD;fr;dollar canadien;dollar canadien
AUD;fr;dollar australien;dollar australien AUD;fr;dollar australien;dollar australien
GBP;fr;livre sterling;pound sterling GBP;fr;livre sterling;pound sterling
ILS;fr;Shekel;shekel israelien ILS;fr;Shekel;shekel israelien
JPY;fr;Yen;Yen japonais
1 code locale name descr
5 AUD de Australischer Dollar Australischer Dollar
6 GBP de Pfund Britische Pfund
7 ILS de Schekel Israelische Schekel
8 JPY de Yen Japanische Yen
9 EUR fr euro de la Zone euro
10 USD fr dollar dollar des États-Unis
11 CAD fr dollar canadien dollar canadien
12 AUD fr dollar australien dollar australien
13 GBP fr livre sterling pound sterling
14 ILS fr Shekel shekel israelien
15 JPY fr Yen Yen japonais

View File

@@ -1,4 +1,5 @@
using { AdminService } from '../../db/schema'; using { AdminService } from '../../db/schema';
using from '../common'; // to help UI linter get the complete annotations
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
@@ -70,7 +71,7 @@ annotate AdminService.Authors with @(
annotate sap.capire.bookshop.Books with @fiori.draft.enabled; annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
annotate AdminService.Books with @odata.draft.enabled; annotate AdminService.Books with @odata.draft.enabled;
annotate AdminService.Books_texts with @( annotate AdminService.Books.texts with @(
UI: { UI: {
Identification: [{Value:title}], Identification: [{Value:title}],
SelectionFields: [ locale, title ], SelectionFields: [ locale, title ],
@@ -83,11 +84,11 @@ annotate AdminService.Books_texts with @(
); );
// Add Value Help for Locales // Add Value Help for Locales
annotate AdminService.Books_texts { annotate AdminService.Books.texts {
locale @ValueList:{entity:'Languages',type:#fixed} locale @ValueList:{entity:'Languages',type:#fixed}
} }
// In addition we need to expose Languages through AdminService // In addition we need to expose Languages through AdminService as a target for ValueList
using { sap } from '@sap/cds/common'; using { sap } from '@sap/cds/common';
extend service AdminService { extend service AdminService {
entity Languages as projection on sap.common.Languages; @readonly entity Languages as projection on sap.common.Languages;
} }

View File

@@ -54,7 +54,7 @@ annotate my.Books with {
title @title:'{i18n>Title}'; title @title:'{i18n>Title}';
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly }; genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly }; author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
price @title:'{i18n>Price}'; price @title:'{i18n>Price}' @Measures.ISOCurrency: currency_code;
stock @title:'{i18n>Stock}'; stock @title:'{i18n>Stock}';
descr @UI.MultiLineText; descr @UI.MultiLineText;
} }

View File

@@ -6,7 +6,7 @@
"@capire/reviews": "*", "@capire/reviews": "*",
"@capire/orders": "*", "@capire/orders": "*",
"@capire/common": "*", "@capire/common": "*",
"@sap/cds": ">=4", "@sap/cds": "^5",
"express": "^4.17.1", "express": "^4.17.1",
"passport": "^0.4.1" "passport": "^0.4.1"
}, },

View File

@@ -1,4 +1,5 @@
const cds = require ('@sap/cds') const cds = require ('@sap/cds')
module.exports = cds.server
cds.once('bootstrap',(app)=>{ cds.once('bootstrap',(app)=>{
app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json')) app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
@@ -8,7 +9,13 @@ cds.once('bootstrap',(app)=>{
cds.once('served', require('./srv/mashup')) cds.once('served', require('./srv/mashup'))
module.exports = cds.server // Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
try {
const cds_swagger = require ('cds-swagger-ui-express')
cds.once ('bootstrap', app => app.use (cds_swagger()) )
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------

View File

@@ -27,11 +27,11 @@ module.exports = async()=>{ // called by server.js
// Create an order with the OrdersService when CatalogService signals a new order // Create an order with the OrdersService when CatalogService signals a new order
// //
CatalogService.on ('OrderedBook', async (msg) => { CatalogService.on ('OrderedBook', async (msg) => {
const { book, amount, buyer } = msg.data const { book, quantity, buyer } = msg.data
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price }) const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
return OrdersService.tx(msg).create ('Orders').entries({ return OrdersService.tx(msg).create ('Orders').entries({
OrderNo: 'Order at '+ (new Date).toLocaleString(), OrderNo: 'Order at '+ (new Date).toLocaleString(),
Items: [{ product:{ID:`${book}`}, title, price, amount }], Items: [{ product:{ID:`${book}`}, title, price, quantity }],
buyer, createdBy: buyer buyer, createdBy: buyer
}) })
}) })
@@ -51,9 +51,9 @@ module.exports = async()=>{ // called by server.js
// //
OrdersService.on ('OrderChanged', (msg) => { OrdersService.on ('OrderChanged', (msg) => {
console.debug ('> received:', msg.event, msg.data) console.debug ('> received:', msg.event, msg.data)
const { product, deltaAmount } = msg.data const { product, deltaQuantity } = msg.data
return UPDATE (Books) .where ('ID =', product) return UPDATE (Books) .where ('ID =', product)
.and ('stock >=', deltaAmount) .and ('stock >=', deltaQuantity)
.set ('stock -=', deltaAmount) .set ('stock -=', deltaQuantity)
}) })
} }

View File

@@ -1,3 +1,3 @@
ID;createdAt;createdBy;buyer;OrderNo;currency_code ID;createdAt;createdBy;buyer;OrderNo;currency_code;status_code
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR 7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR;O
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR 64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR;C
1 ID createdAt createdBy buyer OrderNo currency_code status_code
2 7e2f2640-6866-4dcf-8f4d-3027aa831cad 2019-01-31 john.doe@test.com john.doe@test.com 1 EUR O
3 64e718c9-ff99-47f1-8ca3-950c850777d4 2019-01-30 jane.doe@test.com jane.doe@test.com 2 EUR C

View File

@@ -1,4 +1,4 @@
ID;up__ID;amount;product_ID;title;price ID;up__ID;quantity;product_ID;title;price
58040e66-1dcd-4ffb-ab10-fdce32028b79;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;201;Wuthering Heights;11.11 58040e66-1dcd-4ffb-ab10-fdce32028b79;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;201;Wuthering Heights;11.11
64e718c9-ff99-47f1-8ca3-950c850777d4;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;271;Catweazle;15 64e718c9-ff99-47f1-8ca3-950c850777d4;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;271;Catweazle;15
e9641166-e050-4261-bfee-d1e797e6cb7f;64e718c9-ff99-47f1-8ca3-950c850777d4;2;252;Eleonora;28 e9641166-e050-4261-bfee-d1e797e6cb7f;64e718c9-ff99-47f1-8ca3-950c850777d4;2;252;Eleonora;28
1 ID up__ID amount quantity product_ID title price
2 58040e66-1dcd-4ffb-ab10-fdce32028b79 7e2f2640-6866-4dcf-8f4d-3027aa831cad 1 1 201 Wuthering Heights 11.11
3 64e718c9-ff99-47f1-8ca3-950c850777d4 7e2f2640-6866-4dcf-8f4d-3027aa831cad 1 1 271 Catweazle 15
4 e9641166-e050-4261-bfee-d1e797e6cb7f 64e718c9-ff99-47f1-8ca3-950c850777d4 2 2 252 Eleonora 28

View File

@@ -38,7 +38,11 @@ GET {{bookshop}}/browse/Books(201)?
&$select=ID,title,rating &$select=ID,title,rating
&$expand=reviews &$expand=reviews
###
GET {{bookshop}}/browse/Books?
&$select=title,author&$expand=currency
Accept-Language: de
################################################# #################################################
# #

15
hello/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Hello World Getting Started Sample
## Next Steps
- To run the JavaScript implementation, open a new terminal and run `cds watch`.
- To run the TypeScript implementation, open a new terminal and run `cds-ts watch`.
Then call the service at: http://localhost:4004/say/hello(to='world')
## Learn More
Learn more about:
- [Hello World!](https://cap.cloud.sap/docs/get-started/hello-world)
- [Using TypeScript](https://cap.cloud.sap/docs/get-started/using-typescript)

View File

@@ -2,6 +2,53 @@
"name": "@capire/hello-world", "name": "@capire/hello-world",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"watch": "cds serve world.cds" "test": "npx jest --silent",
"start": "cds serve srv/world.cds",
"start:ts": "cds-ts serve srv/world.cds"
},
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^15.12.0",
"ts-jest": "^27.0.2",
"typescript": "^4.3.5"
},
"jest": {
"testEnvironment": "node",
"preset": "ts-jest",
"globals": {
"ts-jest": {
"diagnostics": {
"_comment": "see https://githubmemory.com/repo/kulshekhar/ts-jest/issues/2722",
"ignoreCodes": [
151001
]
}
}
}
},
"eslintConfig": {
"extends": "eslint:recommended",
"env": {
"es2020": true,
"node": true,
"jest": true,
"mocha": true
},
"globals": {
"SELECT": true,
"INSERT": true,
"UPDATE": true,
"DELETE": true,
"CREATE": true,
"DROP": true,
"CDL": true,
"CQL": true,
"CXL": true,
"cds": true
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off"
}
} }
} }

5
hello/srv/world.ts Normal file
View File

@@ -0,0 +1,5 @@
module.exports = class say {
hello(req: any) {
return `Hello ${req.data.to} from a TypeScript file!`
}
}

View File

@@ -0,0 +1,15 @@
process.env.CDS_TYPESCRIPT = 'true';
import * as cds from '@sap/cds';
//@ts-ignore
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds');
describe('Hello world!', () => {
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
it('should say hello with class impl from a typescript file', async () => {
const {data} = await GET`/say/hello(to='world')`
expect(data.value).toMatch(/Hello world.*typescript.*/i)
})
})

View File

@@ -45,7 +45,8 @@ annotate OrdersService.Orders with @(
], ],
FieldGroup#Details: { FieldGroup#Details: {
Data: [ Data: [
{Value: currency.code, Label:'Currency'} {Value: currency.code, Label:'Currency'},
{Value: status.code, Label:'Status'},
] ]
}, },
FieldGroup#Created: { FieldGroup#Created: {
@@ -66,6 +67,9 @@ annotate OrdersService.Orders with @(
createdBy @UI.HiddenFilter:false; createdBy @UI.HiddenFilter:false;
}; };
annotate OrdersService.OrderStatus with {
code @Common: { Text: name, TextArrangement: #TextOnly };
}
annotate OrdersService.Orders_Items with @( annotate OrdersService.Orders_Items with @(
@@ -74,10 +78,10 @@ annotate OrdersService.Orders_Items with @(
{Value: product_ID, Label:'Product ID'}, {Value: product_ID, Label:'Product ID'},
{Value: title, Label:'Product Title'}, {Value: title, Label:'Product Title'},
{Value: price, Label:'Unit Price'}, {Value: price, Label:'Unit Price'},
{Value: amount, Label:'Quantity'}, {Value: quantity, Label:'Quantity'},
], ],
Identification: [ //Is the main field group Identification: [ //Is the main field group
{Value: amount, Label:'Amount'}, {Value: quantity, Label:'Quantity'},
{Value: title, Label:'Product'}, {Value: title, Label:'Product'},
{Value: price, Label:'Unit Price'}, {Value: price, Label:'Unit Price'},
], ],
@@ -86,7 +90,7 @@ annotate OrdersService.Orders_Items with @(
], ],
}, },
) { ) {
amount @( quantity @(
Common.FieldControl: #Mandatory Common.FieldControl: #Mandatory
); );
}; };

View File

@@ -0,0 +1,4 @@
code;name;descr
O;Open;Order is open
P;In Process;Order is about to be processed
C;Closed;Order is closed
1 code name descr
2 O Open Order is open
3 P In Process Order is about to be processed
4 C Closed Order is closed

View File

@@ -1,4 +1,4 @@
using { Currency, User, managed, cuid } from '@sap/cds/common'; using { Currency, User, managed, cuid, sap.common.CodeList } from '@sap/cds/common';
namespace sap.capire.orders; namespace sap.capire.orders;
entity Orders : cuid, managed { entity Orders : cuid, managed {
@@ -6,14 +6,18 @@ entity Orders : cuid, managed {
Items : Composition of many Orders_Items on Items.up_ = $self; Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User; buyer : User;
currency : Currency; currency : Currency;
status : Association to OrderStatus;
} }
@cds.persistence.data.kind: 'config'
entity OrderStatus : CodeList { key code: String(1) }
entity Orders_Items { entity Orders_Items {
key ID : UUID; key ID : UUID;
up_ : Association to Orders; up_ : Association to Orders;
product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime
amount : Integer; quantity : Integer;
title : String; title : String; //> intentionally replicated as snapshot from product.title
price : Double; price : Double;
} }

View File

@@ -2,6 +2,6 @@
"name": "@capire/orders", "name": "@capire/orders",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@sap/cds": ">=4.3.0" "@sap/cds": "^5"
} }
} }

View File

@@ -7,30 +7,30 @@ class OrdersService extends cds.ApplicationService {
this.before ('UPDATE', 'Orders', async function(req) { this.before ('UPDATE', 'Orders', async function(req) {
const { ID, Items } = req.data const { ID, Items } = req.data
if (Items) for (let { product_ID, amount } of Items) { if (Items) for (let { product_ID, quantity } of Items) {
const { amount:before } = await cds.tx(req).run ( const { quantity:before } = await cds.tx(req).run (
SELECT.one.from (OrderItems, oi => oi.amount) .where ({up__ID:ID, product_ID}) SELECT.one.from (OrderItems, oi => oi.quantity) .where ({up__ID:ID, product_ID})
) )
if (amount != before) await this.orderChanged (product_ID, amount-before) if (quantity != before) await this.orderChanged (product_ID, quantity-before)
} }
}) })
this.before ('DELETE', 'Orders', async function(req) { this.before ('DELETE', 'Orders', async function(req) {
const { ID } = req.data const { ID } = req.data
const Items = await cds.tx(req).run ( const Items = await cds.tx(req).run (
SELECT.from (OrderItems, oi => { oi.product_ID, oi.amount }) .where ({up__ID:ID}) SELECT.from (OrderItems, oi => { oi.product_ID, oi.quantity }) .where ({up__ID:ID})
) )
if (Items) await Promise.all (Items.map(it => this.orderChanged (it.product_ID, -it.amount))) if (Items) await Promise.all (Items.map(it => this.orderChanged (it.product_ID, -it.quantity)))
}) })
return super.init() return super.init()
} }
/** order changed -> broadcast event */ /** order changed -> broadcast event */
orderChanged (product, deltaAmount) { orderChanged (product, deltaQuantity) {
// Emit events to inform subscribers about changes in orders // Emit events to inform subscribers about changes in orders
console.log ('> emitting:', 'OrderChanged', { product, deltaAmount }) console.log ('> emitting:', 'OrderChanged', { product, deltaQuantity })
return this.emit ('OrderChanged', { product, deltaAmount }) return this.emit ('OrderChanged', { product, deltaQuantity })
} }
} }

2634
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,29 +11,37 @@
"@capire/hello": "./hello", "@capire/hello": "./hello",
"@capire/media": "./media", "@capire/media": "./media",
"@capire/orders": "./orders", "@capire/orders": "./orders",
"@capire/reviews": "./reviews" "@capire/reviews": "./reviews",
"@sap/cds": "^5.5.3"
}, },
"devDependencies": { "devDependencies": {
"chai": "^4.2.0", "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",
"sqlite3": "^5.0.0" "sqlite3": "^5"
}, },
"scripts": { "scripts": {
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
"registry": "node .registry/server.js", "registry": "node .registry/server.js",
"bookshop": "cds watch bookshop", "bookshop": "cds watch bookshop",
"fiori": "cds watch fiori", "fiori": "cds watch fiori",
"hello": "cds watch hello",
"media": "cds watch media", "media": "cds watch media",
"mocha": "npx mocha || echo", "mocha": "npx mocha || echo",
"jest": "npx jest", "jest": "npx jest",
"test": "npm run jest --silent" "test": "npm run jest -- --silent",
"test:hello": "cd hello && npm test"
},
"jest": {
"testEnvironment": "node",
"testTimeout": 20000,
"testMatch": [
"**/*.test.js"
]
}, },
"mocha": { "mocha": {
"parallel": true "parallel": true
}, },
"jest": {
"testEnvironment": "node"
},
"license": "SAP SAMPLE CODE LICENSE", "license": "SAP SAMPLE CODE LICENSE",
"private": true "private": true
} }

View File

@@ -7,7 +7,7 @@
"index.cds" "index.cds"
], ],
"dependencies": { "dependencies": {
"@sap/cds": ">=4", "@sap/cds": "^5",
"express": "^4.17.1" "express": "^4.17.1"
}, },
"scripts": { "scripts": {

View File

@@ -2,10 +2,10 @@
## Run all-in-one ## Run all-in-one
Open a terminal window and run the bookshop in it: Open a terminal window and run the `fiori` app in it:
```sh ```sh
npm run bookshop npm run fiori
``` ```
@@ -17,8 +17,8 @@ Open two terminal windows. In the first one start the reviews service stand-alon
npm run reviews-service npm run reviews-service
``` ```
In the second one start the bookshop: In the second one start the `fiori` app:
```sh ```sh
npm run bookshop npm run fiori
``` ```

View File

@@ -7,6 +7,7 @@ Each sub directory essentially is an individual npm package arranged in an [all-
## [@capire/hello-world](hello) ## [@capire/hello-world](hello)
- A simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api). - A simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).
- [Typescript support](https://cap.cloud.sap/docs/get-started/using-typescript)
## [@capire/bookshop](bookshop) ## [@capire/bookshop](bookshop)
@@ -63,6 +64,7 @@ Each sub directory essentially is an individual npm package arranged in an [all-
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help) - Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
- Serving SAP Fiori apps locally - Serving SAP Fiori apps locally
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well - [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
<br> <br>

View File

@@ -1,77 +1,93 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const CQL = ([cql]) => cds.parse.cql(cql) const { expect } = cds.test
const { cdr } = cds.ql
const Foo = { name: 'Foo' } const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' } const Books = { name: 'capire.bookshop.Books' }
const { parse:cdr } = cds.ql
// while jest has 'test' as alias to 'it', mocha doesn't const STAR = cdr ? '*' : { ref: ['*'] }
if (!global.test) global.test = it const skip = {to:{eql:()=>skip}}
const srv = new cds.Service
let cqn
expect.plain = (cqn) => !cqn.SELECT.one && !cqn.SELECT.distinct ? expect(cqn) : skip
expect.one = (cqn) => !cqn.SELECT.distinct ? expect(cqn) : skip
describe('cds.ql → cqn', () => { describe('cds.ql → cqn', () => {
// //
let cqn
describe.skip(`BUGS + GAPS...`, () => { for (let each of ['SELECT', 'SELECT one', 'SELECT distinct']) {
let SELECT; beforeEach(()=> SELECT = (
each === 'SELECT distinct' ? cds.ql.SELECT.distinct :
each === 'SELECT one' ? cds.ql.SELECT.one :
cds.ql.SELECT
))
describe(`${each}...`, () => {
it('should consistently handle *', () => { test(`from Foo`, () => {
expect({ expect(cqn = SELECT `from Foo`)
SELECT: { from: { ref: ['Foo'] }, columns: ['*'] }, .to.eql(SELECT.from `Foo`)
}) .to.eql(SELECT.from('Foo'))
.to.eql(CQL`SELECT * from Foo`) .to.eql(SELECT.from(Foo))
.to.eql(CQL`SELECT from Foo{*}`) .to.eql(SELECT`Foo`)
.to.eql(SELECT('*').from(Foo)) .to.eql(SELECT('Foo'))
.to.eql(SELECT.from(Foo,['*'])) .to.eql(SELECT(Foo))
}) expect.plain(cqn)
.to.eql(CQL`SELECT from Foo`)
.to.eql(srv.read `Foo`)
it('should consistently handle lists', () => { .to.eql(srv.read('Foo'))
const ID = 11, args = [`foo`, "'bar'", 3] .to.eql(srv.read(Foo))
const cqn = CQL`SELECT from Foo where ID=11 and x in (foo,'bar',3)` .to.eql({
expect(SELECT.from(Foo).where(`ID=${ID} and x in (${args})`)).to.eql(cqn)
expect(SELECT.from(Foo).where(`ID=`, ID, `and x in`, args)).to.eql(cqn)
expect(SELECT.from(Foo).where({ ID, x:args })).to.eql(cqn)
})
})
describe(`SELECT...`, () => {
test('from ( Foo )', () => {
expect({
SELECT: { from: { ref: ['Foo'] } }, SELECT: { from: { ref: ['Foo'] } },
}) })
.to.eql(CQL`SELECT from Foo`)
.to.eql(SELECT.from(Foo))
}) })
test('from ( ..., <key>)', () => {
// Compiler test('from Foo [<key>]', () => {
expect(CQL`SELECT from Foo[11]`).to.eql({
SELECT: { expect(cqn = SELECT`from Foo[11]`)
// REVISIT: add one:true? .to.eql(SELECT`from Foo[${11}]`)
from: { ref: [{ id: 'Foo', where: [{ val: 11 }] }] }, .to.eql(SELECT.from `Foo[11]`)
}, .to.eql(SELECT.from `Foo[${11}]`)
.to.eql(SELECT`Foo[11]`)
expect.plain(cqn)
.to.eql(CQL`SELECT from Foo[11]`)
.to.eql(srv.read`Foo[11]`)
.to.eql({
SELECT: { from: {
ref: [{ id: 'Foo', where: [{ val: 11 }] }]
}},
}) })
expect(CQL`SELECT from Foo[ID=11]`).to.eql({ if (cdr) expect.plain (cqn)
SELECT: { .to.eql(srv.read`Foo[${11}]`)
// REVISIT: add one:true .to.eql(SELECT`Foo[${11}]`)
from: {
expect((cqn = SELECT`from Foo[ID=11]`))
.to.eql(SELECT`from Foo[ID=${11}]`)
.to.eql(SELECT.from `Foo[ID=11]`)
.to.eql(SELECT.from `Foo[ID=${11}]`)
.to.eql(SELECT`Foo[ID=11]`)
expect.plain(cqn)
.to.eql(CQL`SELECT from Foo[ID=11]`)
.to.eql(srv.read`Foo[ID=11]`)
.to.eql({
SELECT: { from: {
ref: [{ id: 'Foo', where: [{ ref: ['ID'] }, '=', { val: 11 }] }], ref: [{ id: 'Foo', where: [{ ref: ['ID'] }, '=', { val: 11 }] }],
}, }},
},
}) })
// Runtime ds.ql if (cdr) expect.plain (cqn)
expect(SELECT.from(Foo, 11)) .to.eql(SELECT`Foo[ID=${11}]`)
.to.eql(SELECT.from(Foo, { ID: 11 })) .to.eql(srv.read`Foo[ID=${11}]`)
// Following implicitly resolve to SELECT.one
expect(cqn = SELECT.from(Foo,11))
.to.eql(SELECT.from(Foo,{ID:11}))
.to.eql(SELECT.from(Foo).byKey(11)) .to.eql(SELECT.from(Foo).byKey(11))
.to.eql(SELECT.from(Foo).byKey({ ID: 11 })) .to.eql(SELECT.from(Foo).byKey({ID:11}))
.to.eql(SELECT.one.from(Foo).where({ ID: 11 })) expect.one(cqn)
.to.eql({ .to.eql({
// REVISIT: should produce CQN as the ones above?
SELECT: { SELECT: {
one: true, one: true,
from: { ref: ['Foo'] }, from: { ref: ['Foo'] },
@@ -79,18 +95,44 @@ describe('cds.ql → cqn', () => {
}, },
}) })
expect(CQL`SELECT from Foo[11]{a}`).to.eql({ })
test('from Foo {...}', () => {
expect(cqn = SELECT `*,a,b as c` .from `Foo`)
.to.eql(SELECT `*,a,b as c`. from(Foo))
.to.eql(SELECT('*','a',{b:'c'}).from`Foo`)
.to.eql(SELECT('*','a',{b:'c'}).from(Foo))
.to.eql(SELECT(['*','a',{b:'c'}]).from(Foo))
.to.eql(SELECT.columns('*','a',{b:'c'}).from(Foo))
.to.eql(SELECT.columns(['*','a',{b:'c'}]).from(Foo))
.to.eql(SELECT.columns((foo) => { foo`.*`, foo.a, foo.b`as c` }).from(Foo))
.to.eql(SELECT.columns((foo) => { foo('*'), foo.a, foo.b.as('c') }).from(Foo))
.to.eql(SELECT.from(Foo).columns('*','a',{b:'c'}))
.to.eql(SELECT.from(Foo).columns(['*','a',{b:'c'}]))
.to.eql(SELECT.from(Foo).columns((foo) => { foo`.*`, foo.a, foo.b`as c` }))
.to.eql(SELECT.from(Foo).columns((foo) => { foo('*'), foo.a, foo.b.as('c') }))
.to.eql(SELECT.from(Foo,['*','a',{b:'c'}]))
.to.eql(SELECT.from(Foo, (foo) => { foo`.*`, foo.a, foo.b`as c` }))
.to.eql(SELECT.from(Foo, (foo) => { foo('*'), foo.a, foo.b.as('c') }))
expect.plain(cqn)
.to.eql({
SELECT: { SELECT: {
// REVISIT: add one:true? from: { ref: ['Foo'] },
from: { ref: [{ id: 'Foo', where: [{ val: 11 }] }] }, columns: [ STAR, { ref: ['a'] }, { ref: ['b'], as: 'c' }],
columns: [{ ref: ['a'] }],
}, },
}) })
expect(SELECT.from(Foo, 11, ['a'])) cdr && expect.plain(cqn)
.to.eql(SELECT.from(Foo, 11, (foo) => foo.a)) .to.eql(CQL`SELECT *,a,b as c from Foo`)
.to.eql(CQL`SELECT from Foo {*,a,b as c}`)
// Test combination with key as second argument to .from
expect(cqn = SELECT.from(Foo, 11, ['a']))
.to.eql(SELECT.from(Foo, 11, foo => foo.a))
expect.one(cqn)
.to.eql({ .to.eql({
// REVISIT: should produce CQN as the ones above?
SELECT: { SELECT: {
one: true, one: true,
from: { ref: ['Foo'] }, from: { ref: ['Foo'] },
@@ -98,110 +140,55 @@ describe('cds.ql → cqn', () => {
where: [{ ref: ['ID'] }, '=', { val: 11 }], where: [{ ref: ['ID'] }, '=', { val: 11 }],
}, },
}) })
}) })
test('from ( ..., => {...})', () => { test('with nested expands', () => {
// single *, prefix and postfix, as array and function // SELECT from Foo { *, x, bar.*, car{*}, boo { *, moo.zoo } }
let parsed, fluid expect(cqn =
expect((parsed = CQL`SELECT * from Foo`)).to.eql(CQL`SELECT from Foo{*}`) SELECT.from (Foo, foo => {
//> .to.eql... FIXME: see skipped 'should handle * correctly' below foo`*`, foo.x, foo.car`*`, foo.boo (b => {
expect((fluid = SELECT('*').from(Foo))) b`*`, b.moo.zoo(
.to.eql(SELECT.from(Foo, ['*'])) x => x.y.z
.to.eql(SELECT.from(Foo, (foo) => foo('*'))) )
.to.eql(SELECT.from(Foo).columns('*'))
.to.eql(SELECT.from(Foo).columns((foo) => foo('*')))
.to.eql({
SELECT: { from: { ref: ['Foo'] }, columns: [cdr ? '*' : { ref: ['*'] }] },
}) })
if (cdr) expect(parsed).to.eql(fluid)
// single column, prefix and postfix, as array and function
expect(CQL`SELECT a from Foo`)
expect(CQL`SELECT from Foo {a}`)
.to.eql(SELECT.from(Foo, ['a']))
.to.eql(SELECT.from(Foo, (foo) => foo.a))
.to.eql({
SELECT: { from: { ref: ['Foo'] }, columns: [{ ref: ['a'] }] },
}) })
).to.eql(
// multiple columns, prefix and postfix, as array and function SELECT.from (Foo, foo => {
expect(CQL`SELECT a,b as c from Foo`) foo('*'), foo.x, foo.car('*'), foo.boo (b => {
b('*'), b.moo.zoo(
expect (CQL`SELECT from Foo {a,b as c}`).to.eql(cqn = { x => x.y.z
SELECT: { )
from: { ref: ['Foo'] },
columns: [{ ref: ['a'] }, { ref: ['b'], as: 'c' }],
},
}) })
expect(SELECT.from(Foo, ['a', { b: 'c' }])).to.eql(cqn)
expect(
SELECT.from(Foo, (foo) => {
foo.a, foo.b.as('c')
})
).to.eql(cqn)
expect(SELECT.from(Foo).columns('a', { b: 'c' })).to.eql(cqn)
expect(SELECT.from(Foo).columns(['a', { b: 'c' }])).to.eql(cqn)
expect(
SELECT.from(Foo).columns((foo) => {
foo.a, foo.b.as('c')
})
).to.eql(cqn)
// multiple columns and *, prefix and postfix, as array and function
expect(CQL`SELECT *,a,b from Foo`).to.eql(CQL`SELECT from Foo{*,a,b}`)
//> .to.eql... FIXME: see skipped 'should handle * correctly' below
expect(SELECT.from(Foo, ['a', 'b', '*']))
.to.eql(SELECT.from(Foo).columns('a', 'b', '*'))
.to.eql(SELECT.from(Foo).columns(['a', 'b', '*']))
.to.eql(
SELECT.from(Foo, (foo) => {
foo.a, foo.b, foo('*')
}) })
) )
.to.eql({
SELECT: {
from: { ref: ['Foo'] },
columns: [{ ref: ['a'] }, { ref: ['b'] }, cdr ? '*' : { ref: ['*'] }],
},
})
})
test('from ( ..., => _.expand ( x=>{...}))', () => { expect.plain(cqn)
// SELECT from Foo { *, x, bar.*, car{*}, boo { *, moo.zoo } } .to.eql({
expect(
SELECT.from(Foo, (foo) => {
foo('*'),
foo.x,
foo.car('*'),
foo.boo((b) => {
b('*'), b.moo.zoo((x) => x.y.z)
})
})
).to.eql({
SELECT: { SELECT: {
from: { ref: ['Foo'] }, from: { ref: ['Foo'] },
columns: [ columns: [
cdr ? '*' : { ref: ['*'] }, STAR,
{ ref: ['x'] }, { ref: ['x'] },
{ ref: ['car'], expand: ['*'] }, { ref: ['car'], expand: ['*'] },
{ {
ref: ['boo'], ref: ['boo'],
expand: ['*', { ref: ['moo', 'zoo'], expand: [{ ref: ['y', 'z'] }] }], expand: [ '*', { ref: ['moo', 'zoo'], expand: [{ ref: ['y', 'z'] }] }],
}, },
], ],
}, },
}) })
}) })
test('from ( ..., => _.inline ( _=>{...}))', () => {
test('with nested inlines', () => {
// SELECT from Foo { *, x, bar.*, car{*}, boo { *, moo.zoo } } // SELECT from Foo { *, x, bar.*, car{*}, boo { *, moo.zoo } }
expect( expect.plain(
SELECT.from(Foo, (foo) => { SELECT.from (Foo, foo => {
foo.bar('*'), foo.bar `*`,
foo.bar('.*'), //> leading dot indicates inline foo.bar `.*`, //> leading dot indicates inline
foo.boo((x) => x.moo.zoo), foo.boo(_ => _.moo.zoo), //> underscore arg name indicates inline
foo.boo((_) => _.moo.zoo) //> underscore arg name indicates inline foo.boo(x => x.moo.zoo)
}) })
).to.eql({ ).to.eql({
SELECT: { SELECT: {
@@ -209,49 +196,28 @@ describe('cds.ql → cqn', () => {
columns: [ columns: [
{ ref: ['bar'], expand: ['*'] }, { ref: ['bar'], expand: ['*'] },
{ ref: ['bar'], inline: ['*'] }, { ref: ['bar'], inline: ['*'] },
{ ref: ['boo'], expand: [{ ref: ['moo', 'zoo'] }] },
{ ref: ['boo'], inline: [{ ref: ['moo', 'zoo'] }] }, { ref: ['boo'], inline: [{ ref: ['moo', 'zoo'] }] },
{ ref: ['boo'], expand: [{ ref: ['moo', 'zoo'] }] },
], ],
}, },
}) })
}) })
test('one / distinct ...', () => { })}
expect(SELECT.distinct.from(Foo).SELECT)
// .to.eql(CQL(`SELECT distinct from Foo`).SELECT)
.to.eql(SELECT.distinct(Foo).SELECT)
.to.eql({ distinct: true, from: { ref: ['Foo'] } })
expect(SELECT.one.from(Foo).SELECT) describe ('SELECT where...', ()=>{
// .to.eql(CQL(`SELECT one from Foo`).SELECT)
.to.eql(SELECT.one(Foo).SELECT)
.to.eql({ one: true, from: { ref: ['Foo'] } })
expect(SELECT.one('a').from(Foo).SELECT)
// .to.eql(CQL(`SELECT distinct a from Foo`).SELECT)
.to.eql(SELECT.one(['a']).from(Foo).SELECT)
.to.eql(SELECT.one(Foo, ['a']).SELECT)
.to.eql(SELECT.one(Foo, (foo) => foo.a).SELECT)
.to.eql(SELECT.one.from(Foo, (foo) => foo.a).SELECT)
.to.eql(SELECT.one.from(Foo, ['a']).SELECT)
.to.eql({
one: true,
from: { ref: ['Foo'] },
columns: [{ ref: ['a'] }],
})
// same for works distinct
})
it('should correctly handle { ... and:{...} }', () => { it('should correctly handle { ... and:{...} }', () => {
expect(SELECT.from(Foo).where({ x: 1, and: { y: 2, or: { z: 3 } } })).to.eql({ expect(SELECT.from(Foo).where({ x: 1, and: { y: 2, or: { z: 3 } } })).to.eql({
SELECT: { SELECT: {
from: { ref: ['Foo'] }, from: { ref: ['Foo'] },
where: [ where: cdr ? [
{ ref: ['x'] }, { ref: ['x'] },
'=', '=',
{ val: 1 }, { val: 1 },
'and', 'and',
'(', // '(',
{xpr:[
{ ref: ['y'] }, { ref: ['y'] },
'=', '=',
{ val: 2 }, { val: 2 },
@@ -259,12 +225,113 @@ describe('cds.ql → cqn', () => {
{ ref: ['z'] }, { ref: ['z'] },
'=', '=',
{ val: 3 }, { val: 3 },
]},
// ')',
] : [
{ ref: ['x'] },
'=',
{ val: 1 },
'and',
'(',
// {xpr:[
{ ref: ['y'] },
'=',
{ val: 2 },
'or',
{ ref: ['z'] },
'=',
{ val: 3 },
// ]},
')', ')',
], ],
}, },
}) })
}) })
test ("where x='*'", ()=>{
if (cdr)
expect (SELECT.from(Foo).where({x:'*'}))
.to.eql(SELECT.from(Foo).where("x='*'"))
.to.eql(SELECT.from(Foo).where("x=",'*'))
.to.eql(SELECT.from(Foo).where`x=${'*'}`)
.to.eql(
CQL`SELECT from Foo where x='*'`
)
if (cdr)
expect (SELECT.from(Foo).where({x:['*',1]}))
.to.eql(SELECT.from(Foo).where("x in ('*',1)"))
.to.eql(SELECT.from(Foo).where("x in",['*',1]))
.to.eql(SELECT.from(Foo).where`x in ${['*',1]}`)
.to.eql(
CQL`SELECT from Foo where x in ('*',1)`
)
})
test ('where, and, or', ()=>{
expect (
SELECT.from(Foo).where({x:1,and:{y:2}})
).to.eql (
CQL`SELECT from Foo where x=1 and y=2`
) .to.eql ({ SELECT: {
from: {ref:['Foo']},
where: [
{ref:['x']}, '=', {val:1},
'and',
{ref:['y']}, '=', {val:2}
]
}})
expect (
SELECT.from(Foo).where({x:1,or:{y:2}})
).to.eql (
CQL`SELECT from Foo where x=1 or y=2`
).to.eql ({ SELECT: {
from: {ref:['Foo']},
where: [
{ref:['x']}, '=', {val:1},
'or',
{ref:['y']}, '=', {val:2}
]
}})
expect (
SELECT.from(Foo).where({x:1,and:{y:2}}).or({z:3})
).to.eql (
CQL`SELECT from Foo where x=1 and y=2 or z=3`
)
if (cdr) expect (
SELECT.from(Foo).where({x:1}).and({y:2,or:{z:3}})
).to.eql (
CQL`SELECT from Foo where x=1 and ( y=2 or z=3 )`
)
if (cdr) expect (
SELECT.from(Foo).where({1:1}).and({x:1,or:{x:2}}).and({y:2,or:{z:3}})
).to.eql (
CQL`SELECT from Foo where 1=1 and ( x=1 or x=2 ) and ( y=2 or z=3 )`
)
if (cdr) expect (
SELECT.from(Foo).where({x:1,or:{x:2}}).and({y:2,or:{z:3}})
).to.eql (
CQL`SELECT from Foo where ( x=1 or x=2 ) and ( y=2 or z=3 )`
)
})
test('where ({x:[undefined]})', () => {
if (cdr) expect (
SELECT.from(Foo).where({x:[undefined]})
).to.eql ({ SELECT: {
from: {ref:['Foo']},
where: [
{ref:['x']},
'in',
{ list: [ {val:undefined} ] }
]
}})
})
test('where ( ... cql | {x:y} )', () => { test('where ( ... cql | {x:y} )', () => {
const args = [`foo`, "'bar'", 3] const args = [`foo`, "'bar'", 3]
const ID = 11 const ID = 11
@@ -280,18 +347,17 @@ describe('cds.ql → cqn', () => {
).to.eql({ ).to.eql({
SELECT: { SELECT: {
from: { ref: ['Foo'] }, from: { ref: ['Foo'] },
where: cdr where: cdr ? [
? [
// '(', //> this one is not required
{ ref: ['ID'] }, { ref: ['ID'] },
'=', '=',
{ val: ID }, { val: ID },
'and', 'and',
{ ref: ['args'] }, { ref: ['args'] },
'in', 'in',
{ val: args }, { list: args.map(val => ({ val })) },
'and', 'and',
'(', //> this one is missing, and that's changing the logic -> that's a BUG {
xpr: [
{ ref: ['x'] }, { ref: ['x'] },
'like', 'like',
{ val: '%x%' }, { val: '%x%' },
@@ -299,19 +365,18 @@ describe('cds.ql → cqn', () => {
{ ref: ['y'] }, { ref: ['y'] },
'>=', '>=',
{ val: 9 }, { val: 9 },
')',
] ]
: [ },
// '(', //> this one is not required ] : [
{ ref: ['ID'] }, { ref: ['ID'] },
'=', '=',
{ val: ID }, { val: ID },
'and', 'and',
{ ref: ['args'] }, { ref: ['args'] },
'in', 'in',
{ val: args }, { list: args.map(val => ({ val })) },
'and', 'and',
'(', //> this one is missing, and that's changing the logic -> that's a BUG '(',
{ ref: ['x'] }, { ref: ['x'] },
'like', 'like',
{ val: '%x%' }, { val: '%x%' },
@@ -321,7 +386,7 @@ describe('cds.ql → cqn', () => {
{ val: 9 }, { val: 9 },
')', ')',
], ],
}, }
}) })
// using CQL fragments -> uses cds.parse.expr // using CQL fragments -> uses cds.parse.expr
@@ -406,12 +471,32 @@ describe('cds.ql → cqn', () => {
).to.eql(cqn) ).to.eql(cqn)
}) })
it('w/ plain SQL', () => { test('w/ plain SQL', () => {
expect(SELECT.from(Books) + 'WHERE ...').to.eql( expect(SELECT.from(Books) + 'WHERE ...').to.eql(
'SELECT * FROM capire_bookshop_Books WHERE ...' 'SELECT * FROM capire_bookshop_Books WHERE ...'
) )
}) })
it('should consistently handle *', () => {
if (!cdr) return
expect({
SELECT: { from: { ref: ['Foo'] }, columns: ['*'] },
})
.to.eql(CQL`SELECT * from Foo`)
.to.eql(CQL`SELECT from Foo{*}`)
.to.eql(SELECT('*').from(Foo))
.to.eql(SELECT.from(Foo,['*']))
})
it('should consistently handle lists', () => {
if (!cdr) return
const ID = 11, args = [{ref:['foo']}, "bar", 3]
const cqn = CQL`SELECT from Foo where ID=11 and x in (foo,'bar',3)`
expect(SELECT.from(Foo).where`ID=${ID} and x in ${args}`).to.eql(cqn)
expect(SELECT.from(Foo).where(`ID=`, ID, `and x in`, args)).to.eql(cqn)
expect(SELECT.from(Foo).where({ ID, x:args })).to.eql(cqn)
})
// //
}) })

View File

@@ -1,11 +1,11 @@
const { expect } = require('../test') .run ( const cds = require('@sap/cds/lib')
const { expect } = cds.test (
'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory' 'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory'
) )
const cds = require('@sap/cds/lib')
describe('Consuming Services locally', () => { describe('Consuming Services locally', () => {
// //
it('bootrapped the database successfully', ()=>{ it('bootstrapped the database successfully', ()=>{
const { AdminService } = cds.services const { AdminService } = cds.services
const { Authors } = AdminService.entities const { Authors } = AdminService.entities
expect(AdminService).not.to.be.undefined expect(AdminService).not.to.be.undefined
@@ -15,17 +15,17 @@ describe('Consuming Services locally', () => {
it('supports targets as strings or reflected defs', async () => { it('supports targets as strings or reflected defs', async () => {
const AdminService = await cds.connect.to('AdminService') const AdminService = await cds.connect.to('AdminService')
const { Authors } = AdminService.entities const { Authors } = AdminService.entities
const _ = expect (await AdminService.read(Authors)) expect (await SELECT.from(Authors))
.to.eql(await SELECT.from('Authors'))
.to.eql(await AdminService.read(Authors))
.to.eql(await AdminService.read('Authors')) .to.eql(await AdminService.read('Authors'))
.to.eql(await AdminService.run(SELECT.from(Authors))) .to.eql(await AdminService.run(SELECT.from(Authors)))
// temporary workaround .to.eql(await AdminService.run(SELECT.from('Authors')))
if (cds.version >= '4.2.0')
_.to.eql(await AdminService.run(SELECT.from('Authors')))
}) })
it('allows reading from local services using cds.ql', async () => { it('allows reading from local services using cds.ql', async () => {
const AdminService = await cds.connect.to('AdminService') const AdminService = await cds.connect.to('AdminService')
const query = SELECT.from('Authors', (a) => { const authors = await AdminService.read (`Authors`, a => {
a.name, a.name,
a.books((b) => { a.books((b) => {
b.title, b.title,
@@ -34,10 +34,6 @@ describe('Consuming Services locally', () => {
}) })
}) })
}).where(`name like`, 'E%') }).where(`name like`, 'E%')
// temporary workaround
if (cds.version < '4.2.0')
query.SELECT.from.ref[0] = 'AdminService.Authors'
const authors = await AdminService.run(query)
expect(authors).to.containSubset([ expect(authors).to.containSubset([
{ {
name: 'Emily Brontë', name: 'Emily Brontë',

View File

@@ -1,18 +1,14 @@
const { GET, POST, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Custom Handlers', () => { describe('Custom Handlers', () => {
it('should reject out-of-stock orders', async () => { it('should reject out-of-stock orders', async () => {
await expect( await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
Promise.all([ await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
POST('/browse/submitOrder', { book: 201, amount: 5 }), await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/)
POST('/browse/submitOrder', { book: 201, amount: 5 }),
POST('/browse/submitOrder', { book: 201, amount: 5 }),
])
).to.be.rejectedWith(/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,4 +1,5 @@
const { GET, expect } = require('../test') .run ('serve','hello/world.cds') const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test (__dirname+'/../hello')
describe('Hello world!', () => { describe('Hello world!', () => {

View File

@@ -1,11 +1,10 @@
const {expect} = require('../test')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const {expect} = cds.test
// monkey patching older releases:
if (!cds.compile.cdl) cds.compile.cdl = cds.parse
const { parse:cdr } = cds.ql const { parse:cdr } = cds.ql
const model = cds.compile.cdl (` // should become cds.compile(...) when cds5 is released
const model = cds.compile.to.csn (`
entity Categories { entity Categories {
key ID : Integer; key ID : Integer;
name : String; name : String;
@@ -77,9 +76,9 @@ describe('Hierarchical Data', ()=>{
const expected = [ const expected = [
{ ID:100, name:'Some Cats...' }, { ID:100, name:'Some Cats...' },
{ ID:101, name:'Cat' }, { ID:101, name:'Cat' },
{ ID:104, name:'Aristocat' }, // REVISIT: Should be deleted as well?
{ ID:108, name:'Catweazle' } { ID:108, name:'Catweazle' }
] ]
return 'skipped as this will be fixed in a newer cds version'
if (cdr) expect ( await SELECT.from(Cats) ).to.containSubset (expected) if (cdr) expect ( await SELECT.from(Cats) ).to.containSubset (expected)
else expect ( await SELECT.from(Cats) ).to.eql (expected) else expect ( await SELECT.from(Cats) ).to.eql (expected)
}) })

View File

@@ -1,6 +0,0 @@
const test = require('@sap/cds/lib/utils/tests').in(__dirname,'..')
module.exports = Object.assign(test,{run:test})
// REVISIT: With upcoming release of @sap/cds this should become:
// module.exports = require('@sap/cds/tests').in(__dirname,'..')

View File

@@ -1,5 +1,5 @@
const { GET, expect } = require('../test') .run ('serve', 'test/localized-data.cds', '--in-memory')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test.run ('serve', __dirname+'/localized-data.cds', '--in-memory')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
@@ -43,7 +43,7 @@ describe('Localized Data', () => {
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } }, { title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } },
{ title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } }, { title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } }, { title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Euro' } }, { title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Yen' } },
]) ])
}) })
@@ -85,7 +85,7 @@ describe('Localized Data', () => {
{ title: 'Jane Eyre', currency: { name: 'British Pound' } }, { title: 'Jane Eyre', currency: { name: 'British Pound' } },
{ title: 'The Raven', currency: { name: 'US Dollar' } }, { title: 'The Raven', currency: { name: 'US Dollar' } },
{ title: 'Eleonora', currency: { name: 'US Dollar' } }, { title: 'Eleonora', currency: { name: 'US Dollar' } },
{ title: 'Catweazle', currency: { name: 'Euro' } }, { title: 'Catweazle', currency: { name: 'Yen' } },
]) ])
}) })
}) })

View File

@@ -1,5 +1,5 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { expect } = cds.test
const _model = '@capire/reviews' const _model = '@capire/reviews'
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
@@ -20,11 +20,11 @@ describe('Messaging', ()=>{
let N=0, received=[], M=0 let N=0, received=[], M=0
it ('should add messaging event handlers', ()=>{ it ('should add messaging event handlers', ()=>{
srv.on('reviewed', (msg,next)=> { received.push(msg); return next() }) srv.on('reviewed', (msg)=> received.push(msg))
}) })
it ('should add more messaging event handlers', ()=>{ it ('should add more messaging event handlers', ()=>{
srv.on('reviewed', (_,next)=> { ++M; return next() }) srv.on('reviewed', ()=> ++M)
}) })
it ('should add review', async ()=>{ it ('should add review', async ()=>{

View File

@@ -1,5 +1,5 @@
const { GET, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test ('@capire/bookshop')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
@@ -18,9 +18,9 @@ describe('OData Protocol', () => {
}) })
it('supports $search in multiple fields', async () => { it('supports $search in multiple fields', async () => {
const { data } = await GET(`/browse/Books`, { const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` }, params: { $search: 'Po', $select: `title,author` },
}) }}`
expect(data.value).to.eql([ expect(data.value).to.eql([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' }, { ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' }, { ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },