Compare commits

...

89 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
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
49 changed files with 3189 additions and 357 deletions

View File

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

2
.gitignore vendored
View File

@@ -15,3 +15,5 @@ default-env.json
packages/messageBox
reviews/msg-box
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=PORT] = process.argv
process.chdir(__dirname)
const cwd = __dirname
app.use('/-/:tarball', (req,res,next) => {
console.debug ('GET', req.params)
@@ -13,7 +13,7 @@ app.use('/-/:tarball', (req,res,next) => {
const { tarball } = req.params
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
fs.lstat(tarball,(err => {
if (err) exec(`npm pack ../${pkg}`,next)
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
else next()
}))
} 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."
}
],
"ref": "master"
"ref": "main"
}

View File

@@ -13,5 +13,5 @@
"**/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
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
git clone https://github.com/sap-samples/cloud-cap-samples samples

View File

@@ -10,7 +10,7 @@ const books = new Vue ({
data: {
list: [],
book: undefined,
order: { amount:1, succeeded:'', failed:'' }
order: { quantity:1, succeeded:'', failed:'' }
},
methods: {
@@ -26,18 +26,18 @@ const books = new Vue ({
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
Object.assign (book, res.data)
books.order = { amount:1 }
books.order = { quantity:1 }
setTimeout (()=> $('form > input').focus(), 111)
},
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 {
const res = await POST(`/submitOrder`, { amount, book: book.ID })
const res = await POST(`/submitOrder`, { quantity, book: book.ID })
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) {
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
</label>
<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">
</form>
<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
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
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

@@ -11,6 +11,6 @@ service CatalogService @(path:'/browse') {
} excluding { createdBy, modifiedBy };
@requires: 'authenticated-user'
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
}

View File

@@ -5,14 +5,14 @@ class CatalogService extends cds.ApplicationService { init(){
// Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => {
const {book,amount} = req.data
const {book,quantity} = req.data
let {stock} = await SELECT `stock` .from (Books,book)
if (stock >= amount) {
await UPDATE (Books,book) .with (`stock -=`, amount)
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
if (stock >= quantity) {
await UPDATE (Books,book) .with (`stock -=`, quantity)
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
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

View File

@@ -71,7 +71,7 @@ POST {{server}}/browse/submitOrder
Content-Type: application/json
{{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
GBP;de;Pfund;Britische Pfund
ILS;de;Schekel;Israelische Schekel
JPY;de;Yen;Japanische Yen
EUR;fr;euro;de la Zone euro
USD;fr;dollar;dollar des États-Unis
CAD;fr;dollar canadien;dollar canadien
AUD;fr;dollar australien;dollar australien
GBP;fr;livre sterling;pound sterling
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 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 AdminService.Books with @odata.draft.enabled;
annotate AdminService.Books_texts with @(
annotate AdminService.Books.texts with @(
UI: {
Identification: [{Value:title}],
SelectionFields: [ locale, title ],
@@ -83,11 +84,11 @@ annotate AdminService.Books_texts with @(
);
// Add Value Help for Locales
annotate AdminService.Books_texts {
annotate AdminService.Books.texts {
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';
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}';
genre @title:'{i18n>Genre}' @Common: { Text: genre.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}';
descr @UI.MultiLineText;
}

View File

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

View File

@@ -1,4 +1,5 @@
const cds = require ('@sap/cds')
module.exports = cds.server
cds.once('bootstrap',(app)=>{
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'))
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
//
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 })
return OrdersService.tx(msg).create ('Orders').entries({
OrderNo: 'Order at '+ (new Date).toLocaleString(),
Items: [{ product:{ID:`${book}`}, title, price, amount }],
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
buyer, createdBy: buyer
})
})
@@ -51,9 +51,9 @@ module.exports = async()=>{ // called by server.js
//
OrdersService.on ('OrderChanged', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { product, deltaAmount } = msg.data
const { product, deltaQuantity } = msg.data
return UPDATE (Books) .where ('ID =', product)
.and ('stock >=', deltaAmount)
.set ('stock -=', deltaAmount)
.and ('stock >=', deltaQuantity)
.set ('stock -=', deltaQuantity)
})
}

View File

@@ -1,3 +1,3 @@
ID;createdAt;createdBy;buyer;OrderNo;currency_code
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR
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;O
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
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
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
&$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",
"version": "1.0.0",
"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: {
Data: [
{Value: currency.code, Label:'Currency'}
{Value: currency.code, Label:'Currency'},
{Value: status.code, Label:'Status'},
]
},
FieldGroup#Created: {
@@ -66,6 +67,9 @@ annotate OrdersService.Orders with @(
createdBy @UI.HiddenFilter:false;
};
annotate OrdersService.OrderStatus with {
code @Common: { Text: name, TextArrangement: #TextOnly };
}
annotate OrdersService.Orders_Items with @(
@@ -74,10 +78,10 @@ annotate OrdersService.Orders_Items with @(
{Value: product_ID, Label:'Product ID'},
{Value: title, Label:'Product Title'},
{Value: price, Label:'Unit Price'},
{Value: amount, Label:'Quantity'},
{Value: quantity, Label:'Quantity'},
],
Identification: [ //Is the main field group
{Value: amount, Label:'Amount'},
{Value: quantity, Label:'Quantity'},
{Value: title, Label:'Product'},
{Value: price, Label:'Unit Price'},
],
@@ -86,7 +90,7 @@ annotate OrdersService.Orders_Items with @(
],
},
) {
amount @(
quantity @(
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;
entity Orders : cuid, managed {
@@ -6,13 +6,17 @@ entity Orders : cuid, managed {
Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User;
currency : Currency;
status : Association to OrderStatus;
}
@cds.persistence.data.kind: 'config'
entity OrderStatus : CodeList { key code: String(1) }
entity Orders_Items {
key ID : UUID;
up_ : Association to Orders;
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; //> intentionally replicated as snapshot from product.title
price : Double;
}

View File

@@ -2,6 +2,6 @@
"name": "@capire/orders",
"version": "1.0.0",
"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) {
const { ID, Items } = req.data
if (Items) for (let { product_ID, amount } of Items) {
const { amount:before } = await cds.tx(req).run (
SELECT.one.from (OrderItems, oi => oi.amount) .where ({up__ID:ID, product_ID})
if (Items) for (let { product_ID, quantity } of Items) {
const { quantity:before } = await cds.tx(req).run (
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) {
const { ID } = req.data
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()
}
/** order changed -> broadcast event */
orderChanged (product, deltaAmount) {
orderChanged (product, deltaQuantity) {
// Emit events to inform subscribers about changes in orders
console.log ('> emitting:', 'OrderChanged', { product, deltaAmount })
return this.emit ('OrderChanged', { product, deltaAmount })
console.log ('> emitting:', 'OrderChanged', { product, deltaQuantity })
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,30 +11,37 @@
"@capire/hello": "./hello",
"@capire/media": "./media",
"@capire/orders": "./orders",
"@capire/reviews": "./reviews"
"@capire/reviews": "./reviews",
"@sap/cds": "^5.5.3"
},
"devDependencies": {
"chai": "^4.2.0",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"sqlite3": "^5.0.0"
"sqlite3": "^5"
},
"scripts": {
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
"registry": "node .registry/server.js",
"bookshop": "cds watch bookshop",
"fiori": "cds watch fiori",
"hello": "cds watch hello",
"media": "cds watch media",
"mocha": "npx mocha || echo",
"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": {
"parallel": true
},
"jest": {
"testEnvironment": "node"
},
"license": "SAP SAMPLE CODE LICENSE",
"private": true
}

View File

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

View File

@@ -7,6 +7,7 @@ Each sub directory essentially is an individual npm package arranged in an [all-
## [@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).
- [Typescript support](https://cap.cloud.sap/docs/get-started/using-typescript)
## [@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)
- Serving SAP Fiori apps locally
- [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>

View File

@@ -1,77 +1,93 @@
const { expect } = require('../test')
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 Books = { name: 'capire.bookshop.Books' }
const { parse:cdr } = cds.ql
// while jest has 'test' as alias to 'it', mocha doesn't
if (!global.test) global.test = it
const STAR = cdr ? '*' : { ref: ['*'] }
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', () => {
//
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 *', () => {
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', () => {
const ID = 11, args = [`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)
})
})
describe(`SELECT...`, () => {
test('from ( Foo )', () => {
expect({
test(`from Foo`, () => {
expect(cqn = SELECT `from Foo`)
.to.eql(SELECT.from `Foo`)
.to.eql(SELECT.from('Foo'))
.to.eql(SELECT.from(Foo))
.to.eql(SELECT`Foo`)
.to.eql(SELECT('Foo'))
.to.eql(SELECT(Foo))
expect.plain(cqn)
.to.eql(CQL`SELECT from Foo`)
.to.eql(srv.read `Foo`)
.to.eql(srv.read('Foo'))
.to.eql(srv.read(Foo))
.to.eql({
SELECT: { from: { ref: ['Foo'] } },
})
.to.eql(CQL`SELECT from Foo`)
.to.eql(SELECT.from(Foo))
})
test('from ( ..., <key>)', () => {
// Compiler
expect(CQL`SELECT from Foo[11]`).to.eql({
SELECT: {
// REVISIT: add one:true?
from: { ref: [{ id: 'Foo', where: [{ val: 11 }] }] },
},
test('from Foo [<key>]', () => {
expect(cqn = SELECT`from Foo[11]`)
.to.eql(SELECT`from Foo[${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({
SELECT: {
// REVISIT: add one:true
from: {
if (cdr) expect.plain (cqn)
.to.eql(srv.read`Foo[${11}]`)
.to.eql(SELECT`Foo[${11}]`)
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 }] }],
},
},
}},
})
// Runtime ds.ql
expect(SELECT.from(Foo, 11))
.to.eql(SELECT.from(Foo, { ID: 11 }))
if (cdr) expect.plain (cqn)
.to.eql(SELECT`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({ ID: 11 }))
.to.eql(SELECT.one.from(Foo).where({ ID: 11 }))
.to.eql(SELECT.from(Foo).byKey({ID:11}))
expect.one(cqn)
.to.eql({
// REVISIT: should produce CQN as the ones above?
SELECT: {
one: true,
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: {
// REVISIT: add one:true?
from: { ref: [{ id: 'Foo', where: [{ val: 11 }] }] },
columns: [{ ref: ['a'] }],
from: { ref: ['Foo'] },
columns: [ STAR, { ref: ['a'] }, { ref: ['b'], as: 'c' }],
},
})
expect(SELECT.from(Foo, 11, ['a']))
.to.eql(SELECT.from(Foo, 11, (foo) => foo.a))
cdr && expect.plain(cqn)
.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({
// REVISIT: should produce CQN as the ones above?
SELECT: {
one: true,
from: { ref: ['Foo'] },
@@ -98,110 +140,55 @@ describe('cds.ql → cqn', () => {
where: [{ ref: ['ID'] }, '=', { val: 11 }],
},
})
})
test('from ( ..., => {...})', () => {
// single *, prefix and postfix, as array and function
let parsed, fluid
expect((parsed = CQL`SELECT * from Foo`)).to.eql(CQL`SELECT from Foo{*}`)
//> .to.eql... FIXME: see skipped 'should handle * correctly' below
expect((fluid = SELECT('*').from(Foo)))
.to.eql(SELECT.from(Foo, ['*']))
.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: ['*'] }] },
test('with nested expands', () => {
// SELECT from Foo { *, x, bar.*, car{*}, boo { *, moo.zoo } }
expect(cqn =
SELECT.from (Foo, foo => {
foo`*`, foo.x, foo.car`*`, foo.boo (b => {
b`*`, b.moo.zoo(
x => x.y.z
)
})
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'] }] },
})
// multiple columns, prefix and postfix, as array and function
expect(CQL`SELECT a,b as c from Foo`)
expect (CQL`SELECT from Foo {a,b as c}`).to.eql(cqn = {
SELECT: {
from: { ref: ['Foo'] },
columns: [{ ref: ['a'] }, { ref: ['b'], as: 'c' }],
},
).to.eql(
SELECT.from (Foo, foo => {
foo('*'), foo.x, foo.car('*'), foo.boo (b => {
b('*'), b.moo.zoo(
x => x.y.z
)
})
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=>{...}))', () => {
// SELECT from Foo { *, x, bar.*, car{*}, boo { *, moo.zoo } }
expect(
SELECT.from(Foo, (foo) => {
foo('*'),
foo.x,
foo.car('*'),
foo.boo((b) => {
b('*'), b.moo.zoo((x) => x.y.z)
})
})
).to.eql({
expect.plain(cqn)
.to.eql({
SELECT: {
from: { ref: ['Foo'] },
columns: [
cdr ? '*' : { ref: ['*'] },
STAR,
{ ref: ['x'] },
{ ref: ['car'], expand: ['*'] },
{
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 } }
expect(
SELECT.from(Foo, (foo) => {
foo.bar('*'),
foo.bar('.*'), //> leading dot indicates inline
foo.boo((x) => x.moo.zoo),
foo.boo((_) => _.moo.zoo) //> underscore arg name indicates inline
expect.plain(
SELECT.from (Foo, foo => {
foo.bar `*`,
foo.bar `.*`, //> leading dot indicates inline
foo.boo(_ => _.moo.zoo), //> underscore arg name indicates inline
foo.boo(x => x.moo.zoo)
})
).to.eql({
SELECT: {
@@ -209,49 +196,28 @@ describe('cds.ql → cqn', () => {
columns: [
{ ref: ['bar'], expand: ['*'] },
{ ref: ['bar'], inline: ['*'] },
{ ref: ['boo'], expand: [{ 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)
// .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
})
describe ('SELECT where...', ()=>{
it('should correctly handle { ... and:{...} }', () => {
expect(SELECT.from(Foo).where({ x: 1, and: { y: 2, or: { z: 3 } } })).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [
where: cdr ? [
{ ref: ['x'] },
'=',
{ val: 1 },
'and',
'(',
// '(',
{xpr:[
{ ref: ['y'] },
'=',
{ val: 2 },
@@ -259,12 +225,113 @@ describe('cds.ql → cqn', () => {
{ ref: ['z'] },
'=',
{ 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} )', () => {
const args = [`foo`, "'bar'", 3]
const ID = 11
@@ -280,18 +347,17 @@ describe('cds.ql → cqn', () => {
).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: cdr
? [
// '(', //> this one is not required
where: cdr ? [
{ ref: ['ID'] },
'=',
{ val: ID },
'and',
{ ref: ['args'] },
'in',
{ val: args },
{ list: args.map(val => ({ val })) },
'and',
'(', //> this one is missing, and that's changing the logic -> that's a BUG
{
xpr: [
{ ref: ['x'] },
'like',
{ val: '%x%' },
@@ -299,19 +365,18 @@ describe('cds.ql → cqn', () => {
{ ref: ['y'] },
'>=',
{ val: 9 },
')',
]
: [
// '(', //> this one is not required
},
] : [
{ ref: ['ID'] },
'=',
{ val: ID },
'and',
{ ref: ['args'] },
'in',
{ val: args },
{ list: args.map(val => ({ val })) },
'and',
'(', //> this one is missing, and that's changing the logic -> that's a BUG
'(',
{ ref: ['x'] },
'like',
{ val: '%x%' },
@@ -321,7 +386,7 @@ describe('cds.ql → cqn', () => {
{ val: 9 },
')',
],
},
}
})
// using CQL fragments -> uses cds.parse.expr
@@ -406,12 +471,32 @@ describe('cds.ql → cqn', () => {
).to.eql(cqn)
})
it('w/ plain SQL', () => {
test('w/ plain SQL', () => {
expect(SELECT.from(Books) + 'WHERE ...').to.eql(
'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'
)
const cds = require('@sap/cds/lib')
describe('Consuming Services locally', () => {
//
it('bootrapped the database successfully', ()=>{
it('bootstrapped the database successfully', ()=>{
const { AdminService } = cds.services
const { Authors } = AdminService.entities
expect(AdminService).not.to.be.undefined
@@ -15,17 +15,17 @@ describe('Consuming Services locally', () => {
it('supports targets as strings or reflected defs', async () => {
const AdminService = await cds.connect.to('AdminService')
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.run(SELECT.from(Authors)))
// temporary workaround
if (cds.version >= '4.2.0')
_.to.eql(await AdminService.run(SELECT.from('Authors')))
.to.eql(await AdminService.run(SELECT.from('Authors')))
})
it('allows reading from local services using cds.ql', async () => {
const AdminService = await cds.connect.to('AdminService')
const query = SELECT.from('Authors', (a) => {
const authors = await AdminService.read (`Authors`, a => {
a.name,
a.books((b) => {
b.title,
@@ -34,10 +34,6 @@ describe('Consuming Services locally', () => {
})
})
}).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([
{
name: 'Emily Brontë',

View File

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

View File

@@ -1,5 +1,5 @@
const {expect} = require('../test')
const cds = require('@sap/cds/lib')
const {expect} = cds.test
const { parse:cdr } = cds.ql
@@ -76,9 +76,9 @@ describe('Hierarchical Data', ()=>{
const expected = [
{ ID:100, name:'Some Cats...' },
{ ID:101, name:'Cat' },
{ ID:104, name:'Aristocat' }, // REVISIT: Should be deleted as well?
{ 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)
else expect ( await SELECT.from(Cats) ).to.eql (expected)
})

View File

@@ -1,2 +0,0 @@
const cds = require('@sap/cds')
module.exports = cds.test.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 { 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
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: 'The Raven', 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: 'The Raven', 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 { expect } = cds.test
const _model = '@capire/reviews'
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

View File

@@ -1,5 +1,5 @@
const { GET, expect } = require('../test') .run ('bookshop')
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
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 () => {
const { data } = await GET(`/browse/Books`, {
const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` },
})
}}`
expect(data.value).to.eql([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },