diff --git a/lib/change-log.js b/lib/change-log.js index 17d6651..df95a31 100644 --- a/lib/change-log.js +++ b/lib/change-log.js @@ -482,6 +482,9 @@ function getAssociationDetails (entity) { return { ID, foreignKey, parentEntity }; } +function isEmpty(value) { + return value === null || value === undefined || value === ""; +} async function track_changes (req) { let diff = await req.diff() @@ -527,7 +530,7 @@ async function track_changes (req) { entity: dbEntity.name, entityKey: entityKey, serviceEntity: target.name || target, - changes: changes.filter(c => c.valueChangedFrom || c.valueChangedTo).map((c) => ({ + changes: changes.filter(c => !isEmpty(c.valueChangedFrom) || !isEmpty(c.valueChangedTo)).map((c) => ({ ...c, valueChangedFrom: `${c.valueChangedFrom ?? ''}`, valueChangedTo: `${c.valueChangedTo ?? ''}`, diff --git a/tests/bookshop/db/schema.cds b/tests/bookshop/db/schema.cds index ddb4a03..14dab61 100644 --- a/tests/bookshop/db/schema.cds +++ b/tests/bookshop/db/schema.cds @@ -201,6 +201,7 @@ entity Order : cuid { orderItems : Composition of many OrderItem on orderItems.order = $self; netAmount : Decimal(19, 2); + isUsed : Boolean; status : String; Items : Composition of many { key ID : UUID; diff --git a/tests/integration/fiori-draft-disabled.test.js b/tests/integration/fiori-draft-disabled.test.js index af89c0d..39fdca4 100644 --- a/tests/integration/fiori-draft-disabled.test.js +++ b/tests/integration/fiori-draft-disabled.test.js @@ -153,6 +153,89 @@ describe("change log draft disabled test", () => { delete cds.services.AdminService.entities.Level2Object["@changelog"]; }); + it("1.7 When creating or deleting a record with a numeric type of 0 and a boolean type of false, a changelog should also be generated", async () => { + cds.env.requires["change-tracking"].preserveDeletes = true; + cds.services.AdminService.entities.Order.elements.netAmount["@changelog"] = true; + cds.services.AdminService.entities.Order.elements.isUsed["@changelog"] = true; + + await POST(`/odata/v4/admin/Order`, { + ID: "3e745e35-5974-4383-b60a-2f5c9bdd31ac", + isUsed: false, + netAmount: 0, + }); + + let changes = await adminService.run(SELECT.from(ChangeView)); + + expect(changes).to.have.length(2); + expect( + changes.map((change) => ({ + entityKey: change.entityKey, + entity: change.entity, + valueChangedFrom: change.valueChangedFrom, + valueChangedTo: change.valueChangedTo, + modification: change.modification, + attribute: change.attribute + })) + ).to.have.deep.members([ + { + entityKey: "3e745e35-5974-4383-b60a-2f5c9bdd31ac", + modification: "Create", + entity: "sap.capire.bookshop.Order", + attribute: "netAmount", + valueChangedFrom: "", + valueChangedTo: "0" + }, + { + entityKey: "3e745e35-5974-4383-b60a-2f5c9bdd31ac", + modification: "Create", + entity: "sap.capire.bookshop.Order", + attribute: "isUsed", + valueChangedFrom: "", + valueChangedTo: "false" + }, + ]); + + await DELETE("/odata/v4/admin/Order(ID=3e745e35-5974-4383-b60a-2f5c9bdd31ac)"); + + changes = await adminService.run( + SELECT.from(ChangeView).where({ + modification: "delete", + }) + ); + + expect(changes).to.have.length(2); + expect( + changes.map((change) => ({ + entityKey: change.entityKey, + entity: change.entity, + valueChangedFrom: change.valueChangedFrom, + valueChangedTo: change.valueChangedTo, + modification: change.modification, + attribute: change.attribute + })) + ).to.have.deep.members([ + { + entityKey: "3e745e35-5974-4383-b60a-2f5c9bdd31ac", + modification: "Delete", + entity: "sap.capire.bookshop.Order", + attribute: "netAmount", + valueChangedFrom: "0", + valueChangedTo: "" + }, + { + entityKey: "3e745e35-5974-4383-b60a-2f5c9bdd31ac", + modification: "Delete", + entity: "sap.capire.bookshop.Order", + attribute: "isUsed", + valueChangedFrom: "false", + valueChangedTo: "" + }, + ]); + + delete cds.services.AdminService.entities.Order.elements.netAmount["@changelog"]; + delete cds.services.AdminService.entities.Order.elements.isUsed["@changelog"]; + }); + it("3.1 Composition creatition by odata request on draft disabled entity - should log changes for root entity (ERP4SMEPREPWORKAPPPLAT-670)", async () => { await POST( `/odata/v4/admin/Order(ID=0a41a187-a2ff-4df6-bd12-fae8996e6e31)/orderItems(ID=9a61178f-bfb3-4c17-8d17-c6b4a63e0097)/notes`, diff --git a/tests/integration/fiori-draft-enabled.test.js b/tests/integration/fiori-draft-enabled.test.js index 16eb191..c114449 100644 --- a/tests/integration/fiori-draft-enabled.test.js +++ b/tests/integration/fiori-draft-enabled.test.js @@ -85,6 +85,88 @@ describe("change log integration test", () => { expect(afterChanges.length).to.equal(14); }); + it("1.7 When creating or deleting a record with a numeric type of 0 and a boolean type of false, a changelog should also be generated", async () => { + cds.services.AdminService.entities.Books.elements.price["@changelog"] = true; + + let action = POST.bind( + {}, + `/odata/v4/admin/BookStores(ID=64625905-c234-4d0d-9bc1-283ee8946770,IsActiveEntity=false)/books`, + { + ID: "01234567-89ab-cdef-0123-987654fedcba", + price: 0, + isUsed: false + } + ); + await utils.apiAction("admin", "BookStores", "64625905-c234-4d0d-9bc1-283ee8946770", "AdminService", action); + let changes = await adminService.run(SELECT.from(ChangeView)); + expect(changes).to.have.length(2); + expect( + changes.map((change) => ({ + entityKey: change.entityKey, + entity: change.entity, + valueChangedFrom: change.valueChangedFrom, + valueChangedTo: change.valueChangedTo, + modification: change.modification, + attribute: change.attribute + })) + ).to.have.deep.members([ + { + entityKey: "64625905-c234-4d0d-9bc1-283ee8946770", + modification: "Create", + entity: "Book", + attribute: "price", + valueChangedFrom: "", + valueChangedTo: "0" + }, + { + entityKey: "64625905-c234-4d0d-9bc1-283ee8946770", + modification: "Create", + entity: "Book", + attribute: "isUsed", + valueChangedFrom: "", + valueChangedTo: "false" + }, + ]); + + action = DELETE.bind({}, `/odata/v4/admin/Books(ID=01234567-89ab-cdef-0123-987654fedcba,IsActiveEntity=false)`); + await utils.apiAction("admin", "BookStores", "64625905-c234-4d0d-9bc1-283ee8946770", "AdminService", action); + changes = await adminService.run( + SELECT.from(ChangeView).where({ + modification: "delete", + }) + ); + expect(changes).to.have.length(2); + expect( + changes.map((change) => ({ + entityKey: change.entityKey, + entity: change.entity, + valueChangedFrom: change.valueChangedFrom, + valueChangedTo: change.valueChangedTo, + modification: change.modification, + attribute: change.attribute + })) + ).to.have.deep.members([ + { + entityKey: "64625905-c234-4d0d-9bc1-283ee8946770", + modification: "Delete", + entity: "Book", + attribute: "price", + valueChangedFrom: "0", + valueChangedTo: "" + }, + { + entityKey: "64625905-c234-4d0d-9bc1-283ee8946770", + modification: "Delete", + entity: "Book", + attribute: "isUsed", + valueChangedFrom: "false", + valueChangedTo: "" + }, + ]); + + delete cds.services.AdminService.entities.Books.elements.price["@changelog"]; + }); + it("2.1 Child entity creation - should log basic data type changes (ERP4SMEPREPWORKAPPPLAT-32 ERP4SMEPREPWORKAPPPLAT-613)", async () => { const action = POST.bind( {}, diff --git a/tests/integration/service-api.test.js b/tests/integration/service-api.test.js index 93131d7..8f7a6df 100644 --- a/tests/integration/service-api.test.js +++ b/tests/integration/service-api.test.js @@ -42,6 +42,89 @@ describe("change log integration test", () => { const afterChanges = await adminService.run(SELECT.from(ChangeView)); expect(afterChanges.length).to.equal(6); + }); + + it("1.8 When creating or deleting a record with a numeric type of 0 and a boolean type of false, a changelog should also be generated", async () => { + cds.env.requires["change-tracking"].preserveDeletes = true; + cds.services.AdminService.entities.Order.elements.netAmount["@changelog"] = true; + cds.services.AdminService.entities.Order.elements.isUsed["@changelog"] = true; + + const ordersData = { + ID: "0faaff2d-7e0e-4494-97fe-c815ee973fa1", + isUsed: false, + netAmount: 0 + }; + + await INSERT.into(adminService.entities.Order).entries(ordersData); + let changes = await adminService.run(SELECT.from(ChangeView)); + + expect(changes).to.have.length(2); + expect( + changes.map((change) => ({ + entityKey: change.entityKey, + entity: change.entity, + valueChangedFrom: change.valueChangedFrom, + valueChangedTo: change.valueChangedTo, + modification: change.modification, + attribute: change.attribute + })) + ).to.have.deep.members([ + { + entityKey: "0faaff2d-7e0e-4494-97fe-c815ee973fa1", + modification: "Create", + entity: "sap.capire.bookshop.Order", + attribute: "netAmount", + valueChangedFrom: "", + valueChangedTo: "0" + }, + { + entityKey: "0faaff2d-7e0e-4494-97fe-c815ee973fa1", + modification: "Create", + entity: "sap.capire.bookshop.Order", + attribute: "isUsed", + valueChangedFrom: "", + valueChangedTo: "false" + }, + ]); + + await DELETE.from(adminService.entities.Order).where({ ID: "0faaff2d-7e0e-4494-97fe-c815ee973fa1" }); + changes = await adminService.run( + SELECT.from(ChangeView).where({ + modification: "delete", + }) + ); + + expect(changes).to.have.length(2); + expect( + changes.map((change) => ({ + entityKey: change.entityKey, + entity: change.entity, + valueChangedFrom: change.valueChangedFrom, + valueChangedTo: change.valueChangedTo, + modification: change.modification, + attribute: change.attribute + })) + ).to.have.deep.members([ + { + entityKey: "0faaff2d-7e0e-4494-97fe-c815ee973fa1", + modification: "Delete", + entity: "sap.capire.bookshop.Order", + attribute: "netAmount", + valueChangedFrom: "0", + valueChangedTo: "" + }, + { + entityKey: "0faaff2d-7e0e-4494-97fe-c815ee973fa1", + modification: "Delete", + entity: "sap.capire.bookshop.Order", + attribute: "isUsed", + valueChangedFrom: "false", + valueChangedTo: "" + }, + ]); + + delete cds.services.AdminService.entities.Order.elements.netAmount["@changelog"]; + delete cds.services.AdminService.entities.Order.elements.isUsed["@changelog"]; }); it("2.5 Root entity deep creation by service API - should log changes on root entity (ERP4SMEPREPWORKAPPPLAT-32 ERP4SMEPREPWORKAPPPLAT-613)", async () => {