From fea85040cebf9391277807fa44dc1e9798add2e1 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sun, 11 Oct 2020 19:51:55 +0530 Subject: [PATCH] Adds records diffing function with test --- tests/domain-service.test.js | 157 +++++++++++++++++++++++++---------- utils/domain-service.js | 40 +++++++-- 2 files changed, 148 insertions(+), 49 deletions(-) diff --git a/tests/domain-service.test.js b/tests/domain-service.test.js index dd814737d..46c5a06d8 100644 --- a/tests/domain-service.test.js +++ b/tests/domain-service.test.js @@ -1,5 +1,5 @@ const R = require('ramda'); -const { getDomainService } = require('../utils/domain-service'); +const { getDomainService, diffRecords } = require('../utils/domain-service'); const getCpanel = ({ zone, redir, setZone, setRedir } = {}) => ({ addZoneRecord: (rec) => setZone(rec), @@ -8,7 +8,85 @@ const getCpanel = ({ zone, redir, setZone, setRedir } = {}) => ({ fetchRedirections: (_) => redir(), }); +describe('diffRecords', () => { + it('should show added record', () => { + const oldRecords = [ + { name: 'xx', type: 'CNAME', address: 'fck.com.' }, + { name: 'xa', type: 'A', address: '111.1.1212.1' }, + ]; + const newRecords = [ + { name: 'xx', type: 'CNAME', address: 'fck.com.' }, + { name: 'xa', type: 'A', address: '111.1.1212.1' }, + { name: 'boo', type: 'CNAME', address: 'x.com' }, + ]; + + const result = diffRecords(oldRecords, newRecords); + expect(result).toEqual({ + edit: [], + add: [ + { name: 'boo', type: 'CNAME', address: 'x.com' }, + ], + }); + }); + + it('should show edited records', () => { + const oldRecords = [ + { name: 'xx', type: 'CNAME', address: 'fck.com.' }, + { name: 'xa', type: 'A', address: '111.1.1212.1' }, + ]; + const newRecords = [ + { name: 'xx', type: 'CNAME', address: 'fck.com.' }, + { name: 'xa', type: 'A', address: '69.69.69.69' }, + ]; + + const result = diffRecords(oldRecords, newRecords); + expect(result).toEqual({ + edit: [ + { name: 'xa', type: 'A', address: '69.69.69.69' }, + ], + add: [], + }); + }); + + it('should show added records with the same name and record type', () => { + const oldRecords = [ + { name: 'xx', type: 'CNAME', address: 'fck.com.' }, + { name: 'xa', type: 'A', address: '69.69.69.69' }, + ]; + const newRecords = [ + { name: 'xx', type: 'CNAME', address: 'fck.com.' }, + { name: 'xa', type: 'A', address: '69.69.69.69' }, + { name: 'xa', type: 'A', address: '69.69.4.20' }, + ]; + + const result = diffRecords(oldRecords, newRecords); + expect(result).toEqual({ + edit: [], + add: [ + { name: 'xa', type: 'A', address: '69.69.4.20' }, + ], + }); + }); +}); + describe('Domain service', () => { + const setZone = jest.fn(async () => ({})); + const setRedir = jest.fn(async () => ({})); + + const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({ + zone: async () => ({ hosts: zones }), + redir: async () => ({ hosts: redirections }), + setZone, + setRedir, + }) }); + + const getZoneCalls = () => setZone.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address'])); + + beforeEach(() => { + setZone.mockClear(); + setRedir.mockClear(); + }); + describe('getHosts', () => { it('should resolve with a list of hosts', async () => { const zones = [ @@ -90,62 +168,55 @@ describe('Domain service', () => { }); }); - return; - describe('updateHosts', () => { + return; + it('should append new hosts with existing ones and set it', async () => { - const records = [ - { HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' }, - { HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' }, + const zones = [ + { HostId: 1, name: 'a', type: 'CNAME', address: 'boo' }, + { HostId: 2, name: 'b', type: 'CNAME', address: 'goo' }, ]; + const redirections = []; - const onGet = () => Promise.resolve({ hosts: records }); - const onSet = jest.fn(async () => ({})); - - const mockDomainService = getDomainService({ cpanel: getCpanel({ onSet, onGet }) }); + const mockDomainService = mockDS({ zones, redirections });; await mockDomainService.updateHosts([ - { HostName: 'a', RecordType: 'CNAME', Address: 'boo' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'goo' }, - { HostName: 'c', RecordType: 'A', Address: '12.131321.213' }, + { name: 'a', type: 'CNAME', address: 'boo' }, + { name: 'b', type: 'CNAME', address: 'goo' }, + { name: 'c', type: 'A', address: '12.131321.213' }, ]); - const [hosts] = onSet.mock.calls[0]; - - expect(hosts.map(R.pick(['HostName', 'RecordType', 'Address']))).toEqual([ - { HostName: 'a', RecordType: 'CNAME', Address: 'boo' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'goo' }, - { HostName: 'c', RecordType: 'A', Address: '12.131321.213' }, + expect(setZone).toBeCalledTimes(1); + expect(getZoneCalls()).toEqual([ + { name: 'c', type: 'A', address: '12.131321.213' }, ]); }); it('should update matching host and set it', async () => { - const records = [ - { HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' }, - { HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' }, + const zones = [ + { HostId: 1, Name: 'a', Type: 'CNAME', address: 'boo' }, + { HostId: 2, Name: 'b', Type: 'CNAME', address: 'goo' }, ]; + const redirections = []; - const onGet = () => Promise.resolve({ hosts: records }); - const onSet = jest.fn(async () => ({})); - - const mockDomainService = getDomainService({ cpanel: getCpanel({ onSet, onGet }) }); + const mockDomainService = mockDS({ zones, redirections });; await mockDomainService.updateHosts([ - { HostName: 'a', RecordType: 'CNAME', Address: 'boo' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' }, + { name: 'a', type: 'CNAME', address: 'boo' }, + { name: 'b', type: 'CNAME', address: 'googoogaga' }, ]); - const [hosts] = onSet.mock.calls[0]; - - expect(hosts.map(R.pick(['HostName', 'RecordType', 'Address']))).toEqual([ - { HostName: 'a', RecordType: 'CNAME', Address: 'boo' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' }, + expect(setZone).toBeCalledTimes(2); + expect(getZoneCalls()).toEqual([ + { name: 'a', type: 'CNAME', address: 'boo' }, + { name: 'b', type: 'CNAME', address: 'googoogaga' }, ]); }); + return; it('should update matching host and set it', async () => { const records = [ - { HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' }, - { HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' }, - { HostId: 2, Name: 'b', Type: 'CNAME', Address: 'xaa' }, + { HostId: 1, Name: 'a', Type: 'CNAME', address: 'boo' }, + { HostId: 2, Name: 'b', Type: 'CNAME', address: 'goo' }, + { HostId: 2, Name: 'b', Type: 'CNAME', address: 'xaa' }, ]; const onGet = () => Promise.resolve({ hosts: records }); @@ -153,17 +224,17 @@ describe('Domain service', () => { const mockDomainService = getDomainService({ cpanel: getCpanel({ onSet, onGet }) }); await mockDomainService.updateHosts([ - { HostName: 'a', RecordType: 'CNAME', Address: 'boo' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'farboo' }, + { name: 'a', type: 'CNAME', address: 'boo' }, + { name: 'b', type: 'CNAME', address: 'googoogaga' }, + { name: 'b', type: 'CNAME', address: 'farboo' }, ]); const [hosts] = onSet.mock.calls[0]; - expect(hosts.map(R.pick(['HostName', 'RecordType', 'Address']))).toEqual([ - { HostName: 'a', RecordType: 'CNAME', Address: 'boo' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' }, - { HostName: 'b', RecordType: 'CNAME', Address: 'farboo' }, + expect(hosts.map(R.pick(['name', 'type', 'address']))).toEqual([ + { name: 'a', type: 'CNAME', address: 'boo' }, + { name: 'b', type: 'CNAME', address: 'googoogaga' }, + { name: 'b', type: 'CNAME', address: 'farboo' }, ]); }); }); diff --git a/utils/domain-service.js b/utils/domain-service.js index c4e520b6d..0c6964ae8 100644 --- a/utils/domain-service.js +++ b/utils/domain-service.js @@ -25,6 +25,13 @@ const redirectionToRecord = ({ domain, destination }) => ({ address: `${destination}`, }); +const getHostKey = host => `${host.name}##${host.type}`; + +const toHostMap = hosts => hosts.reduce((acc, host) => { + const key = getHostKey(host); + return { ...acc, [key]: [ ...(acc[key] || []), host ] }; +}, {}); + const getDomainService = ({ cpanel }) => { let hostList = []; @@ -48,12 +55,6 @@ const getDomainService = ({ cpanel }) => { [ R.T, addZoneRecord ], ]))); - const getHostKey = host => `${host.HostName}--${host.RecordType}`; - const toHostMap = hosts => hosts.reduce((acc, host) => { - const key = getHostKey(host); - return { ...acc, [key]: [ ...(acc[key] || []), host ] }; - }, {}); - const updateHosts = async hosts => { const hostList = await getHosts(); const remoteHostMap = toHostMap(hostList); @@ -75,9 +76,36 @@ const getDomainService = ({ cpanel }) => { return { getHosts, setHosts, updateHosts }; }; +const diffRecords = (oldRecords, newRecords) => { + const remoteHostMap = toHostMap(oldRecords); + const localHostMap = toHostMap(newRecords); + + return R.toPairs(localHostMap).reduce((acc, [key, local]) => { + const remote = remoteHostMap[key]; + + if (remote) { + let adds = []; + let edits = []; + + const diff = R.differenceWith((a, b) => a.address === b.address, local, remote); + + if (diff.length === local.length - remote.length) { + adds = diff; + } else { + edits = diff; + } + + return { ...acc, add: acc.add.concat(adds), edit: acc.edit.concat(edits) }; + } + + return { ...acc, add: acc.add.concat(local) }; + }, { add: [], edit: [] }); +}; + const domainService = getDomainService({ cpanel }); module.exports = { getDomainService, domainService, + diffRecords, };