Compare commits

..

2 Commits

Author SHA1 Message Date
Daniel
a9af7b242d Merge branch 'master' into preview 2020-04-30 17:02:14 +02:00
Daniel
8a55c2e68b Testing new compiler features 2020-04-30 17:00:54 +02:00
35 changed files with 131 additions and 434 deletions

1
.env
View File

@@ -1 +1,2 @@
cds.features.snapi = y cds.features.snapi = y
cds.cdsc.beta.aspectCompositions = true

View File

@@ -1,10 +0,0 @@
---
name: Question, feedback or bug?
about: Use our community!
title: ''
labels: ''
assignees: ''
---
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce

View File

@@ -1,28 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.16.2]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test

View File

@@ -1 +0,0 @@
parallel: true

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@sap:registry=https://npm.sap.com

View File

@@ -4,7 +4,7 @@
// List of extensions which should be recommended for users of this workspace. // List of extensions which should be recommended for users of this workspace.
"recommendations": [ "recommendations": [
"SAPSE.vscode-cds", // "CDS Editor !",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"mechatroner.rainbow-csv", "mechatroner.rainbow-csv",

18
.vscode/launch.json vendored
View File

@@ -6,16 +6,24 @@
"configurations": [ "configurations": [
{ {
"name": "bookshop", "name": "bookshop",
"command": "cds watch bookshop", "cwd": "${workspaceFolder}/bookshop",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node",
"runtimeExecutable": "npx",
"runtimeArgs": ["-n"],
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"] "skipFiles": ["<node_internals>/**"]
}, },
{ {
"name": "Fiori app", "name": "Fiori App",
"command": "cds watch fiori", "cwd": "${workspaceFolder}/fiori",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node",
"runtimeExecutable": "npx",
"runtimeArgs": ["-n"],
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"] "skipFiles": ["<node_internals>/**"]
} }
], ],

View File

@@ -2,7 +2,6 @@
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). &rarr; See [**Overview** of contained samples](samples.md) Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). &rarr; See [**Overview** of contained samples](samples.md)
![](https://github.com/SAP-samples/cloud-cap-samples/workflows/CI/badge.svg)
### Preliminaries ### Preliminaries
@@ -40,19 +39,19 @@ After that open this link in your browser: [http://localhost:4004](http://localh
### Testing ### Testing
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example: Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), e.g.:
```sh ```sh
npx jest npx jest
``` ```
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests. > While mocha is a bit smaller and faster, jest runs tests in parallel and isolation which allows to run all tests.
## Get Support ## Get Support
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br> Check out the cap docs at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
In case you have a question, find a bug, or otherwise need support, please use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce). In case you find a bug or need support, please [open an issue in here](https://github.com/SAP-samples/cloud-cap-samples/issues/new).
## License ## License
Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file. Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file.

View File

@@ -11,6 +11,6 @@ ID;parent_ID;name
19;10;Fairy Tale 19;10;Fairy Tale
20;;Non-Fiction 20;;Non-Fiction
21;20;Biography 21;20;Biography
22;21;Autobiography 22;20;Autobiography
23;20;Essay 23;20;Essay
24;20;Speech 24;20;Speech
1 ID parent_ID name
11 19 10 Fairy Tale
12 20 Non-Fiction
13 21 20 Biography
14 22 21 20 Autobiography
15 23 20 Essay
16 24 20 Speech

View File

@@ -2,7 +2,7 @@ code;symbol;name;descr;numcode;minor;exponent
EUR;€;Euro;European Euro;978;Cent;2 EUR;€;Euro;European Euro;978;Cent;2
USD;$;US Dollar;United States Dollar;840;Cent;2 USD;$;US Dollar;United States Dollar;840;Cent;2
CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2 CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2
AUD;$;Australian Dollar;Australian Dollar;036;Cent;2 AUD;$;Australian Dollar;Canadian Dollar;036;Cent;2
GBP;£;British Pound;Great Britain Pound;826;Penny;2 GBP;£;British Pound;Great Britain Pound;826;Penny;2
ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2 ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2
INR;₹;Rupee;Indian Rupee;356;Paise;2 INR;₹;Rupee;Indian Rupee;356;Paise;2
1 code symbol name descr numcode minor exponent
2 EUR Euro European Euro 978 Cent 2
3 USD $ US Dollar United States Dollar 840 Cent 2
4 CAD $ Canadian Dollar Canadian Dollar 124 Cent 2
5 AUD $ Australian Dollar Australian Dollar Canadian Dollar 036 Cent 2
6 GBP £ British Pound Great Britain Pound 826 Penny 2
7 ILS Shekel Israeli New Shekel 376 Agorat 2
8 INR Rupee Indian Rupee 356 Paise 2

View File

@@ -7,19 +7,7 @@ AuthorID = Author ID
Stock = Stock Stock = Stock
Name = Name Name = Name
AuthorName = Author's Name AuthorName = Author's Name
DateOfBirth = Date of Birth
DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death
Authors = Authors Authors = Authors
Order = Order Order = Order
Orders = Orders Orders = Orders
Price = Price Price = Price
Genre = Genre
Genres = Genres
SubGenres = Sub Genres
NumCode = Numeric Code
MinorUnit = Minor Unit
Exponent = Exponent

View File

@@ -1,8 +1,5 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("admin.Component", {
"use strict"; metadata:{ manifest:'json' }
return AppComponent.extend("admin.Component", { }))
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */ /* eslint no-undef:0 */

View File

@@ -24,7 +24,7 @@
"sap.ui5": { "sap.ui5": {
"dependencies": { "dependencies": {
"libs": { "libs": {
"sap.fe.templates": {} "sap.fe": {}
} }
}, },
"models": { "models": {

View File

@@ -1,7 +1,5 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("bookshop.Component", {
"use strict"; metadata:{ manifest:'json' }
return AppComponent.extend("bookshop.Component", { }))
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */ /* eslint no-undef:0 */

View File

@@ -24,7 +24,7 @@
"sap.ui5": { "sap.ui5": {
"dependencies": { "dependencies": {
"libs": { "libs": {
"sap.fe.templates": {} "sap.fe": {}
} }
}, },
"models": { "models": {

View File

@@ -3,7 +3,7 @@
*/ */
using { sap.capire.bookshop as my } from '@capire/bookshop'; using { sap.capire.bookshop as my } from '@capire/bookshop';
using { sap.common } from '@capire/common';
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
@@ -28,6 +28,13 @@ annotate my.Books with @(
author @ValueList.entity:'Authors'; author @ValueList.entity:'Authors';
}; };
annotate my.Authors with @(
UI: {
Identification: [{Value:name}],
}
);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Details // Books Details
@@ -59,199 +66,15 @@ annotate my.Books with {
descr @UI.MultiLineText; descr @UI.MultiLineText;
} }
////////////////////////////////////////////////////////////////////////////
//
// Genres List
//
annotate my.Genres with @(
Common.SemanticKey: [name],
UI: {
SelectionFields: [ name ],
LineItem:[
{Value: name},
{Value: parent.name, Label: 'Main Genre'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Genre Details
//
annotate my.Genres with @(
UI: {
Identification: [{Value:name}],
HeaderInfo: {
TypeName: '{i18n>Genre}',
TypeNamePlural: '{i18n>Genres}',
Title: {Value: name},
Description: {Value: ID}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Genres Elements
//
annotate my.Genres with { annotate my.Genres with {
ID @title: '{i18n>ID}';
name @title: '{i18n>Genre}'; name @title: '{i18n>Genre}';
} }
////////////////////////////////////////////////////////////////////////////
//
// Authors List
//
annotate my.Authors with @(
Common.SemanticKey: [name],
UI: {
Identification: [{Value:name}],
SelectionFields: [ name ],
LineItem:[
{Value: ID},
{Value: name},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: placeOfBirth},
{Value: placeOfDeath},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Author Details
//
annotate my.Authors with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Author}',
TypeNamePlural: '{i18n>Authors}',
Title: {Value: name},
Description: {Value: dateOfBirth}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
],
}
);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Authors Elements // Authors Elements
// //
annotate my.Authors with { annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter; ID @title:'{i18n>ID}' @UI.HiddenFilter;
name @title:'{i18n>Name}'; name @title:'{i18n>AuthorName}';
dateOfBirth @title:'{i18n>DateOfBirth}';
dateOfDeath @title:'{i18n>DateOfDeath}';
placeOfBirth @title:'{i18n>PlaceOfBirth}';
placeOfDeath @title:'{i18n>PlaceOfDeath}';
}
////////////////////////////////////////////////////////////////////////////
//
// Languages List
//
annotate common.Languages with @(
Common.SemanticKey: [code],
Identification: [{Value:code}],
UI: {
SelectionFields: [ name, descr ],
LineItem:[
{Value: code},
{Value: name},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Language Details
//
annotate common.Languages with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Language}',
TypeNamePlural: '{i18n>Languages}',
Title: {Value: name},
Description: {Value: descr}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
],
FieldGroup#Details: {
Data: [
{Value: code},
{Value: name},
{Value: descr}
]
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currencies List
//
annotate common.Currencies with @(
Common.SemanticKey: [code],
Identification: [{Value:code}],
UI: {
SelectionFields: [ name, descr ],
LineItem:[
{Value: descr},
{Value: symbol},
{Value: code},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currency Details
//
annotate common.Currencies with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Currency}',
TypeNamePlural: '{i18n>Currencies}',
Title: {Value: descr},
Description: {Value: code}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
],
FieldGroup#Details: {
Data: [
{Value: name},
{Value: symbol},
{Value: code},
{Value: descr}
]
},
FieldGroup#Extended: {
Data: [
{Value: numcode},
{Value: minor},
{Value: exponent}
]
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currencies Elements
//
annotate common.Currencies with {
numcode @title:'{i18n>NumCode}';
minor @title:'{i18n>MinorUnit}';
exponent @title:'{i18n>Exponent}';
} }

View File

@@ -1,8 +1,5 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("orders.Component", {
"use strict"; metadata:{ manifest:'json' }
return AppComponent.extend("orders.Component", { }))
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */ /* eslint no-undef:0 */

View File

@@ -24,7 +24,7 @@
"sap.ui5": { "sap.ui5": {
"dependencies": { "dependencies": {
"libs": { "libs": {
"sap.fe.templates": {} "sap.fe": {}
} }
}, },
"models": { "models": {

View File

@@ -1,4 +1,4 @@
ID;amount;parent_ID;book_ID;netAmount ID;amount;parent_ID;book_ID
58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201;11.11 58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201
64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271;15 64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271
e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252;28 e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252
1 ID amount parent_ID book_ID netAmount
2 58040e66-1dcd-4ffb-ab10-fdce32028b79 1 7e2f2640-6866-4dcf-8f4d-3027aa831cad 201 11.11
3 64e718c9-ff99-47f1-8ca3-950c850777d4 1 7e2f2640-6866-4dcf-8f4d-3027aa831cad 271 15
4 e9641166-e050-4261-bfee-d1e797e6cb7f 2 64e718c9-ff99-47f1-8ca3-950c850777d4 252 28

View File

@@ -4,13 +4,10 @@ namespace sap.capire.bookshop;
entity Orders : cuid, managed { entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key OrderNo : String @title:'Order Number'; //> readable key
Items : Composition of many OrderItems on Items.parent = $self; Items : Composition of many {
currency : Currency; key pos : Integer;
}
entity OrderItems : cuid {
parent : Association to Orders;
book : Association to Books; book : Association to Books;
amount : Integer; amount : Integer;
netAmount : Decimal(9,2); };
currency : Currency;
} }

View File

@@ -19,8 +19,6 @@
"sqlite3": "^4" "sqlite3": "^4"
}, },
"scripts": { "scripts": {
"bookshop": "cds watch bookshop",
"fiori": "cds watch fiori",
"mocha": "npx mocha || echo", "mocha": "npx mocha || echo",
"jest": "npx jest --verbose", "jest": "npx jest --verbose",
"test": "npm run jest -s" "test": "npm run jest -s"

View File

@@ -1 +0,0 @@
cds.requires.messaging.kind = file-based-messaging

View File

@@ -1,2 +1 @@
cds.requires.messaging.kind = file-based-messaging
PORT = 5005 PORT = 5005

View File

@@ -12,12 +12,15 @@
}, },
"scripts": { "scripts": {
"reviews-service": "cds watch", "reviews-service": "cds watch",
"books-reviewed": "cds watch ../reviewed" "bookshop": "cds watch test/bookshop"
}, },
"cds": { "cds": {
"requires": { "requires": {
"db": { "db": {
"kind": "sql" "kind": "sql"
},
"messaging": {
"kind": "file-based-messaging"
} }
} }
} }

View File

@@ -1,16 +1,13 @@
################################################# #################################################
# #
# To ReviewsService # To ReviewsService mocked in bookshop process
# #
# move the right down:
@reviews-service = http://localhost:4004/reviews
@reviews-service = http://localhost:5005/reviews
### Get all reviews GET http://localhost:4004/reviews/Reviews?
GET {{reviews-service}}/Reviews
### Add a new review (with random rating) ###
POST {{reviews-service}}/Reviews
POST http://localhost:4004/reviews/Reviews
Content-Type: application/json;IEEE754Compatible=true Content-Type: application/json;IEEE754Compatible=true
{"subject":"201", "title":"boo"} {"subject":"201", "title":"boo"}
@@ -23,18 +20,17 @@ Content-Type: application/json;IEEE754Compatible=true
# (both in-process as well as separate one) # (both in-process as well as separate one)
# #
@bookshop = http://localhost:4004
### Request to CatalogService > delegated to ReviewsService ### Request to CatalogService > delegated to ReviewsService
GET {{bookshop}}/browse/Books(201)/reviews? GET http://localhost:4004/browse/Books(201)/reviews?
&$select=rating,date,reviewer,title &$select=rating,date,reviewer,title
### Alternative OData URL ### Alternative OData URL
GET {{bookshop}}/browse/Books/201/reviews? GET http://localhost:4004/browse/Books/201/reviews?
&$select=rating,date,reviewer,title &$select=rating,date,reviewer,title
### ###
GET {{bookshop}}/browse/Books(201)? GET http://localhost:4004/browse/Books(201)?
&$select=ID,title,rating &$select=ID,title,rating
&$expand=reviews &$expand=reviews
# Note: the $expand only works in case of ReviewsService in same process # Note: the $expand only works in case of ReviewsService in same process

13
reviews/test/5005.http Normal file
View File

@@ -0,0 +1,13 @@
#################################################
#
# To ReviewsService running as separate process
#
GET http://localhost:5005/reviews/Reviews?
###
POST http://localhost:5005/reviews/Reviews
Content-Type: application/json;IEEE754Compatible=true
{"subject":"201", "title":"boo"}

View File

@@ -2,8 +2,8 @@
"name": "@capire/bookshop-with-reviews", "name": "@capire/bookshop-with-reviews",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capire/bookshop": "../bookshop", "@capire/bookshop": "../../../bookshop",
"@capire/reviews": "../reviews", "@capire/reviews": "..",
"@sap/cds": "^3.33.1", "@sap/cds": "^3.33.1",
"express": "^4.17.1" "express": "^4.17.1"
}, },
@@ -14,6 +14,9 @@
}, },
"ReviewsService": { "ReviewsService": {
"kind": "odata", "model": "@capire/reviews" "kind": "odata", "model": "@capire/reviews"
},
"messaging": {
"kind": "file-based-messaging"
} }
} }
} }

View File

@@ -12,13 +12,9 @@ cds.once('served', async()=>{
// react on event messages from reviews service // react on event messages from reviews service
const ReviewsService = await cds.connect.to ('ReviewsService') const ReviewsService = await cds.connect.to ('ReviewsService')
const db = await cds.connect.to ('db') const db = await cds.connect.to ('db')
// reflect entities required below...
const { Books } = db.entities('sap.capire.bookshop')
const { Reviews } = ReviewsService.entities
ReviewsService.on ('reviewed', (msg) => { ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data) console.debug ('> received:', msg.event, msg.data)
const { Books } = db.entities('sap.capire.bookshop')
const { subject, rating } = msg.data const { subject, rating } = msg.data
const tx = db.tx (msg) // TODO: db.tx(msg) fully implemented? const tx = db.tx (msg) // TODO: db.tx(msg) fully implemented?
return tx.update (Books,subject) .with ({rating}) return tx.update (Books,subject) .with ({rating})
@@ -28,17 +24,17 @@ cds.once('served', async()=>{
const CatalogService = await cds.connect.to ('CatalogService') const CatalogService = await cds.connect.to ('CatalogService')
CatalogService.impl (srv => srv.on ('READ', 'Books/reviews', (req) => { CatalogService.impl (srv => srv.on ('READ', 'Books/reviews', (req) => {
console.debug ('> delegating to ReviewsService') console.debug ('> delegating to ReviewsService')
const [ id ] = req.params const { Reviews } = ReviewsService.entities
const [ subject ] = req.params
const tx = ReviewsService.tx (req) const tx = ReviewsService.tx (req)
return tx.read (Reviews) .where ({ subject: String(id) }) return tx.read (Reviews) .where({subject}) .columns (req.query.SELECT.columns)
.columns (req.query.SELECT.columns)
})) }))
}) })
// Other bootstrapping events you could hook in to... // Other bootstrapping events you could hook in to...
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
cds.on('bootstrap',(app) => {/* ... */}) cds.on('boostrap',(app) => {/* ... */})
cds.on('loaded', (model) => {/* ... */}) cds.on('loaded', (model) => {/* ... */})
cds.on('connect', (srv) => {/* ... */}) cds.on('connect', (srv) => {/* ... */})
cds.on('serving', (srv) => {/* ... */}) cds.on('serving', (srv) => {/* ... */})

View File

@@ -261,7 +261,7 @@ describe('cds.ql → cqn', () => {
// same for works distinct // same for works distinct
}) })
test.skip('where ( ... cql | {x:y} )', () => { xtest('where ( ... cql | {x:y} )', () => {
const args = [`foo`, "'bar'", 3] const args = [`foo`, "'bar'", 3]
const ID = 11 const ID = 11
@@ -359,11 +359,6 @@ describe('cds.ql → cqn', () => {
CQL`SELECT from Foo where x in (SELECT y from Bar)` CQL`SELECT from Foo where x in (SELECT y from Bar)`
) )
// using query api
expect(SELECT.from('Books').where(
`author.name in`, SELECT('name').from('Authors'))).to.eql(CQL`SELECT from Books where author.name in (SELECT name from Authors)`
)
// in classical semi joins // in classical semi joins
expect( expect(
SELECT('x').from(Foo) .where ( `exists`, SELECT('x').from(Foo) .where ( `exists`,
@@ -466,38 +461,47 @@ describe('cds.ql → cqn', () => {
UPDATE.with allows to pass in plain data payloads, e.g. as obtained from REST clients. UPDATE.with allows to pass in plain data payloads, e.g. as obtained from REST clients.
In addition, UPDATE.with supports specifying expressions, either in CQL fragements In addition, UPDATE.with supports specifying expressions, either in CQL fragements
notation or as simple expression objects. notation or as simple expression objects.
UPDATE.data allows to pass in plain data payloads, e.g. as obtained from REST clients.
The passed in object can be modified subsequently, e.g. by adding or modifying values
before the query is finally executed.
*/ */
test('with + data', () => { test('with', () => {
if (cds.version < '4.1.0') return expect(UPDATE(Foo).with(`foo=`, 11, `bar-=`, 22))
const o = {}
const q = UPDATE(Foo).data(o).with(`bar-=`, 22)
o.foo = 11
expect(q)
.to.eql(UPDATE(Foo).with(`foo=`, 11, `bar-=`, 22))
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } })) .to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
.to.eql({ .to.eql({
UPDATE: { UPDATE: {
entity: 'Foo', entity: 'Foo',
data: { foo: 11 },
with: { with: {
foo: { val: 11 },
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] }, bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
}, },
}, },
}) })
// some more // some more
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({ // expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
// UPDATE: {
// entity: 'Foo',
// with: {
// bar: { func: 'coalesce', args: [{ ref: ['x'] }, { ref: ['y'] }] },
// car: { val: "foo's bar, car" },
// },
// },
// })
})
/*
UPDATE.data allows to pass in plain data payloads, e.g. as obtained from REST clients.
The passed in object can be modified subsequently, e.g. by adding or modifying values
before the query is finally executed.
*/
test('data', () => {
const o = {}
const q = UPDATE(Foo).data(o).with(`bar-=`, 22)
o.foo = 11
expect(q).to.eql({
UPDATE: { UPDATE: {
entity: 'Foo', entity: 'Foo',
data: { data: { foo: 11 },
car: "foo's bar, car",
},
with: { with: {
bar: { func: 'coalesce', args: [{ ref: ['x'] }, { ref: ['y'] }] }, bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
}, },
}, },
}) })

View File

@@ -1,10 +1,6 @@
const { expect } = require('./capire') const { expect } = require('./capire')
const cds = require('@sap/cds') const cds = require('@sap/cds')
const cwd = process.cwd()
before (()=> process.chdir(__dirname))
after(()=> process.chdir(cwd))
describe('Consuming Services locally', () => { describe('Consuming Services locally', () => {
// //
before('bootstrap db and services', async () => { before('bootstrap db and services', async () => {

View File

@@ -1,12 +1,3 @@
const cwd = process.cwd()
const is_jest = !!global.test
if (is_jest) { // it's jest
global.before = (msg,fn) => global.beforeAll(fn||msg)
global.after = (msg,fn) => global.afterAll(fn||msg)
}
before (()=> process.chdir(__dirname))
after (()=> process.chdir(cwd))
describe('Custom Handlers', () => { describe('Custom Handlers', () => {
const { GET, POST, expect } = require('./capire').launch('bookshop') const { GET, POST, expect } = require('./capire').launch('bookshop')

View File

@@ -72,7 +72,7 @@ describe('Localized Data', () => {
]) ])
}) })
xit('supports @cds.localized:false', async ()=>{ it('supports @cds.localized:false', async ()=>{
const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, { const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, {
headers: { 'Accept-Language': 'de' }, headers: { 'Accept-Language': 'de' },
}) })

View File

@@ -21,7 +21,7 @@ describe('Messaging', ()=>{
let N=0, received=[], M=0 let N=0, received=[], M=0
it ('should add messaging event handlers', ()=>{ it ('should add messaging event handlers', ()=>{
srv.on('reviewed', (msg)=> {received.push(msg)}) srv.on('reviewed', (msg)=> received.push(msg))
}) })
it ('should add more messaging event handlers', ()=>{ it ('should add more messaging event handlers', ()=>{

View File

@@ -1,71 +0,0 @@
describe('Querying', () => {
const { GET, expect } = require('./capire').launch('bookshop')
const Authors = {name:'sap.capire.bookshop.Authors'}
it('should SELECT from Authors', async () => {
const authors = await SELECT.from (Authors, a => {
a.name
})
expect(authors).to.eql([
{ name: 'Emily Brontë' },
{ name: 'Charlotte Brontë' },
{ name: 'Edgar Allen Poe' },
{ name: 'Richard Carpenter' },
])
})
it('should SELECT name from Authors', async () => {
const authors = await SELECT.from (Authors, a => {
a.name
})
expect(authors).to.eql([
{ name: 'Emily Brontë' },
{ name: 'Charlotte Brontë' },
{ name: 'Edgar Allen Poe' },
{ name: 'Richard Carpenter' },
])
})
it('should GET /Authors?$select=name&$expand=books($select=title)', async () => {
const {data:{value:authors}} = await GET ('/admin/Authors?$select=name&$expand=books($select=title)')
expect(authors).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('should SELECT from Authors { name, books { title } }', async () => {
const authors = await SELECT.from(Authors, a => { a.name, a.books(b=>{ b.title }) })
expect(authors).to.eql([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('should GET /Authors?$expand=books', async () => {
const {data:{value:authors}} = await GET ('/admin/Authors?$select=name&$expand=books($select=title)')
expect(authors).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('should SELECT from Authors { *, books{*} }', async () => {
const authors = await SELECT.from(Authors, a => { a('*'), a.books(b => b('*')) })
// const authors = await SELECT.from(Authors, ['*',{ref:['books'], expand:['*']}])
// const authors = await SELECT.from(Authors, [{ref:['*']},{ref:['books'], expand:[{ref:['*']}]}])
expect(authors).to.eql([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
})