@sap/cds 5.7.5 → 5.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/log/format/kibana.js +3 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +23 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +3 -3
- package/libx/_runtime/common/composition/insert.js +14 -27
- package/libx/_runtime/common/composition/tree.js +1 -1
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +19 -5
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +29 -6
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +21 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/structured.js +10 -4
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +67 -26
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -4
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +16 -14
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +20 -19
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/fiori/utils/handler.js +1 -11
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/dynatrace.js +11 -5
- package/libx/_runtime/hana/execute.js +132 -19
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +23 -25
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +4 -1
- package/libx/_runtime/remote/utils/client.js +41 -24
- package/libx/_runtime/remote/utils/data.js +54 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/crud/update.js +8 -5
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +49 -21
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
package/lib/i18n/localize.js
CHANGED
|
@@ -33,19 +33,16 @@ function localize (model, /*with:*/ locale, aString) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const TEXT_KEY_MARKER = 'i18n>'
|
|
36
|
-
const TEXT_KEYS = /
|
|
36
|
+
const TEXT_KEYS = /{b?i18n>([^"}]+)}/g
|
|
37
37
|
function localizeString (aString, bundle) {
|
|
38
38
|
if (!bundle || !aString) return aString
|
|
39
39
|
if (typeof aString === 'object') aString = JSON.stringify(aString, null, 2)
|
|
40
40
|
// quick check for presence of any text key, to avoid expensive operation below
|
|
41
41
|
if (aString.indexOf(TEXT_KEY_MARKER) < 0) return aString
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (val && isXml) val = escapeXmlAttr(val)
|
|
47
|
-
else if (val && isJson) val = escapeJson(val)
|
|
48
|
-
return `"${left}${val}${right}"`
|
|
42
|
+
const escape = aString.startsWith('<?xml') ? escapeXmlAttr : /^[{[]/.test(aString) ? escapeJson : v=>v
|
|
43
|
+
return aString.replace (TEXT_KEYS, (_, key) => {
|
|
44
|
+
const val = bundle[key]
|
|
45
|
+
return val ? escape(val) : key
|
|
49
46
|
})
|
|
50
47
|
}
|
|
51
48
|
|
package/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
|
|
|
9
9
|
get builtin() { return super.builtin = require ('./core') }
|
|
10
10
|
get service() { return super.service = extend (this.builtin.classes.service) .with ({
|
|
11
11
|
/** @param x {(this:Service, srv:Service) => any} */ impl: x=>x,
|
|
12
|
+
/** @type {{ [path:string] : Service }} */ paths: {},
|
|
12
13
|
/** @type Service[] */ providers: [],
|
|
13
14
|
factory: require ('./serve/factory'),
|
|
14
15
|
bindings: require ('./connect/bindings'),
|
package/lib/log/errors.js
CHANGED
|
@@ -2,7 +2,7 @@ const error = exports = module.exports = (..._) => { throw _error(..._) }
|
|
|
2
2
|
const _error = (msg, _details, _base = error, ...etc) => {
|
|
3
3
|
if (msg.raw) return _error (String.raw (msg,_details,_base,...etc))
|
|
4
4
|
const e = msg instanceof Error ? msg : new Error (msg)
|
|
5
|
-
Error.captureStackTrace(e,_base)
|
|
5
|
+
if (_base) Error.captureStackTrace (e,_base)
|
|
6
6
|
if (_details) Object.assign (e,_details)
|
|
7
7
|
return e
|
|
8
8
|
}
|
package/lib/log/format/kibana.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require
|
|
1
|
+
const cds = require('../../')
|
|
2
2
|
const util = require('util')
|
|
3
3
|
|
|
4
4
|
const _l2l = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
|
|
@@ -8,7 +8,7 @@ const _l2l = { 1: 'error', 2: 'warn', 3: 'info', 4: 'debug', 5: 'trace' }
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = (module, level, ...args) => {
|
|
10
10
|
// config
|
|
11
|
-
const { user: log_user
|
|
11
|
+
const { user: log_user, kibana_custom_fields } = cds.env.log
|
|
12
12
|
|
|
13
13
|
// build the object to log
|
|
14
14
|
const toLog = {
|
|
@@ -36,7 +36,7 @@ module.exports = (module, level, ...args) => {
|
|
|
36
36
|
if (args.length && typeof args[0] === 'object' && args[0].message) {
|
|
37
37
|
const err = args.shift()
|
|
38
38
|
toLog.msg = err.message
|
|
39
|
-
if (err
|
|
39
|
+
if (typeof err.stack === 'string') toLog.stacktrace = err.stack.split(/\s*\r?\n\s*/)
|
|
40
40
|
Object.assign(toLog, err, { level: toLog.level })
|
|
41
41
|
}
|
|
42
42
|
|
package/lib/ql/SELECT.js
CHANGED
|
@@ -130,8 +130,8 @@ module.exports = class SELECT extends Whereable {
|
|
|
130
130
|
const _columns = (args) => {
|
|
131
131
|
const x = args[0]
|
|
132
132
|
if (x.raw) {
|
|
133
|
-
if (x[0] === '{') return SELECT_('from X ',args).columns
|
|
134
|
-
else
|
|
133
|
+
if (x[0][0] === '{') return SELECT_('from X ',args).columns
|
|
134
|
+
else return SELECT_('from X {',args,'}').columns
|
|
135
135
|
} else {
|
|
136
136
|
if (typeof x === 'string' && x[0] === '{') return parse.cql('SELECT from X '+ x).SELECT.columns
|
|
137
137
|
else return _columns_or_not(x) || args.map(_column_expr)
|
package/lib/req/cds-context.js
CHANGED
|
@@ -61,7 +61,7 @@ exports.spawn = function cds_spawn (o,fn) {
|
|
|
61
61
|
for (const handler of em.listeners('succeeded')) await handler(res)
|
|
62
62
|
for (const handler of em.listeners('done')) await handler()
|
|
63
63
|
}
|
|
64
|
-
fn(tx).then(commit, tx.rollback) .catch (async e => {
|
|
64
|
+
Promise.resolve().then(() => fn(tx)).then(commit, tx.rollback) .catch (async e => {
|
|
65
65
|
// tx.rollback throws passed error -> we will arrive here for any error
|
|
66
66
|
cds.log().error(`ERROR occured in background job:`, e)
|
|
67
67
|
for (const handler of em.listeners('failed')) await handler(e)
|
package/lib/req/context.js
CHANGED
|
@@ -75,7 +75,7 @@ class EventContext {
|
|
|
75
75
|
// The following properties are inherited from root contexts, if exist...
|
|
76
76
|
//
|
|
77
77
|
|
|
78
|
-
set context(c) { if (c)
|
|
78
|
+
set context(c) { if (c) this._set('context',c) }
|
|
79
79
|
get context() { return this }
|
|
80
80
|
|
|
81
81
|
set id(c) { if (c) super.id = c }
|
package/lib/serve/Transaction.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const cds = require('../index'), {
|
|
1
|
+
const cds = require('../index'), { cds_tx_protection } = cds.env.features
|
|
2
|
+
const EventContext = require('../req/context')
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* This is the implementation of the `srv.tx(req)` method. It constructs
|
|
@@ -74,8 +75,8 @@ class Transaction {
|
|
|
74
75
|
* in order to prevent continuous use without explicit reopen (i.e., begin).
|
|
75
76
|
*/
|
|
76
77
|
async rollback (err) {
|
|
77
|
-
// nothing to do if transaction already rolled back
|
|
78
|
-
if (this.ready === 'rolled back'
|
|
78
|
+
// nothing to do if transaction already rolled back
|
|
79
|
+
if (this.ready === 'rolled back') return
|
|
79
80
|
|
|
80
81
|
/*
|
|
81
82
|
* srv.on('error', function (err, req) { ... })
|
|
@@ -85,7 +86,8 @@ class Transaction {
|
|
|
85
86
|
if (err) for (const each of this._handlers._error) each.handler.call(this, err, this.context)
|
|
86
87
|
|
|
87
88
|
if (this.ready) { //> nothing to do if no transaction started at all
|
|
88
|
-
if (
|
|
89
|
+
// don't actually roll back if already committed (e.g., error thrown in on succeeded or on done)
|
|
90
|
+
if (this.ready !== 'committed' && this.__proto__.rollback) await this.__proto__.rollback.call (this,err)
|
|
89
91
|
_init(this).ready = 'rolled back'
|
|
90
92
|
}
|
|
91
93
|
if (err) throw err
|
|
@@ -98,6 +100,7 @@ class RootTransaction extends Transaction {
|
|
|
98
100
|
|
|
99
101
|
/**
|
|
100
102
|
* Register the new transaction with the root context.
|
|
103
|
+
* @param {EventContext} root
|
|
101
104
|
*/
|
|
102
105
|
static for (srv,root) {
|
|
103
106
|
return root._tx = super.for (srv,root)
|
|
@@ -110,8 +113,9 @@ class RootTransaction extends Transaction {
|
|
|
110
113
|
async commit (res) {
|
|
111
114
|
if (cds_tx_protection) this.context._done = 'committed'
|
|
112
115
|
try {
|
|
113
|
-
await this.context.emit ('
|
|
116
|
+
await this.context.emit ('commit',res) //> allow custom handlers req.before('commit')
|
|
114
117
|
await super.commit (res)
|
|
118
|
+
await this.context.emit ('succeeded',res)
|
|
115
119
|
await this.context.emit ('done')
|
|
116
120
|
} catch (err) {
|
|
117
121
|
await this.rollback (err)
|
package/lib/serve/index.js
CHANGED
|
@@ -75,7 +75,9 @@ function cds_serve (som, _options) { // NOSONAR
|
|
|
75
75
|
let ready = provided.then (()=> Promise.all (all.map (async srv => {
|
|
76
76
|
srv.init && await srv.prepend (srv.init)
|
|
77
77
|
srv.options.impl && await srv.prepend (srv.options.impl)
|
|
78
|
-
|
|
78
|
+
cds.services[srv.name] = cds.service.paths[srv.path] = srv
|
|
79
|
+
cds.service.providers.push (srv)
|
|
80
|
+
srv[_ready](srv)
|
|
79
81
|
return srv
|
|
80
82
|
})))
|
|
81
83
|
|
|
@@ -83,7 +85,14 @@ function cds_serve (som, _options) { // NOSONAR
|
|
|
83
85
|
// 6) Fluent method to serve constructed providers to express app
|
|
84
86
|
fluent.in = (app) => {
|
|
85
87
|
ready = ready.then (()=>{
|
|
88
|
+
const edms = {}
|
|
89
|
+
if (cds.env.features.precompile_edms) { // > unofficial config for eval
|
|
90
|
+
const all = cds.compile.to.edm(cds.model, { service: 'all' })
|
|
91
|
+
for (let [edm,{ file }] of all) edms[file] = edm
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
for (let each of all) {
|
|
95
|
+
each._edm = edms[each.name]
|
|
87
96
|
ProtocolAdapter.serve(each).in(app)
|
|
88
97
|
if (!o.silent) cds.emit ('serving',each)
|
|
89
98
|
}
|
|
@@ -116,8 +125,7 @@ function _new (Service, d,m,o) {
|
|
|
116
125
|
if (required.name) srv.name = required.name
|
|
117
126
|
if (o.mocked) srv.mocked = true
|
|
118
127
|
}
|
|
119
|
-
if (!srv.path) srv.path = path4(srv,o.at)
|
|
120
|
-
cds.service.providers.push (srv)
|
|
128
|
+
if (!srv.path) srv.path = cds.service.path4(srv,o.at)
|
|
121
129
|
_pending[srv.name] = new Promise (r => srv[_ready]=r).finally(()=>{
|
|
122
130
|
delete _pending[srv.name]
|
|
123
131
|
delete srv[_ready]
|
|
@@ -126,25 +134,9 @@ function _new (Service, d,m,o) {
|
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
|
|
129
|
-
/**
|
|
130
|
-
* Resolve a service endpoint path to mount it to as follows...
|
|
131
|
-
* Use _path or def[@path] if given with leading '/' prepended if necessary.
|
|
132
|
-
* Otherwise, use the service definition name with stripped 'Service'
|
|
133
|
-
*/
|
|
134
|
-
function path4 (srv, _path = (srv.definition || srv)['@path']) {
|
|
135
|
-
if (_path) return _path.replace(/^[^/]/, c => '/'+c)
|
|
136
|
-
else return '/' + ( // generate one from the service's name
|
|
137
|
-
/[^.]+$/.exec(srv.name)[0] //> my.very.CatalogService --> CatalogService
|
|
138
|
-
.replace(/Service$/,'') //> CatalogService --> Catalog
|
|
139
|
-
.replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C.toLowerCase()) //> ODataFooBarX9 --> odata-foo-bar-x9
|
|
140
|
-
.replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
|
|
141
|
-
.toLowerCase() //> FOO --> foo
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
137
|
const is_csn = x => x && x.definitions
|
|
147
138
|
const is_file = x => typeof x === 'string' && !/^[\w$]*$/.test(x)
|
|
148
139
|
const is_class = x => typeof x === 'function' && x.prototype && /^class\b/.test(x)
|
|
149
140
|
|
|
150
|
-
|
|
141
|
+
Object.defineProperty (cds_serve, 'path4', { get(){ return cds.service.path4 } })
|
|
142
|
+
module.exports = cds_serve
|
package/lib/utils/tests.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { is_mocha } = support_jest_and_mocha()
|
|
2
2
|
|
|
3
3
|
class Test extends require('./axios') {
|
|
4
4
|
|
|
5
|
-
static run(..._) { return (new Test).run(..._) }
|
|
6
|
-
get cds() { return require('../index') }
|
|
7
|
-
get Test() { return Test }
|
|
8
|
-
|
|
9
5
|
/**
|
|
10
6
|
* Launches a cds server with arbitrary port and returns a subclass which
|
|
11
7
|
* also acts as an axios lookalike, providing methods to send requests.
|
|
12
8
|
*/
|
|
13
9
|
run (cmd='.', ...args) {
|
|
14
|
-
|
|
10
|
+
|
|
11
|
+
this.cmd = cmd, this.args = args
|
|
15
12
|
|
|
16
13
|
// launch cds server...
|
|
17
|
-
|
|
14
|
+
before (`launching ${cmd} ${args.join(' ')}...`, () => { // NOSONAR
|
|
18
15
|
|
|
19
16
|
const {cds} = this
|
|
20
17
|
if (!/^(serve|run)$/.test(cmd)) try {
|
|
21
|
-
const project = cds.utils.isdir (cmd) ||
|
|
22
|
-
cmd='serve'; args.push ('--
|
|
18
|
+
const project = cds.utils.isdir (cmd) || require.resolve (cmd+'/package.json').slice(0,-13)
|
|
19
|
+
cmd='serve'; args.push ('--in-memory?', '--project', project)
|
|
23
20
|
} catch(e) {
|
|
24
21
|
throw cds.error (`No such folder or package '${process.cwd()}' -> '${cmd}'`)
|
|
25
22
|
}
|
|
@@ -32,16 +29,16 @@ class Test extends require('./axios') {
|
|
|
32
29
|
})
|
|
33
30
|
|
|
34
31
|
try { return cds.exec (cmd, ...args, '--port','0') }
|
|
35
|
-
catch (e) { if (is_mocha) console.error(e) }
|
|
32
|
+
catch (e) { if (is_mocha) console.error(e) } // eslint-disable-line no-console
|
|
36
33
|
})
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// shutdown cds server...
|
|
36
|
+
after (done => {
|
|
37
|
+
this.server ? this.server.close (done) : done && done()
|
|
40
38
|
})
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.server ? this.server.close (done) : done()
|
|
40
|
+
beforeEach (async () => {
|
|
41
|
+
if (this.data._autoReset) await this.data.reset()
|
|
45
42
|
})
|
|
46
43
|
|
|
47
44
|
return this
|
|
@@ -50,18 +47,20 @@ class Test extends require('./axios') {
|
|
|
50
47
|
/**
|
|
51
48
|
* Serving projects from subfolders under the root specified by a sequence
|
|
52
49
|
* of path components which are concatenated with path.resolve().
|
|
50
|
+
* Checks conflicts with cds.env loaded in other folder before.
|
|
53
51
|
*/
|
|
54
52
|
in (...paths) {
|
|
55
|
-
const {cds} = this; cds.root = resolve (cds.root, ...paths)
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
const {cds} = this; cds.root = require('path').resolve (cds.root, ...paths)
|
|
54
|
+
// const env = Reflect.getOwnPropertyDescriptor(global.cds,'env')
|
|
55
|
+
// if (env && env.value && env.value._home !== cds.root) {
|
|
56
|
+
// throw new Error (`[cds.test] - 'cds.env' was invoked before 'cds.test.in' from a different home:
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
// cds.env._home: ${cds.env._home}
|
|
59
|
+
// cds.test.in: ${cds.root}
|
|
62
60
|
|
|
63
|
-
> throwing this as tests would likely behave erratically.
|
|
64
|
-
`)
|
|
61
|
+
// > throwing this as tests would likely behave erratically.
|
|
62
|
+
// `)
|
|
63
|
+
// }
|
|
65
64
|
return this
|
|
66
65
|
}
|
|
67
66
|
|
|
@@ -74,64 +73,89 @@ class Test extends require('./axios') {
|
|
|
74
73
|
return this
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
get
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
`)}}
|
|
86
|
-
const chai = require('chai')
|
|
87
|
-
chai.use (require('chai-subset'))
|
|
88
|
-
chai.use (require('chai-as-promised'))
|
|
89
|
-
return super.chai = chai
|
|
90
|
-
}
|
|
91
|
-
get expect(){ return this.chai.expect }
|
|
92
|
-
get assert(){ return this.chai.assert }
|
|
93
|
-
get sleep(){ return require('util').promisify(setTimeout) }
|
|
94
|
-
get data() { return this._data || (this._data = new (require('./data')))}
|
|
76
|
+
/** Lazily loads and returns an instance of chai */
|
|
77
|
+
get chai() { return super.chai = load_chai() }
|
|
78
|
+
get expect() { global.describe.each || support_jest_and_mocha(); return this.chai.expect }
|
|
79
|
+
get assert() { global.describe.each || support_jest_and_mocha(); return this.chai.assert }
|
|
80
|
+
get sleep() { return super.sleep = require('util').promisify(setTimeout) }
|
|
81
|
+
get data() { return super.data = new (require('./data'))}
|
|
82
|
+
get cds() { return require('../index') }
|
|
83
|
+
get spy() { return spy }
|
|
95
84
|
|
|
96
85
|
}
|
|
97
86
|
|
|
98
|
-
|
|
99
|
-
const is_jest = !!global.beforeAll
|
|
100
|
-
const is_mocha = !!global.before
|
|
101
|
-
if (is_mocha) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
87
|
+
function support_jest_and_mocha() {
|
|
88
|
+
const is_jest = !!global.beforeAll
|
|
89
|
+
const is_mocha = !!global.before
|
|
90
|
+
if (is_mocha) {
|
|
91
|
+
global.beforeAll = global.before
|
|
92
|
+
global.afterAll = global.after
|
|
93
|
+
global.test = global.it
|
|
94
|
+
const { format } = require('util')
|
|
95
|
+
for (let td of [ 'test', 'describe' ]) global[td].each = function(table) {
|
|
96
|
+
return (title,fn) => Promise.all (table.map (each => {
|
|
97
|
+
if (!Array.isArray(each)) each = [each]
|
|
98
|
+
return this (format(title,...each), ()=> fn(...each))
|
|
99
|
+
}))
|
|
100
|
+
}
|
|
101
|
+
after(()=>{
|
|
102
|
+
delete global.cds
|
|
103
|
+
for (let k in require.cache) delete require.cache[k]
|
|
104
|
+
})
|
|
105
|
+
} else if (is_jest) { // it's jest
|
|
106
|
+
global.before = (msg,fn) => global.beforeAll(fn||msg)
|
|
107
|
+
global.after = (msg,fn) => global.afterAll(fn||msg)
|
|
108
|
+
} else { // it's none of both
|
|
109
|
+
global.before = global.beforeAll = (_,fn) => fn()
|
|
110
|
+
global.beforeEach = ()=>{}
|
|
111
|
+
global.afterEach = ()=>{}
|
|
112
|
+
global.after = global.afterAll = (fn) => {
|
|
113
|
+
const repl = global.cds.repl
|
|
114
|
+
repl && repl.on('exit',fn)
|
|
115
|
+
}
|
|
116
|
+
process.env.CDS_TEST_VERBOSE = true
|
|
117
|
+
}
|
|
118
|
+
initLogging()
|
|
119
|
+
return { is_jest, is_mocha }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function load_chai() {
|
|
123
|
+
const require = (mod) => { try { return module.require(mod) } catch(e) {
|
|
124
|
+
if (e.code === 'MODULE_NOT_FOUND') throw new Error (`
|
|
125
|
+
Failed to load required package '${mod}'. Please add it thru:
|
|
126
|
+
npm add -D chai chai-as-promised chai-subset
|
|
127
|
+
`)}}
|
|
128
|
+
const chai = require('chai')
|
|
129
|
+
chai.use (require('chai-subset'))
|
|
130
|
+
chai.use (require('chai-as-promised'))
|
|
131
|
+
return chai
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
function initLogging() {
|
|
121
135
|
const levels = process.env.CDS_TEST_VERBOSE
|
|
122
136
|
? { deploy:'info', serve:'info', server:'info',cds:'info' }
|
|
123
|
-
: { deploy:'warn', serve:'warn', server:'warn',cds:'silent'/*silences provoked request errors */ }
|
|
137
|
+
: { deploy:'warn', serve:'warn', server:'warn',cds:'silent' /* silences provoked request errors */ }
|
|
124
138
|
|
|
125
139
|
const env = Reflect.getOwnPropertyDescriptor(global.cds,'env')
|
|
126
140
|
for (const id of Object.keys(levels)) {
|
|
127
141
|
if (env && env.value)
|
|
128
142
|
global.cds.log(id, { level:levels[id] })
|
|
129
143
|
else // uninitialized cds.env -> set env variables to avoid initializing cds.env eagerly
|
|
130
|
-
process.env['
|
|
144
|
+
process.env['cds_log_levels_'+id] = levels[id]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const spy = (o,f) => {
|
|
149
|
+
const origin = o[f]
|
|
150
|
+
const fn = function (...args) {
|
|
151
|
+
++fn.called
|
|
152
|
+
return origin.apply(this,args)
|
|
131
153
|
}
|
|
154
|
+
fn.called = 0
|
|
155
|
+
fn.restore = ()=> o[f] = origin
|
|
156
|
+
return o[f] = fn
|
|
132
157
|
}
|
|
133
158
|
|
|
134
|
-
/** @type Test.run & Test */
|
|
135
|
-
module.exports = Object.setPrototypeOf (Test.run, Test.prototype)
|
|
136
159
|
|
|
137
|
-
|
|
160
|
+
/** @type Test & ()=>Test */
|
|
161
|
+
module.exports = Object.setPrototypeOf ((..._) => (new Test).run(..._), Test.prototype)
|
|
@@ -17,14 +17,6 @@ const {
|
|
|
17
17
|
let als
|
|
18
18
|
|
|
19
19
|
const attachDiffToContextHandler = async function (req) {
|
|
20
|
-
// REVISIT: what does this do?
|
|
21
|
-
Object.defineProperty(req.query, '_selectAll', {
|
|
22
|
-
enumerable: false,
|
|
23
|
-
writable: false,
|
|
24
|
-
configurable: true,
|
|
25
|
-
value: true
|
|
26
|
-
})
|
|
27
|
-
|
|
28
20
|
// store diff in audit data structure at context
|
|
29
21
|
if (!req.context._audit.diffs) req.context._audit.diffs = new Map()
|
|
30
22
|
req.context._audit.diffs.set(req._.query, await req.diff())
|
|
@@ -2,7 +2,7 @@ const cds = require('../cds')
|
|
|
2
2
|
const LOG = cds.log('app')
|
|
3
3
|
|
|
4
4
|
const _require = require('../common/utils/require')
|
|
5
|
-
const { UNAUTHORIZED } = require('
|
|
5
|
+
const { UNAUTHORIZED } = require('./utils')
|
|
6
6
|
|
|
7
7
|
let passport
|
|
8
8
|
|
|
@@ -17,6 +17,10 @@ const _initializers = {
|
|
|
17
17
|
const DummyStrategy = require('./strategies/dummy')
|
|
18
18
|
passport.use(new DummyStrategy())
|
|
19
19
|
},
|
|
20
|
+
dwc: () => {
|
|
21
|
+
const DwcStrategy = require('./strategies/dwc')
|
|
22
|
+
passport.use(new DwcStrategy())
|
|
23
|
+
},
|
|
20
24
|
JWT: ({ uaa }) => {
|
|
21
25
|
const JWTStrategy = require('./strategies/JWT')
|
|
22
26
|
passport.use(new JWTStrategy(uaa))
|
|
@@ -89,14 +93,11 @@ const _callback = (req, res, next, err, user, info) => {
|
|
|
89
93
|
module.exports = (srv, app, options) => {
|
|
90
94
|
let config = options.auth
|
|
91
95
|
|
|
92
|
-
const isRestricted = _isRestricted(srv)
|
|
96
|
+
const isRestricted = _isRestricted(srv) || (cds.env.requires.auth && cds.env.requires.auth.restrict_all_services)
|
|
93
97
|
const isMultiTenant = !!(cds.env.requires && cds.env.requires.db && cds.env.requires.db.multiTenant)
|
|
94
98
|
|
|
95
99
|
if (!config && !isRestricted && (!isMultiTenant || process.env.NODE_ENV !== 'production')) {
|
|
96
|
-
if (isMultiTenant) {
|
|
97
|
-
LOG._warn && LOG.warn(`[${srv.name}] - Authentication needed for multitenancy in production.`)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
+
if (isMultiTenant) LOG._warn && LOG.warn(`[${srv.name}] - Authentication needed for multitenancy in production.`)
|
|
100
101
|
return
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const LOG = cds.log()
|
|
3
|
+
|
|
4
|
+
function decode(req, header, parsed) {
|
|
5
|
+
if (!req.headers[header]) return
|
|
6
|
+
return parsed
|
|
7
|
+
? JSON.parse(Buffer.from(req.headers[header], 'base64').toString('utf-8'))
|
|
8
|
+
: Buffer.from(req.headers[header], 'base64').toString('utf-8')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class DwcStrategy {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.name = 'dwc'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
authenticate(req) {
|
|
17
|
+
let tenant, usr, scopes, attr
|
|
18
|
+
try {
|
|
19
|
+
tenant = decode(req, 'dwc-tenant')
|
|
20
|
+
usr = decode(req, 'dwc-user', true)
|
|
21
|
+
scopes = decode(req, 'dwc-scopes', true) || []
|
|
22
|
+
attr = decode(req, 'dwc-xsuaa-attributes', true) || {}
|
|
23
|
+
} catch (e) {
|
|
24
|
+
LOG._warn && LOG.warn('Error while parsing headers for dwc-auth:', e)
|
|
25
|
+
return this.fail()
|
|
26
|
+
}
|
|
27
|
+
if (!tenant || !usr) return this.fail()
|
|
28
|
+
|
|
29
|
+
const user = new cds.User({
|
|
30
|
+
id: usr.logonName,
|
|
31
|
+
tenant,
|
|
32
|
+
_roles: ['any', 'authenticated-user', ...scopes],
|
|
33
|
+
attr: Object.assign(attr, usr)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// set _req for locale getter
|
|
37
|
+
Object.defineProperty(user, '_req', { enumerable: false, value: req })
|
|
38
|
+
|
|
39
|
+
this.success(user)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = DwcStrategy
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const cds = require('../cds')
|
|
2
|
+
|
|
3
|
+
const UNAUTHORIZED = { statusCode: 401, code: '401', message: 'Unauthorized' }
|
|
4
|
+
const FORBIDDEN = { statusCode: 403, code: '403', message: 'Forbidden' }
|
|
5
|
+
|
|
6
|
+
const getRequiresAsArray = definition => {
|
|
7
|
+
const requires = []
|
|
8
|
+
|
|
9
|
+
if (definition['@requires']) {
|
|
10
|
+
if (Array.isArray(definition['@requires'])) requires.push(...definition['@requires'])
|
|
11
|
+
else requires.push(definition['@requires'])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { restrict_all_services: restrictAllServices } = cds.env.requires.auth || {}
|
|
15
|
+
if (!requires.length && definition.kind === 'service' && restrictAllServices) requires.push('authenticated-user')
|
|
16
|
+
|
|
17
|
+
return requires
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
UNAUTHORIZED,
|
|
22
|
+
FORBIDDEN,
|
|
23
|
+
getRequiresAsArray
|
|
24
|
+
}
|
|
@@ -45,9 +45,7 @@ function _getTarget(service, segments) {
|
|
|
45
45
|
: last.getEdmType().csdlStructuredType.name
|
|
46
46
|
|
|
47
47
|
// autoexposed entities now used . in csn and _ in edm
|
|
48
|
-
const target =
|
|
49
|
-
findCsnTargetFor(name, service.model, namespace) ||
|
|
50
|
-
(name.endsWith('Parameters') && service.model.definitions[namespace + '.' + name.replace(/Parameters$/, '')])
|
|
48
|
+
const target = findCsnTargetFor(name, service.model, namespace)
|
|
51
49
|
|
|
52
50
|
if (target && target.kind === 'entity') {
|
|
53
51
|
return target
|
|
@@ -14,31 +14,6 @@ const { setStatusCodeAndHeader, getKeyProperty } = require('../../../../fiori/ut
|
|
|
14
14
|
const { toODataResult, postProcess } = require('../utils/result')
|
|
15
15
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
16
16
|
|
|
17
|
-
/*
|
|
18
|
-
* Get the returns object for the (un)bound action from CSN.
|
|
19
|
-
*/
|
|
20
|
-
const _getTypeReturns = (definitions, req, service) => {
|
|
21
|
-
if (req.event === 'draftPrepare' || req.event === 'EDIT' || req.event === 'draftActivate') {
|
|
22
|
-
return 'Other'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (req.target && req._.odataReq.getUriInfo().getLastSegment().getKind() === 'BOUND.ACTION') {
|
|
26
|
-
return definitions[req.target.name].actions[req.event].returns
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Also support correct req.event without service prefix
|
|
30
|
-
return (definitions[req.event] || definitions[`${service.name}.${req.event}`]).returns
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/*
|
|
34
|
-
* Check if the return is an array or any other.
|
|
35
|
-
*/
|
|
36
|
-
const _getActionReturnType = (service, req) => {
|
|
37
|
-
const returns = _getTypeReturns(service.model.definitions, req, service)
|
|
38
|
-
|
|
39
|
-
return returns && returns.items ? 'Array' : 'Other'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
17
|
const _postProcessDraftActivate = async (req, result, service) => {
|
|
43
18
|
// update req.data (keys needed in readAfterWrite)
|
|
44
19
|
req.data = result
|
|
@@ -59,8 +34,9 @@ const _postProcess = async (req, odataReq, odataRes, tx, result) => {
|
|
|
59
34
|
// REVISIT: harmonize getactionreturntype functions
|
|
60
35
|
const actionReturnType = getActionOrFunctionReturnType(odataReq.getUriInfo().getPathSegments(), tx.model.definitions)
|
|
61
36
|
if (actionReturnType && actionReturnType.kind === 'entity' && odataReq.getQueryOptions()) {
|
|
62
|
-
await actionAndFunctionQueries(req, odataReq, result, tx, actionReturnType)
|
|
37
|
+
result = await actionAndFunctionQueries(req, odataReq, result, tx, actionReturnType)
|
|
63
38
|
}
|
|
39
|
+
return result
|
|
64
40
|
}
|
|
65
41
|
|
|
66
42
|
/**
|
|
@@ -72,6 +48,7 @@ const _postProcess = async (req, odataReq, odataRes, tx, result) => {
|
|
|
72
48
|
const action = service => {
|
|
73
49
|
return async (odataReq, odataRes, next) => {
|
|
74
50
|
let req
|
|
51
|
+
|
|
75
52
|
try {
|
|
76
53
|
validateResourcePath(odataReq, service)
|
|
77
54
|
req = new ODataRequest(ACTION_EXECUTE_HANDLER, service, odataReq, odataRes)
|
|
@@ -84,6 +61,7 @@ const action = service => {
|
|
|
84
61
|
cds.context = tx
|
|
85
62
|
|
|
86
63
|
let result, err
|
|
64
|
+
|
|
87
65
|
try {
|
|
88
66
|
result = await tx.dispatch(req)
|
|
89
67
|
|
|
@@ -95,7 +73,7 @@ const action = service => {
|
|
|
95
73
|
setStatusCodeAndHeader(odataRes, { [k]: result[k] }, req.target.name.replace(`${service.name}.`, ''), true)
|
|
96
74
|
}
|
|
97
75
|
|
|
98
|
-
await _postProcess(req, odataReq, odataRes, tx, result)
|
|
76
|
+
result = await _postProcess(req, odataReq, odataRes, tx, result)
|
|
99
77
|
|
|
100
78
|
if (changeset) {
|
|
101
79
|
// for passing into commit
|
|
@@ -105,18 +83,19 @@ const action = service => {
|
|
|
105
83
|
}
|
|
106
84
|
} catch (e) {
|
|
107
85
|
err = e
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
await tx.rollback(e).catch(() => {})
|
|
111
|
-
} else if (changeset) {
|
|
86
|
+
|
|
87
|
+
if (changeset) {
|
|
112
88
|
// for passing into rollback
|
|
113
89
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
90
|
+
} else {
|
|
91
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
92
|
+
await tx.rollback(e).catch(() => {})
|
|
114
93
|
}
|
|
115
94
|
} finally {
|
|
116
95
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
117
96
|
|
|
118
97
|
if (err) next(err)
|
|
119
|
-
else next(null, toODataResult(result,
|
|
98
|
+
else next(null, toODataResult(result, req))
|
|
120
99
|
}
|
|
121
100
|
}
|
|
122
101
|
}
|