Compare commits

...

31 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
36 changed files with 964 additions and 30233 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:

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"
} }

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

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 ordered ${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

@@ -11,6 +11,6 @@ service CatalogService @(path:'/browse') {
} excluding { createdBy, modifiedBy }; } excluding { createdBy, modifiedBy };
@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,14 +5,14 @@ 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 const {book,quantity} = req.data
let {stock} = await SELECT `stock` .from (Books,book) let {stock} = await SELECT `stock` .from (Books,book)
if (stock >= amount) { if (stock >= quantity) {
await UPDATE (Books,book) .with (`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

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

@@ -1,4 +1,4 @@
using { AdminService, sap.capire.bookshop } from '../../db/schema'; using { AdminService } from '../../db/schema';
using from '../common'; // to help UI linter get the complete annotations using from '../common'; // to help UI linter get the complete annotations
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -87,9 +87,8 @@ annotate AdminService.Books.texts with @(
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 and Books.texts 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;
entity Books.texts as projection on bookshop.Books.texts;
} }

View File

@@ -10,9 +10,11 @@ cds.once('bootstrap',(app)=>{
cds.once('served', require('./srv/mashup')) cds.once('served', require('./srv/mashup'))
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi // Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
if (process.env.NODE_ENV !== 'production') { try {
const cds_swagger = require ('cds-swagger-ui-express') const cds_swagger = require ('cds-swagger-ui-express')
cds.once ('bootstrap', app => app.use (cds_swagger()) ) 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

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

@@ -3,8 +3,8 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"test": "npx jest --silent", "test": "npx jest --silent",
"watch": "cds serve world.cds", "start": "cds serve srv/world.cds",
"watch:ts": "cds-ts serve world.cds" "start:ts": "cds-ts serve srv/world.cds"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",
@@ -25,5 +25,30 @@
} }
} }
} }
},
"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

@@ -2,7 +2,7 @@ process.env.CDS_TYPESCRIPT = 'true';
import * as cds from '@sap/cds'; import * as cds from '@sap/cds';
//@ts-ignore //@ts-ignore
const {GET} = cds.test.in(__dirname,'..').run('serve', 'world.cds'); const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds');
describe('Hello world!', () => { describe('Hello world!', () => {
afterAll(() => { delete process.env.CDS_TYPESCRIPT; }); afterAll(() => { delete process.env.CDS_TYPESCRIPT; });

View File

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

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,13 +6,17 @@ 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; //> intentionally replicated as snapshot from product.title title : String; //> intentionally replicated as snapshot from product.title
price : Double; price : Double;
} }

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 })
} }
} }

30402
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,30 +12,32 @@
"@capire/media": "./media", "@capire/media": "./media",
"@capire/orders": "./orders", "@capire/orders": "./orders",
"@capire/reviews": "./reviews", "@capire/reviews": "./reviews",
"@sap/cds": "^5", "@sap/cds": "^5.5.3"
"express": "^4"
}, },
"devDependencies": { "devDependencies": {
"cds-swagger-ui-express": "^0.2.0", "chai": "^4.3.4",
"chai": "^4.2.0",
"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", "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 && npm run test:hello", "test": "npm run jest -- --silent",
"test:hello": "cd hello && npm test" "test:hello": "cd hello && npm test"
}, },
"jest": { "jest": {
"testEnvironment": "node", "testEnvironment": "node",
"testMatch": ["**/*.test.js"] "testTimeout": 20000,
"testMatch": [
"**/*.test.js"
]
}, },
"mocha": { "mocha": {
"parallel": true "parallel": true

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,9 +347,7 @@ describe('cds.ql → cqn', () => {
).to.eql({ ).to.eql({
SELECT: { SELECT: {
from: { ref: ['Foo'] }, from: { ref: ['Foo'] },
where: cds.version >= '5.3.0' where: cdr ? [
? [
// '(', //> this one is not required
{ ref: ['ID'] }, { ref: ['ID'] },
'=', '=',
{ val: ID }, { val: ID },
@@ -291,7 +356,8 @@ describe('cds.ql → cqn', () => {
'in', 'in',
{ list: args.map(val => ({ val })) }, { 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,14 +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 POST('/browse/submitOrder', { book: 201, amount: 5 }) await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
await POST('/browse/submitOrder', { book: 201, amount: 5 }) await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
await expect(POST('/browse/submitOrder', { book: 201, amount: 5 })).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/) 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` 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,5 +1,5 @@
const {expect} = require('../test')
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const {expect} = cds.test
const { parse:cdr } = cds.ql const { parse:cdr } = cds.ql
@@ -76,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,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 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

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

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ë' },