import { cloneDeep } from 'lodash';
import { expect } from '../../../test-utils';
import { checkDiff, checkDiffMisc } from '../high-level-diff';
import {
CLIProductModuleDefinitionOutput,
CLIWorkflows,
productModuleDefinitionFromNetwork,
} from '../../domain/product-module-definition';
import { changedEmbedConfig, cliDefinitionExample } from '../../../test-utils/test-root-funeral-pm';
import { DocsFileName, EmbedFileName, RequiredWorkflowFileName, TopLevelFileName } from '../../domain/file-names';
const getTestPmDefinition = () => cloneDeep(productModuleDefinitionFromNetwork(cliDefinitionExample));
describe('High level diff', function () {
it('should not find any differences when doing the diff check on identical modules', function () {
const pmDefinition = getTestPmDefinition();
const { changes, hasChanged } = checkDiff(pmDefinition, pmDefinition);
expect(changes.settings.length).to.equals(0);
expect(changes.scheduledFunctions.length).to.equals(0);
expect(changes.fulfillmentTypes.length).to.equals(0);
expect(changes.alterationHooks.length).to.equals(0);
expect(changes.applicationAlterationHooks.length).to.equals(0);
expect(changes.memberAlterationHooks.length).to.equals(0);
expect(changes.codeFiles.length).to.equals(0);
expect(changes.unitTestCodeFiles.length).to.equals(0);
expect(changes.documents.length).to.equals(0);
expect(changes.requiredWorkflows.length).to.equals(0);
expect(changes.embed.length).to.equals(0);
expect(changes.alterationHookSchemas.length).to.equals(0);
expect(changes.applicationAlterationHookSchemas.length).to.equals(0);
expect(changes.memberAlterationHookSchemas.length).to.equals(0);
expect(changes.miscellaneous.length).to.equals(0);
expect(hasChanged).to.equals(false);
});
it('should correctly identify changes to all documents', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition: CLIProductModuleDefinitionOutput = {
...getTestPmDefinition(),
documents: {
policyScheduleHtml: '
',
anniversaryLetterHtml: '',
welcomeLetterHtml: 'h2>',
quoteSummaryHtml: '',
memberCertificateHtml: 'h4',
termsPdfBase64: 'hello',
certificateHtml: 'changed content',
invoiceTemplateHtml: 'changed invoice content',
supplementaryTerms: [
{
type: 'test-type',
pdfBase64: 'changed',
},
{
type: 'new-type',
pdfBase64: 'test',
},
],
},
};
const { changes, hasChanged } = checkDiff(pmDefinition, remotePmDefinition);
const changedDocs = [
'welcome-letter.html',
'policy-schedule.html',
'anniversary-letter.html',
'quote-summary.html',
'member-certificate.html',
'terms.pdf',
'certificate.html',
'invoice.html',
'test-type.pdf',
'new-type.pdf',
];
expect(changes.documents.sort()).to.deep.equals(changedDocs.sort());
expect(hasChanged).to.equals(true);
});
it('should not fail when checking a product module embed config with no markdown documents', function () {
const localPmDefinition = getTestPmDefinition() as any;
const remotePmDefinition = getTestPmDefinition() as any;
delete remotePmDefinition.workflows.productModuleEmbedConfig.prePaymentCompliance;
delete localPmDefinition.workflows.productModuleEmbedConfig.prePaymentCompliance;
const result = checkDiff(localPmDefinition, remotePmDefinition);
expect(result.hasChanged).to.equals(false);
});
it('should correctly identify changes to embed config pre-payment-compliance.md', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.workflows.productModuleEmbedConfig!.prePaymentCompliance.wording.content = 'changed';
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.embed).to.deep.equal([EmbedFileName.PrePaymentCompliance]);
});
it('should correctly identify changes to embed config pre-personal-details-compliance.md', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition() as any;
remotePmDefinition.workflows.productModuleEmbedConfig.prePersonalDetailsCompliance.wording.content = 'changed';
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.embed).to.deep.equal([EmbedFileName.PrePersonalDetailsCompliance]);
});
it('should not fail on a product module with no embed config', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
delete pmDefinition.workflows.productModuleEmbedConfig;
delete remotePmDefinition.workflows.productModuleEmbedConfig;
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.hasChanged).to.equals(false);
});
it('should not fail on a product module with no local embed config', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
delete pmDefinition.workflows.productModuleEmbedConfig;
checkDiff(pmDefinition, remotePmDefinition);
});
it('should not fail on a product module with no remote embed config', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
delete remotePmDefinition.workflows.productModuleEmbedConfig;
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.hasChanged).to.equals(true);
});
it('should not fail when the compliance sections of embed config are missing', function () {
const clonedPm = getTestPmDefinition() as any;
delete clonedPm.workflows.productModuleEmbedConfig.prePersonalDetailsCompliance;
delete clonedPm.workflows.productModuleEmbedConfig.prePaymentCompliance;
const result = checkDiff(clonedPm, clonedPm);
expect(result.hasChanged).to.equals(false);
});
it('should correctly identify changes to policy document templates', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = {
...getTestPmDefinition(),
documents: {
...pmDefinition.documents,
policyScheduleHtml: '',
anniversaryLetterHtml: '',
},
};
const result = checkDiff(pmDefinition, remotePmDefinition);
const changedDocs = ['policy-schedule.html', 'anniversary-letter.html'];
expect(result.changes.documents.sort()).to.deep.equals(changedDocs.sort());
expect(result.hasChanged).to.equals(true);
});
it('should correctly identify changes to code files', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.codeFiles[0].fileContent = 'console.log()';
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.codeFiles).to.deep.equal(['main.js']);
expect(result.hasChanged).to.equals(true);
});
it('should correctly identify changes when remote has files that local does not', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.codeFiles.push({ fileName: 'test.js', fileContent: 'console.log()' });
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.codeFiles).to.deep.equal(['test.js']);
expect(result.hasChanged).to.equal(true);
});
it('should correctly identify changes when local has files that remote does not', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.codeFiles.push({ fileName: 'test.js', fileContent: 'console.log()' });
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.codeFiles).to.deep.equal(['test.js']);
expect(result.hasChanged).to.equal(true);
});
it('should correctly identify changes to unit test code files', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.unitTestCodeFiles = [{ fileName: 'example-test.js', fileContent: "console.log('local')" }];
remotePmDefinition.unitTestCodeFiles = [{ fileName: 'example-test.js', fileContent: "console.log('test')" }];
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.unitTestCodeFiles).to.deep.equal(['example-test.js']);
expect(result.hasChanged).to.equals(true);
});
it('should correctly identify the changes to miscellaneous items', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.readmeMarkdown = 'I have changed';
remotePmDefinition.apiDocsSchema = [] as any;
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.hasChanged).to.equals(true);
// api docs diff is skipped when either side is not a valid object (legacy array format)
expect(result.changes.miscellaneous).to.deep.equal([TopLevelFileName.Readme]);
});
it('should correctly identify changes to scheduled functions', function () {
const pmDefinition = getTestPmDefinition();
pmDefinition.rootConfig.scheduledFunctions = [];
const remotePmDefinition = getTestPmDefinition();
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.scheduledFunctions.length).to.equals(1);
expect(result.changes.scheduledFunctions).to.deep.equals(['customScheduledFunction']);
expect(result.hasChanged).to.equals(true);
});
it('should correctly identify changes to select miscellaneous items', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.readmeMarkdown = 'I have changed';
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.hasChanged).to.equals(true);
expect(result.changes.miscellaneous).to.deep.equal(['README.md']);
});
it('should correctly identify changes to all workflows', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.workflows.applicationSchema = [];
remotePmDefinition.workflows.claimsBlocks = [];
remotePmDefinition.workflows.quoteSchema = [];
remotePmDefinition.workflows.alterationHooks[0].schema = [];
remotePmDefinition.workflows.productModuleEmbedConfig = changedEmbedConfig;
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.hasChanged).to.equals(true);
const changedRequiredWorkflowFiles = [
RequiredWorkflowFileName.ApplicationSchema,
RequiredWorkflowFileName.ClaimsBlocks,
RequiredWorkflowFileName.QuoteSchema,
];
expect(result.changes.requiredWorkflows).to.have.members(changedRequiredWorkflowFiles);
expect(result.changes.alterationHookSchemas).to.have.members(['alteration_hook_1']);
expect(result.changes.embed).to.have.members([EmbedFileName.EmbedConfig]);
});
it("should correctly identify a change in an alteration hook's name", function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.workflows = {
...pmDefinition.workflows,
alterationHooks: [{ key: 'hook_1', name: 'New name', schema: [] }],
};
remotePmDefinition.workflows = {
...remotePmDefinition.workflows,
alterationHooks: [{ key: 'hook_1', name: 'Alteration hook 1', schema: [] }],
};
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.alterationHooks.length).to.equals(1);
expect(result.changes.alterationHooks).to.deep.equals([`hook_1`]);
expect(result.hasChanged).to.equals(true);
});
it('should correctly identify when a local alteration hook exists, that is not present in remote alteration hooks', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.workflows = {
...pmDefinition.workflows,
alterationHooks: [{ key: 'hook_1', name: 'Alteration hook 1', schema: [] }],
};
remotePmDefinition.workflows = {
...remotePmDefinition.workflows,
alterationHooks: [],
};
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.alterationHooks).to.deep.equal([`hook_1`]);
expect(result.hasChanged).to.equal(true);
});
it('should correctly identify when a remote alteration hook exists, that is not present in local alteration hooks', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.workflows = {
...pmDefinition.workflows,
alterationHooks: [{ key: 'hook_1', name: 'Alteration hook 1', schema: [] }],
};
pmDefinition.workflows = {
...remotePmDefinition.workflows,
alterationHooks: [],
};
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.alterationHooks).to.deep.equal([`hook_1`]);
expect(result.hasChanged).to.equal(true);
});
it("should correctly identify a change in a member alteration hook's name", function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.workflows = {
...pmDefinition.workflows,
memberAlterationHooks: [{ key: 'change_insured_value', name: 'New name', schema: [] }],
};
remotePmDefinition.workflows = {
...remotePmDefinition.workflows,
memberAlterationHooks: [{ key: 'change_insured_value', name: 'Change insured value', schema: [] }],
};
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.memberAlterationHooks).to.deep.equals([`change_insured_value`]);
expect(result.hasChanged).to.equals(true);
});
it('should correctly identify when a local member alteration hook is not present in the remote definition', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
pmDefinition.workflows = {
...pmDefinition.workflows,
memberAlterationHooks: [{ key: 'change_insured_value', name: 'Change insured value', schema: [] }],
};
remotePmDefinition.workflows = {
...remotePmDefinition.workflows,
memberAlterationHooks: [],
};
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.memberAlterationHooks).to.deep.equal([`change_insured_value`]);
expect(result.hasChanged).to.equal(true);
});
it('should correctly identify a change to a member alteration hook schema', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
remotePmDefinition.workflows.memberAlterationHooks[0].schema = [];
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.memberAlterationHookSchemas).to.have.members(['member_alteration_hook_1']);
expect(result.changes.requiredWorkflows).to.deep.equal([]);
expect(result.hasChanged).to.equal(true);
});
it('should report no member alteration hook changes when neither definition has the list', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
delete (pmDefinition.workflows as Partial).memberAlterationHooks;
delete (remotePmDefinition.workflows as Partial).memberAlterationHooks;
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.memberAlterationHooks).to.deep.equal([]);
expect(result.changes.memberAlterationHookSchemas).to.deep.equal([]);
expect(result.changes.requiredWorkflows).to.deep.equal([]);
expect(result.hasChanged).to.equal(false);
});
it('should correctly identify changes to settings', function () {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
const changedSettings = [
'claims.annuity_types.0.key',
'policy_scheme_type',
'dashboard_issuing_enabled',
'policyholder.individuals_allowed',
'policy_anniversary_notification.days_before_to_send',
'beneficiaries.make_policyholder_a_beneficiary',
'grace_period.lapse_on.after_first_missed_payment.period',
'waiting_period.apply_to.the_full_policy.period',
'waiting_period.send_notification.0.period',
'billing.payment_method_types.debit_orders.enabled',
];
pmDefinition.rootConfig.settings.claims = { annuity_types: [{ key: 'before changed' }] };
remotePmDefinition.rootConfig.settings.claims = { annuity_types: [{ key: 'after changed' }] };
pmDefinition.rootConfig.settings.policy_scheme_type = 'individual';
remotePmDefinition.rootConfig.settings.policy_scheme_type = 'group';
pmDefinition.rootConfig.settings.dashboard_issuing_enabled = false;
remotePmDefinition.rootConfig.settings.dashboard_issuing_enabled = true;
pmDefinition.rootConfig.settings.policyholder.individuals_allowed = false;
remotePmDefinition.rootConfig.settings.policyholder.individuals_allowed = true;
pmDefinition.rootConfig.settings.policy_anniversary_notification = { days_before_to_send: 20 };
remotePmDefinition.rootConfig.settings.policy_anniversary_notification = { days_before_to_send: 21 };
pmDefinition.rootConfig.settings.beneficiaries.make_policyholder_a_beneficiary = true;
remotePmDefinition.rootConfig.settings.beneficiaries.make_policyholder_a_beneficiary = false;
pmDefinition.rootConfig.settings.grace_period = { lapse_on: { after_first_missed_payment: { period: 1 } } };
remotePmDefinition.rootConfig.settings.grace_period = { lapse_on: { after_first_missed_payment: { period: 2 } } };
pmDefinition.rootConfig.settings.waiting_period = { apply_to: { the_full_policy: { period: 1 } } };
remotePmDefinition.rootConfig.settings.waiting_period = { apply_to: { the_full_policy: { period: 2 } } };
pmDefinition.rootConfig.settings.waiting_period.send_notification = [{ period: 1 }];
remotePmDefinition.rootConfig.settings.waiting_period.send_notification = [{ period: 2 }];
pmDefinition.rootConfig.settings.billing = { payment_method_types: { debit_orders: { enabled: true } } };
remotePmDefinition.rootConfig.settings.billing = { payment_method_types: { debit_orders: { enabled: false } } };
const result = checkDiff(pmDefinition, remotePmDefinition);
expect(result.changes.settings.sort()).to.deep.equals(changedSettings.sort());
expect(result.hasChanged).to.equals(true);
});
it('should skip diff when remote api docs is null (legacy/invalid format).', () => {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
const result = checkDiffMisc(pmDefinition, { ...remotePmDefinition, apiDocsSchema: null as any });
expect(result).to.deep.equals([]);
});
it('should skip diff when remote api docs is undefined (legacy/invalid format).', () => {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
const result = checkDiffMisc(pmDefinition, { ...remotePmDefinition, apiDocsSchema: undefined as any });
expect(result).to.deep.equals([]);
});
it('should skip diff when remote api docs is invalid JSON (legacy/invalid format).', () => {
const pmDefinition = getTestPmDefinition();
const remotePmDefinition = getTestPmDefinition();
const resultForTypeString = checkDiffMisc(pmDefinition, {
...remotePmDefinition,
apiDocsSchema: 'not_json' as any,
});
expect(resultForTypeString).to.deep.equals([]);
const resultForTypeInvalidJSON = checkDiffMisc(pmDefinition, { ...remotePmDefinition, apiDocsSchema: '{' as any });
expect(resultForTypeInvalidJSON).to.deep.equals([]);
});
it('should detect diff when both api docs are valid objects and differ.', () => {
const pmDefinition = { ...getTestPmDefinition(), apiDocsSchema: { version: '1.0', endpoints: [] } };
const remotePmDefinition = { ...getTestPmDefinition(), apiDocsSchema: { version: '2.0', endpoints: [] } };
const result = checkDiffMisc(pmDefinition, remotePmDefinition);
expect(result).to.deep.equals([DocsFileName.ApiDocs]);
});
it('should not detect diff when both api docs are valid objects and equal.', () => {
const pmDefinition = { ...getTestPmDefinition(), apiDocsSchema: { version: '1.0', endpoints: [] } };
const remotePmDefinition = { ...getTestPmDefinition(), apiDocsSchema: { version: '1.0', endpoints: [] } };
const result = checkDiffMisc(pmDefinition, remotePmDefinition);
expect(result).to.deep.equals([]);
});
});