From 1bff617f971a938825f252591fd3407905c1a715 Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 28 Nov 2024 14:32:35 +0100 Subject: [PATCH 01/12] always rewrite to path expression on new db --- lib/utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index d3b0df5..cfb81b3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -123,8 +123,10 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo let w = relative._relations[element.name].join(targetAlias, relativeAlias) + const NEW_DB = cds.env.requires.db.impl?.startsWith('@cap-js') + // REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error - if (previousCqn?._aliases.has(relativeAlias)) { + if (NEW_DB || previousCqn?._aliases.has(relativeAlias)) { let t for (const a in entity.associations) if (entity.associations[a].target === relative.name) t = entity.associations[a] if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) From 804b071971bf77bf841f8a0531b1fd46facecaa5 Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 28 Nov 2024 21:26:54 +0100 Subject: [PATCH 02/12] a !== 'SiblingEntity' --- lib/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index cfb81b3..ddc784c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -128,7 +128,8 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo // REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error if (NEW_DB || previousCqn?._aliases.has(relativeAlias)) { let t - for (const a in entity.associations) if (entity.associations[a].target === relative.name) t = entity.associations[a] + for (const a in entity.associations) + if (a !== 'SiblingEntity' && entity.associations[a].target === relative.name) t = entity.associations[a] if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) } childCqn._aliases = new Set(previousCqn ? [...previousCqn._aliases.values(), as] : [as]) From 0ca1ce0f9422295798c632e2f130020dca63927d Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 28 Nov 2024 22:54:08 +0100 Subject: [PATCH 03/12] cleanup + prep v0.8.3 --- CHANGELOG.md | 6 ++++++ lib/utils.js | 7 ++----- package.json | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7651049..d86f19f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). The format is based on [Keep a Changelog](http://keepachangelog.com/). +## Version 0.8.3 - tbd + +### Fixed + +- Rewrite subselects to use path expressions on @cap-js databases + ## Version 0.8.2 - 2024-11-27 ### Fixed diff --git a/lib/utils.js b/lib/utils.js index ddc784c..819676d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -123,16 +123,13 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo let w = relative._relations[element.name].join(targetAlias, relativeAlias) - const NEW_DB = cds.env.requires.db.impl?.startsWith('@cap-js') - - // REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error - if (NEW_DB || previousCqn?._aliases.has(relativeAlias)) { + // rewrite to path expression on new databases + if (cds.env.requires.db.impl?.startsWith('@cap-js')) { let t for (const a in entity.associations) if (a !== 'SiblingEntity' && entity.associations[a].target === relative.name) t = entity.associations[a] if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) } - childCqn._aliases = new Set(previousCqn ? [...previousCqn._aliases.values(), as] : [as]) childCqn.where(w) diff --git a/package.json b/package.json index 4c552ce..c8cd434 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cap-js/audit-logging", - "version": "0.8.2", + "version": "0.8.3", "description": "CDS plugin providing integration to the SAP Audit Log service as well as out-of-the-box personal data-related audit logging based on annotations.", "repository": "cap-js/audit-logging", "author": "SAP SE (https://www.sap.com)", From 655d19d1bdfbf5c82e002ca687079a3650397836 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:27:27 +0100 Subject: [PATCH 04/12] 2024-11-28 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86f19f..5cbc6ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). The format is based on [Keep a Changelog](http://keepachangelog.com/). -## Version 0.8.3 - tbd +## Version 0.8.3 - 2024-11-28 ### Fixed From adb15d1c9bb6cb2c4d754b2027dd45d7fe701cde Mon Sep 17 00:00:00 2001 From: D050513 Date: Tue, 3 Dec 2024 10:46:17 +0100 Subject: [PATCH 05/12] fix where exists order --- lib/utils.js | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 819676d..e63a086 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -108,7 +108,7 @@ const _keyColumns = (keys, alias) => { const _alias = entity => entity.name.replace(`${entity._service.name}.`, '').replace('.', '_') -const _buildSubSelect = (model, { entity, relative, element, next }, row, previousCqn) => { +const _buildSubSelect = (model, { entity, relative, element, next }, row, acc, previousCqn) => { // relative is a parent or an entity itself const keys = Object.values(entity.keys) @@ -123,22 +123,26 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo let w = relative._relations[element.name].join(targetAlias, relativeAlias) - // rewrite to path expression on new databases - if (cds.env.requires.db.impl?.startsWith('@cap-js')) { - let t - for (const a in entity.associations) - if (a !== 'SiblingEntity' && entity.associations[a].target === relative.name) t = entity.associations[a] - if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) - } + // // rewrite to path expression on new databases + // if (cds.env.requires.db.impl?.startsWith('@cap-js')) { + // let t + // for (const a in entity.associations) + // if (a !== 'SiblingEntity' && entity.associations[a].target === relative.name) t = entity.associations[a] + // if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) + // } childCqn.where(w) - if (previousCqn) childCqn.where('exists', previousCqn) + if (previousCqn) { + // childCqn.where('exists', previousCqn) + } else childCqn.where(_addKeysToWhere(keys, row, as)) - if (next) return _buildSubSelect(model, next, {}, childCqn) + acc.push(childCqn) - return childCqn + if (next) /* return */ _buildSubSelect(model, next, {}, acc, childCqn) + + // return childCqn } const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { @@ -148,10 +152,21 @@ const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { const cqn = SELECT.one .from({ ref: [dataSubjectEntity.name], as }) .columns(_keyColumns(keys, as)) - .where(['exists', _buildSubSelect(model, subs[0], row)]) + // .where(['exists', _buildSubSelect(model, subs[0], row)])# + + const acc = [] + + const x = _buildSubSelect(model, subs[0], row, acc) + + let cur = cqn + for (let i = acc.length - 1; i >= 0; i--) { + const sub = acc[i] + cur.where('exists', sub) + cur = sub + } - // entity reused in different branches => must check all - for (let i = 1; i < subs.length; i++) cqn.or(['exists', _buildSubSelect(model, subs[i], row)]) + // // entity reused in different branches => must check all + // for (let i = 1; i < subs.length; i++) cqn.or(['exists', _buildSubSelect(model, subs[i], row)]) return cqn } @@ -258,7 +273,7 @@ const resolveDataSubjects = (logs, req) => { if (each.data_subject.id instanceof cds.ql.Query) { const q = each.data_subject.id if (!map.has(q)) { - const p = cds.run(q).then(res => map.set(q, res)) + const p = cds.run(q).then(res => {debugger; map.set(q, res)}) map.set(q, p) ps.push(p) } From 7369453e47eca389de57fe27f7835e2168a94853 Mon Sep 17 00:00:00 2001 From: D050513 Date: Tue, 3 Dec 2024 10:49:20 +0100 Subject: [PATCH 06/12] skip test with comp to one --- lib/utils.js | 11 ++++------- test/personal-data/crud.test.js | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index e63a086..25138aa 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -135,8 +135,7 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, acc, p if (previousCqn) { // childCqn.where('exists', previousCqn) - } - else childCqn.where(_addKeysToWhere(keys, row, as)) + } else childCqn.where(_addKeysToWhere(keys, row, as)) acc.push(childCqn) @@ -149,10 +148,8 @@ const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { const keys = Object.values(dataSubjectEntity.keys) const as = _alias(dataSubjectEntity) - const cqn = SELECT.one - .from({ ref: [dataSubjectEntity.name], as }) - .columns(_keyColumns(keys, as)) - // .where(['exists', _buildSubSelect(model, subs[0], row)])# + const cqn = SELECT.one.from({ ref: [dataSubjectEntity.name], as }).columns(_keyColumns(keys, as)) + // .where(['exists', _buildSubSelect(model, subs[0], row)])# const acc = [] @@ -273,7 +270,7 @@ const resolveDataSubjects = (logs, req) => { if (each.data_subject.id instanceof cds.ql.Query) { const q = each.data_subject.id if (!map.has(q)) { - const p = cds.run(q).then(res => {debugger; map.set(q, res)}) + const p = cds.run(q).then(res => map.set(q, res)) map.set(q, p) ps.push(p) } diff --git a/test/personal-data/crud.test.js b/test/personal-data/crud.test.js index 1ea12c0..7450aac 100644 --- a/test/personal-data/crud.test.js +++ b/test/personal-data/crud.test.js @@ -952,7 +952,8 @@ describe('personal data audit logging in CRUD', () => { }) }) - test('update Customer - deep with reusing notes', async () => { + // FIXME + xtest('update Customer - deep with reusing notes', async () => { let response response = await GET( From 413ef8c764d66c2d9d875c9c9344e0f472e15454 Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 5 Dec 2024 23:06:20 +0100 Subject: [PATCH 07/12] feat: use path expressions on new dbs --- lib/utils.js | 143 +++++++++++++++++++++++++------- test/personal-data/crud.test.js | 3 +- 2 files changed, 113 insertions(+), 33 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 25138aa..06ebc8e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -108,7 +108,7 @@ const _keyColumns = (keys, alias) => { const _alias = entity => entity.name.replace(`${entity._service.name}.`, '').replace('.', '_') -const _buildSubSelect = (model, { entity, relative, element, next }, row, acc, previousCqn) => { +const _buildSubSelect = (model, { entity, relative, element, next }, row, previousCqn) => { // relative is a parent or an entity itself const keys = Object.values(entity.keys) @@ -123,47 +123,35 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, acc, p let w = relative._relations[element.name].join(targetAlias, relativeAlias) - // // rewrite to path expression on new databases - // if (cds.env.requires.db.impl?.startsWith('@cap-js')) { - // let t - // for (const a in entity.associations) - // if (a !== 'SiblingEntity' && entity.associations[a].target === relative.name) t = entity.associations[a] - // if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) - // } + // REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error + if (previousCqn?._aliases.has(relativeAlias)) { + let t + for (const a in entity.associations) if (entity.associations[a].target === relative.name) t = entity.associations[a] + if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name) + } + childCqn._aliases = new Set(previousCqn ? [...previousCqn._aliases.values(), as] : [as]) childCqn.where(w) - if (previousCqn) { - // childCqn.where('exists', previousCqn) - } else childCqn.where(_addKeysToWhere(keys, row, as)) - - acc.push(childCqn) + if (previousCqn) childCqn.where('exists', previousCqn) + else childCqn.where(_addKeysToWhere(keys, row, as)) - if (next) /* return */ _buildSubSelect(model, next, {}, acc, childCqn) + if (next) return _buildSubSelect(model, next, {}, childCqn) - // return childCqn + return childCqn } const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { const keys = Object.values(dataSubjectEntity.keys) const as = _alias(dataSubjectEntity) - const cqn = SELECT.one.from({ ref: [dataSubjectEntity.name], as }).columns(_keyColumns(keys, as)) - // .where(['exists', _buildSubSelect(model, subs[0], row)])# - - const acc = [] - - const x = _buildSubSelect(model, subs[0], row, acc) + const cqn = SELECT.one + .from({ ref: [dataSubjectEntity.name], as }) + .columns(_keyColumns(keys, as)) + .where(['exists', _buildSubSelect(model, subs[0], row)]) - let cur = cqn - for (let i = acc.length - 1; i >= 0; i--) { - const sub = acc[i] - cur.where('exists', sub) - cur = sub - } - - // // entity reused in different branches => must check all - // for (let i = 1; i < subs.length; i++) cqn.or(['exists', _buildSubSelect(model, subs[i], row)]) + // entity reused in different branches => must check all + for (let i = 1; i < subs.length; i++) cqn.or(['exists', _buildSubSelect(model, subs[i], row)]) return cqn } @@ -183,6 +171,96 @@ const _getUps = (entity, model) => { return entity.own($parents) } +const _rel = (left, right, abort) => { + let a + for (const assoc in left.associations) { + if (left.associations[assoc].target === right.name) { + a = left.associations[assoc] + break + } + } + if (a) { + return { base: left, target: right, assoc: a } + } + if (abort) return + return _rel(right, left, true) +} + +const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { + const qs = [] + + for (const sub of subs) { + const path = [] + let s = sub + while (s) { + if (!path.length) { + const kp = Object.keys(s.entity.keys).reduce((acc, cur) => { + if (cur !== 'IsActiveEntity') acc.push(`${cur}='${row[cur]}'`) + return acc + }, []) + path.push({ id: s.entity.name, where: kp }) + } + + let relation = _rel(s.entity, s.next?.entity || dataSubjectEntity) + if (!relation) { + debugger + // TODO: no link + } else if (relation.base === s.entity) { + // link + if (relation.assoc === s.element) { + path.push({ to: relation.assoc.name }) + } else { + path[0].id = s.element.name + path.unshift({ id: relation.target.name }) + } + } else { + debugger + // no link + path[0].id = s.element.name + path.unshift({ id: relation.base.name }) + } + + s = s.next + } + + const p = path.reduce((acc, cur) => { + if (!acc) { + acc += `${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}` + } else { + if (cur.id) { + const close = acc.match(/([\]]+)$/)?.[1] + if (close) + acc = + acc.slice(0, close.length * -1) + + `[exists ${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}]` + + close + else acc += `[exists ${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}]` + } else if (cur.to) acc += `.${cur.to}` + } + return acc + }, '') /* + + path + .map(() => ']') + .join('') + .substring(1) */ + + // TODO: other subs + + const q = SELECT.one.from(p).columns(...Object.keys(dataSubjectEntity.keys)) + + qs.push(q) + } + + const q = qs[0] + + for (let i = 1; i < qs.length; i++) { + debugger + q.SELECT.from.ref[0].where.push('or', ...qs[i].SELECT.from.ref[0].where) + } + + return q +} + const _getDataSubjectUp = (root, model, entity, prev, next, result) => { for (const element of _getUps(entity, model)) { // cycle detection @@ -258,7 +336,10 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => { const map = _getDataSubjectsMap(req) if (map.has(role)) log.data_subject.id = map.get(role) // REVISIT by downward lookups row might already contain ID - some potential to optimize - else map.set(role, _getDataSubjectIdQuery(dataSubjectInfo, row, model)) + else { + const q = _new_getDataSubjectIdQuery(dataSubjectInfo, row, model) + map.set(role, q) + } } const resolveDataSubjects = (logs, req) => { diff --git a/test/personal-data/crud.test.js b/test/personal-data/crud.test.js index 7450aac..1ea12c0 100644 --- a/test/personal-data/crud.test.js +++ b/test/personal-data/crud.test.js @@ -952,8 +952,7 @@ describe('personal data audit logging in CRUD', () => { }) }) - // FIXME - xtest('update Customer - deep with reusing notes', async () => { + test('update Customer - deep with reusing notes', async () => { let response response = await GET( From 1233b58f0bed5e587aefa3001e8a724267072ce5 Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 5 Dec 2024 23:32:34 +0100 Subject: [PATCH 08/12] cleanup --- lib/utils.js | 82 +++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 06ebc8e..763714b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -141,7 +141,7 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo return childCqn } -const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { +const _old_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { const keys = Object.values(dataSubjectEntity.keys) const as = _alias(dataSubjectEntity) @@ -156,22 +156,7 @@ const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { return cqn } -const _getUps = (entity, model) => { - if (entity.own($parents) == null) { - const ups = [] - for (const def of Object.values(model.definitions)) { - if (def.kind !== 'entity' || !def.associations) continue - for (const element of Object.values(def.associations)) { - if (element.target !== entity.name || element._isBacklink || element.name === 'SiblingEntity') continue - ups.push(element) - } - } - entity.set($parents, ups) - } - return entity.own($parents) -} - -const _rel = (left, right, abort) => { +const _getRelation = (left, right, abort) => { let a for (const assoc in left.associations) { if (left.associations[assoc].target === right.name) { @@ -179,21 +164,20 @@ const _rel = (left, right, abort) => { break } } - if (a) { - return { base: left, target: right, assoc: a } - } - if (abort) return - return _rel(right, left, true) + if (a) return { base: left, target: right, assoc: a } + return abort ? undefined : _getRelation(right, left, true) } -const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => { +const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row) => { const qs = [] + // multiple subs => entity reused in different branches => must check all for (const sub of subs) { const path = [] let s = sub while (s) { if (!path.length) { + // the known entity instance as starting point const kp = Object.keys(s.entity.keys).reduce((acc, cur) => { if (cur !== 'IsActiveEntity') acc.push(`${cur}='${row[cur]}'`) return acc @@ -201,21 +185,21 @@ const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => path.push({ id: s.entity.name, where: kp }) } - let relation = _rel(s.entity, s.next?.entity || dataSubjectEntity) + let relation = _getRelation(s.entity, s.next?.entity || dataSubjectEntity) if (!relation) { - debugger - // TODO: no link + // TODO: no relation found } else if (relation.base === s.entity) { - // link + // assoc in base if (relation.assoc === s.element) { + // forwards path.push({ to: relation.assoc.name }) } else { + // backwards path[0].id = s.element.name path.unshift({ id: relation.target.name }) } } else { - debugger - // no link + // assoc in target path[0].id = s.element.name path.unshift({ id: relation.base.name }) } @@ -223,6 +207,7 @@ const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => s = s.next } + // construct path as string const p = path.reduce((acc, cur) => { if (!acc) { acc += `${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}` @@ -238,27 +223,30 @@ const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => } else if (cur.to) acc += `.${cur.to}` } return acc - }, '') /* + - path - .map(() => ']') - .join('') - .substring(1) */ - - // TODO: other subs + }, '') - const q = SELECT.one.from(p).columns(...Object.keys(dataSubjectEntity.keys)) - - qs.push(q) + qs.push(SELECT.one.from(p).columns(...Object.keys(dataSubjectEntity.keys))) } + // merge queries, if necessary const q = qs[0] + for (let i = 1; i < qs.length; i++) q.SELECT.from.ref[0].where.push('or', ...qs[i].SELECT.from.ref[0].where) + return q +} - for (let i = 1; i < qs.length; i++) { - debugger - q.SELECT.from.ref[0].where.push('or', ...qs[i].SELECT.from.ref[0].where) +const _getUps = (entity, model) => { + if (entity.own($parents) == null) { + const ups = [] + for (const def of Object.values(model.definitions)) { + if (def.kind !== 'entity' || !def.associations) continue + for (const element of Object.values(def.associations)) { + if (element.target !== entity.name || element._isBacklink || element.name === 'SiblingEntity') continue + ups.push(element) + } + } + entity.set($parents, ups) } - - return q + return entity.own($parents) } const _getDataSubjectUp = (root, model, entity, prev, next, result) => { @@ -337,8 +325,10 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => { if (map.has(role)) log.data_subject.id = map.get(role) // REVISIT by downward lookups row might already contain ID - some potential to optimize else { - const q = _new_getDataSubjectIdQuery(dataSubjectInfo, row, model) - map.set(role, q) + module.exports._getDataSubjectIdQuery ??= cds.env.requires.db?.impl?.startsWith('@cap-js/') + ? _new_getDataSubjectIdQuery + : _old_getDataSubjectIdQuery + map.set(role, module.exports._getDataSubjectIdQuery(dataSubjectInfo, row, model)) } } From 2692475b73b9da416431854869f1369d465f488d Mon Sep 17 00:00:00 2001 From: Marten Schiwek Date: Thu, 12 Dec 2024 11:14:00 +0100 Subject: [PATCH 09/12] Test to reproduce issue --- test/personal-data-complex/crud.test.js | 55 +++++ .../db/collaborations.cds | 192 ++++++++++++++++++ .../db/data/sap.hcm-Employees.csv | 3 + ...sap.taco.collaborations-Collaborations.csv | 2 + .../sap.taco.collaborations-Participants.csv | 2 + ...aborations-SubCollaborationAssignments.csv | 2 + ....taco.collaborations-SubCollaborations.csv | 2 + .../db/data/sap.taco.core-Students.csv | 2 + test/personal-data-complex/db/schema.cds | 98 +++++++++ test/personal-data-complex/package.json | 5 + .../srv/crud-service.cds | 121 +++++++++++ .../personal-data-complex/srv/crud-service.js | 10 + 12 files changed, 494 insertions(+) create mode 100644 test/personal-data-complex/crud.test.js create mode 100644 test/personal-data-complex/db/collaborations.cds create mode 100644 test/personal-data-complex/db/data/sap.hcm-Employees.csv create mode 100644 test/personal-data-complex/db/data/sap.taco.collaborations-Collaborations.csv create mode 100644 test/personal-data-complex/db/data/sap.taco.collaborations-Participants.csv create mode 100644 test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborationAssignments.csv create mode 100644 test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborations.csv create mode 100644 test/personal-data-complex/db/data/sap.taco.core-Students.csv create mode 100644 test/personal-data-complex/db/schema.cds create mode 100644 test/personal-data-complex/package.json create mode 100644 test/personal-data-complex/srv/crud-service.cds create mode 100644 test/personal-data-complex/srv/crud-service.js diff --git a/test/personal-data-complex/crud.test.js b/test/personal-data-complex/crud.test.js new file mode 100644 index 0000000..58e6aaa --- /dev/null +++ b/test/personal-data-complex/crud.test.js @@ -0,0 +1,55 @@ +const cds = require('@sap/cds') + +const { POST: _POST, PATCH: _PATCH, GET: _GET, DELETE: _DELETE, data } = cds.test().in(__dirname) + +// the persistent outbox adds a delay +const wait = require('util').promisify(setTimeout) +const DELAY = process.env.CI ? 42 : 7 +const POST = (...args) => _POST(...args).then(async res => (await wait(DELAY), res)) +const PATCH = (...args) => _PATCH(...args).then(async res => (await wait(DELAY), res)) +const GET = (...args) => _GET(...args).then(async res => (await wait(DELAY), res)) +const DELETE = (...args) => _DELETE(...args).then(async res => (await wait(DELAY), res)) + +// TODO: @cap-js/sqlite doesn't support structured properties +// // needed for testing structured properties +// cds.env.odata.flavor = 'x4' + +const _logger = require('../utils/logger')({ debug: true }) +cds.log.Logger = _logger + +describe('personal data audit logging in CRUD with complex model', () => { + let __log, _logs + const _log = (...args) => { + if (!(args.length === 2 && typeof args[0] === 'string' && args[0].match(/\[audit-log\]/i))) { + // > not an audit log (most likely, anyway) + return __log(...args) + } + + _logs.push(args[1]) + } + + const ALICE = { username: 'alice', password: 'password' } + + beforeAll(() => { + __log = global.console.log + global.console.log = _log + }) + + afterAll(() => { + global.console.log = __log + }) + + beforeEach(async () => { + await data.reset() + _logs = [] + _logger._resetLogs() + }) + + describe('data deletion logging', () => { + test('Delete PII record in action', async () => { + const { status: statusLeave } = await POST('/collaborations/Collaborations(ID=36ca041a-a337-4d08-8099-c2a0980823a0,IsActiveEntity=true)/CollaborationsService.leave', {}, {auth: ALICE}) + expect(statusLeave).toEqual(204); + }); + + }) +}) diff --git a/test/personal-data-complex/db/collaborations.cds b/test/personal-data-complex/db/collaborations.cds new file mode 100644 index 0000000..82fc0ae --- /dev/null +++ b/test/personal-data-complex/db/collaborations.cds @@ -0,0 +1,192 @@ +using { + cuid, + managed +} from '@sap/cds/common'; + +using { + sap.taco.core.Students, + sap.hcm.Employees +} from './schema'; + +namespace sap.taco.collaborations; + +entity Collaborations : cuid, managed { + title : String(100); + participants : Composition of many Participants + on participants.collaboration = $self; + applications : Composition of many Applications + on applications.collaboration = $self; + subCollaborations : Composition of many SubCollaborations + on subCollaborations.collaboration = $self; + leadAssignments : Composition of many CollaborationLeadAssignments + on leadAssignments.collaboration = $self; + collaborationLogs : Association to many CollaborationLogs + on collaborationLogs.collaboration = $self; + leads = participants[validFrom <= $now + and validTo >= $now + and isLead = true]; + activeParticipants = participants[validFrom <= $now + and validTo >= $now]; +} + +@assert.unique: {onlyOne: [ + collaboration, + student +], } +entity Participants : cuid { + collaboration : Association to one Collaborations; + student : Association to one Students; + employeeNav : Association to one Employees + on employeeNav.userID = student.userID; + validFrom : Date; + validTo : Date; + isLead : Boolean default false; + leadAssignment : Association to one CollaborationLeadAssignments + on leadAssignment.collaboration = collaboration + and leadAssignment.student = student; + subCollaborations : Composition of many SubCollaborationAssignments + on subCollaborations.participant = $self; + collaborationLogs : Association to many CollaborationLogs + on collaborationLogs.collaboration_ID = collaboration.ID + and student.userID = collaborationLogs.userID; +} + +@assert.unique: {onlyOne: [ + collaboration, + student +], } +entity CollaborationLeadAssignments : cuid { + collaboration : Association to one Collaborations; + student : Association to one Students; + employeeNav : Association to one Employees + on employeeNav.userID = student.userID; + validFrom : Date; + validTo : Date; +} + +@assert.unique: {onlyOne: [ + collaboration, + student +], } +entity Applications : cuid { + collaboration : Association to one Collaborations; + student : Association to one Students; + employeeNav : Association to one Employees + on employeeNav.userID = student.userID; + application : String(1000); + subCollaborationApplications : Composition of many SubCollaborationApplications + on subCollaborationApplications.application = $self; +} + +@assert.unique: {onlyOne: [ + subCollaboration, + application +], } +entity SubCollaborationApplications : cuid { + subCollaboration : Association to one SubCollaborations; + application : Association to one Applications; + leads : Association to many SubCollaborationLeads + on leads.subCollaboration_ID = subCollaboration.ID; +} + +entity SubCollaborations : cuid { + collaboration : Association to one Collaborations; + title : String(100); + participants : Association to many SubCollaborationAssignments + on participants.subCollaboration = $self @odata.draft.enclosed; + activeParticipants : Association to many ActiveSubCollaborationAssignments + on activeParticipants.subCollaboration = $self; + leads : Association to many SubCollaborationLeads + on leads.subCollaboration = $self /* and leads.isLead = true */; +} + +entity ActiveSubCollaborationAssignments as + select from SubCollaborationAssignments { + *, + participant.student.userID as student_userID @UI.Hidden, + } + where + validFrom <= $now + and validTo >= $now; + +entity SubCollaborationsVH as select from SubCollaborations; + +annotate SubCollaborationsVH with { + ID @UI.Hidden: false @Common.Text: title @Common.TextArrangement: #TextOnly +} + +@assert.unique: {onlyOne: [ + subCollaboration_ID, + participant +], } +entity SubCollaborationAssignments : cuid { + subCollaboration_ID : UUID; + subCollaboration : Association to one SubCollaborations + on subCollaboration.ID = subCollaboration_ID; + participant : Association to one Participants; + isLead : Boolean default false; + validFrom : Date; + validTo : Date; +} + +//REVISIT: Once isLead = true works also in associations and within exists navigations can be used +@readonly +entity SubCollaborationLeads as + projection on SubCollaborationAssignments { + *, + participant.student.userID as student_userID @readonly + } + where + isLead = true + and validFrom <= $now + and validTo >= $now; + +annotate SubCollaborationLeads with { + participant @readonly; +} + +entity CollaborationLogs : cuid { + userID : String; + student : Association to one Students + on student.userID = userID; + collaboration_ID : UUID; + collaboration : Association to one Collaborations + on collaboration.ID = collaboration_ID; + participant : Association to one Participants + on participant.student.userID = userID + and participant.collaboration.ID = collaboration_ID; + title : String(100); + approver : Association to one Employees; +} + +annotate Collaborations { + endDate @PersonalData.FieldSemantics: 'EndOfBusinessDate'; +} + +annotate Applications with @PersonalData : { + DataSubjectRole : 'Student', + EntitySemantics : 'Other' +} { + student @PersonalData.FieldSemantics: 'DataSubjectID'; + application @PersonalData.IsPotentiallyPersonal; +} + +annotate CollaborationLeadAssignments with @PersonalData : { + DataSubjectRole : 'Student', + EntitySemantics : 'Other' +} { + student @PersonalData.FieldSemantics: 'DataSubjectID'; +} + +annotate Participants with @PersonalData: { + DataSubjectRole: 'Student', + EntitySemantics: 'Other' +} { + student @PersonalData.FieldSemantics: 'DataSubjectID'; + validTo @PersonalData.FieldSemantics: 'EndOfBusinessDate'; +} + +annotate CollaborationLogs with { + userID @PersonalData.FieldSemantics: 'DataSubjectID'; + approver @PersonalData.FieldSemantics: 'UserID'; +} diff --git a/test/personal-data-complex/db/data/sap.hcm-Employees.csv b/test/personal-data-complex/db/data/sap.hcm-Employees.csv new file mode 100644 index 0000000..7cfa27f --- /dev/null +++ b/test/personal-data-complex/db/data/sap.hcm-Employees.csv @@ -0,0 +1,3 @@ +userID;displayName;firstName;lastName;initials;email;mobilePhone;officePhone;manager_userID +alice;Alice;Alice;;A;alice@sap.com;123;456;I123456 +I123456;Bettina;Bettina;;B;bettina@sap.com;789;101112; \ No newline at end of file diff --git a/test/personal-data-complex/db/data/sap.taco.collaborations-Collaborations.csv b/test/personal-data-complex/db/data/sap.taco.collaborations-Collaborations.csv new file mode 100644 index 0000000..bb0dd25 --- /dev/null +++ b/test/personal-data-complex/db/data/sap.taco.collaborations-Collaborations.csv @@ -0,0 +1,2 @@ +ID;title +36ca041a-a337-4d08-8099-c2a0980823a0;Test 1 \ No newline at end of file diff --git a/test/personal-data-complex/db/data/sap.taco.collaborations-Participants.csv b/test/personal-data-complex/db/data/sap.taco.collaborations-Participants.csv new file mode 100644 index 0000000..fd7aad2 --- /dev/null +++ b/test/personal-data-complex/db/data/sap.taco.collaborations-Participants.csv @@ -0,0 +1,2 @@ +ID;collaboration_ID;student_userID;isLead;validFrom;validTo +eea721d7-01cb-4862-a5fc-6e35cc55dd8f;36ca041a-a337-4d08-8099-c2a0980823a0;alice;true;2020-09-01;2023-09-30 \ No newline at end of file diff --git a/test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborationAssignments.csv b/test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborationAssignments.csv new file mode 100644 index 0000000..11a4bed --- /dev/null +++ b/test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborationAssignments.csv @@ -0,0 +1,2 @@ +ID;subCollaboration_ID;participant_ID;validFrom;validTo;isLead +9ced9799-d482-4f02-91ea-69457bc01d48;3913541a-5419-429c-b476-ed07ae8ed3b0;eea721d7-01cb-4862-a5fc-6e35cc55dd8f;2020-09-01;2023-09-30;false \ No newline at end of file diff --git a/test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborations.csv b/test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborations.csv new file mode 100644 index 0000000..0b41061 --- /dev/null +++ b/test/personal-data-complex/db/data/sap.taco.collaborations-SubCollaborations.csv @@ -0,0 +1,2 @@ +ID;collaboration_ID;title +3913541a-5419-429c-b476-ed07ae8ed3b0;36ca041a-a337-4d08-8099-c2a0980823a0;Test 1 subcollab \ No newline at end of file diff --git a/test/personal-data-complex/db/data/sap.taco.core-Students.csv b/test/personal-data-complex/db/data/sap.taco.core-Students.csv new file mode 100644 index 0000000..6b6ef9f --- /dev/null +++ b/test/personal-data-complex/db/data/sap.taco.core-Students.csv @@ -0,0 +1,2 @@ +userID;trainingHours;sumCompletedCollaborationLogs;validTo +alice;0;0;2023-09-30 \ No newline at end of file diff --git a/test/personal-data-complex/db/schema.cds b/test/personal-data-complex/db/schema.cds new file mode 100644 index 0000000..b79044e --- /dev/null +++ b/test/personal-data-complex/db/schema.cds @@ -0,0 +1,98 @@ +using { + cuid, + managed +} from '@sap/cds/common'; + +using { + sap.taco.collaborations.CollaborationLogs, + sap.taco.collaborations.CollaborationLeadAssignments, + sap.taco.collaborations.Participants, + sap.taco.collaborations.Applications, +} from './schema'; + +extend managed with { + createdByNav : Association to one sap.hcm.Employees on createdByNav.userID = createdBy; + modifiedByNav : Association to one sap.hcm.Employees on modifiedByNav.userID = modifiedBy; +}; + +context sap.hcm { + entity Employees { + key userID: String; + displayName: String; + firstName: String; + lastName: String; + initials: String; + email: String; + mobilePhone: String; + officePhone: String; + manager_userID: String; + } + + annotate Employees with @PersonalData : { + DataSubjectRole : 'Employee', + EntitySemantics : 'DataSubject', + } { + userID @PersonalData.FieldSemantics : 'DataSubjectID'; + displayName @PersonalData.IsPotentiallyPersonal; + firstName @PersonalData.IsPotentiallyPersonal; + lastName @PersonalData.IsPotentiallyPersonal; + initials @PersonalData.IsPotentiallyPersonal; + email @PersonalData.IsPotentiallyPersonal; + mobilePhone @PersonalData.IsPotentiallyPersonal; + officePhone @PersonalData.IsPotentiallyPersonal; + manager_userID @PersonalData.FieldSemantics : 'UserID' + }; +} + +context sap.taco.core { + entity Events : cuid, managed { + referenceID : UUID @UI.readonly; + students : Composition of many EventStudentAssignments + on students.event = $self; + title : String; + type_code : String; + collaborationLog : Association to one CollaborationLogs + on collaborationLog.ID = referenceID; + } + + entity EventStudentAssignments : cuid { + event : Association to one Events; + student : Association to one Students; + } + + entity Students { + key userID: String; + validTo: Date; + employeeNav : Association to one sap.hcm.Employees on employeeNav.userID = userID; + trainingHours: Decimal; + sumCompletedCollaborationLogs: Integer; + collaborations : Composition of many CollaborationLogs + on collaborations.userID = userID @UI.ExcludeFromNavigationContext; + collaborationApplications : Composition of many Applications + on collaborationApplications.student = $self; + leadAssignments : Composition of many CollaborationLeadAssignments + on leadAssignments.student = $self; + collaborationAssignments : Composition of many Participants + on collaborationAssignments.student = $self; + eventAssignments : Composition of many EventStudentAssignments + on eventAssignments.student = $self; + } + + annotate Events with { + createdBy @PersonalData.FieldSemantics: 'UserID'; + modifiedBy @PersonalData.FieldSemantics: 'UserID'; + } + + annotate EventStudentAssignments with { + student @PersonalData.FieldSemantics: 'DataSubjectID'; + } + + annotate Students with @PersonalData: { + DataSubjectRole: 'Student', + EntitySemantics: 'DataSubject', + } { + userID @PersonalData.FieldSemantics: 'DataSubjectID'; + trainingHours @PersonalData.IsPotentiallyPersonal; + openTasks @PersonalData.IsPotentiallyPersonal; + } +} \ No newline at end of file diff --git a/test/personal-data-complex/package.json b/test/personal-data-complex/package.json new file mode 100644 index 0000000..4318efe --- /dev/null +++ b/test/personal-data-complex/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@cap-js/audit-logging": "*" + } +} diff --git a/test/personal-data-complex/srv/crud-service.cds b/test/personal-data-complex/srv/crud-service.cds new file mode 100644 index 0000000..b805b93 --- /dev/null +++ b/test/personal-data-complex/srv/crud-service.cds @@ -0,0 +1,121 @@ +using {sap.taco.collaborations as persistence} from '../db/collaborations'; +using {sap.taco.core as core} from '../db/schema'; +using {sap.hcm as hcm} from '../db/schema'; + +@(requires: [ + 'admin' +]) +service CollaborationsService @(path: '/collaborations') { + + @cds.redirection.target + @odata.draft.enabled + entity Collaborations as projection on persistence.Collaborations + actions { + action leave(); + }; + + entity Applications as + projection on I_CollaborationApplications; + + entity CollaborationLeadAssignments as projection on persistence.CollaborationLeadAssignments; + + @readonly + entity Events as + projection on core.Events { + *, + } excluding { + students + }; + + @cds.redirection.target + entity Participants as + projection on persistence.Participants; + + @cds.redirection.target + entity SubCollaborationApplications as projection on persistence.SubCollaborationApplications; + + view SubCollaborationApplicationsView as + select from persistence.SubCollaborationApplications { + *, + subCollaboration.title as subcollabtitle + } + where + exists application.collaboration.leads[student.userID = $user.id] + or exists subCollaboration.leads[student_userID = $user.id]; + + @cds.redirection.target + entity SubCollaborations as + projection on I_SubCollaborations; + + @cds.redirection.target + entity SubCollaborationAssignments as + projection on persistence.SubCollaborationAssignments { + *, + participant.ID as participantID, + subCollaboration.ID as subCollaborationID, + }; + + entity SubCollaborationLeads as projection on persistence.SubCollaborationLeads + entity ActiveSubCollaborationAssignments as projection on persistence.ActiveSubCollaborationAssignments; + + @odata.draft.enabled + entity MyCollaborationLogs as + projection on persistence.CollaborationLogs { + *, + virtual null as certificate : LargeBinary @Core.MediaType: 'application/pdf' @title: '{i18n>CERTIFICATE}', + }; + + //That this comes after MyCollaborationLogs is important for audit-logging test + @cds.redirection.target + entity CollaborationLogs as + projection on I_CollaborationLogs + as ic { + *, + }; + + @cds.redirection.target + entity Students as + projection on core.Students { + userID, + employeeNav, + validTo, + eventAssignments, + }; + + @cds.redirection.target + entity Employees as projection on hcm.Employees; +} + +entity I_CollaborationApplications as + projection on persistence.Applications { + *, + collaboration : redirected to CollaborationsService.Collaborations, + }; + +entity I_SubCollaborations as + projection on persistence.SubCollaborations { + *, + collaboration : redirected to CollaborationsService.Collaborations, + }; + +entity I_CollaborationLogs as + projection on persistence.CollaborationLogs { + *, + collaboration : redirected to CollaborationsService.Collaborations, + }; + +entity I_SubCollaborationAssignments as + projection on persistence.SubCollaborationAssignments + as a { + *, + participant.student.userID as student_userID, + (( + exists( + select subCollaboration.ID from persistence.SubCollaborationAssignments + where + participant.student.userID = $user.id + and isLead = true + and subCollaboration.ID = a.subCollaboration.ID + ) + ) ? true : false) as isSessionUserSubLead : Boolean, + }; diff --git a/test/personal-data-complex/srv/crud-service.js b/test/personal-data-complex/srv/crud-service.js new file mode 100644 index 0000000..b762a0d --- /dev/null +++ b/test/personal-data-complex/srv/crud-service.js @@ -0,0 +1,10 @@ +const cds = require('@sap/cds'); + +module.exports = async srv => { + const {Collaborations, Participants} = srv.entities; + srv.on('leave', Collaborations, async req => { + await DELETE.from(Participants).where({ + student_userID: req.user.id, + }); + }); +} \ No newline at end of file From 44d61c8fb7d609142d03b9f73474b8006067418c Mon Sep 17 00:00:00 2001 From: D050513 Date: Fri, 13 Dec 2024 12:02:07 +0100 Subject: [PATCH 10/12] backlink name + all necessary assocs must be in projection --- lib/utils.js | 19 ++++++++++++++++--- .../srv/crud-service.cds | 1 + 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 763714b..5f1cc3c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -164,7 +164,16 @@ const _getRelation = (left, right, abort) => { break } } - if (a) return { base: left, target: right, assoc: a } + if (a) { + let backlink + for (const assoc in right.associations) { + if (right.associations[assoc].target === left.name) { + backlink = right.associations[assoc] + break + } + } + return { base: left, target: right, assoc: a, backlink } + } return abort ? undefined : _getRelation(right, left, true) } @@ -195,7 +204,7 @@ const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row) => { path.push({ to: relation.assoc.name }) } else { // backwards - path[0].id = s.element.name + path[0].id = relation.backlink?.name || s.element.name path.unshift({ id: relation.target.name }) } } else { @@ -341,7 +350,11 @@ const resolveDataSubjects = (logs, req) => { if (each.data_subject.id instanceof cds.ql.Query) { const q = each.data_subject.id if (!map.has(q)) { - const p = cds.run(q).then(res => map.set(q, res)) + const p = cds.run(q).then(res => map.set(q, res)).catch(err => { + q + debugger + throw err + }) map.set(q, p) ps.push(p) } diff --git a/test/personal-data-complex/srv/crud-service.cds b/test/personal-data-complex/srv/crud-service.cds index b805b93..ed212dd 100644 --- a/test/personal-data-complex/srv/crud-service.cds +++ b/test/personal-data-complex/srv/crud-service.cds @@ -80,6 +80,7 @@ service CollaborationsService @(path: '/collaborations') { employeeNav, validTo, eventAssignments, + collaborations }; @cds.redirection.target From 88dc5e437dd6476919134468e01353555f71228f Mon Sep 17 00:00:00 2001 From: D050513 Date: Fri, 13 Dec 2024 12:04:35 +0100 Subject: [PATCH 11/12] cleanup --- lib/utils.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 5f1cc3c..9eee66f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -350,11 +350,7 @@ const resolveDataSubjects = (logs, req) => { if (each.data_subject.id instanceof cds.ql.Query) { const q = each.data_subject.id if (!map.has(q)) { - const p = cds.run(q).then(res => map.set(q, res)).catch(err => { - q - debugger - throw err - }) + const p = cds.run(q).then(res => map.set(q, res)) map.set(q, p) ps.push(p) } From 0091a348bd048f0cc5bbb02c632f28bba8ad8419 Mon Sep 17 00:00:00 2001 From: D050513 Date: Fri, 13 Dec 2024 12:54:50 +0100 Subject: [PATCH 12/12] skip tests on cds^7 --- test/personal-data-complex/crud.test.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/personal-data-complex/crud.test.js b/test/personal-data-complex/crud.test.js index 58e6aaa..23b9767 100644 --- a/test/personal-data-complex/crud.test.js +++ b/test/personal-data-complex/crud.test.js @@ -1,14 +1,14 @@ const cds = require('@sap/cds') -const { POST: _POST, PATCH: _PATCH, GET: _GET, DELETE: _DELETE, data } = cds.test().in(__dirname) +const { POST: _POST, /* PATCH: _PATCH, GET: _GET, DELETE: _DELETE, */ data } = cds.test().in(__dirname) // the persistent outbox adds a delay const wait = require('util').promisify(setTimeout) const DELAY = process.env.CI ? 42 : 7 const POST = (...args) => _POST(...args).then(async res => (await wait(DELAY), res)) -const PATCH = (...args) => _PATCH(...args).then(async res => (await wait(DELAY), res)) -const GET = (...args) => _GET(...args).then(async res => (await wait(DELAY), res)) -const DELETE = (...args) => _DELETE(...args).then(async res => (await wait(DELAY), res)) +// const PATCH = (...args) => _PATCH(...args).then(async res => (await wait(DELAY), res)) +// const GET = (...args) => _GET(...args).then(async res => (await wait(DELAY), res)) +// const DELETE = (...args) => _DELETE(...args).then(async res => (await wait(DELAY), res)) // TODO: @cap-js/sqlite doesn't support structured properties // // needed for testing structured properties @@ -18,6 +18,8 @@ const _logger = require('../utils/logger')({ debug: true }) cds.log.Logger = _logger describe('personal data audit logging in CRUD with complex model', () => { + if (cds.version.split('.')[0] < 8) return test.skip('only for cds >= 8', () => {}) + let __log, _logs const _log = (...args) => { if (!(args.length === 2 && typeof args[0] === 'string' && args[0].match(/\[audit-log\]/i))) { @@ -47,9 +49,12 @@ describe('personal data audit logging in CRUD with complex model', () => { describe('data deletion logging', () => { test('Delete PII record in action', async () => { - const { status: statusLeave } = await POST('/collaborations/Collaborations(ID=36ca041a-a337-4d08-8099-c2a0980823a0,IsActiveEntity=true)/CollaborationsService.leave', {}, {auth: ALICE}) - expect(statusLeave).toEqual(204); - }); - + const { status: statusLeave } = await POST( + '/collaborations/Collaborations(ID=36ca041a-a337-4d08-8099-c2a0980823a0,IsActiveEntity=true)/CollaborationsService.leave', + {}, + { auth: ALICE } + ) + expect(statusLeave).toEqual(204) + }) }) })