diff --git a/db-t0.sqlite-shm b/db-t0.sqlite-shm new file mode 100644 index 0000000..5d48ae0 Binary files /dev/null and b/db-t0.sqlite-shm differ diff --git a/db-t0.sqlite-wal b/db-t0.sqlite-wal new file mode 100644 index 0000000..0156291 Binary files /dev/null and b/db-t0.sqlite-wal differ diff --git a/db-t1.sqlite-shm b/db-t1.sqlite-shm new file mode 100644 index 0000000..9b683d4 Binary files /dev/null and b/db-t1.sqlite-shm differ diff --git a/db-t1.sqlite-wal b/db-t1.sqlite-wal new file mode 100644 index 0000000..f04d3d1 Binary files /dev/null and b/db-t1.sqlite-wal differ diff --git a/db.sqlite-shm b/db.sqlite-shm new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/db.sqlite-shm differ diff --git a/db.sqlite-wal b/db.sqlite-wal new file mode 100644 index 0000000..e69de29 diff --git a/ext/.cdsrc.json b/ext/.cdsrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/ext/.cdsrc.json @@ -0,0 +1 @@ +{} diff --git a/ext/.eslintrc b/ext/.eslintrc new file mode 100644 index 0000000..74d6149 --- /dev/null +++ b/ext/.eslintrc @@ -0,0 +1,26 @@ +{ + "extends": "eslint:recommended", + "env": { + "es2022": true, + "node": true, + "jest": true, + "mocha": true + }, + "globals": { + "SELECT": true, + "INSERT": true, + "UPSERT": true, + "UPDATE": true, + "DELETE": true, + "CREATE": true, + "DROP": true, + "CDL": true, + "CQL": true, + "CXL": true, + "cds": true + }, + "rules": { + "no-console": "off", + "require-atomic-updates": "off" + } +} diff --git a/ext/.gitignore b/ext/.gitignore new file mode 100644 index 0000000..ca31a67 --- /dev/null +++ b/ext/.gitignore @@ -0,0 +1,34 @@ +# CAP ext +_out +*.db +*.sqlite +connection.properties +default-*.json +.cdsrc-private.json +gen/ +node_modules/ +target/ + +# Web IDE, App Studio +.che/ +.gen/ + +# MTA +*_mta_build_tmp +*.mtar +mta_archives/ + +# Other +.DS_Store +*.orig +*.log + +*.iml +*.flattened-pom.xml + +# IDEs +# .vscode +# .idea + +# @cap-js/cds-typer +@cds-models diff --git a/ext/.vscode/extensions.json b/ext/.vscode/extensions.json new file mode 100644 index 0000000..d558687 --- /dev/null +++ b/ext/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "SAPSE.vscode-cds", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "mechatroner.rainbow-csv", + "qwtel.sqlite-viewer", + "humao.rest-client" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} diff --git a/ext/.vscode/launch.json b/ext/.vscode/launch.json new file mode 100644 index 0000000..188a19c --- /dev/null +++ b/ext/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "cds serve", + "request": "launch", + "type": "node", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "cds", + "args": [ + "serve", + "--with-mocks", + "--in-memory?" + ], + "skipFiles": [ + "/**" + ] + } + ] +} diff --git a/ext/.vscode/tasks.json b/ext/.vscode/tasks.json new file mode 100644 index 0000000..68697ef --- /dev/null +++ b/ext/.vscode/tasks.json @@ -0,0 +1,25 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "cds watch", + "command": "cds", + "args": ["watch"], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "type": "shell", + "label": "cds serve", + "command": "cds", + "args": ["serve", "--with-mocks", "--in-memory?"], + "problemMatcher": [] + } + ] +} diff --git a/ext/README.md b/ext/README.md new file mode 100644 index 0000000..dbac29e --- /dev/null +++ b/ext/README.md @@ -0,0 +1,25 @@ +# Getting Started + +Welcome to your new project. + +It contains these folders and files, following our recommended project layout: + +File or Folder | Purpose +---------|---------- +`app/` | content for UI frontends goes here +`db/` | your domain models and data go here +`srv/` | your service models and code go here +`package.json` | project metadata and configuration +`readme.md` | this getting started guide + + +## Next Steps + +- Open a new terminal and run `cds watch` +- (in VS Code simply choose _**Terminal** > Run Task > cds watch_) +- Start adding content, for example, a [db/schema.cds](db/schema.cds). + + +## Learn More + +Learn more at https://cap.cloud.sap/docs/get-started/. diff --git a/ext/db/ext.cds b/ext/db/ext.cds new file mode 100644 index 0000000..5f2f775 --- /dev/null +++ b/ext/db/ext.cds @@ -0,0 +1,6 @@ +namespace x_ext; // for new entities like SalesRegion below +using from '_base'; + +extend sap.capire.incidents.Incidents with { foo: String}; + +annotate ProcessorService.Incidents with @UI.LineItem : [..., { Value: foo}]; \ No newline at end of file diff --git a/ext/package.json b/ext/package.json new file mode 100644 index 0000000..73018f1 --- /dev/null +++ b/ext/package.json @@ -0,0 +1,21 @@ +{ + "name": "ext", + "version": "1.0.0", + "description": "A simple CAP project.", + "repository": "", + "license": "UNLICENSED", + "private": true, + "dependencies": { + "@sap/cds": "^7", + "express": "^4" + }, + "devDependencies": { + "@cap-js/sqlite": "^1" + }, + "scripts": { + "start": "cds-serve" + }, + "cds": { + "extends": "_base" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 9bb781f..d3d7b8b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dummy": 0 }, "dependencies": { + "@cap-js/change-tracking": "^1.0.5", "@sap/cds": ">=7", "@sap/cds-mtxs": "^1.9", "express": "^4" @@ -25,7 +26,6 @@ "chai-subset": "^1.6.0" }, "scripts": { - "watch": "cds watch", "start": "cds-serve", "test": "npx jest --silent", "add-change-tracking": "npm add @cap-js/change-tracking && cp xmpls/change-tracking.cds ./srv && cp xmpls/change-tracking.test.js ./test", @@ -53,13 +53,15 @@ "[development]": { "users": { "alice": { + "tenant": "t1", "roles": [ "support", "admin", - "cds.Subscriber" + "cds.Subscriber", + "cds.ExtensionDeveloper" ] }, - "bob": { + "bob": { "tenant" : "t1", "roles": [ "support" ] @@ -67,9 +69,12 @@ } } }, - "[local-multitenancy]": { - "multitenancy": true - } + "[production]": { + "multitenancy": true, + "extensibility": true + }, + "multitenancy": true, + "extensibility": true }, "profile": "with-mtx-sidecar" }, diff --git a/srv/change-tracking.cds b/srv/change-tracking.cds new file mode 100644 index 0000000..47341e2 --- /dev/null +++ b/srv/change-tracking.cds @@ -0,0 +1,11 @@ +using { ProcessorService.Incidents } from './services'; + +annotate Incidents with { + customer @changelog: [customer.name]; + title @changelog; + status @changelog; +} + +annotate Incidents.conversation with @changelog: [author, timestamp] { + message @changelog @Common.Label: 'Message'; +} diff --git a/test/change-tracking.test.js b/test/change-tracking.test.js new file mode 100644 index 0000000..89bf809 --- /dev/null +++ b/test/change-tracking.test.js @@ -0,0 +1,133 @@ +const cds = require("@sap/cds"); +const { GET, POST, PATCH, DELETE , expect, axios} = cds.test(__dirname + '/..', '--with-mocks') + +describe("Integration Test for ChangeTracking", () => { + let draftId,incidentId; + axios.defaults.auth = { username: "alice" }; + let processorService = null; + let ChangeView = null; + beforeAll(async () => { + processorService = await cds.connect.to('ProcessorService'); + ChangeView = processorService.entities.ChangeView; + }); + it('Create an incident ', async () => { + const { status, statusText, data } = await POST(`/odata/v4/processor/Incidents`, { + title: 'Urgent attention required !', + status_code: 'N', + "customer": {ID:"1004100"} + }); + draftId = data.ID; + expect(status).to.equal(201); + expect(statusText).to.equal('Created'); + }); + + it('+ Activate the draft & check Urgency code as H using custom logic', async () => { + const response = await POST( + `/odata/v4/processor/Incidents(ID=${draftId},IsActiveEntity=false)/ProcessorService.draftActivate` + ); + expect(response.status).to.eql(201); + expect(response.data.urgency_code).to.eql('H'); + }); + + it('+ Test the incident status', async () => { + const { status, data: { status_code, ID } } = await GET(`/odata/v4/processor/Incidents(ID=${draftId},IsActiveEntity=true)`); + incidentId = ID; + expect(status).to.eql(200); + expect(status_code).to.eql('N'); + }); + + it('+ Test the title detail in ChangeView', async () => { + const response = await GET(`/odata/v4/processor/Incidents?$filter=ID eq ${draftId}`); + const incidentChanges = await SELECT.from(ChangeView).where({ + entity: "sap.capire.incidents.Incidents", + attribute: "title", + }) + expect(incidentChanges.length).to.equal(1); + const incidentChange = incidentChanges[0]; + expect(incidentChange.entityKey).to.equal(draftId); + expect(incidentChange.attribute).to.equal("title"); + expect(incidentChange.modification).to.equal("create"); + expect(incidentChange.valueChangedFrom).to.equal(""); + expect(incidentChange.valueChangedTo).to.equal("Urgent attention required !"); + }); + + it('+ Test the status detail in ChangeView', async () => { + const response = await GET(`/odata/v4/processor/Incidents?$filter=ID eq ${draftId}`); + const incidentChanges = await SELECT.from(ChangeView).where({ + entity: "sap.capire.incidents.Incidents", + attribute: "status", + }) + expect(incidentChanges.length).to.equal(1); + const incidentChange = incidentChanges[0]; + expect(incidentChange.entityKey).to.equal(draftId); + expect(incidentChange.attribute).to.equal("status"); + expect(incidentChange.modification).to.equal("create"); + expect(incidentChange.valueChangedFrom).to.equal(""); + expect(incidentChange.valueChangedTo).to.equal("N"); + }); + + it('+ Test the customer detail in ChangeView', async () => { + const response = await GET(`/odata/v4/processor/Incidents?$filter=ID eq ${draftId}`); + const incidentChanges = await SELECT.from(ChangeView).where({ + entity: "sap.capire.incidents.Incidents", + attribute: "customer", + }) + expect(incidentChanges.length).to.equal(1); + const incidentChange = incidentChanges[0]; + expect(incidentChange.entityKey).to.equal(draftId); + expect(incidentChange.attribute).to.equal("customer"); + expect(incidentChange.modification).to.equal("create"); + expect(incidentChange.valueChangedFrom).to.equal(""); + expect(incidentChange.valueChangedTo).to.equal("Sunny Sunshine"); + }); + + describe("Test Changes for Update Incident", () => { + it(`Should Close the Incident-${draftId}`, async ()=>{ + const {status} = await POST(`/odata/v4/processor/Incidents(ID=${draftId},IsActiveEntity=true)/ProcessorService.draftEdit`, + { + "PreserveChanges": true + }); + expect(status).to.equal(201); + }); + it(`Should Close the Incident-${draftId}`, async ()=>{ + const {status } = await PATCH(`/odata/v4/processor/Incidents(ID=${draftId},IsActiveEntity=false)`,{status_code: 'C'}); + expect(status).to.equal(200); + }); + it('+ Activate the draft & check Status code as C using custom logic', async () => { + const response = await POST( + `/odata/v4/processor/Incidents(ID=${draftId},IsActiveEntity=false)/ProcessorService.draftActivate` + ); + expect(response.status).to.eql(200); + }); + it('+ Test the status detail in ChangeView', async () => { + const response = await GET(`/odata/v4/processor/Incidents?$filter=ID eq ${draftId}`); + const incidentChanges = await SELECT.from(ChangeView).where({ + entity: "sap.capire.incidents.Incidents", + attribute: "status", + modification: 'update', + }) + expect(incidentChanges.length).to.equal(1); + const incidentChange = incidentChanges[0]; + expect(incidentChange.entityKey).to.equal(draftId); + expect(incidentChange.attribute).to.equal("status"); + expect(incidentChange.modification).to.equal("update"); + expect(incidentChange.valueChangedFrom).to.equal("N"); + expect(incidentChange.valueChangedTo).to.equal("C"); + }); + }); + + describe("Test Changes for Delete Incident", () => { + it('- Delete the Incident', async () => { + const response = await DELETE(`/odata/v4/processor/Incidents(ID=${draftId},IsActiveEntity=true)`); + expect(response.status).to.eql(204); + }); + + it('+ Test the status detail in ChangeView', async () => { + const incidentChanges = await SELECT.from(ChangeView).where({ + entity: "sap.capire.incidents.Incidents", + attribute: "status", + }) + expect(incidentChanges.length).to.equal(0); + }); + }); +});