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([]); }); });