@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.
Files changed (151) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/app/fiori/routes.js +1 -1
  3. package/bin/deploy/to-hana/cfUtil.js +251 -138
  4. package/bin/deploy/to-hana/gitUtil.js +55 -0
  5. package/bin/deploy/to-hana/hana.js +92 -93
  6. package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
  7. package/bin/deploy/to-hana/index.js +14 -13
  8. package/bin/mtx/in-cds.js +1 -0
  9. package/bin/serve.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/lib/compile/cdsc.js +0 -6
  12. package/lib/compile/resolve.js +1 -1
  13. package/lib/compile/to/srvinfo.js +1 -1
  14. package/lib/core/classes.js +21 -1
  15. package/lib/env/index.js +3 -2
  16. package/lib/env/requires.js +4 -0
  17. package/lib/i18n/localize.js +5 -8
  18. package/lib/index.js +1 -0
  19. package/lib/log/errors.js +1 -1
  20. package/lib/log/format/kibana.js +3 -3
  21. package/lib/ql/SELECT.js +2 -2
  22. package/lib/req/cds-context.js +1 -1
  23. package/lib/req/context.js +1 -1
  24. package/lib/serve/Transaction.js +9 -5
  25. package/lib/serve/index.js +13 -21
  26. package/lib/utils/tests.js +90 -66
  27. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  28. package/libx/_runtime/auth/index.js +7 -6
  29. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  30. package/libx/_runtime/auth/utils.js +24 -0
  31. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -3
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  38. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  43. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  44. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +23 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  56. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  58. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
  64. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  66. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  67. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  68. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  69. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  70. package/libx/_runtime/common/aspects/Association.js +16 -0
  71. package/libx/_runtime/common/composition/data.js +28 -37
  72. package/libx/_runtime/common/composition/delete.js +107 -58
  73. package/libx/_runtime/common/composition/index.js +3 -3
  74. package/libx/_runtime/common/composition/insert.js +14 -27
  75. package/libx/_runtime/common/composition/tree.js +1 -1
  76. package/libx/_runtime/common/composition/update.js +39 -34
  77. package/libx/_runtime/common/error/frontend.js +19 -5
  78. package/libx/_runtime/common/generic/auth.js +20 -85
  79. package/libx/_runtime/common/generic/crud.js +22 -1
  80. package/libx/_runtime/common/i18n/messages.properties +2 -1
  81. package/libx/_runtime/common/utils/cqn.js +2 -6
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  83. package/libx/_runtime/common/utils/csn.js +29 -6
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +21 -1
  85. package/libx/_runtime/common/utils/keys.js +2 -1
  86. package/libx/_runtime/common/utils/path.js +1 -1
  87. package/libx/_runtime/common/utils/resolveView.js +12 -4
  88. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  89. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  90. package/libx/_runtime/common/utils/structured.js +10 -4
  91. package/libx/_runtime/common/utils/vcap.js +27 -10
  92. package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
  93. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  94. package/libx/_runtime/db/expand/expandCQNToJoin.js +67 -26
  95. package/libx/_runtime/db/expand/index.js +3 -0
  96. package/libx/_runtime/db/generic/create.js +0 -10
  97. package/libx/_runtime/db/generic/index.js +3 -0
  98. package/libx/_runtime/db/generic/read.js +2 -24
  99. package/libx/_runtime/db/generic/rewrite.js +1 -3
  100. package/libx/_runtime/db/generic/update.js +1 -1
  101. package/libx/_runtime/db/query/delete.js +10 -4
  102. package/libx/_runtime/db/query/insert.js +3 -4
  103. package/libx/_runtime/db/query/read.js +4 -1
  104. package/libx/_runtime/db/query/update.js +5 -5
  105. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  106. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  107. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  108. package/libx/_runtime/db/sql-builder/index.js +3 -0
  109. package/libx/_runtime/db/utils/columns.js +5 -2
  110. package/libx/_runtime/db/utils/deep.js +16 -14
  111. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  112. package/libx/_runtime/fiori/generic/before.js +73 -49
  113. package/libx/_runtime/fiori/generic/edit.js +14 -18
  114. package/libx/_runtime/fiori/generic/patch.js +8 -11
  115. package/libx/_runtime/fiori/generic/read.js +20 -19
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  117. package/libx/_runtime/fiori/utils/handler.js +1 -11
  118. package/libx/_runtime/hana/Service.js +1 -1
  119. package/libx/_runtime/hana/conversion.js +12 -1
  120. package/libx/_runtime/hana/dynatrace.js +11 -5
  121. package/libx/_runtime/hana/execute.js +132 -19
  122. package/libx/_runtime/hana/search.js +3 -3
  123. package/libx/_runtime/hana/search2cqn4sql.js +23 -25
  124. package/libx/_runtime/hana/searchToContains.js +1 -1
  125. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  126. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  127. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  128. package/libx/_runtime/messaging/file-based.js +3 -1
  129. package/libx/_runtime/messaging/service.js +4 -1
  130. package/libx/_runtime/remote/utils/client.js +41 -24
  131. package/libx/_runtime/remote/utils/data.js +54 -12
  132. package/libx/_runtime/sqlite/Service.js +1 -1
  133. package/libx/_runtime/sqlite/conversion.js +10 -0
  134. package/libx/_runtime/types/api.js +2 -2
  135. package/libx/gql/resolvers/crud/update.js +8 -5
  136. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  137. package/libx/odata/afterburner.js +29 -6
  138. package/libx/odata/cqn2odata.js +9 -0
  139. package/libx/odata/grammar.pegjs +49 -21
  140. package/libx/odata/index.js +2 -2
  141. package/libx/odata/parser.js +1 -1
  142. package/libx/odata/utils.js +2 -2
  143. package/libx/rest/RestAdapter.js +29 -1
  144. package/libx/rest/middleware/auth.js +1 -3
  145. package/libx/rest/middleware/parse.js +1 -0
  146. package/package.json +1 -1
  147. package/server.js +1 -1
  148. package/bin/deploy/to-hana/logger.js +0 -27
  149. package/bin/deploy/to-hana/runCommand.js +0 -113
  150. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  151. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -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 = /"([^"{]+)?{b?i18n>([^"}]+)}([^"]+)?"/g
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 isXml = aString.startsWith('<?xml')
43
- const isJson = /^[{[]/.test(aString)
44
- return aString.replace (TEXT_KEYS, (_, left='', key, right='') => {
45
- let val = bundle[key] || key
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
  }
@@ -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 , kibana_custom_fields } = cds.env.log
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 instanceof Error) toLog.stacktrace = err.stack.split(/\s*\r?\n\s*/)
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 return SELECT_('from X {',args,'}').columns
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)
@@ -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)
@@ -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) super.context = 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 }
@@ -1,4 +1,5 @@
1
- const cds = require('../index'), { EventContext } = cds, { cds_tx_protection } = cds.env.features
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 or committed (committed occurs if error thrown in on succeeded handler)
78
- if (this.ready === 'rolled back' || this.ready === 'committed') return
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 (this.__proto__.rollback) await this.__proto__.rollback.call (this,err)
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 ('succeeded',res)
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)
@@ -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
- srv[_ready](cds.services[srv.name] = srv)
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
- module.exports = Object.assign (cds_serve, { path4 })
141
+ Object.defineProperty (cds_serve, 'path4', { get(){ return cds.service.path4 } })
142
+ module.exports = cds_serve
@@ -1,25 +1,22 @@
1
- const { resolve, dirname } = require('path')
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
- initLogging()
10
+
11
+ this.cmd = cmd, this.args = args
15
12
 
16
13
  // launch cds server...
17
- global.before (`launching ${cmd} ${args.join(' ')}...`, () => { // NOSONAR
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) || dirname (require.resolve (cmd+'/package.json'))
22
- cmd='serve'; args.push ('--project', project, '--in-memory?')
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
- global.beforeEach (async () => {
39
- if (this.data._autoReset) await this.data.reset()
35
+ // shutdown cds server...
36
+ after (done => {
37
+ this.server ? this.server.close (done) : done && done()
40
38
  })
41
39
 
42
- // shutdown cds server...
43
- global.after (done => {
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
- // Checking conflicts with global.cds loaded in other folder before
57
- const ep = Reflect.getOwnPropertyDescriptor(global.cds,'env')
58
- if (ep && ep.value && ep.value._home !== cds.root) throw new Error (`[cds.test] - 'cds.env' was invoked before 'cds.test.in' from a different home:
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
- cds.env._home: ${cds.env._home}
61
- cds.test.in: ${cds.root}
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
- * Lazily loads and returns an instance of chai
79
- */
80
- get chai() {
81
- const require = (mod) => { try { return module.require(mod) } catch(e) {
82
- if (e.code === 'MODULE_NOT_FOUND') throw new Error (`
83
- Failed to load required package '${mod}'. Please add it thru:
84
- npm add -D chai chai-as-promised chai-subset
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
- // harmonizing jest and mocha
99
- const is_jest = !!global.beforeAll
100
- const is_mocha = !!global.before
101
- if (is_mocha) {
102
- const { format } = require('util')
103
- global.beforeAll = global.before
104
- global.afterAll = global.after
105
- global.test = global.it
106
- global.it.each = (table) => (title,fn) => Promise.all (table.map(each => {
107
- if (!Array.isArray(each)) each = [each]
108
- return it (format(title,...each), ()=> fn(...each))
109
- }))
110
- } else if (is_jest) { // it's jest
111
- global.before = (msg,fn) => global.beforeAll(fn||msg)
112
- global.after = (msg,fn) => global.afterAll(fn||msg)
113
- } else { // it's none of both
114
- global.before = global.beforeAll = (_,fn) => fn()
115
- global.beforeEach = ()=>{}
116
- global.afterEach = ()=>{}
117
- global.after = global.afterAll = ()=>{}
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['CDS_LOG_LEVELS_'+id.toUpperCase()] = levels[id]
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
- /* eslint no-console: off */
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('../common/utils/auth')
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
- if (!changeset) {
109
- // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
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, _getActionReturnType(service, req)))
98
+ else next(null, toODataResult(result, req))
120
99
  }
121
100
  }
122
101
  }