@sap/cds 5.4.6 → 5.5.0

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 (228) hide show
  1. package/CHANGELOG.md +208 -2
  2. package/apis/ql.d.ts +17 -15
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskEngine.js +26 -42
  5. package/bin/build/buildTaskFactory.js +6 -10
  6. package/bin/build/buildTaskHandler.js +2 -4
  7. package/bin/build/buildTaskProvider.js +3 -1
  8. package/bin/build/buildTaskProviderFactory.js +9 -15
  9. package/bin/build/constants.js +15 -3
  10. package/bin/build/index.js +5 -4
  11. package/bin/build/mtaUtil.js +8 -11
  12. package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
  13. package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
  14. package/bin/build/provider/buildTaskProviderInternal.js +16 -42
  15. package/bin/build/provider/fiori/index.js +13 -24
  16. package/bin/build/provider/hana/2migration.js +17 -15
  17. package/bin/build/provider/hana/2tabledata.js +52 -48
  18. package/bin/build/provider/hana/index.js +27 -25
  19. package/bin/build/provider/hana/migrationtable.js +91 -67
  20. package/bin/build/provider/java-cf/index.js +14 -24
  21. package/bin/build/provider/mtx/index.js +12 -14
  22. package/bin/build/provider/node-cf/index.js +18 -32
  23. package/bin/cds.js +5 -5
  24. package/bin/serve.js +29 -23
  25. package/bin/version.js +0 -1
  26. package/lib/compile/etc/_localized.js +4 -9
  27. package/lib/compile/for/sql.js +5 -2
  28. package/lib/compile/parse.js +25 -17
  29. package/lib/compile/to/srvinfo.js +2 -1
  30. package/lib/connect/bindings.js +2 -1
  31. package/lib/connect/index.js +48 -49
  32. package/lib/core/classes.js +1 -1
  33. package/lib/core/reflect.js +10 -2
  34. package/lib/deploy.js +26 -23
  35. package/lib/env/defaults.js +13 -6
  36. package/lib/env/index.js +73 -78
  37. package/lib/env/requires.js +38 -19
  38. package/lib/index.js +9 -10
  39. package/lib/lazy.js +2 -2
  40. package/lib/log/index.js +33 -45
  41. package/lib/log/service/index.js +2 -2
  42. package/lib/ql/CREATE.js +14 -9
  43. package/lib/ql/DELETE.js +6 -5
  44. package/lib/ql/DROP.js +12 -9
  45. package/lib/ql/INSERT.js +40 -16
  46. package/lib/ql/Query.js +67 -40
  47. package/lib/ql/SELECT.js +162 -127
  48. package/lib/ql/UPDATE.js +74 -42
  49. package/lib/ql/Whereable.js +77 -87
  50. package/lib/ql/index.js +36 -24
  51. package/lib/ql/parse.js +35 -0
  52. package/lib/req/context.js +44 -8
  53. package/lib/req/locale.js +7 -7
  54. package/lib/serve/Service-api.js +21 -14
  55. package/lib/serve/Service-dispatch.js +28 -12
  56. package/lib/serve/Transaction.js +22 -10
  57. package/lib/serve/index.js +16 -11
  58. package/lib/utils/axios.js +23 -16
  59. package/lib/utils/data.js +35 -0
  60. package/lib/utils/tests.js +27 -18
  61. package/libx/_runtime/audit/generic/personal/access.js +81 -0
  62. package/libx/_runtime/audit/generic/personal/constants.js +4 -0
  63. package/libx/_runtime/audit/generic/personal/index.js +50 -0
  64. package/libx/_runtime/audit/generic/personal/modification.js +138 -0
  65. package/libx/_runtime/audit/generic/personal/utils.js +186 -0
  66. package/libx/_runtime/audit/utils/v2.js +10 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
  68. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
  78. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
  79. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
  80. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
  81. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
  85. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
  86. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
  87. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  89. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
  91. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
  99. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
  101. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
  102. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
  103. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
  104. package/libx/_runtime/cds-services/services/Service.js +40 -5
  105. package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
  106. package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
  107. package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
  108. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
  109. package/libx/_runtime/common/composition/data.js +44 -55
  110. package/libx/_runtime/common/composition/delete.js +97 -71
  111. package/libx/_runtime/common/composition/index.js +2 -1
  112. package/libx/_runtime/common/composition/insert.js +34 -11
  113. package/libx/_runtime/common/composition/tree.js +119 -92
  114. package/libx/_runtime/common/composition/update.js +4 -1
  115. package/libx/_runtime/common/composition/utils.js +1 -3
  116. package/libx/_runtime/common/constants/draft.js +12 -1
  117. package/libx/_runtime/common/generic/auth.js +6 -22
  118. package/libx/_runtime/common/generic/crud.js +14 -13
  119. package/libx/_runtime/common/generic/input.js +23 -26
  120. package/libx/_runtime/common/generic/put.js +1 -1
  121. package/libx/_runtime/common/generic/sorting.js +16 -16
  122. package/libx/_runtime/common/i18n/index.js +1 -1
  123. package/libx/_runtime/common/i18n/messages.properties +4 -0
  124. package/libx/_runtime/common/utils/backlinks.js +12 -5
  125. package/libx/_runtime/common/utils/cqn.js +6 -1
  126. package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
  127. package/libx/_runtime/common/utils/csn.js +47 -4
  128. package/libx/_runtime/common/utils/data.js +0 -37
  129. package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
  130. package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
  131. package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
  132. package/libx/_runtime/common/utils/generateOnCond.js +11 -12
  133. package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
  134. package/libx/_runtime/common/utils/path.js +35 -0
  135. package/libx/_runtime/common/utils/postProcessing.js +86 -0
  136. package/libx/_runtime/common/utils/quotingStyles.js +37 -26
  137. package/libx/_runtime/common/utils/resolveView.js +223 -171
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +6 -12
  140. package/libx/_runtime/common/utils/template.js +10 -5
  141. package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
  142. package/libx/_runtime/common/utils/templateProcessor.js +22 -30
  143. package/libx/_runtime/common/utils/union.js +31 -0
  144. package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
  145. package/libx/_runtime/db/Service.js +1 -1
  146. package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
  147. package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
  148. package/libx/_runtime/db/expand/index.js +3 -3
  149. package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
  150. package/libx/_runtime/db/generic/index.js +1 -1
  151. package/libx/_runtime/db/generic/input.js +5 -7
  152. package/libx/_runtime/db/generic/integrity.js +1 -1
  153. package/libx/_runtime/db/generic/rewrite.js +2 -10
  154. package/libx/_runtime/db/generic/update.js +13 -5
  155. package/libx/_runtime/db/generic/virtual.js +22 -58
  156. package/libx/_runtime/db/query/delete.js +7 -4
  157. package/libx/_runtime/db/query/insert.js +6 -4
  158. package/libx/_runtime/db/query/read.js +13 -20
  159. package/libx/_runtime/db/query/run.js +4 -1
  160. package/libx/_runtime/db/query/update.js +5 -4
  161. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
  162. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
  163. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
  164. package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
  165. package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
  166. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
  167. package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
  168. package/libx/_runtime/db/utils/deep.js +8 -0
  169. package/libx/_runtime/db/utils/generateAliases.js +2 -1
  170. package/libx/_runtime/fiori/generic/activate.js +19 -15
  171. package/libx/_runtime/fiori/generic/before.js +3 -11
  172. package/libx/_runtime/fiori/generic/cancel.js +1 -1
  173. package/libx/_runtime/fiori/generic/delete.js +3 -1
  174. package/libx/_runtime/fiori/generic/edit.js +12 -2
  175. package/libx/_runtime/fiori/generic/new.js +5 -5
  176. package/libx/_runtime/fiori/generic/patch.js +0 -18
  177. package/libx/_runtime/fiori/generic/read.js +241 -189
  178. package/libx/_runtime/fiori/utils/delete.js +36 -7
  179. package/libx/_runtime/fiori/utils/handler.js +43 -44
  180. package/libx/_runtime/fiori/utils/where.js +30 -15
  181. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
  182. package/libx/_runtime/hana/execute.js +2 -2
  183. package/libx/_runtime/hana/localized.js +4 -4
  184. package/libx/_runtime/hana/pool.js +29 -14
  185. package/libx/_runtime/hana/search2cqn4sql.js +2 -1
  186. package/libx/_runtime/hana/searchToContains.js +18 -14
  187. package/libx/_runtime/index.js +0 -5
  188. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
  189. package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
  190. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
  191. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  192. package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
  193. package/libx/_runtime/messaging/service.js +7 -6
  194. package/libx/_runtime/odata/cqn2odata.js +110 -43
  195. package/libx/_runtime/odata/index.js +26 -48
  196. package/libx/_runtime/odata/odata2cqn.js +1 -6154
  197. package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
  198. package/libx/_runtime/odata/readToCqn.js +94 -64
  199. package/libx/_runtime/remote/Service.js +74 -21
  200. package/libx/_runtime/remote/cqn2odata/index.js +1 -5
  201. package/libx/_runtime/remote/utils/client.js +24 -101
  202. package/libx/_runtime/remote/utils/dataConversion.js +27 -12
  203. package/libx/_runtime/sqlite/Service.js +3 -5
  204. package/libx/_runtime/sqlite/execute.js +23 -24
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +1 -1
  208. package/server.js +16 -2
  209. package/lib/ql/grammar.pegjs +0 -208
  210. package/lib/ql/parser.js +0 -1
  211. package/lib/ql/rt/DELETE.js +0 -29
  212. package/lib/ql/rt/INSERT.js +0 -23
  213. package/lib/ql/rt/Query.js +0 -84
  214. package/lib/ql/rt/SELECT.js +0 -174
  215. package/lib/ql/rt/UPDATE.js +0 -119
  216. package/lib/ql/rt/_helpers.js +0 -91
  217. package/lib/ql/rt/index.js +0 -32
  218. package/libx/_runtime/audit/generic/personal.js +0 -260
  219. package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
  220. package/libx/_runtime/cds-services/statements/Create.js +0 -57
  221. package/libx/_runtime/cds-services/statements/Delete.js +0 -33
  222. package/libx/_runtime/cds-services/statements/Drop.js +0 -42
  223. package/libx/_runtime/cds-services/statements/Insert.js +0 -201
  224. package/libx/_runtime/cds-services/statements/Select.js +0 -826
  225. package/libx/_runtime/cds-services/statements/Update.js +0 -181
  226. package/libx/_runtime/cds-services/statements/Where.js +0 -726
  227. package/libx/_runtime/cds-services/statements/index.js +0 -25
  228. package/libx/_runtime/common/generic/resolve-mock.js +0 -9
@@ -42,16 +42,18 @@ function cds_serve (som, _options) { // NOSONAR
42
42
 
43
43
  // Shortcut for directly passed service classes
44
44
  if (o.service && o.service._is_service_class) {
45
- const Service = o.service, d = { name: o.service = o.service.name }
45
+ const Service = o.service, d = { name: o.service.name }
46
46
  return all.push (_new (Service, d,csn,o))
47
47
  }
48
48
 
49
49
  // Get relevant service definitions from model...
50
50
  let {services} = csn = cds.linked (cds.compile.for.odata (csn))
51
- if (o.service && o.service !== 'all') {
51
+ let specified = o.service
52
+ if (specified && specified !== 'all') {
52
53
  // skip services not chosen by o.service, if specified
53
- services = services.filter (s => s.name.endsWith (o.service))
54
- if (!services.length) throw cds.error (`No such service: '${o.service}'`)
54
+ if (cds.requires[specified]) specified = cds.requires[specified].service || specified
55
+ services = services.filter (s => s.name.endsWith (specified))
56
+ if (!services.length) throw cds.error (`No such service: '${specified}'`)
55
57
  }
56
58
  services = services.filter (d => !(
57
59
  // skip all services marked to be ignored
@@ -108,13 +110,16 @@ function cds_serve (som, _options) { // NOSONAR
108
110
 
109
111
 
110
112
  function _new (Service, d,m,o) {
111
- if (o.service === 'all') o = {...o, service:d.name}
112
113
  const srv = new Service (d.name,m,o)
113
- if (!srv.path) srv.path = path4(d,o.at)
114
- if (o.mocked && d.name in cds.requires) srv.mocked = true
114
+ const required = cds.requires[d.name]
115
+ if (required) {
116
+ if (required.name) srv.name = required.name
117
+ if (o.mocked) srv.mocked = true
118
+ }
119
+ if (!srv.path) srv.path = path4(srv,o.at)
115
120
  cds.service.providers.push (srv)
116
- _pending[d.name] = new Promise (r => srv[_ready]=r).finally(()=>{
117
- delete _pending[d.name]
121
+ _pending[srv.name] = new Promise (r => srv[_ready]=r).finally(()=>{
122
+ delete _pending[srv.name]
118
123
  delete srv[_ready]
119
124
  })
120
125
  return srv
@@ -126,10 +131,10 @@ function _new (Service, d,m,o) {
126
131
  * Use _path or def[@path] if given with leading '/' prepended if necessary.
127
132
  * Otherwise, use the service definition name with stripped 'Service'
128
133
  */
129
- function path4 (def, _path = def['@path']) {
134
+ function path4 (srv, _path = (srv.definition || srv)['@path']) {
130
135
  if (_path) return _path.replace(/^[^/]/, c => '/'+c)
131
136
  else return '/' + ( // generate one from the service's name
132
- /[^.]+$/.exec(def.name)[0] //> my.very.CatalogService --> CatalogService
137
+ /[^.]+$/.exec(srv.name)[0] //> my.very.CatalogService --> CatalogService
133
138
  .replace(/Service$/,'') //> CatalogService --> Catalog
134
139
  .replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C.toLowerCase()) //> ODataFooBarX9 --> odata-foo-bar-x9
135
140
  .replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
@@ -6,26 +6,33 @@ class Axios {
6
6
  baseURL: this.url,
7
7
  })
8
8
  }
9
- get (path,...etc) { return this.axios.get (_url(path),...etc) .catch(_error) }
10
- put (path,...etc) { return this.axios.put (_url(path),...etc) .catch(_error) }
11
- post (path,...etc) { return this.axios.post (_url(path),...etc) .catch(_error) }
12
- patch (path,...etc) { return this.axios.patch (_url(path),...etc) .catch(_error) }
13
- delete (path,...etc) { return this.axios.delete (_url(path),...etc) .catch(_error) }
9
+ get (..._) { return this.axios.get (..._args(_)) .catch(_error) }
10
+ put (..._) { return this.axios.put (..._args(_)) .catch(_error) }
11
+ post (..._) { return this.axios.post (..._args(_)) .catch(_error) }
12
+ patch (..._) { return this.axios.patch (..._args(_)) .catch(_error) }
13
+ delete (..._) { return this.axios.delete (..._args(_)) .catch(_error) }
14
+ options (..._) { return this.axios.options (..._args(_)) .catch(_error) }
14
15
 
15
- /** @type typeof _.get */ get GET() { return this.get .bind (this) }
16
- /** @type typeof _.put */ get PUT() { return this.put .bind (this) }
17
- /** @type typeof _.post */ get POST() { return this.post .bind (this) }
18
- /** @type typeof _.patch */ get PATCH() { return this.patch .bind (this) }
19
- /** @type typeof _.delete */ get DELETE() { return this.delele .bind (this) }
20
- /** @type typeof _.delete */ get DEL() { return this.delete .bind (this) } //> to avoid conflicts with cds.ql.DELETE
16
+ /** @type typeof _.get */ get GET() { return this.get .bind (this) }
17
+ /** @type typeof _.put */ get PUT() { return this.put .bind (this) }
18
+ /** @type typeof _.post */ get POST() { return this.post .bind (this) }
19
+ /** @type typeof _.patch */ get PATCH() { return this.patch .bind (this) }
20
+ /** @type typeof _.delete */ get DELETE() { return this.delele .bind (this) }
21
+ /** @type typeof _.delete */ get DEL() { return this.delete .bind (this) } //> to avoid conflicts with cds.ql.DELETE
22
+ /** @type typeof _.options */ get OPTIONS() { return this.options .bind (this) }
21
23
 
22
24
  }
23
25
 
24
- const _url = (path) => {
25
- if (path.raw) path = String.raw(path)
26
- if (typeof path !== 'string') throw new Error (`Argument path is expected to be a string but got ${typeof path}`)
27
- if (!path.startsWith('/')) throw new Error (`Argument path is expected to start with a /`)
28
- return path
26
+ const _args = (args) => {
27
+ const first = args[0], last = args[args.length-1]
28
+ if (first.raw) {
29
+ if (first[first.length-1] === '' && typeof last === 'object')
30
+ return [ String.raw(...args.slice(0,-1)), last ]
31
+ return [ String.raw(...args) ]
32
+ }
33
+ else if (typeof first !== 'string')
34
+ throw new Error (`Argument path is expected to be a string but got ${typeof first}`)
35
+ return args
29
36
  }
30
37
 
31
38
  const _error = (e) => {
@@ -0,0 +1,35 @@
1
+ const cds = require('../index')
2
+
3
+ class DataUtil {
4
+
5
+ async delete(db) {
6
+ if (!db) db = await cds.connect.to('db')
7
+ if (!this._deletes) {
8
+ this._deletes = []
9
+ for (const entity of db.model.minified().all('entity')) {
10
+ if (!entity.query && entity['@cds.persistence.skip'] !== true) {
11
+ this._deletes.push(cds.ql.DELETE.from(entity))
12
+ }
13
+ }
14
+ }
15
+ if (this._deletes.length > 0) {
16
+ const log = cds.log('deploy')
17
+ if (log._info) log.info('Deleting all data in', this._deletes)
18
+ await db.run(this._deletes)
19
+ }
20
+ }
21
+
22
+ /* delete + new deploy from csv */
23
+ async reset(db) {
24
+ if (!db) db = await cds.connect.to('db')
25
+ await this.delete(db)
26
+ await cds.deploy(db.model).to(db, {ddl:false})
27
+ }
28
+
29
+ autoReset(enabled) { this._autoReset = enabled; return this }
30
+
31
+ }
32
+
33
+ module.exports = DataUtil
34
+
35
+ /* eslint no-console: off */
@@ -1,5 +1,4 @@
1
1
  const { resolve, dirname } = require('path')
2
- const console = global.console
3
2
 
4
3
  class Test extends require('./axios') {
5
4
 
@@ -12,12 +11,12 @@ class Test extends require('./axios') {
12
11
  * also acts as an axios lookalike, providing methods to send requests.
13
12
  */
14
13
  run (cmd, ...args) {
14
+ initLogging()
15
15
 
16
16
  // launch cds server...
17
17
  global.before (`launching ${cmd} ${args.join(' ')}...`, () => { // NOSONAR
18
18
 
19
19
  const {cds} = this
20
-
21
20
  if (!/^(serve|run)$/.test(cmd)) try {
22
21
  const project = cds.utils.isdir (cmd) || dirname (require.resolve (cmd+'/package.json'))
23
22
  cmd='serve'; args.push ('--project', project, '--in-memory?')
@@ -36,6 +35,10 @@ class Test extends require('./axios') {
36
35
  catch (e) { if (is_mocha) console.error(e) }
37
36
  })
38
37
 
38
+ global.beforeEach (async () => {
39
+ if (this.data._autoReset) await this.data.reset()
40
+ })
41
+
39
42
  // shutdown cds server...
40
43
  global.after (done => {
41
44
  this.server ? this.server.close (done) : done()
@@ -66,7 +69,8 @@ class Test extends require('./axios') {
66
69
  * Switch on/off console log output.
67
70
  */
68
71
  verbose(v) {
69
- process.env.CDS_TEST_VERBOSE = v
72
+ v === false ? delete process.env.CDS_TEST_VERBOSE : process.env.CDS_TEST_VERBOSE=v
73
+ initLogging()
70
74
  return this
71
75
  }
72
76
 
@@ -87,10 +91,12 @@ class Test extends require('./axios') {
87
91
  get expect(){ return this.chai.expect }
88
92
  get assert(){ return this.chai.assert }
89
93
  get sleep(){ return require('util').promisify(setTimeout) }
90
- }
94
+ get data() { return this._data || (this._data = new (require('./data')))}
91
95
 
96
+ }
92
97
 
93
98
  // harmonizing jest and mocha
99
+ const is_jest = !!global.beforeAll
94
100
  const is_mocha = !!global.before
95
101
  if (is_mocha) {
96
102
  const { format } = require('util')
@@ -101,26 +107,29 @@ if (is_mocha) {
101
107
  if (!Array.isArray(each)) each = [each]
102
108
  return it (format(title,...each), ()=> fn(...each))
103
109
  }))
104
- } else { // it's jest
110
+ } else if (is_jest) { // it's jest
105
111
  global.before = (msg,fn) => global.beforeAll(fn||msg)
106
112
  global.after = (msg,fn) => global.afterAll(fn||msg)
113
+ } else { // it's none of both
114
+ global.before = (_,fn) => fn()
115
+ global.after = ()=>{}
107
116
  }
108
117
 
109
- if (!process.env.CDS_TEST_VERBOSE) silent()
110
-
111
- function silent() {
112
- const console = global.console, logs=[]
113
- global.console = { __proto__: global.console, logs,
114
- time: ()=>{}, timeEnd: (...args)=> logs.push(args),
115
- debug: (...args)=> logs.push(args),
116
- log: (...args)=> logs.push(args),
117
- warn: (...args)=> logs.push(args),
118
- error: (...args)=> logs.push(args),
119
- dump(){ for (let each of logs) console.log (...each) },
118
+ function initLogging() {
119
+ const levels = process.env.CDS_TEST_VERBOSE
120
+ ? { deploy:'info', serve:'info', server:'info',cds:'info' }
121
+ : { deploy:'warn', serve:'warn', server:'warn',cds:'silent'/*silences provoked request errors */ }
122
+
123
+ const env = Reflect.getOwnPropertyDescriptor(global.cds,'env')
124
+ for (const id of Object.keys(levels)) {
125
+ if (env && env.value)
126
+ global.cds.log(id, { level:levels[id] })
127
+ else // uninitialized cds.env -> set env variables to avoid initializing cds.env eagerly
128
+ process.env['CDS_LOG_LEVELS_'+id.toUpperCase()] = levels[id]
120
129
  }
121
- global.after (()=> global.console = console)
122
130
  }
123
131
 
124
-
125
132
  /** @type Test.run & Test */
126
133
  module.exports = Object.setPrototypeOf (Test.run, Test.prototype)
134
+
135
+ /* eslint no-console: off */
@@ -0,0 +1,81 @@
1
+ const cds = require('../../../cds')
2
+
3
+ const getTemplate = require('../../../common/utils/template')
4
+ const templateProcessor = require('../../../common/utils/templateProcessor')
5
+
6
+ const {
7
+ getRootEntity,
8
+ getPick,
9
+ createLogEntry,
10
+ addObjectID,
11
+ addDataSubject,
12
+ addDataSubjectForDetailsEntity,
13
+ resolveDataSubjectPromises
14
+ } = require('./utils')
15
+
16
+ let als
17
+
18
+ const _processorFnAccess = (accessLogs, model, req) => {
19
+ return ({ row, key, element, plain }) => {
20
+ const entity = getRootEntity(element)
21
+
22
+ // create or augment log entry
23
+ const accessLog = createLogEntry(accessLogs, entity, row)
24
+
25
+ // process categories
26
+ for (const category of plain.categories) {
27
+ if (category === 'ObjectID') {
28
+ addObjectID(accessLog, row, key)
29
+ } else if (category === 'DataSubjectID') {
30
+ addDataSubject(accessLog, row, key, entity)
31
+ } else if (category === 'IsPotentiallySensitive') {
32
+ // add attribute
33
+ if (!accessLog.attributes.find(ele => ele.name === key)) accessLog.attributes.push({ name: key })
34
+ // REVISIT: attribute vs. attachment?
35
+ }
36
+ }
37
+
38
+ // add promise to determine data subject if a DataSubjectDetails entity
39
+ if (
40
+ element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
41
+ accessLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
42
+ ) {
43
+ addDataSubjectForDetailsEntity(row, accessLog, req, entity, model)
44
+ }
45
+ }
46
+ }
47
+
48
+ const _getDataAccessLogs = (data, req, tx) => {
49
+ const template = getTemplate(
50
+ 'personal_read',
51
+ Object.assign({ name: req.target._service.name, model: tx.model }),
52
+ req.target,
53
+ { pick: getPick('READ') }
54
+ )
55
+
56
+ const accessLogs = {}
57
+
58
+ const processFn = _processorFnAccess(accessLogs, tx.model, req)
59
+ const data_ = Array.isArray(data) ? data : [data]
60
+ data_.forEach(row => {
61
+ templateProcessor({ processFn, row, template })
62
+ })
63
+
64
+ return accessLogs
65
+ }
66
+
67
+ const auditAccessHandler = async function (data, req) {
68
+ als = als || (await cds.connect.to('audit-log'))
69
+ if (!als.ready) return
70
+
71
+ const accessLogs = _getDataAccessLogs(data, req, this)
72
+ const accesses = Object.keys(accessLogs).map(k => accessLogs[k])
73
+
74
+ await resolveDataSubjectPromises(accesses)
75
+
76
+ if (accesses.length) await als.emit('dataAccessLog', { accesses })
77
+ }
78
+
79
+ module.exports = {
80
+ auditAccessHandler
81
+ }
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ ASPECTS: { CREATE: '_auditCreate', READ: '_auditRead', UPDATE: '_auditUpdate', DELETE: '_auditDelete' },
3
+ WRITE: { CREATE: 1, UPDATE: 1, DELETE: 1 }
4
+ }
@@ -0,0 +1,50 @@
1
+ const cds = require('../../../cds')
2
+
3
+ const {
4
+ attachDiffToContextHandler,
5
+ calcModificationLogsHandler4Before,
6
+ calcModificationLogsHandler4After,
7
+ emitModificationHandler
8
+ } = require('./modification')
9
+ const { auditAccessHandler } = require('./access')
10
+
11
+ module.exports = function () {
12
+ /*
13
+ * prep context
14
+ */
15
+ this.before('*', req => (req.context._audit = req.context._audit || {}))
16
+
17
+ /*
18
+ * data modification
19
+ */
20
+ // REVISIT: diff() doesn't work in srv after phase but foreign key propagation has not yet taken place in srv before phase
21
+ // -> calc diff in db layer and store in audit data structure at context
22
+ // -> REVISIT for GA: clear req._.partialPersistentState?
23
+ attachDiffToContextHandler._initial = true
24
+ for (const entity of Object.values(this.entities).filter(e => e._auditCreate)) {
25
+ cds.db.before('CREATE', entity, attachDiffToContextHandler)
26
+ // create -> all new -> calcModificationLogsHandler in after phase
27
+ cds.db.after('CREATE', entity, calcModificationLogsHandler4After)
28
+ this.after('CREATE', entity, emitModificationHandler)
29
+ }
30
+ for (const entity of Object.values(this.entities).filter(e => e._auditUpdate)) {
31
+ cds.db.before('UPDATE', entity, attachDiffToContextHandler)
32
+ // update -> mixed (via deep) -> calcModificationLogsHandler in before and after phase
33
+ cds.db.before('UPDATE', entity, calcModificationLogsHandler4Before)
34
+ cds.db.after('UPDATE', entity, calcModificationLogsHandler4After)
35
+ this.after('UPDATE', entity, emitModificationHandler)
36
+ }
37
+ for (const entity of Object.values(this.entities).filter(e => e._auditDelete)) {
38
+ cds.db.before('DELETE', entity, attachDiffToContextHandler)
39
+ // delete -> all done -> calcModificationLogsHandler in before phase
40
+ cds.db.before('DELETE', entity, calcModificationLogsHandler4Before)
41
+ this.after('DELETE', entity, emitModificationHandler)
42
+ }
43
+
44
+ /*
45
+ * data access
46
+ */
47
+ for (const entity of Object.values(this.entities).filter(e => e._auditRead)) {
48
+ this.after('READ', entity, auditAccessHandler)
49
+ }
50
+ }
@@ -0,0 +1,138 @@
1
+ const cds = require('../../../cds')
2
+
3
+ const getTemplate = require('../../../common/utils/template')
4
+ const templateProcessor = require('../../../common/utils/templateProcessor')
5
+
6
+ const {
7
+ getMapKeyForCurrentRequest,
8
+ getRootEntity,
9
+ getPick,
10
+ createLogEntry,
11
+ addObjectID,
12
+ addDataSubject,
13
+ addDataSubjectForDetailsEntity,
14
+ resolveDataSubjectPromises
15
+ } = require('./utils')
16
+
17
+ let als
18
+
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
+ // store diff in audit data structure at context
29
+ if (!req.context._audit.diffs) req.context._audit.diffs = new Map()
30
+ req.context._audit.diffs.set(req._.query, await req.diff())
31
+ }
32
+
33
+ const _getOldAndNew = (action, row, key) => {
34
+ let oldValue = action === 'Create' ? null : row._old && row._old[key]
35
+ if (oldValue === undefined) oldValue = null
36
+ let newValue = action === 'Delete' ? null : row[key]
37
+ if (newValue === undefined) newValue = null
38
+ return { oldValue, newValue }
39
+ }
40
+
41
+ const _addAttribute = (log, action, row, key) => {
42
+ if (!log.attributes.find(ele => ele.name === key)) {
43
+ const { oldValue, newValue } = _getOldAndNew(action, row, key)
44
+ if (oldValue !== newValue) log.attributes.push({ name: key, oldValue, newValue })
45
+ }
46
+ }
47
+
48
+ const _processorFnModification = (modificationLogs, model, req, beforeWrite) => processArgs => {
49
+ if (!processArgs.row._op) return
50
+
51
+ const { row, key, element, plain } = processArgs
52
+
53
+ // delete in before phase, create and update in after phase
54
+ if ((row._op === 'delete') !== !!beforeWrite) return
55
+
56
+ const entity = getRootEntity(element)
57
+ const action = row._op[0].toUpperCase() + row._op.slice(1)
58
+
59
+ // create or augment log entry
60
+ const modificationLog = createLogEntry(modificationLogs, entity, row)
61
+
62
+ // process categories
63
+ for (const category of plain.categories) {
64
+ if (category === 'ObjectID') {
65
+ addObjectID(modificationLog, row, key)
66
+ } else if (category === 'DataSubjectID') {
67
+ addDataSubject(modificationLog, row, key, entity)
68
+ } else if (category === 'IsPotentiallyPersonal' || category === 'IsPotentiallySensitive') {
69
+ _addAttribute(modificationLog, action, row, key)
70
+ }
71
+ }
72
+
73
+ // add promise to determine data subject if a DataSubjectDetails entity
74
+ if (
75
+ element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
76
+ modificationLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
77
+ ) {
78
+ addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
79
+ }
80
+ }
81
+
82
+ const _getDataModificationLogs = (req, tx, diff, beforeWrite) => {
83
+ const template = getTemplate(
84
+ `personal_${req.event}`.toLowerCase(),
85
+ Object.assign({ name: req.target._service.name, model: tx.model }),
86
+ req.target,
87
+ { pick: getPick(req.event) }
88
+ )
89
+
90
+ const modificationLogs = {}
91
+ const processFn = _processorFnModification(modificationLogs, tx.model, req, beforeWrite)
92
+ templateProcessor({ processFn, row: diff, template })
93
+
94
+ return modificationLogs
95
+ }
96
+
97
+ const _calcModificationLogsHandler = async function (req, beforeWrite, that) {
98
+ const mapKey = getMapKeyForCurrentRequest(req)
99
+
100
+ const modificationLogs = _getDataModificationLogs(req, that, req.context._audit.diffs.get(mapKey), beforeWrite)
101
+
102
+ // store modificationLogs in audit data structure at context
103
+ if (!req.context._audit.modificationLogs) req.context._audit.modificationLogs = new Map()
104
+ const existingLogs = req.context._audit.modificationLogs.get(mapKey) || {}
105
+ req.context._audit.modificationLogs.set(mapKey, Object.assign(existingLogs, modificationLogs))
106
+
107
+ // execute the data subject promises before going along to on phase
108
+ // guarantees that the reads are executed before the data is modified
109
+ await Promise.all(Object.keys(modificationLogs).map(k => modificationLogs[k].dataSubject.id))
110
+ }
111
+
112
+ const calcModificationLogsHandler4Before = function (req) {
113
+ return _calcModificationLogsHandler(req, true, this)
114
+ }
115
+
116
+ const calcModificationLogsHandler4After = function (_, req) {
117
+ return _calcModificationLogsHandler(req, false, this)
118
+ }
119
+
120
+ const emitModificationHandler = async function (_, req) {
121
+ als = als || (await cds.connect.to('audit-log'))
122
+ if (!als.ready) return
123
+
124
+ const modificationLogs = req.context._audit.modificationLogs.get(req.query)
125
+ const modifications = Object.keys(modificationLogs)
126
+ .map(k => modificationLogs[k])
127
+ .filter(ele => ele.attributes.length)
128
+
129
+ await resolveDataSubjectPromises(modifications)
130
+ await als.emit('dataModificationLog', { modifications })
131
+ }
132
+
133
+ module.exports = {
134
+ attachDiffToContextHandler,
135
+ calcModificationLogsHandler4Before,
136
+ calcModificationLogsHandler4After,
137
+ emitModificationHandler
138
+ }