diff --git a/.registry/.gitignore b/.registry/.gitignore deleted file mode 100644 index aa1ec1ea..00000000 --- a/.registry/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.tgz diff --git a/.registry/server.js b/.registry/server.js deleted file mode 100644 index d3489325..00000000 --- a/.registry/server.js +++ /dev/null @@ -1,81 +0,0 @@ -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,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 pkgFull = tarball.substring(0, tarball.lastIndexOf('-')) - const [, pkg ] = /^\w+-(.+)/.exec(pkgFull) - fs.lstat(tarball,(err => { - if (err) console.debug (`npm pack ../${pkg}`) - if (err) exec(`npm pack ../${pkg}`,{cwd},next) - else next() - })) - } catch (e) { - console.error(e) - res.sendStatus(500) - } -}) - -app.use('/-', express.static(__dirname)) - -app.get('/*', (req,res)=>{ - const urlRegex = /^\/(@[\w-]+)\/(.+)/ - const url = decodeURIComponent(req.url) - console.debug ('GET',url) - try { - 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, - "dist-tags": { - "latest": package.version - }, - "versions": { - [package.version]: { - "name": package.name, - "version": package.version, - "dist": { - "tarball": `${server.url}/-/${tarball}` - }, - } - }, - }) - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') return res.sendStatus(404) - console.error(e); throw e - } -}) - -const server = app.listen(port, ()=>{ - const url = server.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 = ()=>{ - server.close() - exec(`npm conf rm "${scope}:registry"`, ()=> { process.exit() }) -} - -process.on ('SIGTERM',_exit) -process.on ('SIGHUP',_exit) -process.on ('SIGINT',_exit) -process.on ('SIGUSR2',_exit) diff --git a/.vscode/launch.json b/.vscode/launch.json index 40b41090..40d3ef3c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,7 +13,7 @@ "/**", "**/node_modules/**", "**/cds/lib/lazy.js", - "**/cds/lib/req/cls.js", + "**/cds/lib/req/cds-context.js", "**/odata-v4/okra/**" ] }, @@ -26,10 +26,24 @@ "/**", "**/node_modules/**", "**/cds/lib/lazy.js", - "**/cds/lib/req/cls.js", + "**/cds/lib/req/cds-context.js", "**/odata-v4/okra/**" ] - } + }, + { + "name": "Debug Mocha Tests", + "type": "node", + "request": "attach", + "port": 9229, + "continueOnAttach": true, + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/cds/lib/lazy.js", + "**/cds/lib/req/cds-context.js", + "**/odata-v4/okra/**", + ] + }, ], "inputs": [ { diff --git a/.vscode/settings.json b/.vscode/settings.json index 44342b41..786fe8d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,10 +10,11 @@ "/**", "**/node_modules/**", "**/cds/lib/lazy.js", - "**/cds/lib/req/cls.js", + "**/cds/lib/req/cds-context.js", "**/odata-v4/okra/**" ] }, + "mochaExplorer.debuggerConfig": "Debug Mocha Tests", "mochaExplorer.parallel": true, "eslint.validate": [ "cds", @@ -22,5 +23,5 @@ "csv (semicolon)", "tsv", "tab" - ] + ], } \ No newline at end of file diff --git a/bookshop/db/init.js b/bookshop/db/init.js index 0db736ca..bfa0fc89 100644 --- a/bookshop/db/init.js +++ b/bookshop/db/init.js @@ -4,21 +4,21 @@ * currencies, if not obtained through @capire/common. */ -module.exports = async (db)=>{ +module.exports = async (tx)=>{ - const has_common = db.model.definitions['sap.common.Currencies'].elements.numcode + const has_common = tx.model.definitions['sap.common.Currencies']?.elements.numcode if (has_common) return - const already_filled = await db.exists('sap.common.Currencies',{code:'EUR'}) + const already_filled = await tx.exists('sap.common.Currencies',{code:'EUR'}) if (already_filled) return - await INSERT.into ('sap.common.Currencies') .columns ( - 'code','symbol','name' + await tx.run (INSERT.into ('sap.common.Currencies') .columns ( + [ 'code', 'symbol', 'name' ] ) .rows ( - [ 'EUR','€','Euro' ], - [ 'USD','$','US Dollar' ], - [ 'GBP','£','British Pound' ], - [ 'ILS','₪','Shekel' ], - [ 'JPY','¥','Yen' ], - ) + [ 'EUR', '€', 'Euro' ], + [ 'USD', '$', 'US Dollar' ], + [ 'GBP', '£', 'British Pound' ], + [ 'ILS', '₪', 'Shekel' ], + [ 'JPY', '¥', 'Yen' ], + )) } diff --git a/fiori/server.js b/fiori/server.js index 70cf701b..b3ab5dde 100644 --- a/fiori/server.js +++ b/fiori/server.js @@ -1,8 +1,8 @@ -const cds = require("@sap/cds") - // install OData v2 adapter +const cds = require("@sap/cds") const proxy = require('@sap/cds-odata-v2-adapter-proxy') -const proxyOpts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically -cds.on('bootstrap', app => app.use(proxy(proxyOpts))) +const opts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically +cds.on('bootstrap', app => app.use(proxy(opts))) // install proxy +cds.log('cov2ap','silent') // suppress anoying log outpout, e.g. for `npm run mocha -- --reporter nyan` -module.exports = require('@capire/bookstore/server.js') \ No newline at end of file +module.exports = require('@capire/bookstore/server.js') diff --git a/package.json b/package.json index f2001e6f..10d0e3ea 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,12 @@ }, "scripts": { "cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules", - "registry": "node .registry/server.js", "bookshop": "cds watch bookshop", "fiori": "cds watch fiori", "hello": "cds watch hello", "media": "cds watch media", - "mocha": "npx mocha || echo", - "jest": "npx jest", + "mocha": "CDS_TEST_SILENT=y npx mocha", + "jest": "npx jest --silent", "start": "cds watch fiori", "test": "npm run jest -- --silent", "test:hello": "cd hello && npm test" @@ -48,7 +47,8 @@ }, "mocha": { "recursive": true, - "parallel": true + "parallel": true, + "timeout": 6666 }, "license": "SAP SAMPLE CODE LICENSE", "private": true diff --git a/samples.md b/samples.md index afaa083a..9caed0e8 100644 --- a/samples.md +++ b/samples.md @@ -1,7 +1,7 @@ # Overview of Samples The following list gives an overview of the samples provided in subdirectories. -Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](all-in-one-monorepo) umbrella setup. +Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](#all-in-one-monorepo) umbrella setup. ## [@capire/hello-world](hello) diff --git a/test/custom-handlers.test.js b/test/custom-handlers.test.js index ae2184d3..5bd8acda 100644 --- a/test/custom-handlers.test.js +++ b/test/custom-handlers.test.js @@ -3,8 +3,9 @@ const cds = require('@sap/cds/lib') describe('cap/samples - Custom Handlers', () => { const { GET, POST, expect } = cds.test(__dirname+'/../bookshop') - 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 + beforeAll(()=>{ + cds.User.default = cds.User.Privileged // hard core monkey patch + }) it('should reject out-of-stock orders', async () => { await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}` diff --git a/test/fiori.test.js b/test/fiori.test.js index 9da8eb5e..a48abc3c 100644 --- a/test/fiori.test.js +++ b/test/fiori.test.js @@ -1,10 +1,12 @@ const cds = require('@sap/cds/lib') -describe('cap/samples - Fiori APIs - v2', () => { +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 `/v2/browse/$metadata` expect(headers).to.contain({ diff --git a/test/localized-data/services.test.js b/test/localized-data/services.test.js index a440e60e..8ec30485 100644 --- a/test/localized-data/services.test.js +++ b/test/localized-data/services.test.js @@ -1,8 +1,12 @@ +const cds = require('@sap/cds/lib') + describe('cap/samples - Localized Data', () => { - const { GET, expect, cds } = require('@sap/cds/lib').test (__dirname) - 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 + 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' }}) diff --git a/test/messaging.test.js b/test/messaging.test.js index f31f715a..9e9ab66c 100644 --- a/test/messaging.test.js +++ b/test/messaging.test.js @@ -1,16 +1,13 @@ const cds = require('@sap/cds/lib') -const {resolve} = require('path') describe('cap/samples - Messaging', ()=>{ - const { expect } = cds.test + const { expect } = cds.test.in(__dirname,'..') const _model = '@capire/reviews' const Reviews = 'sap.capire.reviews.Reviews' - 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 - - beforeAll(() => { cds.root = resolve(__dirname, '..') }) - afterAll(() => { cds.root = process.cwd() }) + beforeAll(()=>{ + cds.User.default = cds.User.Privileged // hard core monkey patch + }) it ('should bootstrap sqlite in-memory db', async()=>{ const db = await cds.deploy (_model) .to ('sqlite::memory:') @@ -35,6 +32,7 @@ describe('cap/samples - Messaging', ()=>{ it ('should add review', async ()=>{ const review = { subject: "201", title: "Captivating", rating: ++N } + cds._debug = 1 const response = await srv.create ('Reviews') .entries (review) expect (response) .to.containSubset (review) }) diff --git a/test/odata.test.js b/test/odata.test.js index c0987828..73ac0ff4 100644 --- a/test/odata.test.js +++ b/test/odata.test.js @@ -4,47 +4,6 @@ describe('cap/samples - Bookshop APIs', () => { const { GET, expect, axios } = cds.test ('@capire/bookshop') axios.defaults.auth = { username: 'alice', password: 'admin' } - // Genres - const Drama = { - "name": "Drama", - "descr": null, - "ID": 11, - "parent_ID": 10 - } - const Mystery = { - "name": "Mystery", - "descr": null, - "ID": 16, - "parent_ID": 10 - } - const Romance = { - "name": "Romance", - "descr": null, - "ID": 15, - "parent_ID": 10 - } - - // Currencies - const GBP = { - "name": "British Pound", - "descr": null, - "code": "GBP", - "symbol": "£" - } - const USD = { - "name": "US Dollar", - "descr": null, - "code": "USD", - "symbol": "$" - } - const JPY = { - "name": "Yen", - "descr": null, - "code": "JPY", - "symbol": "¥" - } - - it('serves $metadata documents in v4', async () => { const { headers, status, data } = await GET `/browse/$metadata` expect(status).to.equal(200) @@ -57,6 +16,9 @@ describe('cap/samples - Bookshop APIs', () => { }) it('serves ListOfBooks?$expand=genre,currency', async () => { + const Mystery = { ID: 16, name: 'Mystery', descr: null, parent_ID: 10 } + const Romance = { ID: 15, name: 'Romance', descr: null, parent_ID: 10 } + const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' } const { data } = await GET `/browse/ListOfBooks ${{ params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` }, }}` @@ -126,14 +88,10 @@ describe('cap/samples - Bookshop APIs', () => { }) it('serves user info', async () => { - { - const { data } = await GET (`/user/me`) - expect(data).to.containSubset({ id: 'alice', locale:'en' }) - } - { - const { data } = await GET (`/user/me`, {auth: { username: 'joe' }}) - expect(data).to.containSubset({ id: 'joe', locale:'en' }) - } + const { data: alice } = await GET `/user/me` + expect(alice).to.containSubset({ id: 'alice', locale:'en' }) + const { data: joe } = await GET (`/user/me`, {auth: { username: 'joe' }}) + expect(joe).to.containSubset({ id: 'joe', locale:'en' }) }) }) diff --git a/test/registry.test.js b/test/registry.test.js deleted file mode 100644 index e203e25b..00000000 --- a/test/registry.test.js +++ /dev/null @@ -1,57 +0,0 @@ - -const cds = require('@sap/cds/lib') -const { fork } = require('child_process') -const { resolve } = require('path') -const verbose = process.env.CDS_TEST_VERBOSE - -describe('cap/samples - Local NPM registry', () => { - - const { expect } = cds.test - // ||true - - let registry - let axios - const cwd = resolve(__dirname, '..') - - before(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 = require('axios').create ({ baseURL: res.url, validateStatus: (status)=>status<500 }) - }) - - after(done => { registry.once('exit',done); registry.kill() }) - - for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) { - it(`should serve ${mod}`, async () => { - const resp = await axios.get(`/@capire/${mod}`) - expect(resp.data).to.containSubset({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).to.equal(404) - resp = await axios.get(`/foo`) - expect(resp.status).to.equal(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()) - }) - }) - -}