Compare commits

...

14 Commits

Author SHA1 Message Date
Daniel
721796a1e8 Test for chinese locales 2021-01-20 15:50:36 +01:00
Christian Georgi
5b966c503c Add hint for authentication
So that fewer users stumble over the basic authentication popup.
2021-01-18 14:40:24 +01:00
Christian Georgi
75628b6096 Use latest UI5 again 2021-01-18 14:31:57 +01:00
Daniel
c12e516f5d Prep for upcomming releases 2021-01-13 16:30:40 +01:00
Marius Obert
01073fd6a5 Restructure sentence 2021-01-11 10:54:59 +01:00
Christian Georgi
dc72442764 Better brand name 2020-12-18 15:09:38 +01:00
Manuel Blechschmidt
7e04f50852 Added installation for cds (#177)
When running these samples for the first time,
it is necessary to install the cds binaries globally.

Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
Co-authored-by: Daniel Hutzel <daniel.hutzel@sap.com>
2020-12-18 09:25:11 +01:00
Daniel
ea6e274810 Fixed: missing await srv.emit 2020-12-17 16:39:06 +01:00
Christian Georgi
86e5c429bd Example for draft choreography 2020-12-16 17:58:03 +01:00
Daniel
f32398ba8d Using cds.User.default in 4.4.4 2020-12-07 12:41:22 +01:00
Daniel
684c2d53f1 Preparing test for upcomming release 4.4.4 2020-12-03 14:02:15 +01:00
Daniel
b4594e23c5 remove .db file 2020-11-23 14:23:36 +01:00
Daniel
b6028721af make tests more robust 2020-11-22 10:57:13 +01:00
Daniel
e15a6192b6 Aligned orders w/ managed compositions 2020-11-21 01:32:39 +01:00
29 changed files with 134 additions and 67 deletions

View File

@@ -21,6 +21,7 @@
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off"
"require-atomic-updates": "off",
"require-await":"warn"
}
}

View File

@@ -5,12 +5,15 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
![](https://github.com/SAP-samples/cloud-cap-samples/workflows/CI/badge.svg)
[![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-cap-samples)](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples)
### Preliminaries
1. [Install @sap/cds-dk](https://cap.cloud.sap/docs/get-started/) as documented in [capire](https://cap.cloud.sap)
1. [Install @sap/cds-dk](https://cap.cloud.sap/docs/get-started/) globally as documented in [capire](https://cap.cloud.sap)
```sh
npm i -g @sap/cds-dk
```
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/in-vscode)
### Download
Clone this repo as shown below, if you have [git](https://git-scm.com/downloads) installed,
@@ -39,6 +42,8 @@ cds watch bookshop
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
When asked to log in, type `alice` as user and leave the password field blank, which is the [default user](https://cap.cloud.sap/docs/node.js/authentication#mocked).
### Testing
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
@@ -48,15 +53,15 @@ npx jest
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
### Serve `npm`
### Serve `npm`
We've simple npm registry mock included which allows you to do an `npm install @capire/<package>` anywhere locally. Use it as follows:
We've included a simple npm registry mock which allows you to do an `npm install @capire/<package>` locally. Use it as follows:
1. Start the @capire registry:
```sh
npm run registry
```
> While running this will have `@capire:registry=http://localhost:4444` set with npmrc.
> While running this will have `@capire:registry=http://localhost:4444` set with npmrc.
2. Install one of the @capire packages wherever you like, e.g.:
```sh

View File

View File

@@ -1,12 +1,12 @@
using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
@readonly entity Books as SELECT from my.Books {*,
@readonly entity Books as SELECT from my.Books { *,
author.name as author
} excluding { createdBy, modifiedBy };
@readonly entity ListOfBooks as SELECT from Books
excluding { descr, stock };
excluding { descr };
@requires: 'authenticated-user'
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };

View File

@@ -1,7 +1,7 @@
const cds = require('@sap/cds')
const { Books } = cds.entities ('sap.capire.bookshop')
class CatalogService extends cds.ApplicationService { async init(){
class CatalogService extends cds.ApplicationService { init(){
// Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => {
@@ -9,7 +9,7 @@ class CatalogService extends cds.ApplicationService { async init(){
let {stock} = await tx.read('stock').from(Books,book)
if (stock >= amount) {
await tx.update (Books,book).with ({ stock: stock -= amount })
this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
return { stock }
}
else return req.error (409,`${amount} exceeds stock for book #${book}`)

View File

@@ -1,4 +1,8 @@
{
"name": "@capire/common",
"version": "1.0.0"
"description": "Provides a pre-built extension package for std @sap/cds/common",
"version": "1.0.0",
"dependencies": {
"@sap/cds": "latest"
}
}

View File

@@ -0,0 +1 @@
# this file is not used

View File

@@ -0,0 +1,14 @@
Books = Chinesische Bücher
Book = Chinesiches Buch
ID = CN ID
Title = Chinese Titel
Authors = Chinese Autoren
Author = Chinese Autor
AuthorID = Chinese ID des Autors
AuthorName = Chinese Name des Autors
Name = Chinese Name
Stock = Chinese Bestand
Order = Chinese Bestellung
Orders = Chinese Bestellungen
Price = Chinese Preis
Genre = Chinese Genre

View File

@@ -13,7 +13,7 @@
applications: {
"browse-books": {
title: "Browse Books",
description: "... testing FE v42",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType : "URL",
url: "/browse/webapp",
@@ -21,7 +21,7 @@
},
"manage-books": {
title: "Manage Books",
description: "... testing FE v42",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
@@ -29,7 +29,7 @@
},
"manage-orders": {
title: "Manage Orders",
description: "... testing FE v42",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
@@ -40,8 +40,7 @@
</script>
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<!-- <script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" -->
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/1.78.6/resources/sap-ui-core.js"
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3"

View File

@@ -7,8 +7,8 @@ using CatalogService from '@capire/bookshop';
annotate CatalogService.Books with @(
UI: {
HeaderInfo: {
TypeName: 'Book',
TypeNamePlural: 'Books',
TypeName: '{i18n>Book}',
TypeNamePlural: '{i18n>Books}',
Description: {Value: author}
},
HeaderFacets: [

View File

@@ -19,7 +19,7 @@ extend Books with {
// Extend Orders with Books as Products
//
using { sap.capire.orders.OrderItems } from '@capire/orders';
extend OrderItems with {
using { sap.capire.orders.Orders_Items } from '@capire/orders';
extend Orders_Items with {
book : Association to Books on product.ID = book.ID
}

View File

@@ -49,7 +49,7 @@ module.exports = async()=>{ // called by server.js
//
// Reduce stock of ordered books for orders are created from Orders admin UI
//
OrdersService.on ('OrderChanged', async (msg) => {
OrdersService.on ('OrderChanged', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { product, deltaAmount } = msg.data
return UPDATE (Books) .where ('ID =', product)

View File

@@ -42,7 +42,24 @@ GET {{bookshop}}/browse/Books(201)?
#################################################
#
# Orders Service
# Orders Service, incl. draft choreography
#
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
GET {{bookshop}}/orders/Orders
### Create order, still inactive
POST {{bookshop}}/orders/Orders
Content-Type: application/json
{"ID": "{{newOrderID}}"}
### Get inactive order. We have to specify `IsActiveEntity`.
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
### Activate order using `.../<servicename>.draftActivate`
POST {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
Content-Type: application/json
### Get active order
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)

View File

@@ -68,7 +68,7 @@ annotate OrdersService.Orders with @(
annotate OrdersService.OrderItems with @(
annotate OrdersService.Orders_Items with @(
UI: {
LineItem: [
{Value: product_ID, Label:'Product ID'},

View File

@@ -121,7 +121,7 @@
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings" : {
"entitySet": "OrderItems"
"entitySet": "Orders_Items"
}
}
},

View File

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

View File

@@ -1,26 +1,26 @@
using { Currency, User, managed, cuid } from '@sap/cds/common';
using from '@capire/common';
namespace sap.capire.orders;
entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key
Items : Composition of many OrderItems on Items.order = $self;
Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User;
currency : Currency;
}
entity OrderItems {
entity Orders_Items {
key ID : UUID;
order : Association to Orders;
@assert.integrity:false // REVISIT: this is a temporary workaround for a glitch in cds-runtime
product : Association to Products;
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;
title : String;
price : Double;
}
/** This is a stand-in for arbitrary ordered Products */
@cds.persistence.skip:'always'
entity Products {
entity Products @(cds.persistence.skip:'always') {
key ID : String;
}
// Activate extension package
using from '@capire/common';

View File

@@ -3,24 +3,24 @@ class OrdersService extends cds.ApplicationService {
/** register custom handlers */
init(){
const { OrderItems } = this.entities
const { Orders_Items:OrderItems } = this.entities
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 ({order_ID:ID, product_ID})
SELECT.one.from (OrderItems, oi => oi.amount) .where ({up__ID:ID, product_ID})
)
if (amount != before) this.orderChanged (product_ID, amount-before)
if (amount != before) await this.orderChanged (product_ID, amount-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 ({order_ID:ID})
SELECT.from (OrderItems, oi => { oi.product_ID, oi.amount }) .where ({up__ID:ID})
)
if (Items) for (let it of Items) this.orderChanged (it.product_ID, -it.amount)
if (Items) await Promise.all (Items.map(it => this.orderChanged (it.product_ID, -it.amount)))
})
return super.init()

View File

@@ -37,7 +37,7 @@ const reviews = new Vue ({
reviews.message = {}
},
async newReview () {
newReview () {
reviews.review = {}
reviews.message = {}
setTimeout (()=> $('form > input').focus(), 111)

View File

@@ -1,5 +1,5 @@
const cds = require ('@sap/cds')
module.exports = cds.service.impl (async function(){
module.exports = cds.service.impl (function(){
// Get the CSN definition for Reviews from the db schema for sub-sequent queries
// ( Note: we explicitly specify the namespace to support embedded reuse )
@@ -16,7 +16,7 @@ module.exports = cds.service.impl (async function(){
SELECT.one (['round(avg(rating),2) as rating']) .from (Reviews) .where ({subject})
)
global.it || console.log ('< emitting:', 'reviewed', { subject, rating })
this.emit ('reviewed', { subject, rating })
await this.emit ('reviewed', { subject, rating })
})
// Increment counter for reviews considered helpful

View File

@@ -1,5 +1,5 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib')
const { expect } = cds.test
const CQL = ([cql]) => cds.parse.cql(cql)
const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' }
@@ -325,7 +325,26 @@ describe('cds.ql → cqn', () => {
})
// using CQL fragments -> uses cds.parse.expr
expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
const is_v2 = !!cds.parse.expr('(1,2)').list
if (is_v2) expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [
{ ref: ['ID'] },
'=',
{ val: ID },
'and',
{ ref: ['x'] },
'in',
{list:[
{ ref: ['foo'] },
{ val: 'bar' },
{ val: 3 },
]}
],
},
})
else expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [

View File

@@ -1,7 +1,7 @@
const cds = require('@sap/cds/lib')
const { expect } = cds.test (
const { expect } = require('../test') .run (
'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory'
).in(__dirname)
)
const cds = require('@sap/cds/lib')
describe('Consuming Services locally', () => {
//

View File

@@ -1,5 +1,7 @@
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, POST, expect } = cds.test('bookshop').in(__dirname,'..')
const { GET, POST, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib')
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', () => {

View File

@@ -1,5 +1,4 @@
const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test('serve','hello/world.cds').in(__dirname,'..')
const { GET, expect } = require('../test') .run ('serve','hello/world.cds')
describe('Hello world!', () => {

View File

@@ -1,6 +1,5 @@
const cwd = process.cwd(); process.chdir (__dirname) //> only for internal CI/CD@SAP
const {expect} = require('../test')
const cds = require('@sap/cds/lib')
const {expect} = cds.test
// monkey patching older releases:
if (!cds.compile.cdl) cds.compile.cdl = cds.parse
@@ -25,8 +24,6 @@ describe('Hierarchical Data', ()=>{
expect (cds.db.model) .to.exist
})
after(()=> process.chdir(cwd))
it ('supports deeply nested inserts', ()=> INSERT.into (Cats,
{ ID:100, name:'Some Cats...', children:[
{ ID:101, name:'Cat', children:[

6
test/index.js Normal file
View File

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

View File

@@ -1,5 +1,7 @@
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, expect } = cds.test ('serve', __dirname+'/localized-data.cds', '--in-memory')
const { GET, expect } = require('../test') .run ('serve', 'test/localized-data.cds', '--in-memory')
const cds = require('@sap/cds/lib')
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('Localized Data', () => {

View File

@@ -1,13 +1,11 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib')
const cwd = process.cwd(); process.chdir (__dirname) //> only for internal CI/CD@SAP
const {expect} = cds.test
const _model = '@capire/reviews'
cds.User = 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
describe('Messaging', ()=>{
after(()=> process.chdir(cwd))
it ('should bootstrap sqlite in-memory db', async()=>{
const db = await cds.deploy (_model) .to ('sqlite::memory:')
await db.delete('Reviews')
@@ -44,16 +42,16 @@ describe('Messaging', ()=>{
// { ID: 111 + (++N), subject: "201", title: "Captivating", rating: N },
// ),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
]))

View File

@@ -1,8 +1,11 @@
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, expect } = cds.test('bookshop').in(__dirname,'..')
const { GET, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib')
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('OData Protocol', () => {
it('serves $metadata documents in v4', async () => {
const { headers, status, data } = await GET `/browse/$metadata`
expect(status).to.equal(200)