diff --git a/tests/domain-service.test.js b/tests/domain-service.test.js index 9b774f117..d51c0916f 100644 --- a/tests/domain-service.test.js +++ b/tests/domain-service.test.js @@ -2,16 +2,16 @@ const R = require('ramda'); const { getDomainService, diffRecords } = require('../utils/domain-service'); const {DOMAIN_DOMAIN} = require('../utils/constants'); -const getCpanel = ({ zone, addZone, editZone, redir, addRedir, editRedir } = {}) => ({ +const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir } = {}) => ({ zone: { fetch: (_) => zone(), add: (rec) => addZone(rec), - edit: (rec) => editZone(rec), + remove: (rec) => removeZone(rec), }, redirection: { fetch: (_) => redir(), add: (rec) => addRedir(rec), - edit: (rec) => editRedir(rec), + remove: (rec) => removeRedir(rec), }, }); @@ -29,7 +29,7 @@ describe('diffRecords', () => { const result = diffRecords(oldRecords, newRecords); expect(result).toEqual({ - edit: [], + remove: [], add: [ { name: 'boo', type: 'CNAME', address: 'x.com' }, ], @@ -48,10 +48,12 @@ describe('diffRecords', () => { const result = diffRecords(oldRecords, newRecords); expect(result).toEqual({ - edit: [ + remove: [ + { name: 'xa', type: 'A', address: '111.1.1212.1' }, + ], + add: [ { name: 'xa', type: 'A', address: '69.69.69.69' }, ], - add: [], }); }); @@ -68,36 +70,67 @@ describe('diffRecords', () => { const result = diffRecords(oldRecords, newRecords); expect(result).toEqual({ - edit: [], + remove: [], add: [ { name: 'xa', type: 'A', address: '69.69.4.20' }, ], }); }); + + it('should diff complex changes', () => { + const oldRecords = [ + { name: 'a', type: 'CNAME', address: 'fck.com.' }, + { name: 'b', type: 'A', address: '69.69.69.69' }, + { name: '111', type: 'CNAME', address: 'x' }, + { name: 'd', type: 'A', address: '69.69.4.20' }, + ]; + const newRecords = [ + { name: '111', type: 'CNAME', address: 'x' }, + { name: 'd', type: 'CNAME', address: 'duck.com' }, + { name: 'a', type: 'CNAME', address: 'og.com' }, + { name: 'b', type: 'A', address: '69.69.69.69' }, + { name: 'b', type: 'A', address: '69.69.4.20' }, + { name: 'c', type: 'CNAME', address: 'ccc.cc' }, + ]; + + const result = diffRecords(oldRecords, newRecords); + expect(result).toEqual({ + remove: [ + { name: 'a', type: 'CNAME', address: 'fck.com.' }, + { name: 'd', type: 'A', address: '69.69.4.20' }, + ], + add: [ + { name: 'd', type: 'CNAME', address: 'duck.com' }, + { name: 'a', type: 'CNAME', address: 'og.com' }, + { name: 'b', type: 'A', address: '69.69.4.20' }, + { name: 'c', type: 'CNAME', address: 'ccc.cc' }, + ], + }); + }); }); describe('Domain service', () => { const addZone = jest.fn(async () => ({})); - const editZone = jest.fn(async () => ({})); + const removeZone = jest.fn(async () => ({})); const addRedir = jest.fn(async () => ({})); - const editRedir = jest.fn(async () => ({})); + const removeRedir = jest.fn(async () => ({})); const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({ zone: async () => zones, redir: async () => redirections, addZone, addRedir, - editZone, - editRedir, + removeZone, + removeRedir, }) }); - const getRecordCalls = recfn => recfn.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address', 'redirect', 'domain'])); + const getRecordCalls = recfn => recfn.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address', 'redirect', 'domain', 'line'])); beforeEach(() => { addZone.mockClear(); - editZone.mockClear(); + removeZone.mockClear(); addRedir.mockClear(); - editRedir.mockClear(); + removeRedir.mockClear(); }); describe('getHosts', () => { @@ -144,8 +177,8 @@ describe('Domain service', () => { describe('updateHosts', () => { it('should append new hosts with existing ones and set it', async () => { const zones = [ - { someid: 1, name: 'a', type: 'CNAME', address: 'boo' }, - { someid: 2, name: 'b', type: 'CNAME', address: 'goo' }, + { line: 1, name: 'a', type: 'CNAME', address: 'boo' }, + { line: 2, name: 'b', type: 'CNAME', address: 'goo' }, ]; const redirections = []; @@ -160,13 +193,13 @@ describe('Domain service', () => { expect(getRecordCalls(addZone)).toEqual([ { name: 'c', type: 'A', address: '12.131321.213' }, ]); - expect(editZone).toBeCalledTimes(0); + expect(removeZone).toBeCalledTimes(0); }); it('should update matching host and set it', async () => { const zones = [ - { someid: 1, name: 'a', type: 'CNAME', address: 'boo' }, - { someid: 2, name: 'b', type: 'CNAME', address: 'goo' }, + { line: 1, name: 'a', type: 'CNAME', address: 'boo' }, + { line: 2, name: 'b', type: 'CNAME', address: 'goo' }, ]; const redirections = []; @@ -176,18 +209,21 @@ describe('Domain service', () => { { name: 'b', type: 'CNAME', address: 'googoogaga' }, ]); - expect(addZone).toBeCalledTimes(0); - expect(editZone).toBeCalledTimes(1); - expect(getRecordCalls(editZone)).toEqual([ + expect(addZone).toBeCalledTimes(1); + expect(getRecordCalls(addZone)).toEqual([ { name: 'b', type: 'CNAME', address: 'googoogaga' }, ]); + expect(removeZone).toBeCalledTimes(1); + expect(getRecordCalls(removeZone)).toEqual([ + { line: 2 }, + ]); }); it('should update matching host and set it', async () => { const zones = [ - { someid: 1, name: 'a', type: 'CNAME', address: 'boo' }, - { someid: 2, name: 'b', type: 'CNAME', address: 'goo' }, - { someid: 2, name: 'b', type: 'CNAME', address: 'xaa' }, + { line: 1, name: 'a', type: 'CNAME', address: 'boo' }, + { line: 2, name: 'b', type: 'CNAME', address: 'goo' }, + { line: 3, name: 'b', type: 'CNAME', address: 'xaa' }, ]; const redirections = []; @@ -198,20 +234,24 @@ describe('Domain service', () => { { name: 'b', type: 'CNAME', address: 'farboo' }, ]); - expect(addZone).toBeCalledTimes(0); - expect(editZone).toBeCalledTimes(2); - expect(getRecordCalls(editZone)).toEqual([ + expect(addZone).toBeCalledTimes(2); + expect(getRecordCalls(addZone)).toEqual([ { name: 'b', type: 'CNAME', address: 'googoogaga' }, { name: 'b', type: 'CNAME', address: 'farboo' }, ]); + expect(removeZone).toBeCalledTimes(2); + expect(getRecordCalls(removeZone)).toEqual([ + { line: 2 }, + { line: 3 }, + ]); }); it('should workout this complex example', async () => { const zones = [ - { someid: 1, name: 'a', type: 'CNAME', address: 'world' }, - { someid: 2, name: 'b', type: 'A', address: '1' }, - { someid: 2, name: 'b', type: 'A', address: '2' }, - { someid: 2, name: 'c', type: 'CNAME', address: 'hello.com' }, + { line: 1, name: 'a', type: 'CNAME', address: 'world' }, + { line: 2, name: 'b', type: 'A', address: '1' }, + { line: 3, name: 'b', type: 'A', address: '2' }, + { line: 4, name: 'c', type: 'CNAME', address: 'hello.com' }, ]; const redirections = [ { domain: `b.${DOMAIN_DOMAIN}`, destination: 'https://foobar.com' }, @@ -233,24 +273,27 @@ describe('Domain service', () => { { name: 'x', type: 'URL', address: 'https://example69.com' }, ]); - expect(addZone).toBeCalledTimes(2); - expect(editZone).toBeCalledTimes(1); - expect(addRedir).toBeCalledTimes(1); - expect(editRedir).toBeCalledTimes(2); + expect(addZone).toBeCalledTimes(3); expect(getRecordCalls(addZone)).toEqual([ - { name: 'b', type: 'A', address: '3' }, - { name: 'd', type: 'CNAME', address: 'helo.com' } - ]); - expect(getRecordCalls(editZone)).toEqual([ { name: 'a', type: 'CNAME', address: 'boo' }, + { name: 'b', type: 'A', address: '3' }, + { name: 'd', type: 'CNAME', address: 'helo.com' }, ]); + expect(removeZone).toBeCalledTimes(1); + expect(getRecordCalls(removeZone)).toEqual([ + { line: 1 }, + ]); + expect(addRedir).toBeCalledTimes(3); expect(getRecordCalls(addRedir)).toEqual([ - { domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' }, - ]); - expect(getRecordCalls(editRedir)).toEqual([ { domain: `b.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://wowow.com' }, + { domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' }, { domain: `x.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://example69.com' }, ]); + expect(removeRedir).toBeCalledTimes(2); + expect(getRecordCalls(removeRedir)).toEqual([ + { domain: `b.${DOMAIN_DOMAIN}` }, + { domain: `x.${DOMAIN_DOMAIN}` }, + ]); }); }); }); diff --git a/utils/domain-service.js b/utils/domain-service.js index 34a40c60c..6aa6649f8 100644 --- a/utils/domain-service.js +++ b/utils/domain-service.js @@ -34,37 +34,16 @@ const redirectionToRecord = ({ domain, destination }) => ({ address: `${destination}`.replace(/\/$/g, ''), }); -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 getHostKey = host => `${host.name}##${host.type}##${host.address}`; const diffRecords = (oldRecords, newRecords) => { - const remoteHostMap = toHostMap(oldRecords); - const localHostMap = toHostMap(newRecords); + const isMatchingRecord = (a, b) => getHostKey(a) === getHostKey(b); - return R.toPairs(localHostMap).reduce((acc, [key, local]) => { - const remote = remoteHostMap[key]; + const remove = R.differenceWith(isMatchingRecord, oldRecords, newRecords); + const add = R.differenceWith(isMatchingRecord, newRecords, oldRecords) + .filter(r => !['www', '@'].includes(r.name)); - 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: [] }); + return { add, remove }; }; const print = fn => x => log(fn(x)) || x; @@ -108,8 +87,9 @@ const getDomainService = ({ cpanel }) => { recordToZone, print(({ name }) => `Adding zone for ${name}...`), )); - const editZoneRecord = lazyTask(R.compose( - cpanel.zone.edit, + const removeZoneRecord = lazyTask(R.compose( + cpanel.zone.remove, + R.pick(['line']), recordToZone, print(({ name }) => `Editing zone for ${name}...`), )); @@ -118,8 +98,9 @@ const getDomainService = ({ cpanel }) => { recordToRedirection, print(({ name }) => `Adding redirection for ${name}`), )); - const editRedirection = lazyTask(R.compose( - cpanel.redirection.edit, + const removeRedirection = lazyTask(R.compose( + cpanel.redirection.remove, + R.pick(['domain']), recordToRedirection, print(({ name }) => `Editing redirection for ${name}`), )); @@ -140,17 +121,20 @@ const getDomainService = ({ cpanel }) => { [ R.propEq('type', 'URL'), addRedirection ], [ R.T, addZoneRecord ], ]))); - const editRecords = R.compose(batchLazyTasks(BATCH_SIZE), R.map(R.cond([ - [ R.propEq('type', 'URL'), editRedirection ], - [ R.T, editZoneRecord ], + const removeRecords = R.compose(batchLazyTasks(BATCH_SIZE), R.map(R.cond([ + [ R.propEq('type', 'URL'), removeRedirection ], + [ R.T, removeZoneRecord ], ]))); const updateHosts = async hosts => { const remoteHostList = await getHosts(); - const { add, edit } = diffRecords(remoteHostList, hosts); + const { add, remove } = diffRecords(remoteHostList, hosts); - await executeBatch(addRecords(add).concat(editRecords(edit))); - return { additions: add.length, edits: edit.length }; + await executeBatch([ + ...removeRecords(remove), + ...addRecords(add), + ]); + return { added: add.length, removed: remove.length }; }; return { getHosts, updateHosts };