Compare commits

..

20 Commits

Author SHA1 Message Date
Vitaly Kozyura
d5c45d0326 Merge branch 'main' into test-extensibility 2021-11-03 13:32:34 +01:00
nkaputnik
390bd82502 fixed windows error 2021-11-03 09:30:33 +01:00
Christian Georgi
7f6b87171a Merge pull request #284 from SAP-samples/registry
NPM registry: more robust,  work on Windows
2021-10-26 22:21:32 +02:00
Christian Georgi
21e74bbbfb Add test for NPM registry 2021-10-26 22:17:25 +02:00
Christian Georgi
102b15c3cd NPM registry: more robust, work on Windows
- Run `npm rm` in correct dir
- Encode args in `npm conf rm` command for Powershell
- Don't cleanup in exit handler, this might not complete.
  Instead, do it on startup.
- Different cleanup command for Windows and Linux
- Explicitly close server, instead of relying on process.exit()
- Make scope configurable
2021-10-26 19:11:08 +02:00
Dr. David A. Kunz
73db2e96bc Merge pull request #283 from SAP-samples/ql-changes-typo-fix
fix typo in ql changes
2021-10-26 11:57:44 +02:00
Dr. David A. Kunz
7bb58ee2d5 Merge branch 'main' into ql-changes-typo-fix 2021-10-26 11:56:15 +02:00
D065023
27e82d16e0 fix typo 2021-10-26 11:55:33 +02:00
Dr. David A. Kunz
ff9bbe6d8d Merge pull request #282 from SAP-samples/ql-changes
Changes in ql for cds version 5.6.0
2021-10-26 11:32:09 +02:00
Dr. David A. Kunz
5f1b7b8cbf Merge branch 'main' into ql-changes 2021-10-26 11:27:27 +02:00
D065023
0253300557 Changes in ql for cds version 5.6.0 2021-10-26 11:22:43 +02:00
nkaputnik
5429617e0b Update to latest UIFlex changes and CDS master 2021-10-25 14:54:51 +02:00
Daniel
139d957495 Added reviewed.count to messsages 2021-10-18 18:04:33 +02:00
Daniel
404427237b Updated reviews samples for messaging GA 2021-10-18 18:04:33 +02:00
nkaputnik
211c7e43ae Merge branch 'test-extensibility' of https://github.com/SAP-samples/cloud-cap-samples into test-extensibility 2021-10-01 14:07:22 +02:00
nkaputnik
08ec0c7d0d Added fiori configuration for UIFlex enablement 2021-10-01 14:07:10 +02:00
Daniel
0b8e7cece9 . 2021-10-01 10:31:53 +02:00
Daniel
c33613fa57 .. 2021-09-30 19:43:52 +02:00
D051920
c841765791 add test 2021-09-30 16:46:59 +02:00
D051920
aeee07cbcd Test UIFlex 2021-09-30 16:45:44 +02:00
31 changed files with 392 additions and 157 deletions

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ reviews/msg-box
reviews/db/test.db
*.openapi3.json
sqlite.db

View File

@@ -1,17 +1,22 @@
const { exec } = require ('child_process')
const isWin = process.platform === 'win32'
const express = require ('express')
const fs = require ('fs')
const app = express()
const { PORT=4444 } = process.env
const [,,port=PORT] = process.argv
const [,,port=PORT,scope='@capire'] = process.argv
const cwd = __dirname
// clean up on start (exit handler might not complete on Windows)
exec(isWin ? 'del *.tgz' : 'rm *.tgz', {cwd})
app.use('/-/:tarball', (req,res,next) => {
console.debug ('GET', req.params)
try {
const { tarball } = req.params
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
const [, pkg ] = /^\w+-(\w+)/.exec(tarball)
fs.lstat(tarball,(err => {
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
else next()
@@ -25,12 +30,14 @@ app.use('/-/:tarball', (req,res,next) => {
app.use('/-', express.static(__dirname))
app.get('/*', (req,res)=>{
const urlRegex = /^\/(@\w+)\/(\w+)/
const url = decodeURIComponent(req.url)
console.debug ('GET',url)
try {
const [, capire, pkg ] = /^\/(@capire)\/(\w+)/.exec(url)
const package = require (`${capire}/${pkg}/package.json`)
const tarball = `capire-${pkg}-${package.version}.tgz`
if (!urlRegex.test(url)) return res.sendStatus(404)
const [, scpe, pkg ] = urlRegex.exec(url)
const package = require (`${scpe}/${pkg}/package.json`)
const tarball = `${scpe.slice(1)}-${pkg}-${package.version}.tgz`
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
res.json({
"name": package.name,
@@ -42,29 +49,30 @@ app.get('/*', (req,res)=>{
"name": package.name,
"version": package.version,
"dist": {
"tarball": `http://localhost:${port}/-/${tarball}`
"tarball": `/-/${tarball}`
},
}
},
})
} catch (e) {
console.error(e)
res.sendStatus(404)
if (e.code === 'MODULE_NOT_FOUND') return res.sendStatus(404)
console.error(e); throw e
}
})
app.listen(port, ()=>{
console.log (`npm set @capire:registry=http://localhost:${port}`)
console.log (`@capire registry listening on http://localhost:${port}`)
exec(`npm set @capire:registry=http://localhost:${port}`)
const server = app.listen(port, ()=>{
const url = `http://localhost:${server.address().port}`
console.log (`npm set ${scope}:registry=${url}`)
exec(`npm set ${scope}:registry=${url}`)
console.log (`${scope} registry listening on ${url}`)
})
const _exit = ()=>{
console.log ('\nnpm conf rm @capire:registry')
exec('npm conf rm @capire:registry')
exec('rm *.tgz')
process.exit()
server.close()
exec(`npm conf rm "${scope}:registry"`, ()=> { process.exit() })
}
process.on ('SIGTERM',_exit)
process.on ('SIGHUP',_exit)
process.on ('SIGINT',_exit)

View File

@@ -34,7 +34,7 @@
<td>{{ book.author }}</td>
<td>{{ book.genre.name }}</td>
<td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} ({{ book.numberOfReviews }})
</td>
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
</tr>

View File

@@ -1,7 +1,7 @@
using { Currency, managed, sap } from '@sap/cds/common';
using { Currency, managed, sap, extensible } from '@sap/cds/common';
namespace sap.capire.bookshop;
entity Books : managed {
entity Books : managed, extensible {
key ID : Integer;
title : localized String(111);
descr : localized String(1111);
@@ -13,7 +13,7 @@ entity Books : managed {
image : LargeBinary @Core.MediaType : 'image/png';
}
entity Authors : managed {
entity Authors : managed, extensible {
key ID : Integer;
name : String(111);
dateOfBirth : Date;

View File

@@ -1 +1,2 @@
exports.CatalogService = require('./srv/cat-service')
const { CatalogService } = require('./srv/cat-service')
module.exports = { CatalogService }

View File

@@ -1,55 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bookshop</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bookshop</title>
<script>
window["sap-ushell-config"] = {
defaultRenderer: "fiori2",
applications: {
"browse-books": {
title: "Browse Books",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType: "URL",
url: "/browse/webapp",
navigationMode: "embedded",
},
"manage-books": {
title: "Manage Books",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=admin",
applicationType: "URL",
url: "/admin/webapp",
navigationMode: "embedded",
},
"manage-orders": {
title: "Manage Orders",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=orders",
applicationType: "URL",
url: "/orders/webapp",
navigationMode: "embedded",
},
},
bootstrapPlugins: {
RuntimeAuthoringPlugin: {
component: "sap.ushell.plugins.rta",
config: {
validateAppVersion: false,
},
},
PersonalizePlugin: {
component: "sap.ushell.plugins.rta-personalize",
config: {
validateAppVersion: false,
},
},
},
};
</script>
<script>
window["sap-ushell-config"] = {
defaultRenderer: "fiori2",
applications: {
"browse-books": {
title: "Browse Books",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType : "URL",
url: "/browse/webapp",
navigationMode: "embedded"
},
"manage-books": {
title: "Manage Books",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
navigationMode: "embedded"
},
"manage-orders": {
title: "Manage Orders",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
navigationMode: "embedded"
}
}
};
</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"
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"
data-sap-ui-frameOptions="allow"
></script>
<script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
</script>
</head>
<body class="sapUiBody" id="content"></body>
<script
id="sap-ushell-bootstrap"
src="https://sapui5nightly.int.sap.eu2.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"
></script>
<script
id="sap-ui-bootstrap"
src="https://sapui5nightly.int.sap.eu2.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"
data-sap-ui-frameOptions="allow"
data-sap-ui-bindingSyntax="complex"
></script>
<script>
sap.ui
.getCore()
.attachInit(() =>
sap.ushell.Container.createRenderer().placeAt("content")
);
sap.ui
.getCore()
.getConfiguration()
.setFlexibilityServices([{ connector: "SessionStorageConnector" }]);
</script>
</head>
<body class="sapUiBody" id="content"></body>
</html>

View File

@@ -22,6 +22,10 @@
}
},
"sap.ui5": {
"flexEnabled": true,
"config": {
"experimentalCAPScenario": true
},
"dependencies": {
"libs": {
"sap.fe.templates": {}

View File

@@ -22,6 +22,7 @@
}
},
"sap.ui5": {
"flexEnabled": true,
"dependencies": {
"libs": {
"sap.fe.templates": {}

View File

@@ -30,14 +30,19 @@
"kind": "odata",
"model": "@capire/orders"
},
"extensibility": {
"kind": "uiflex"
},
"db": {
"kind": "sql",
"[development]": {
"model": "db/sqlite"
},
"[production]": {
"model": "db/hana"
"kind": "sqlite",
"credentials": {
"database": "sqlite.db"
}
},
"messaging": {
"[development]": { "kind": "file-based-messaging" },
"[hybrid]": { "kind": "enterprise-messaging-shared" },
"kind": "enterprise-messaging"
}
}
}

View File

@@ -12,6 +12,7 @@ using { sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID;
numberOfReviews : Integer;
rating : Decimal;
}

View File

@@ -41,8 +41,8 @@ module.exports = async()=>{ // called by server.js
//
ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { subject, rating } = msg.data
return UPDATE(Books,subject).with({rating})
const { subject, count, rating } = msg.data
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
// ^ Note: the framework will execute this and take care for db.tx
})

14
fiori/test.http Normal file
View File

@@ -0,0 +1,14 @@
# Add integer extension field to Books
POST http://localhost:4004/extensibility/addExtension
Content-Type: application/json
{
"extensions": [
"{\"extend\":\"AdminService.Books\",\"elements\":{\"neuesFeld2\":{\"type\":\"cds.String\", \"default\":\"hallo\"}}}"
]
}
###
# { "extend": "AdminService.Books", "elements":{
# "abc":{"type":"cds.Integer"}
# }},

View File

@@ -19,19 +19,38 @@
url: "/orders/webapp",
navigationMode: "embedded"
}
}
},
bootstrapPlugins: {
RuntimeAuthoringPlugin: {
component: "sap.ushell.plugins.rta",
config: {
validateAppVersion: false,
},
},
PersonalizePlugin: {
component: "sap.ushell.plugins.rta-personalize",
config: {
validateAppVersion: false,
},
},
},
};
</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"
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"
data-sap-ui-frameOptions="allow"
></script>
<script id="sap-ushell-bootstrap" src="https://sapui5nightly.int.sap.eu2.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://sapui5nightly.int.sap.eu2.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"
data-sap-ui-frameOptions="allow"
data-sap-ui-bindingSyntax="complex"
></script>
<script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"));
sap.ui
.getCore()
.getConfiguration()
.setFlexibilityServices([{ connector: "SessionStorageConnector" }]);
</script>
</head>

View File

@@ -45,8 +45,7 @@ annotate OrdersService.Orders with @(
],
FieldGroup#Details: {
Data: [
{Value: currency.code, Label:'Currency'},
{Value: status.code, Label:'Status'},
{Value: currency.code, Label:'Currency'}
]
},
FieldGroup#Created: {
@@ -67,9 +66,6 @@ annotate OrdersService.Orders with @(
createdBy @UI.HiddenFilter:false;
};
annotate OrdersService.OrderStatus with {
code @Common: { Text: name, TextArrangement: #TextOnly };
}
annotate OrdersService.Orders_Items with @(

View File

@@ -22,6 +22,10 @@
}
},
"sap.ui5": {
"flexEnabled": true,
"config": {
"experimentalCAPScenario": true
},
"dependencies": {
"libs": {
"sap.fe.templates": {}

View File

@@ -1,4 +0,0 @@
code;name;descr
O;Open;Order is open
P;In Process;Order is about to be processed
C;Closed;Order is closed
1 code name descr
2 O Open Order is open
3 P In Process Order is about to be processed
4 C Closed Order is closed

View File

@@ -1,3 +1,3 @@
ID;createdAt;createdBy;buyer;OrderNo;currency_code;status_code
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR;O
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR;C
ID;createdAt;createdBy;buyer;OrderNo;currency_code
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR
1 ID createdAt createdBy buyer OrderNo currency_code status_code
2 7e2f2640-6866-4dcf-8f4d-3027aa831cad 2019-01-31 john.doe@test.com john.doe@test.com 1 EUR O
3 64e718c9-ff99-47f1-8ca3-950c850777d4 2019-01-30 jane.doe@test.com jane.doe@test.com 2 EUR C

View File

@@ -1,4 +1,4 @@
using { Currency, User, managed, cuid, sap.common.CodeList } from '@sap/cds/common';
using { Currency, User, managed, cuid } from '@sap/cds/common';
namespace sap.capire.orders;
entity Orders : cuid, managed {
@@ -6,12 +6,8 @@ entity Orders : cuid, managed {
Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User;
currency : Currency;
status : Association to OrderStatus;
}
@cds.persistence.data.kind: 'config'
entity OrderStatus : CodeList { key code: String(1) }
entity Orders_Items {
key ID : UUID;
up_ : Association to Orders;

3
package-lock.json generated
View File

@@ -1,3 +1,6 @@
// 20211103133211
// https://raw.githubusercontent.com/SAP-samples/cloud-cap-samples/main/package-lock.json
{
"name": "@capire/samples",
"version": "2.0.0",

View File

@@ -12,7 +12,8 @@
"@capire/media": "./media",
"@capire/orders": "./orders",
"@capire/reviews": "./reviews",
"@sap/cds": "^5.5.3"
"@sap/cds": "git+https://github.tools.sap/cap/cds.git",
"@sap/cds-dk": "*"
},
"devDependencies": {
"chai": "^4.3.4",

View File

@@ -8,10 +8,11 @@ service ReviewsService {
action unlike (review: type of Reviews:ID);
// Async API
event reviewed : {
subject: type of Reviews:subject;
rating: Decimal(2,1)
}
event reviewed : {
subject : type of Reviews:subject;
count : Integer;
rating : Decimal;
}
// Input validation
annotate Reviews with {

View File

@@ -12,11 +12,11 @@ module.exports = cds.service.impl (function(){
// Emit an event to inform subscribers about new avg ratings for reviewed subjects
this.after (['CREATE','UPDATE','DELETE'], 'Reviews', async function(_,req) {
const {subject} = req.data
const {rating} = await cds.tx(req) .run (
SELECT.one (['round(avg(rating),2) as rating']) .from (Reviews) .where ({subject})
const { count, rating } = await cds.tx(req) .run (
SELECT.one `round(avg(rating),2) as rating, count(*) as count` .from (Reviews) .where ({subject})
)
global.it || console.log ('< emitting:', 'reviewed', { subject, rating })
await this.emit ('reviewed', { subject, rating })
global.it || console.log ('< emitting:', 'reviewed', { subject, count, rating })
await this.emit ('reviewed', { subject, count, rating })
})
// Increment counter for reviews considered helpful

View File

@@ -0,0 +1,3 @@
<head>
<meta http-equiv="refresh" content="0;url=app/bookshop/index.html">
</head>

View File

@@ -0,0 +1,3 @@
<head>
<meta http-equiv="refresh" content="0;url=app/reviews/index.html">
</head>

17
reviews/test/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "@capire/bookshop-with-reviews",
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/reviews": "*",
"@sap/cds": "^5",
"express": "^4.17.1"
},
"cds": {
"requires": {
"db": {
"kind": "sql"
}
}
}
}

View File

@@ -0,0 +1,13 @@
// Use enhanced implementation for CatalogService
using { CatalogService } from '@capire/bookshop';
annotate CatalogService with @impl:'srv/bookshop.js';
// Extend Books with access to Reviews and average ratings
using { sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : Decimal;
numberOfReviews : Integer;
}

View File

@@ -0,0 +1,27 @@
const { CatalogService } = require('@capire/bookshop')
const cds = require ('@sap/cds')
module.exports = class extends CatalogService {async init(){
const { Books } = cds.entities('sap.capire.bookshop')
// Connect to ReviewsService to receive `reviewed` events from it
const ReviewsService = await cds.connect.to ('ReviewsService')
ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { subject, count, rating } = msg.data
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
})
return super.init()
}}
// -----------------------------------------------------------------------
// Helper for serving static content from npm-installed packages
const {dirname,resolve} = require('path')
const {static} = require('express')
cds.once('listening',()=>{
cds.app.use ('/app/bookshop', static (dirname (require.resolve('@capire/bookshop'))+'/app/vue'))
cds.app.use ('/app/reviews', static (resolve (__dirname, '../../app/vue')))
})

View File

@@ -86,14 +86,24 @@ describe('cds.ql → cqn', () => {
.to.eql(SELECT.from(Foo,{ID:11}))
.to.eql(SELECT.from(Foo).byKey(11))
.to.eql(SELECT.from(Foo).byKey({ID:11}))
expect.one(cqn)
.to.eql({
SELECT: {
one: true,
from: { ref: ['Foo'] },
where: [{ ref: ['ID'] }, '=', { val: 11 }],
},
})
if (cds.version >= '5.6.0') {
expect.one(cqn)
.to.eql({
SELECT: {
one: true,
from: { ref: [{ id: 'Foo', where: [{ ref: ['ID'] }, '=', { val: 11 }] }] },
},
})
} else {
expect.one(cqn)
.to.eql({
SELECT: {
one: true,
from: { ref: ['Foo'] },
where: [{ ref: ['ID'] }, '=', { val: 11 }],
},
})
}
})
@@ -131,15 +141,27 @@ describe('cds.ql → cqn', () => {
// Test combination with key as second argument to .from
expect(cqn = SELECT.from(Foo, 11, ['a']))
.to.eql(SELECT.from(Foo, 11, foo => foo.a))
expect.one(cqn)
.to.eql({
SELECT: {
one: true,
from: { ref: ['Foo'] },
columns: [{ ref: ['a'] }],
where: [{ ref: ['ID'] }, '=', { val: 11 }],
},
})
if (cds.version >= '5.6.0') {
expect.one(cqn)
.to.eql({
SELECT: {
one: true,
from: { ref: [{ id: 'Foo', where: [{ ref: ['ID'] }, '=', { val: 11 }]}] },
columns: [{ ref: ['a'] }]
},
})
} else {
expect.one(cqn)
.to.eql({
SELECT: {
one: true,
from: { ref: ['Foo'] },
columns: [{ ref: ['a'] }],
where: [{ ref: ['ID'] }, '=', { val: 11 }],
},
})
}
})
@@ -551,21 +573,31 @@ describe('cds.ql → cqn', () => {
describe(`UPDATE...`, () => {
test('entity (..., <key>)', () => {
expect(UPDATE(Books, 4711))
.to.eql(UPDATE(Books, { ID: 4711 }))
.to.eql(UPDATE(Books).byKey(4711))
.to.eql(UPDATE(Books).byKey({ ID: 4711 }))
.to.eql(UPDATE(Books).where({ ID: 4711 }))
.to.eql(UPDATE(Books).where(`ID=`, 4711))
.to.eql(UPDATE.entity(Books, 4711))
.to.eql(UPDATE.entity(Books, { ID: 4711 }))
// etc...
.to.eql({
const cqnWhere = {
UPDATE: {
entity: 'capire.bookshop.Books',
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
},
})
}
expect(UPDATE(Books).where({ ID: 4711 }))
.to.eql(UPDATE(Books).where(`ID=`, 4711))
.to.eql(cqnWhere)
const cqnKey = (cds.version >= '5.6.0') ?
{
UPDATE: {
entity: { ref: [{ id: 'capire.bookshop.Books', where: [{ ref: ['ID'] }, '=', { val: 4711 }] }] }
}
}
: cqnWhere
expect(UPDATE(Books, 4711))
.to.eql(UPDATE(Books, { ID: 4711 }))
.to.eql(UPDATE(Books).byKey(4711))
.to.eql(UPDATE(Books).byKey({ ID: 4711 }))
.to.eql(UPDATE.entity(Books, 4711))
.to.eql(UPDATE.entity(Books, { ID: 4711 }))
// etc...
.to.eql(cqnKey)
})
/*
@@ -616,20 +648,29 @@ describe('cds.ql → cqn', () => {
describe(`DELETE...`, () => {
test('from (..., <key>)', () => {
const cqnWhere = {
DELETE: {
from: 'capire.bookshop.Books',
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
},
}
expect(DELETE.from(Books).where({ ID: 4711 }))
.to.eql(DELETE.from(Books).where(`ID=`, 4711))
.to.eql(cqnWhere)
const cqnKey = (cds.version >= '5.6.0') ?
{
DELETE: {
from: { ref: [{ id: 'capire.bookshop.Books', where: [{ ref: ['ID'] }, '=', { val: 4711 }]}] }
},
} : cqnWhere
expect(DELETE(Books, 4711))
.to.eql(DELETE(Books, { ID: 4711 }))
.to.eql(DELETE.from(Books, 4711))
.to.eql(DELETE.from(Books, { ID: 4711 }))
.to.eql(DELETE.from(Books).byKey(4711))
.to.eql(DELETE.from(Books).byKey({ ID: 4711 }))
.to.eql(DELETE.from(Books).where({ ID: 4711 }))
.to.eql(DELETE.from(Books).where(`ID=`, 4711))
.to.eql({
DELETE: {
from: 'capire.bookshop.Books',
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
},
})
.to.eql(cqnKey)
})
test('/w plain SQL', () => {

View File

@@ -60,11 +60,11 @@ describe('Messaging', ()=>{
expect(M).equals(N)
expect(received.length).equals(N)
expect(received.map(m=>m.data)).to.deep.equal([
{ subject: '201', rating: 1 },
{ subject: '201', rating: 1.5 },
{ subject: '201', rating: 2 },
{ subject: '201', rating: 2.5 },
{ subject: '201', rating: 3 },
{ count: 1, subject: '201', rating: 1 },
{ count: 2, subject: '201', rating: 1.5 },
{ count: 3, subject: '201', rating: 2 },
{ count: 4, subject: '201', rating: 2.5 },
{ count: 5, subject: '201', rating: 3 },
])
})
})

54
test/registry.test.js Normal file
View File

@@ -0,0 +1,54 @@
const { fork } = require('child_process')
const { resolve } = require('path')
const Axios = require('axios')
const verbose = process.env.CDS_TEST_VERBOSE
// ||true
describe('Local NPM registry', () => {
let registry
let axios
const cwd = resolve(__dirname, '..')
beforeAll(async ()=> {
const env = Object.assign(process.env, {PORT:'0'})
const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env})
registry = res.cp
axios = Axios.default.create ({ baseURL: res.url, validateStatus: (status)=>status<500 })
})
afterAll(() => { registry.kill() })
for (const mod of ['bookshop','fiori','orders','reviews']) {
it(`should serve ${mod}`, async () => {
const resp = await axios.get(`/@capire/${mod}`)
expect(resp.data).toMatchObject({name: `@capire/${mod}`, versions:{}})
const versions = Object.values(resp.data.versions)
await axios.get(versions[0].dist.tarball)
})
}
it(`should return 404 for unknown packages`, async () => {
let resp = await axios.get(`/@capire/foo`)
expect(resp.status).toEqual(404)
resp = await axios.get(`/foo`)
expect(resp.status).toEqual(404)
})
})
function exec (script, opts) {
return new Promise((resolve, reject)=> {
const cp = fork (script, [], opts)
.on('error', err => reject(new Error(err)))
cp.stdout.on('data', chunk => {
if (verbose) console.log(chunk.toString())
if (chunk.toString().match(/listening.*(http:.*:\d+)/i)) {
resolve({cp, url:RegExp.$1})
}
})
cp.stderr.on('data', chunk => {
if (verbose) console.error(chunk.toString())
})
})
}