@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.
- package/CHANGELOG.md +208 -2
- package/apis/ql.d.ts +17 -15
- package/app/index.js +1 -1
- package/bin/build/buildTaskEngine.js +26 -42
- package/bin/build/buildTaskFactory.js +6 -10
- package/bin/build/buildTaskHandler.js +2 -4
- package/bin/build/buildTaskProvider.js +3 -1
- package/bin/build/buildTaskProviderFactory.js +9 -15
- package/bin/build/constants.js +15 -3
- package/bin/build/index.js +5 -4
- package/bin/build/mtaUtil.js +8 -11
- package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
- package/bin/build/provider/buildTaskProviderInternal.js +16 -42
- package/bin/build/provider/fiori/index.js +13 -24
- package/bin/build/provider/hana/2migration.js +17 -15
- package/bin/build/provider/hana/2tabledata.js +52 -48
- package/bin/build/provider/hana/index.js +27 -25
- package/bin/build/provider/hana/migrationtable.js +91 -67
- package/bin/build/provider/java-cf/index.js +14 -24
- package/bin/build/provider/mtx/index.js +12 -14
- package/bin/build/provider/node-cf/index.js +18 -32
- package/bin/cds.js +5 -5
- package/bin/serve.js +29 -23
- package/bin/version.js +0 -1
- package/lib/compile/etc/_localized.js +4 -9
- package/lib/compile/for/sql.js +5 -2
- package/lib/compile/parse.js +25 -17
- package/lib/compile/to/srvinfo.js +2 -1
- package/lib/connect/bindings.js +2 -1
- package/lib/connect/index.js +48 -49
- package/lib/core/classes.js +1 -1
- package/lib/core/reflect.js +10 -2
- package/lib/deploy.js +26 -23
- package/lib/env/defaults.js +13 -6
- package/lib/env/index.js +73 -78
- package/lib/env/requires.js +38 -19
- package/lib/index.js +9 -10
- package/lib/lazy.js +2 -2
- package/lib/log/index.js +33 -45
- package/lib/log/service/index.js +2 -2
- package/lib/ql/CREATE.js +14 -9
- package/lib/ql/DELETE.js +6 -5
- package/lib/ql/DROP.js +12 -9
- package/lib/ql/INSERT.js +40 -16
- package/lib/ql/Query.js +67 -40
- package/lib/ql/SELECT.js +162 -127
- package/lib/ql/UPDATE.js +74 -42
- package/lib/ql/Whereable.js +77 -87
- package/lib/ql/index.js +36 -24
- package/lib/ql/parse.js +35 -0
- package/lib/req/context.js +44 -8
- package/lib/req/locale.js +7 -7
- package/lib/serve/Service-api.js +21 -14
- package/lib/serve/Service-dispatch.js +28 -12
- package/lib/serve/Transaction.js +22 -10
- package/lib/serve/index.js +16 -11
- package/lib/utils/axios.js +23 -16
- package/lib/utils/data.js +35 -0
- package/lib/utils/tests.js +27 -18
- package/libx/_runtime/audit/generic/personal/access.js +81 -0
- package/libx/_runtime/audit/generic/personal/constants.js +4 -0
- package/libx/_runtime/audit/generic/personal/index.js +50 -0
- package/libx/_runtime/audit/generic/personal/modification.js +138 -0
- package/libx/_runtime/audit/generic/personal/utils.js +186 -0
- package/libx/_runtime/audit/utils/v2.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
- 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/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
- package/libx/_runtime/cds-services/services/Service.js +40 -5
- package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
- package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
- package/libx/_runtime/common/composition/data.js +44 -55
- package/libx/_runtime/common/composition/delete.js +97 -71
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +34 -11
- package/libx/_runtime/common/composition/tree.js +119 -92
- package/libx/_runtime/common/composition/update.js +4 -1
- package/libx/_runtime/common/composition/utils.js +1 -3
- package/libx/_runtime/common/constants/draft.js +12 -1
- package/libx/_runtime/common/generic/auth.js +6 -22
- package/libx/_runtime/common/generic/crud.js +14 -13
- package/libx/_runtime/common/generic/input.js +23 -26
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +16 -16
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +4 -0
- package/libx/_runtime/common/utils/backlinks.js +12 -5
- package/libx/_runtime/common/utils/cqn.js +6 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
- package/libx/_runtime/common/utils/csn.js +47 -4
- package/libx/_runtime/common/utils/data.js +0 -37
- package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
- package/libx/_runtime/common/utils/generateOnCond.js +11 -12
- package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
- package/libx/_runtime/common/utils/path.js +35 -0
- package/libx/_runtime/common/utils/postProcessing.js +86 -0
- package/libx/_runtime/common/utils/quotingStyles.js +37 -26
- package/libx/_runtime/common/utils/resolveView.js +223 -171
- package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
- package/libx/_runtime/common/utils/structured.js +6 -12
- package/libx/_runtime/common/utils/template.js +10 -5
- package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
- package/libx/_runtime/common/utils/templateProcessor.js +22 -30
- package/libx/_runtime/common/utils/union.js +31 -0
- package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
- package/libx/_runtime/db/Service.js +1 -1
- package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
- package/libx/_runtime/db/expand/index.js +3 -3
- package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
- package/libx/_runtime/db/generic/index.js +1 -1
- package/libx/_runtime/db/generic/input.js +5 -7
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +2 -10
- package/libx/_runtime/db/generic/update.js +13 -5
- package/libx/_runtime/db/generic/virtual.js +22 -58
- package/libx/_runtime/db/query/delete.js +7 -4
- package/libx/_runtime/db/query/insert.js +6 -4
- package/libx/_runtime/db/query/read.js +13 -20
- package/libx/_runtime/db/query/run.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -4
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
- package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
- package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
- package/libx/_runtime/db/utils/deep.js +8 -0
- package/libx/_runtime/db/utils/generateAliases.js +2 -1
- package/libx/_runtime/fiori/generic/activate.js +19 -15
- package/libx/_runtime/fiori/generic/before.js +3 -11
- package/libx/_runtime/fiori/generic/cancel.js +1 -1
- package/libx/_runtime/fiori/generic/delete.js +3 -1
- package/libx/_runtime/fiori/generic/edit.js +12 -2
- package/libx/_runtime/fiori/generic/new.js +5 -5
- package/libx/_runtime/fiori/generic/patch.js +0 -18
- package/libx/_runtime/fiori/generic/read.js +241 -189
- package/libx/_runtime/fiori/utils/delete.js +36 -7
- package/libx/_runtime/fiori/utils/handler.js +43 -44
- package/libx/_runtime/fiori/utils/where.js +30 -15
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
- package/libx/_runtime/hana/execute.js +2 -2
- package/libx/_runtime/hana/localized.js +4 -4
- package/libx/_runtime/hana/pool.js +29 -14
- package/libx/_runtime/hana/search2cqn4sql.js +2 -1
- package/libx/_runtime/hana/searchToContains.js +18 -14
- package/libx/_runtime/index.js +0 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
- package/libx/_runtime/messaging/service.js +7 -6
- package/libx/_runtime/odata/cqn2odata.js +110 -43
- package/libx/_runtime/odata/index.js +26 -48
- package/libx/_runtime/odata/odata2cqn.js +1 -6154
- package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
- package/libx/_runtime/odata/readToCqn.js +94 -64
- package/libx/_runtime/remote/Service.js +74 -21
- package/libx/_runtime/remote/cqn2odata/index.js +1 -5
- package/libx/_runtime/remote/utils/client.js +24 -101
- package/libx/_runtime/remote/utils/dataConversion.js +27 -12
- package/libx/_runtime/sqlite/Service.js +3 -5
- package/libx/_runtime/sqlite/execute.js +23 -24
- package/libx/_runtime/sqlite/localized.js +12 -7
- package/libx/_runtime/types/api.js +10 -0
- package/package.json +1 -1
- package/server.js +16 -2
- package/lib/ql/grammar.pegjs +0 -208
- package/lib/ql/parser.js +0 -1
- package/lib/ql/rt/DELETE.js +0 -29
- package/lib/ql/rt/INSERT.js +0 -23
- package/lib/ql/rt/Query.js +0 -84
- package/lib/ql/rt/SELECT.js +0 -174
- package/lib/ql/rt/UPDATE.js +0 -119
- package/lib/ql/rt/_helpers.js +0 -91
- package/lib/ql/rt/index.js +0 -32
- package/libx/_runtime/audit/generic/personal.js +0 -260
- package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
- package/libx/_runtime/cds-services/statements/Create.js +0 -57
- package/libx/_runtime/cds-services/statements/Delete.js +0 -33
- package/libx/_runtime/cds-services/statements/Drop.js +0 -42
- package/libx/_runtime/cds-services/statements/Insert.js +0 -201
- package/libx/_runtime/cds-services/statements/Select.js +0 -826
- package/libx/_runtime/cds-services/statements/Update.js +0 -181
- package/libx/_runtime/cds-services/statements/Where.js +0 -726
- package/libx/_runtime/cds-services/statements/index.js +0 -25
- package/libx/_runtime/common/generic/resolve-mock.js +0 -9
package/lib/serve/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
51
|
+
let specified = o.service
|
|
52
|
+
if (specified && specified !== 'all') {
|
|
52
53
|
// skip services not chosen by o.service, if specified
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
114
|
-
if (
|
|
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[
|
|
117
|
-
delete _pending[
|
|
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 (
|
|
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(
|
|
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
|
package/lib/utils/axios.js
CHANGED
|
@@ -6,26 +6,33 @@ class Axios {
|
|
|
6
6
|
baseURL: this.url,
|
|
7
7
|
})
|
|
8
8
|
}
|
|
9
|
-
get
|
|
10
|
-
put
|
|
11
|
-
post
|
|
12
|
-
patch
|
|
13
|
-
delete
|
|
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
|
|
16
|
-
/** @type typeof _.put
|
|
17
|
-
/** @type typeof _.post
|
|
18
|
-
/** @type typeof _.patch
|
|
19
|
-
/** @type typeof _.delete
|
|
20
|
-
/** @type typeof _.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
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
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 */
|
package/lib/utils/tests.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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,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
|
+
}
|