Spring cleaning, part 2 (#813)

* Remove OData v2 show case: Genres TreeView
* Refactored bookstore and fiori services: consolidate routing and remove deprecated files
* Add Jest configuration file with silent mode enabled
* Refactor test files: streamline imports and remove redundant code
* Change price field type to Price for better clarity and precision
* Example for separation of concerns: access-control.cds
This commit is contained in:
Daniel Hutzel
2025-03-21 09:48:11 +01:00
committed by GitHub
parent 737e766d53
commit 7b92463394
30 changed files with 130 additions and 2029 deletions

3
.npmrc
View File

@@ -1,3 +0,0 @@
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
@sap:registry=https://registry.npmjs.org/
registry=https://registry.npmjs.org/

View File

@@ -1,117 +0,0 @@
{
"$schema": "https://aka.ms/codetour-schema",
"title": "Database Functions",
"steps": [
{
"title": "Introduction",
"description": "### Database Functions in CDS Models\n\nIn this tour, you'll learn how to add database-specific functions to CDS models in your application."
},
{
"file": "bookshop/db/schema.cds",
"description": "#### Basic Schema\n\nWe want to add two fields to the `Authors` entity, one for the author's age and one for the span of years that she or he lived.\n\nThese two fields can be computed out of the existing `dateOfBirth` and `dateOfDeath` fields.",
"selection": {
"start": {
"line": 19,
"character": 1
},
"end": {
"line": 21,
"character": 1
}
},
"title": "Base fields in Author"
},
{
"file": "bookshop/srv/admin-service.cds",
"description": "This is how the `Authors` entity gets exposed in an OData or REST service.\n\nIn the next step, you'll see how we extend this projection.",
"selection": {
"start": {
"line": 4,
"character": 1
},
"end": {
"line": 5,
"character": 1
}
},
"title": "Authors service"
},
{
"file": "fiori/db/sqlite/index.cds",
"description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n",
"selection": {
"start": {
"line": 7,
"character": 1
},
"end": {
"line": 11,
"character": 1
}
},
"title": "SQLite implementation"
},
{
"file": "fiori/db/hana/index.cds",
"description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _fiori_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.",
"selection": {
"start": {
"line": 7,
"character": 1
},
"end": {
"line": 11,
"character": 1
}
},
"title": "SAP HANA implementation"
},
{
"file": "fiori/package.json",
"description": "#### Configuration\n\nThe `cds.requires` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\n\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile. `db-ext` is a pseudo datasource, its name doesn't matter.\n\nSee [`cds.resolve`](https://cap.cloud.sap/docs/node.js/cds-compile#cds-resolve) to learn more about how models are found.",
"selection": {
"start": {
"line": 41,
"character": 1
},
"end": {
"line": 48,
"character": 1
}
},
"title": "Configuration"
},
{
"file": "fiori/package.json",
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
"line": 43,
"title": "Run with SQLite"
},
{
"file": "fiori/package.json",
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
"line": 46,
"title": "Run with SAP HANA"
},
{
"file": "fiori/test/requests.http",
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
"line": 72,
"selection": {
"start": {
"line": 67,
"character": 1
},
"end": {
"line": 73,
"character": 1
}
},
"title": "Add Data"
},
{
"title": "Wrap-up",
"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."
}
]
}

View File

@@ -1,139 +0,0 @@
{
"$schema": "https://aka.ms/codetour-schema",
"title": "CAP Samples",
"steps": [
{
"title": "Welcome",
"file": "README.md",
"description": "### Welcome to CAP Samples!\n\nThis tour leads you through a collection of samples for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap).\nYou will learn which features of the programming model are demonstrated in which sample.\n\nLet's start!",
"line": 2,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 3,
"character": 108
}
}
},
{
"file": "hello/srv/world.cds",
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
"line": 2,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 4,
"character": 1
}
},
"title": "Hello World!"
},
{
"file": "bookshop/db/schema.cds",
"description": "### A Bookshop!\n\nIntroduces:\n- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)\n- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)\n- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)\n- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)\n- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)\n- [Using Databases](https://cap.cloud.sap/docs/guides/databases)\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 32,
"character": 1
}
},
"title": "Bookshop"
},
{
"file": "common/index.cds",
"description": "### Extend and Reuse\n\nShowcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:\n- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)\n- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)\n- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)\n- Using [Aspects](https://cap.cloud.sap/docs/cds/cdl#aspects)\n- Used in the [fiori app sample](#fiori)\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 46,
"character": 1
}
},
"title": "Common"
},
{
"file": "orders/db/schema.cds",
"description": "### Orders - Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 27,
"character": 1
}
},
"title": "Orders"
},
{
"file": "reviews/db/schema.cds",
"description": "### Reviews - More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 39,
"character": 1
}
},
"title": "Reviews"
},
{
"title": "Bookstore",
"description": "### Bookstore - Reuse and UI\n\n- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/reuse-and-compose) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well\n- [The Vue.js app](reviews/app/vue) imported from reviews is served as well\n- [The Fiori app](orders/app) imported from orders is served as well\n- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)"
},
{
"file": "fiori/app/services.cds",
"description": "### Annotations for SAP Fiori Elements\n\nAdds an SAP Fiori elements application to bookstore, thereby introducing:\n- OData Annotations in `.cds` files\n- Support for Fiori Draft\n- Support for Value Helps\n- Serving SAP Fiori apps locally\n\nSee the [Serving Fiori UIs](https://cap.cloud.sap/docs/advanced/fiori) documentation for more information.",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 13,
"character": 1
}
},
"title": "Fiori"
},
{
"file": "package.json",
"description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n",
"selection": {
"start": {
"line": 8,
"character": 1
},
"end": {
"line": 16,
"character": 1
}
},
"title": "Packages"
}
],
"isPrimary": true,
"description": "Overview of CAP Samples for Node.js"
}

View File

@@ -9,8 +9,6 @@
"dbaeumer.vscode-eslint",
"mechatroner.rainbow-csv",
"humao.rest-client",
"sdras.night-owl",
"vsls-contrib.codetour"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [

View File

@@ -1,9 +1,8 @@
{
"files.exclude": {
".reuse/**": true,
"**/.gitignore": true,
"**/.vscode": true,
"LICENSES/**": true
"**/node_modules": true,
"LICENSES": true,
".reuse": true
},
"debug.javascript.terminalOptions": {
"skipFiles": [

View File

@@ -8,7 +8,7 @@ entity Books : managed {
author : Association to Authors @mandatory;
genre : Association to Genres;
stock : Integer;
price : Decimal;
price : Price;
currency : Currency;
image : LargeBinary @Core.MediaType: 'image/png';
}
@@ -29,3 +29,5 @@ entity Genres : sap.common.CodeList {
parent : Association to Genres;
children : Composition of many Genres on children.parent = $self;
}
type Price : Decimal(9,2);

View File

@@ -6,8 +6,7 @@
"app",
"srv",
"db",
"index.cds",
"index.js"
"index.cds"
],
"devDependencies": {
"@cap-js/sqlite": "*"

View File

@@ -0,0 +1,2 @@
using { AdminService } from './admin-service';
annotate AdminService with @requires:'admin';

View File

@@ -1,5 +1,6 @@
using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(requires:'admin', path:'/admin') {
entity Books as projection on my.Books;
service AdminService @(path:'/admin') {
entity Authors as projection on my.Authors;
entity Books as projection on my.Books;
entity Genres as projection on my.Genres;
}

7
bookstore/app/routes.js Normal file
View File

@@ -0,0 +1,7 @@
// Add routes to UIs from imported packages
module.exports = (app) => {
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
app.serve ('/orders') .from('@capire/orders','app/orders')
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
}

1
bookstore/cds-plugin.js Normal file
View File

@@ -0,0 +1 @@
require('./srv/server')

View File

@@ -1,20 +0,0 @@
const cds = require ('@sap/cds')
// Add mashup logic
cds.once('served', require('./srv/mashup'))
// Add routes to UIs from imported packages
cds.once('bootstrap',(app)=>{
try {
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
app.serve ('/orders') .from('@capire/orders','app/orders')
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') throw new Error('Run "npm ci" to install the required dependencies', { cause: err })
throw err
}
})
// Add Swagger UI
require('./srv/swagger-ui')

View File

@@ -1,10 +1,11 @@
////////////////////////////////////////////////////////////////////////////
//
// Mashing up bookshop services with required services...
//
module.exports = async()=>{ // called by server.js
const cds = require ('@sap/cds')
// Add routes to UIs from imported packages
cds.once ('bootstrap', require('../app/routes'))
// Mashing up bookshop services with required services...
cds.once ('served', async ()=>{ // called by server.js
const cds = require('@sap/cds')
const CatalogService = await cds.connect.to ('CatalogService')
const ReviewsService = await cds.connect.to ('ReviewsService')
const OrdersService = await cds.connect.to ('OrdersService')
@@ -55,4 +56,4 @@ module.exports = async()=>{ // called by server.js
.and ('stock >=', deltaQuantity)
.set ('stock -=', deltaQuantity)
})
}
})

View File

@@ -1,10 +0,0 @@
// -----------------------------------------------------------------------
// Adding Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
const cds = require ('@sap/cds')
try {
const cds_swagger = require ('cds-swagger-ui-express')
cds.once ('bootstrap', app => app.use (cds_swagger()) )
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err
}

View File

@@ -3,7 +3,6 @@
"LaunchPage": {
"adapter": {
"config": {
"catalogs": [],
"groups": [
{
"id": "Bookshop",
@@ -19,14 +18,6 @@
"title": "Browse Books",
"targetURL": "#Books-display"
}
},
{
"id": "BrowseGenres",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Genres (OData v2)",
"targetURL": "#Genres-display"
}
}
]
},
@@ -115,24 +106,6 @@
"url": "/admin-authors/webapp"
}
},
"BrowseGenres": {
"semanticObject": "Genres",
"action": "display",
"title": "Browse Genres",
"signature": {
"parameters": {
"Genre.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=genres",
"url": "/genres/webapp"
}
},
"ManageBooks": {
"semanticObject": "Books",
"action": "manage",

View File

@@ -1,8 +0,0 @@
using { sap.capire.bookshop } from '../../db/common';
annotate bookshop.GenreHierarchy {
ID @sap.hierarchy.node.for;
parent @sap.hierarchy.parent.node.for;
hierarchyLevel @sap.hierarchy.level.for;
drillState @sap.hierarchy.drill.state.for;
}

View File

@@ -1,7 +0,0 @@
sap.ui.define(["sap/suite/ui/generic/template/lib/AppComponent"], (AppComponent) =>
AppComponent.extend("genres.Component", {
metadata: {
manifest: "json",
},
})
);

View File

@@ -1,4 +0,0 @@
#XTIT
appTitle=Genres
#XTXT
appDescription=Browse Genres

View File

@@ -1,155 +0,0 @@
{
"_version": "1.8.0",
"sap.app": {
"id": "genres",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "Browse Genres Hierarchy (OData v2)",
"description": "{{appDescription}}",
"tags": {
"keywords": []
},
"crossNavigation": {
"inbounds": {
"appShow": {
"title": "{{appTitle}}",
"semanticObject": "GenreHierarchy",
"action": "display",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"icon": "sap-icon://settings",
"size": "1x1"
}
},
"outbounds": {}
},
"ach": "",
"resources": "resources.json",
"dataSources": {
"main": {
"uri": "/odata/v2/browse",
"type": "OData",
"settings": {
"annotations": ["localAnnotations"],
"localUri": "localService/metadata.xml"
}
},
"localAnnotations": {
"type": "ODataAnnotation",
"uri": "annotations/localAnnotations.xml",
"settings": {
"localUri": "annotations/localAnnotations.xml"
}
}
},
"offline": false,
"sourceTemplate": {
"id": "ui5template.smartTemplate",
"version": "1.40.12"
}
},
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone@2": "",
"tablet": "",
"tablet@2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": ["sap_hcb", "sap_belize", "sap_belize_deep", "sap_fiori_3"]
},
"sap.ui5": {
"resources": {
"js": [],
"css": []
},
"dependencies": {
"minUI5Version": "1.65.6",
"libs": {},
"components": {}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"@i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"json": {
"type": "sap.ui.model.json.JSONModel"
},
"i18n|sap.suite.ui.generic.template.ListReport|Genres": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/ListReport/Genres/i18n.properties"
},
"": {
"dataSource": "main",
"preload": true,
"settings": {
"useBatch": true,
"defaultBindingMode": "TwoWay",
"defaultCountMode": "Inline",
"refreshAfterChange": true,
"metadataUrlParams": {
"sap-value-list": "none"
}
}
}
},
"contentDensities": {
"compact": true,
"cozy": true
}
},
"sap.ui.generic.app": {
"_version": "1.3.0",
"settings": {
"forceGlobalRefresh": false,
"useColumnLayoutForSmartForm": false,
"showBasicSearch": false
},
"pages": {
"ListReport|Genres": {
"entitySet": "GenreHierarchy",
"component": {
"name": "sap.suite.ui.generic.template.ListReport",
"list": true,
"settings": {
"condensedTableLayout": true,
"smartVariantManagement": true,
"tableType": "TreeTable",
"enableTableFilterInPageVariant": true,
"dataLoadSettings": {
"loadDataOnAppLaunch": "always"
}
}
}
}
}
},
"sap.fiori": {
"registrationIds": [],
"archeType": "transactional"
},
"sap.platform.hcp": {
"uri": ""
},
"sap.platform.cf": {
"oAuthScopes": []
}
}

View File

@@ -5,6 +5,6 @@
using from './admin-authors/fiori-service';
using from './admin-books/fiori-service';
using from './browse/fiori-service';
using from './genres/fiori-service';
using from './common';
using from '@capire/bookstore/srv/mashup';

View File

@@ -4,7 +4,6 @@
"dependencies": {
"@capire/bookstore": "*",
"@sap/cds": ">=5",
"@cap-js-community/odata-v2-adapter": "^1",
"express": "^4.17.1"
},
"devDependencies": {
@@ -24,20 +23,8 @@
"kind": "odata",
"model": "@capire/orders"
},
"messaging": {
"[production]": {
"kind": "enterprise-messaging"
},
"[development]": {
"kind": "file-based-messaging"
},
"[hybrid]": {
"kind": "enterprise-messaging-shared"
}
},
"db": {
"kind": "sql"
},
"messaging": true,
"db": true,
"db-ext": {
"[development]": {
"model": "db/sqlite"
@@ -46,9 +33,6 @@
"model": "db/hana"
}
}
},
"hana": {
"deploy-format": "hdbtable"
}
},
"sapux": [

3
jest.config.mjs Normal file
View File

@@ -0,0 +1,3 @@
export default {
silent: true
}

1494
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,6 @@
"description": "A monorepo with several samples for CAP.",
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com",
"dependencies": {
"@sap/cds": ">=8"
},
"workspaces": [
"./bookshop",
"./bookstore",
@@ -19,21 +16,19 @@
],
"devDependencies": {
"@cap-js/cds-test": "^0",
"@cap-js/cds-types": "^0",
"@cap-js/sqlite": "^1",
"eslint": "^9",
"semver": "^7"
"@eslint/js": "^9.22.0"
},
"scripts": {
"build": "mbt build -t gen --mtar mta.tar",
"deploy": "cf deploy gen/mta.tar",
"undeploy": "cf undeploy capire.samples --delete-services --delete-service-keys",
"bookstore": "cds watch bookstore",
"bookshop": "cds watch bookshop",
"fiori": "cds watch fiori",
"lint": "eslint",
"test": "npx jest --silent",
"jest": "npx jest --silent",
"mocha": "CDS_TEST_SILENT=y npx mocha"
"orders": "cds watch orders",
"reviews": "cds watch reviews",
"lint": "npx eslint",
"test": "chest test",
"build": "mbt build -t gen --mtar mta.tar",
"deploy": "cf deploy gen/mta.tar",
"undeploy": "cf undeploy capire.samples --delete-services --delete-service-keys"
},
"mocha": {
"recursive": true,
@@ -42,4 +37,4 @@
},
"license": "SEE LICENSE IN LICENSE",
"private": true
}
}

View File

@@ -1,9 +1,8 @@
const cds = require('@sap/cds')
const { expect } = cds.test ('@capire/bookshop')
describe('cap/samples - Consuming Services locally', () => {
const { expect } = cds.test ('@capire/bookshop')
it('bootstrapped the database successfully', ()=>{
const { AdminService } = cds.services
const { Authors } = AdminService.entities
@@ -33,33 +32,11 @@ describe('cap/samples - Consuming Services locally', () => {
})
})
}).where(`name like`, 'E%')
if (require('semver').gte(cds.version, '5.9.0')) {
expect(authors).to.containSubset([
{
name: 'Emily Brontë',
books: [
{
title: 'Wuthering Heights',
currency: { name: 'British Pound', symbol: '£' },
},
],
},
{
name: 'Edgar Allen Poe',
books: [
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
],
},
])
return
}
expect(authors).to.containSubset([
{
name: 'Emily Brontë',
books: [
{
ID: 201,
title: 'Wuthering Heights',
currency: { name: 'British Pound', symbol: '£' },
},
@@ -68,8 +45,8 @@ describe('cap/samples - Consuming Services locally', () => {
{
name: 'Edgar Allen Poe',
books: [
{ ID: 251, title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
{ ID: 252, title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
],
},
])

View File

@@ -1,12 +1,9 @@
const cds = require('@sap/cds')
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
cds.User.default = cds.User.Privileged // hard core monkey patch
describe('cap/samples - Custom Handlers', () => {
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
beforeAll(()=>{
cds.User.default = cds.User.Privileged // hard core monkey patch
})
it('should reject out-of-stock orders', async () => {
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled

View File

@@ -1,29 +0,0 @@
// Quick hack: suppress deprecation warnings w/ Node22 caused by http-proxy (used by OData v2 proxy)
// See also: https://github.com/http-party/node-http-proxy/pull/1666
require('util')._extend = Object.assign
const cds = require('@sap/cds')
describe('cap/samples - Fiori APIs - v2', function() {
const { GET, expect, axios } = cds.test ('@capire/fiori', '--with-mocks')
axios.defaults.auth = { username: 'alice', password: 'admin' }
// if (this.timeout) this.timeout(1e6)
it('serves $metadata documents in v2', async () => {
const { headers, data } = await GET `/odata/v2/browse/$metadata`
expect(headers).to.contain({
'content-type': 'application/xml',
'dataserviceversion': '2.0',
})
expect(data).to.contain('<EntitySet Name="GenreHierarchy" EntityType="CatalogService.GenreHierarchy"/>')
})
it('serves Books in v2', async () => {
const { data } = await GET `/odata/v2/browse/Books`
expect(data).to.containSubset({d:{results:[]}})
expect(data.d.results.length).to.be.greaterThanOrEqual(5)
})
})

View File

@@ -1,13 +1,9 @@
const cds = require('@sap/cds')
const { GET, expect } = cds.test (__dirname)
cds.User.default = cds.User.Privileged // hard core monkey patch
describe('cap/samples - Localized Data', () => {
const { GET, expect } = cds.test (__dirname)
beforeAll(()=>{
cds.User.default = cds.User.Privileged // hard core monkey patch
})
it('serves localized $metadata documents', async () => {
const { data } = await GET(`/browse/$metadata?sap-language=de`, { headers: { 'accept-language': 'de' }})
expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>')

View File

@@ -1,8 +1,8 @@
const cds = require('@sap/cds')
const { expect } = cds.test.in(__dirname,'..')
describe('cap/samples - Messaging', ()=>{
const { expect } = cds.test.in(__dirname,'..')
const _model = '@capire/reviews'
const Reviews = 'sap.capire.reviews.Reviews'
beforeAll(()=>{

View File

@@ -1,8 +1,8 @@
const cds = require('@sap/cds')
const { GET, expect, axios } = cds.test ('@capire/bookshop')
axios.defaults.auth = { username: 'alice', password: 'admin' }
describe('cap/samples - Bookshop APIs', () => {
const { GET, expect, axios } = cds.test ('@capire/bookshop')
axios.defaults.auth = { username: 'alice', password: 'admin' }
it('serves $metadata documents in v4', async () => {
const { headers, status, data } = await GET `/browse/$metadata`