Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit tests for ntypes #20

Merged
merged 7 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 150
"printWidth": 200
}
8 changes: 6 additions & 2 deletions lib/content-deployment.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
const cds = require("@sap/cds");
const { validateNotificationTypes, readFile } = require("./utils");
const { createNotificationTypesMap, processNotificationTypes } = require("./notificationTypes");
const { processNotificationTypes } = require("./notificationTypes");
const { setGlobalLogLevel } = require("@sap-cloud-sdk/util");

async function deployNotificationTypes() {
setGlobalLogLevel("error");

// read notification types
const notificationTypes = readFile(cds.env.requires?.notifications?.types);
const notificationTypes = readFile(cds.env?.requires?.notifications?.types);

if (validateNotificationTypes(notificationTypes)) {
await processNotificationTypes(notificationTypes);
}
}

deployNotificationTypes();

module.exports = {
deployNotificationTypes
}
112 changes: 55 additions & 57 deletions lib/notificationTypes.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
const { executeHttpRequest } = require("@sap-cloud-sdk/http-client");
const { buildHeadersForDestination } = require("@sap-cloud-sdk/connectivity");
const { getNotificationDestination, doesKeyExist, getPrefix, getNotificationTypesKeyWithPrefix, executeRequest } = require("./utils");
const { getNotificationDestination, doesKeyExist, getPrefix, getNotificationTypesKeyWithPrefix } = require("./utils");
const _ = require("lodash");

const NOTIFICATION_TYPES_API_ENDPOINT = "v2/NotificationType.svc";

const defaultTemplate = {
"NotificationTypeKey": "Default",
"NotificationTypeVersion": "1",
"Templates": [
NotificationTypeKey: "Default",
NotificationTypeVersion: "1",
Templates: [
{
"Language": "en",
"Description": "Other Notifications",
"TemplatePublic": "{{title}}",
"TemplateSensitive": "{{title}}",
"TemplateGrouped": "Other Notifications",
"TemplateLanguage": "mustache",
"Subtitle": "{{description}}"
}
]
}
Language: "en",
Description: "Other Notifications",
TemplatePublic: "{{title}}",
TemplateSensitive: "{{title}}",
TemplateGrouped: "Other Notifications",
TemplateLanguage: "mustache",
Subtitle: "{{description}}",
},
],
};

function fromOdataArrayFormat(objectInArray) {
if (objectInArray === undefined || objectInArray === null || Array.isArray(objectInArray)) {
return objectInArray;
return objectInArray === undefined || objectInArray === null ? [] : objectInArray;
AnmolBinani marked this conversation as resolved.
Show resolved Hide resolved
} else {
return objectInArray.results;
return objectInArray.results === undefined || objectInArray.results === null ? [] : objectInArray.results;
}
}

function createNotificationTypesMap(notificationTypesJSON, isLocal = false) {
const types = {};

if(isLocal) {
types["Default"] = { "1": defaultTemplate };
if (isLocal) {
types["Default"] = { 1: defaultTemplate };
}

// add user provided templates
notificationTypesJSON.forEach((notificationType) => {
// set default NotificationTypeVersion if required
if(notificationType.NotificationTypeVersion === undefined) {
if (notificationType.NotificationTypeVersion === undefined) {
notificationType.NotificationTypeVersion = "1";
}

Expand Down Expand Up @@ -91,7 +91,7 @@ async function updateNotificationType(id, notificationType) {
const csrfHeaders = await buildHeadersForDestination(notificationDestination, {
url: NOTIFICATION_TYPES_API_ENDPOINT,
});

console.log(
`Detected change in notification type of key ${notificationType.NotificationTypeKey} and version ${notificationType.NotificationTypeVersion}. Updating it...`
);
Expand Down Expand Up @@ -124,30 +124,26 @@ async function deleteNotificationType(notificationType) {
}

function _createChannelsMap(channels) {
if(channels === null || channels === undefined) {
return {};
}

const channelMap = {};

channels.forEach((channel) => {
channelMap[channel.Type] = channel;
})
});

return channelMap;
}

function areDeliveryChannelsEqual(oldChannels, newChannels) {
if(_.size(oldChannels) !== _.size(newChannels)) {
if (_.size(oldChannels) !== _.size(newChannels)) {
return false;
}

const oldChannelsMap = _createChannelsMap(oldChannels);
const newChannelsMap = _createChannelsMap(newChannels);

for(type of Object.keys(oldChannelsMap)) {
if(!(type in newChannelsMap)) return false;
for (type of Object.keys(oldChannelsMap)) {
if (!(type in newChannelsMap)) return false;

const oldChannel = oldChannelsMap[type];
const newChannel = newChannelsMap[type];

Expand All @@ -158,7 +154,7 @@ function areDeliveryChannelsEqual(oldChannels, newChannels) {
oldChannel.DefaultPreference == newChannel.DefaultPreference &&
oldChannel.EditablePreference == newChannel.EditablePreference;

if(!equal) return false;
if (!equal) return false;
delete newChannelsMap[type];
}

Expand All @@ -172,11 +168,11 @@ function isActionEqual(oldAction, newAction) {
oldAction.ActionText == newAction.ActionText &&
oldAction.GroupActionText == newAction.GroupActionText &&
oldAction.Nature == newAction.Nature
)
);
}

function areActionsEqual(oldActions, newActions) {
if(_.size(oldActions) !== _.size(newActions)) {
if (_.size(oldActions) !== _.size(newActions)) {
return false;
}

Expand Down Expand Up @@ -208,11 +204,11 @@ function isTemplateEqual(oldTemplate, newTemplate) {
oldTemplate.EmailSubject == newTemplate.EmailSubject &&
oldTemplate.EmailText == newTemplate.EmailText &&
oldTemplate.EmailHtml == newTemplate.EmailHtml
)
);
}

function areTemplatesEqual(oldTemplates, newTemplates) {
if(_.size(oldTemplates) !== _.size(newTemplates)) {
if (_.size(oldTemplates) !== _.size(newTemplates)) {
return false;
}

Expand All @@ -233,16 +229,16 @@ function areTemplatesEqual(oldTemplates, newTemplates) {
}

function isNotificationTypeEqual(oldNotificationType, newNotificationType) {
if(newNotificationType.IsGroupable === undefined) {
if (newNotificationType.IsGroupable === undefined) {
newNotificationType.IsGroupable = true;
}

return (
oldNotificationType.IsGroupable == newNotificationType.IsGroupable &&
areTemplatesEqual(oldNotificationType.Templates.results, fromOdataArrayFormat(newNotificationType.Templates)) &&
areActionsEqual(oldNotificationType.Actions.results, fromOdataArrayFormat(newNotificationType.Actions)) &&
areDeliveryChannelsEqual(oldNotificationType.DeliveryChannels.results, fromOdataArrayFormat(newNotificationType.DeliveryChannels))
)
areTemplatesEqual(fromOdataArrayFormat(oldNotificationType.Templates), fromOdataArrayFormat(newNotificationType.Templates)) &&
areActionsEqual(fromOdataArrayFormat(oldNotificationType.Actions), fromOdataArrayFormat(newNotificationType.Actions)) &&
areDeliveryChannelsEqual(fromOdataArrayFormat(oldNotificationType.DeliveryChannels), fromOdataArrayFormat(newNotificationType.DeliveryChannels))
);
}

async function processNotificationTypes(notificationTypesJSON) {
Expand All @@ -254,56 +250,58 @@ async function processNotificationTypes(notificationTypesJSON) {
const existingTypes = await getNotificationTypes();

// iterate through notification types
for(const existingType of existingTypes) {
if(existingType.NotificationTypeKey == "Default") {
for (const existingType of existingTypes) {
if (existingType.NotificationTypeKey == "Default") {
defaultTemplateExists = true;
continue;
}

if(!existingType.NotificationTypeKey.startsWith(`${prefix}/`)) {
console.log(
`Skipping Notification Type of other application: ${existingType.NotificationTypeKey}.`
);
if (!existingType.NotificationTypeKey.startsWith(`${prefix}/`)) {
console.log(`Skipping Notification Type of other application: ${existingType.NotificationTypeKey}.`);
continue;
}

// if the type isn't present in the JSON file, delete it
if(notificationTypes[existingType.NotificationTypeKey] === undefined || notificationTypes[existingType.NotificationTypeKey][existingType.NotificationTypeVersion] === undefined) {
if (
notificationTypes[existingType.NotificationTypeKey] === undefined ||
notificationTypes[existingType.NotificationTypeKey][existingType.NotificationTypeVersion] === undefined
) {
await deleteNotificationType(existingType);
continue;
}

const newType = JSON.parse(JSON.stringify(notificationTypes[existingType.NotificationTypeKey][existingType.NotificationTypeVersion]));

// if the type is there then verify if everything is same or not
if(!isNotificationTypeEqual(existingType, newType)) {
await updateNotificationType(existingType.NotificationTypeId, notificationTypes[existingType.NotificationTypeKey][existingType.NotificationTypeVersion])
} else {
console.log(
`Notification Type of key ${existingType.NotificationTypeKey} and version ${existingType.NotificationTypeVersion} unchanged.`
if (!isNotificationTypeEqual(existingType, newType)) {
await updateNotificationType(
existingType.NotificationTypeId,
notificationTypes[existingType.NotificationTypeKey][existingType.NotificationTypeVersion]
);
} else {
console.log(`Notification Type of key ${existingType.NotificationTypeKey} and version ${existingType.NotificationTypeVersion} unchanged.`);
}

delete notificationTypes[existingType.NotificationTypeKey][existingType.NotificationTypeVersion];
if(Object.keys(notificationTypes[existingType.NotificationTypeKey]).length == 0) {
if (Object.keys(notificationTypes[existingType.NotificationTypeKey]).length == 0) {
delete notificationTypes[existingType.NotificationTypeKey];
}
}

// create default template if required
if(!defaultTemplateExists) {
if (!defaultTemplateExists) {
await createNotificationType(defaultTemplate);
}

// create notification types that aren't there
for(const notificationTypeKey in notificationTypes) {
for(const notificationTypeVersion in notificationTypes[notificationTypeKey]) {
for (const notificationTypeKey in notificationTypes) {
for (const notificationTypeVersion in notificationTypes[notificationTypeKey]) {
await createNotificationType(notificationTypes[notificationTypeKey][notificationTypeVersion]);
}
}
}

module.exports = {
createNotificationTypesMap,
processNotificationTypes
}
processNotificationTypes,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

, is not required

};
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
"lodash": "4.17.21"
},
"devDependencies": {
"jest": "^29.6.4"
"jest": "^29.6.4",
"chai": "^4.3.10"
},
"scripts": {
"lint": "npx eslint .",
"test": "npx jest --silent"
"test": "npx jest",
"test-with-coverage": "npx jest --coverage"
},
"cds": {
"requires": {
Expand Down
49 changes: 49 additions & 0 deletions test/lib/content-deployment.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// import required modules and functions
const cds = require("@sap/cds");
const { validateNotificationTypes, readFile } = require("../../lib/utils");
const { processNotificationTypes } = require("../../lib/notificationTypes");
const { setGlobalLogLevel } = require("@sap-cloud-sdk/util");
const assert = require("chai");

jest.mock("@sap/cds");
jest.mock("../../lib/utils");
jest.mock("../../lib/notificationTypes");
jest.mock("@sap-cloud-sdk/util");

const contentDeployment = require("../../lib/content-deployment");

describe("contentDeployment", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("Given valid notification types | When Deploy is called | Then process is called", async () => {
setGlobalLogLevel.mockImplementation(() => undefined);
readFile.mockImplementation(() => []);
validateNotificationTypes.mockImplementation(() => true);
processNotificationTypes.mockImplementation(() => Promise.resolve());

await contentDeployment.deployNotificationTypes();

console.log(setGlobalLogLevel.mock.calls)
assert.expect(setGlobalLogLevel.mock.calls[0][0]).to.be.equal("error");
assert.expect(readFile.mock.calls[0][0]).to.be.equal(cds.env?.requires?.notifications?.types);
assert.expect(validateNotificationTypes.mock.calls[0][0]).to.be.deep.equal([]);
assert.expect(processNotificationTypes.mock.calls[0][0]).to.be.deep.equal([]);
});

test("Given invalid notification types | When Deploy is called | Then process is called", async () => {
setGlobalLogLevel.mockImplementation(() => undefined);
readFile.mockImplementation(() => []);
validateNotificationTypes.mockImplementation(() => false);
processNotificationTypes.mockImplementation(() => Promise.resolve());

await contentDeployment.deployNotificationTypes();

console.log(setGlobalLogLevel.mock.calls)
assert.expect(setGlobalLogLevel.mock.calls[0][0]).to.be.equal("error");
assert.expect(readFile.mock.calls[0][0]).to.be.equal(cds.env?.requires?.notifications?.types);
assert.expect(validateNotificationTypes.mock.calls[0][0]).to.be.deep.equal([]);
assert.expect(processNotificationTypes.mock.calls[0]).to.be.deep.equal(undefined);
});
});
Loading