Compare commits

..

35 Commits

Author SHA1 Message Date
Christian Georgi
2a1a7b1f2b Make test more robust against future model changes
Compare subset of JSON, not full JSON.
2023-04-11 15:38:58 +02:00
Dr. David A. Kunz
a560e12106 Fix locale handling 2022-09-12 10:50:47 +02:00
Christian Georgi
3dccfc30f0 Increase test timeout 2021-04-23 13:58:46 +02:00
Christian Georgi
cc0be09341 Add CF login instructions 2020-04-07 13:58:59 +02:00
Christian Georgi
175a183f27 Update README.md 2020-04-07 13:41:50 +02:00
Christian Georgi
a1d663a717 Update README.md 2020-04-07 13:40:25 +02:00
Christian Georgi
e75cc7a0a5 Add package-lock.json, fix readme 2020-04-02 15:52:37 +02:00
Christian Georgi
37ed881102 Change localhost link in BAS 2020-03-23 09:33:11 +01:00
Harini Gunabalan
a3ab8cd51f Update README.md 2020-03-20 17:35:06 +01:00
Christian Georgi
6c1f06a06c New readme 2020-03-20 16:23:01 +01:00
Christian Georgi
e9a18c9938 Add launch for bookshop 2020-02-11 11:26:28 +00:00
Christian Georgi
eb5334cafc Cosmetics 2 2020-02-11 07:55:46 +00:00
Christian Georgi
d54b16bd94 Cosmetics 2020-02-11 07:54:27 +00:00
Christian Georgi
ba054305a6 Use beforeAll in tests 2020-02-10 22:44:31 +00:00
Christian Georgi
e89fc1d9bb Test cosmetics 2020-02-10 22:24:37 +00:00
Christian Georgi
659fe52509 Cosmetics 2020-02-10 22:19:14 +00:00
Christian Georgi
4ad21a47d6 Some more code, cosmetics 2020-02-10 22:09:14 +01:00
Christian Georgi
7b5b2210ae Bring old HANA driver back for the moment 2020-02-10 10:05:28 +00:00
Christian Georgi
c20c1609a7 Add Jest as dev dependency to bookshop 2020-02-10 09:30:26 +00:00
Christian Georgi
ffc9fbd5f5 Test cosmetics 2020-02-10 08:03:38 +00:00
Christian Georgi
11957c93d8 Remove scripts 2020-02-09 13:44:12 +00:00
Christian Georgi
2ca71b0f7d Cosmetics 2020-02-09 12:03:46 +00:00
Christian Georgi
452b442246 More handlers for demo 2020-02-07 13:14:16 +00:00
Christian Georgi
bf21d0220c Test cosmetics, remove obsolete stuff 2020-02-07 12:32:39 +00:00
Christian Georgi
b65be0cb41 Fix tests to use productive cds model
Also remove strange whitespace
2020-02-07 12:06:08 +00:00
tieyan.fu@sap.com
c3c778b2be Merge branch 'OpenSAP-week2-unit4567' of github.com:SAP-samples/cloud-cap-samples into OpenSAP-week2-unit4567 2020-02-06 13:22:39 +01:00
tieyan.fu@sap.com
c38c5d832c add model into tests folder (avoid?) 2020-02-06 13:17:41 +01:00
Christian Georgi
5929d22f98 Merge branch 'OpenSAP-week2-unit4567' of https://github.com/SAP-samples/cloud-cap-samples into OpenSAP-week2-unit4567 2020-02-05 14:05:41 +00:00
Christian Georgi
4030173bbc Delete unused files, add package-lock.json 2020-02-05 14:04:58 +00:00
tieyan.fu@sap.com
74ce1a5fd1 add odata test 2020-02-05 09:17:51 +01:00
d049740
cbb27132ec upload code for uweek2unit7 testing 2020-02-03 01:13:19 +01:00
d049740
72a17ffcf7 remove auto generated cds requires section 2019-12-19 15:44:27 +01:00
d049740
0e2218a5f1 add hdb depencies 2019-12-19 15:42:14 +01:00
d049740
fa8a7861d5 delete unneeded test files 2019-12-19 15:09:29 +01:00
d049740
ceb5487e75 adapt bookshop for openSAP course 2019-12-19 15:08:34 +01:00
48 changed files with 5707 additions and 265 deletions

View File

@@ -6,7 +6,7 @@
"jest": true "jest": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 2018 "ecmaVersion": 2017
}, },
"globals": { "globals": {
"SELECT": true, "SELECT": true,

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@ target/
connection.properties connection.properties
default-env.json default-env.json
packages/messageBox packages/messageBox
*.db

37
.vscode/launch.json vendored
View File

@@ -5,18 +5,30 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "bookshop", "request": "launch", "type": "node", "runtimeExecutable": "npx", "runtimeArgs": [ "-n" ], "name": "cds run",
"args": [ "--", "cds", "run", "--in-memory" ], "request": "launch",
"cwd": "${workspaceFolder}/packages/bookshop", "type": "node",
"console": "integratedTerminal", "runtimeExecutable": "npx",
"skipFiles": ["<node_internals>/**"] "runtimeArgs": [
}, "-n"
{ ],
"name": "cds run ...", "request": "launch", "type": "node", "runtimeExecutable": "npx", "runtimeArgs": [ "-n" ], "args": [
"args": [ "--", "cds", "run", "--with-mocks", "--in-memory?" ], "--",
"cds",
"run",
"--with-mocks",
"--in-memory"
], // the leading "--" arg ensures it works with as well as without debugging
"cwd": "${workspaceFolder}/packages/${input:service}", "cwd": "${workspaceFolder}/packages/${input:service}",
"console": "integratedTerminal", "console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"] "serverReadyAction": {
"pattern": "server listening on (https?://\\S+|[0-9]+)",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
},
"skipFiles": [
"<node_internals>/**"
]
} }
], ],
"inputs": [ "inputs": [
@@ -29,7 +41,10 @@
"bookstore", "bookstore",
"media-server", "media-server",
"office-supplies", "office-supplies",
"reviews-service" "orders-service",
"products-service",
"reviews-service",
"user-service"
], ],
"default": "bookshop" "default": "bookshop"
} }

View File

@@ -1,6 +1,7 @@
{ {
"files.exclude": { "files.exclude": {
"**/.gitignore": true, "**/.gitignore": false,
"**/.vscode": true "**/.git": false,
"**/.vscode": false
} }
} }

19
.vscode/tasks.json vendored
View File

@@ -4,14 +4,23 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"type": "npm", "script": "watch", "path": "packages/bookshop/", "type": "shell", "label": "cds run bookshop",
"options": { "env": { "PORT": "4004" }}, "command": "npx", "args": [ "cds", "watch", "packages/bookshop" ],
"presentation": { "group": "A" } "presentation": { "group": "A" },
"problemMatcher": []
}, },
{ {
"type": "npm", "script": "watch", "path": "packages/reviews-service/", "type": "shell", "label": "cds run bookshop-enhanced",
"command": "npx", "args": [ "cds", "watch", "packages/bookshop-enhanced" ],
"presentation": { "group": "A" },
"problemMatcher": []
},
{
"type": "shell", "label": "cds run reviews-service",
"command": "npx", "args": [ "cds", "watch", "packages/reviews-service" ],
"options": {"env": { "PORT":"5005" }}, "options": {"env": { "PORT":"5005" }},
"presentation": { "group": "A" } "presentation": { "group": "A" },
"problemMatcher": []
} }
] ]
} }

24
Jenkinsfile vendored
View File

@@ -1,24 +0,0 @@
#!/usr/bin/env groovy
/*
* This file bootstraps the codified Continuous Delivery pipeline for extensions of SAP solutions, such as SAP S/4HANA.
* The pipeline helps you to deliver software changes quickly and in a reliable manner.
* A suitable Jenkins instance is required to run the pipeline.
* The Jenkins can easily be bootstraped using the life-cycle script located inside the 'cx-server' directory.
*
* More information on getting started with Continuous Delivery can be found in the following places:
* - GitHub repository: https://github.com/SAP/cloud-s4-sdk-pipeline
* - Blog Post: https://blogs.sap.com/2017/09/20/continuous-integration-and-delivery
*/
/*
* Set pipelineVersion to a fixed released version (e.g. "v15") when running in a productive environment.
* To find out about available versions and release notes, visit: https://github.com/SAP/cloud-s4-sdk-pipeline/releases
*/
String pipelineVersion = "v28"
node {
deleteDir()
sh "git clone --depth 1 https://github.com/SAP/cloud-s4-sdk-pipeline.git -b ${pipelineVersion} pipelines"
load './pipelines/s4sdk-pipeline.groovy'
}

View File

@@ -2,20 +2,6 @@
Find here the samples for the openSAP course [Building Applications with the SAP Cloud Application Programming Model](https://open.sap.com/courses/cp7). Find here the samples for the openSAP course [Building Applications with the SAP Cloud Application Programming Model](https://open.sap.com/courses/cp7).
## Notes on the Demo in Week 4 Unit 4
To add all pipeline specific file to your project, run the following command:
```sh
cds add pipeline
```
Details on how to start your Jenkins in your own environment can be found in the [Operations Guide](https://github.com/SAP/devops-docker-cx-server/blob/master/docs/operations/cx-server-operations-guide.md).
Please note that other than shown in the video Jenkins now is secured by default with an admin user and password.
After you have started Jenkins with the command `cx-server start`, you can get the initial password by running `./cx-server initial-credentials`.
The pipeline documentation can be found [here](https://sap.github.io/jenkins-library/pipelines/cloud-sdk/introduction/).
## Get Access to SAP Business Application Studio ## Get Access to SAP Business Application Studio
The recommended environment for the course is SAP Business Application Studio. Watch [unit 2 of week 1](https://open.sap.com/courses/cp7/items/51pzQUzbXHr2kdbOmVs6jI) for how to get access. The recommended environment for the course is SAP Business Application Studio. Watch [unit 2 of week 1](https://open.sap.com/courses/cp7/items/51pzQUzbXHr2kdbOmVs6jI) for how to get access.
@@ -25,8 +11,8 @@ In SAP Business Application Studio, open a terminal.
Then clone the repo with this specific branch: Then clone the repo with this specific branch:
```sh ```sh
git clone https://github.com/sap-samples/cloud-cap-samples -b openSAP-week4-unit4 git clone https://github.com/sap-samples/cloud-cap-samples projects/cloud-cap-samples -b openSAP-week2-unit4567
cd cloud-cap-samples cd projects/cloud-cap-samples
``` ```
In the `cloud-cap-samples` folder run: In the `cloud-cap-samples` folder run:
@@ -34,6 +20,27 @@ In the `cloud-cap-samples` folder run:
npm install npm install
``` ```
### Cloud Foundry Login
This is required later in the demo. In Studio's terminal, execute:
```sh
cf login
```
As input, provide
- The Cloud Foundry API endpoint, which is usally `https://api.cf.eu10.hana.ondemand.com`. It can be obtained from the Overview page of your Subaccount in Cloud Cockpit.
- Your user's email address and password
- The name of your trial organization and space
## Run
Now you're ready to run the samples, for example:
```sh
cd packages/bookshop
cds watch
```
After that, watch out for the little popup in the lower right corner of SAP Business Application Studio that asks you to open the application in your browser.
## Get Support ## Get Support
Check out the cap docs at https://cap.cloud.sap. <br> Check out the cap docs at https://cap.cloud.sap. <br>

1
lerna.json Normal file
View File

@@ -0,0 +1 @@
{"packages":["packages/*"],"version":"1.0.0"}

View File

@@ -1,64 +0,0 @@
####### Generated mta.yaml based on template version 0.2.0
####### appName = capire-bookshop
####### language=nodejs; multiTenant=
####### approuter=
_schema-version: '3.1'
ID: sap.capire-bookshop
version: 1.0.0
description: "A simple bookshop application, build in a self-contained all-in-one fashion, i.e. w/o reusing other packages."
build-parameters:
before-all:
- builder: custom
commands:
- npm install
- cds build/all
parameters:
enable-parallel-deployments: true
modules:
############## SERVER MODULE ##########################
- name: capire-bookshop-srv
type: nodejs
path: gen/srv
properties:
EXIT: 1 # required by deploy.js task to terminate
requires:
#### Resources extracted from CAP configuration ####
- name: capire-bookshop-db
provides:
- name: srv-binding # required by consumers of CAP services (e.g. approuter)
properties:
srv-url: ${default-url}
############################################################
############## SIDECAR MODULE #########################
- name: db
type: hdb
path: gen/db
parameters:
app-name: capire-bookshop-db
requires:
#### Hana and xsuaa resources extracted from CAP configuration ####
- name: capire-bookshop-db
############################################################
############## RESOURCES ##################################
resources:
##### Services extracted from CAP configuration ####
##### 'service-plan' can be configured via 'cds.requires.<name>.vcap.plan'
- name: capire-bookshop-db
type: com.sap.xs.hdi-container
parameters:
properties:
hdi-service-name: ${service-name} # required for Java case
############################################################

5461
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,26 @@
{ {
"name": "@sap/capire-bookshop", "name": "@sap/capire-samples",
"version": "1.0.0", "description": "The umbrella project for all samples to easily setup for local development and tests.",
"description": "A simple bookshop application, build in a self-contained all-in-one fashion, i.e. w/o reusing other packages.", "repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
"license": "SAP SAMPLE CODE LICENSE", "author": "daniel.hutzel@sap.com",
"private": true,
"scripts": {
"cleanup": "lerna clean -y && rm -fr node_modules",
"install": "(npm -s run lerna) && lerna bootstrap --hoist",
"lerna": "npx --no-install lerna -v > /dev/null || npm i lerna --no-save",
"test": "jest",
"bookshop": "cds watch packages/bookshop"
},
"dependencies": { "dependencies": {
"@sap/cds": "^3", "@sap/cds": "^3",
"express": "^4", "express": "^4"
"@sap/hana-client": "^2.4.182"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "^4.1.1" "@types/jest": "*",
"sqlite3": "*",
"jest": "*",
"supertest": "^4.0.2",
"@sap/hdi-deploy": "3.7.0"
}, },
"scripts": { "license": "SAP SAMPLE CODE LICENSE"
"start": "cds run",
"watch": "cds watch"
},
"cds": {
"requires": {
"db": {
"kind": "hana",
"model": [
"db",
"srv"
]
}
}
}
} }

31
packages/bookshop/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,31 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
{
"name": "Run bookshop",
"request": "launch",
"type": "node",
"cwd": "/home/user/projects/cloud-cap-samples/packages/bookshop",
"runtimeExecutable": "npx",
"runtimeArgs": [
"-n"
],
"args": [
"--",
"cds",
"run",
"--in-memory?"
],
"console": "internalConsole",
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
],
"env": {
"run.config": "{\"handlerId\":\"cap_run_config_handler_id\",\"runnableId\":\"/home/user/projects/cloud-cap-samples/packages/bookshop\"}"
}
}
]
}

View File

@@ -0,0 +1,18 @@
{
"name": "@sap/capire-bookshop",
"version": "1.0.0",
"description": "A simple bookshop application, build in a self-contained all-in-one fashion, i.e. w/o reusing other packages.",
"license": "SAP SAMPLE CODE LICENSE",
"dependencies": {
"@sap/cds": "^3",
"express": "^4",
"hdb": "^0.17.1"
},
"devDependencies": {
"jest": "*"
},
"scripts": {
"start": "npx cds run",
"test": "jest"
}
}

View File

@@ -8,9 +8,8 @@ service AdminService @(_requires:'authenticated-user') {
// Enable Fiori Draft for Orders // Enable Fiori Draft for Orders
annotate AdminService.Orders with @odata.draft.enabled; annotate AdminService.Orders with @odata.draft.enabled;
// annotate AdminService.Books with @odata.draft.enabled;
// Temporary workaround -> https://github.wdf.sap.corp/cap/issues/issues/3121 // Temporary workaround -> cap/issues#3121
extend service AdminService with { extend service AdminService with {
entity OrderItems as select from my.OrderItems; entity OrderItems as select from my.OrderItems;
} }

View File

@@ -0,0 +1,22 @@
const cds = require('@sap/cds')
/** Service implementation for AdminService */
module.exports = cds.service.impl(srv => {
const { OrderItems } = srv.entities ('sap.capire.bookshop')
srv.after (['READ','EDIT'], 'Orders', _calculateTotals)
// on-the-fly calculate the total Order price based on the OrderItems' netAmounts
async function _calculateTotals (orders, req) {
const ordersByID = Array.isArray(orders)
? orders.reduce ((all,o) => { (all[o.ID] = o).total=0; return all },{})
: { [orders.ID]: orders }
return cds.transaction(req) .run (
SELECT.from(OrderItems) .columns ('parent_ID', 'netAmount')
.where ({ parent_ID: {in: Object.keys(ordersByID)} })
) .then (items =>
items.forEach (item => ordersByID [item.parent_ID] .total += item.netAmount)
)
}
})

View File

@@ -1,6 +1,7 @@
using { sap.capire.bookshop as my } from '../db/schema'; using { sap.capire.bookshop as my } from '../db/schema';
@path:'/browse' @path:'/browse'
// @impl: './cat-service.js'
service CatalogService { service CatalogService {
@readonly entity Books as SELECT from my.Books {*, @readonly entity Books as SELECT from my.Books {*,

View File

@@ -0,0 +1,28 @@
const cds = require('@sap/cds')
const { Books } = cds.entities
/** Service implementation for CatalogService */
module.exports = cds.service.impl(srv => {
srv.after ('READ', 'Books', each => each.stock > 111 && _addDiscount2(each,11))
srv.before ('CREATE', 'Orders', _reduceStock)
// srv.before ('*', (req) => { console.debug ('>>>', req.method, req.target.name) })
})
/** Add some discount for overstocked books */
function _addDiscount2 (each,discount) {
each.title += ` -- ${discount}% discount!`
}
/** Reduce stock of ordered books if available stock suffices */
async function _reduceStock (req) {
const { Items: orderItems } = req.data
return cds.transaction(req) .run (()=> orderItems.map (item =>
UPDATE (Books)
.set ('stock -=', item.amount)
.where ('ID =', item.book_ID) .and ('stock >=', item.amount)
)).then (all => all.forEach ((affectedRows,i) => {
if (affectedRows === 0) {
req.error (409, `${orderItems[i].amount} exceeds stock for book #${orderItems[i].book_ID}`)
}
}))
}

View File

@@ -0,0 +1,79 @@
const cds = require('@sap/cds')
describe('Bookshop: OData Protocol Level Testing', () => {
jest.setTimeout(20*1000)
const app = require('express')()
const request = require('supertest')(app)
beforeAll(async () => {
await cds.deploy(__dirname + '/../srv/cat-service').to('sqlite::memory:')
await cds.serve('CatalogService').from(__dirname + '/../srv/cat-service').in(app)
})
it('Service $metadata document', async () => {
const response = await request
.get('/browse/$metadata')
.expect('Content-Type', /^application\/xml/)
.expect(200)
const expectedVersion = '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">'
const expectedBooksEntitySet = '<EntitySet Name="Books" EntityType="CatalogService.Books">'
expect(response.text.includes(expectedVersion)).toBeTruthy()
expect(response.text.includes(expectedBooksEntitySet)).toBeTruthy()
})
it('Get with select, expand and localized', async () => {
const response = await request
.get('/browse/Books?$select=title,author&$expand=currency')
.set('Accept-Language', 'de')
.expect('Content-Type', /^application\/json/)
.expect(200)
expect(response.body.value).toMatchObject([
{
ID: 201, title: "Sturmhöhe", author: "Emily Brontë",
currency: { name: "Pfund", descr: "Britische Pfund", code: "GBP", symbol: "£" }
},
{
ID: 207, title: "Jane Eyre", author: "Charlotte Brontë",
currency: { name: "Pfund", descr: "Britische Pfund", code: "GBP", symbol: "£" }
},
{
ID: 251, title: "The Raven", author: "Edgar Allen Poe",
currency: { name: "US-Dollar", descr: "United States Dollar", code: "USD", symbol: "$" }
},
{
ID: 252, title: "Eleonora", author: "Edgar Allen Poe",
currency: { name: "US-Dollar", descr: "United States Dollar", code: "USD", symbol: "$" }
},
{
ID: 271, title: "Catweazle", author: "Richard Carpenter",
currency: { name: "Euro", descr: "European Euro", code: "EUR", symbol: "€" }
}
])
})
})
describe('Bookshop: CDS Service Level Testing', () => {
let srv, Books
beforeAll(async () => {
srv = await cds.serve('CatalogService').from(__dirname + '/../srv/cat-service')
Books = srv.entities.Books
expect(Books).toBeDefined()
})
it('GETs all books', async () => {
const books = await srv.read(Books, b => { b.title })
expect(books).toMatchObject([
{ title: 'Wuthering Heights' },
{ title: 'Jane Eyre' },
{ title: 'The Raven' },
{ title: 'Eleonora' },
{ title: 'Catweazle' }
])
})
})

View File

@@ -1,47 +0,0 @@
###
# This file configures the SAP Cloud SDK Continuous Delivery pipeline of your project.
# For a reference of the configuration concept and available options, please have a look into its documentation.
#
# The documentation for the most recent pipeline version can always be found at:
# https://github.com/SAP/cloud-s4-sdk-pipeline/blob/master/configuration.md
# If you are using a fixed version of the pipeline, please make sure to view the corresponding version from the tag
# list of GitHub (e.g. "v15" when you configured pipelineVersion = "v15" in the Jenkinsfile).
#
# For general information on how to get started with Continuous Delivery, visit:
# https://blogs.sap.com/2017/09/20/continuous-integration-and-delivery
#
# We aim to keep the pipeline configuration as stable as possible. However, major changes might also imply breaking
# changes in the configuration. Before doing an update, please check the the release notes of all intermediate releases
# and adapt this file if necessary.
#
# This is a YAML-file. YAML is a indentation-sensitive file format. Please make sure to properly indent changes to it.
###
### General project setup
general:
productiveBranch: 'openSAP-week4-unit4'
### Step-specific configuration
steps:
### Stage-specific configuration
stages:
# integrationTests:
# credentials:
# - alias: 'mySystemAlias'
# credentialId: 'mySystemCredentialsId'
# s4SdkQualityChecks:
# nonErpDestinations:
# - 'myCustomDestination'
# productionDeployment:
# cfTargets:
# - org: 'myOrg'
# space: 'mySpace'
# apiEndpoint: 'https://api.cf.eu10.hana.ondemand.com'
# credentialsId: 'cf-deploy-sap'

View File

@@ -1,26 +0,0 @@
const cds = require('@sap/cds')
const { Books } = cds.entities
/** Service implementation for CatalogService */
module.exports = cds.service.impl(function() {
this.after ('READ', 'Books', each => each.stock > 111 && _addDiscount2(each,11))
this.before ('CREATE', 'Orders', _reduceStock)
})
/** Add some discount for overstocked books */
function _addDiscount2 (each,discount) {
each.title += ` -- ${discount}% discount!`
}
/** Reduce stock of ordered books if available stock suffices */
async function _reduceStock (req) {
const { Items: OrderItems } = req.data
return cds.transaction(req) .run (()=> OrderItems.map (order =>
UPDATE (Books) .set ('stock -=', order.amount)
.where ('ID =', order.book_ID) .and ('stock >=', order.amount)
)) .then (all => all.forEach ((affectedRows,i) => {
if (affectedRows === 0) req.error (409,
`${OrderItems[i].amount} exceeds stock for book #${OrderItems[i].book_ID}`
)
}))
}