From d8837a14ad5d21470ad1cff847eb5f8039ccd3e2 Mon Sep 17 00:00:00 2001 From: Adam Smith Date: Sat, 27 Jan 2018 21:58:05 -0800 Subject: [PATCH] add tests for DnsMadeEasy provider --- octodns/provider/dnsmadeeasy.py | 10 +- tests/fixtures/dnsmadeeasy-domains.json | 16 ++ tests/fixtures/dnsmadeeasy-records.json | 312 +++++++++++++++++++++ tests/test_octodns_provider_dnsmadeeasy.py | 202 +++++++++++++ 4 files changed, 531 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/dnsmadeeasy-domains.json create mode 100644 tests/fixtures/dnsmadeeasy-records.json create mode 100644 tests/test_octodns_provider_dnsmadeeasy.py diff --git a/octodns/provider/dnsmadeeasy.py b/octodns/provider/dnsmadeeasy.py index 8af45a4..550aa0b 100644 --- a/octodns/provider/dnsmadeeasy.py +++ b/octodns/provider/dnsmadeeasy.py @@ -34,12 +34,6 @@ class DnsMadeEasyClientUnauthorized(DnsMadeEasyClientException): super(DnsMadeEasyClientUnauthorized, self).__init__('Unauthorized') -class DnsMadeEasyClientForbidden(DnsMadeEasyClientException): - - def __init__(self): - super(DnsMadeEasyClientForbidden, self).__init__('Forbidden') - - class DnsMadeEasyClientNotFound(DnsMadeEasyClientException): def __init__(self): @@ -81,10 +75,8 @@ class DnsMadeEasyClient(object): params=params, json=data) if resp.status_code == 400: raise DnsMadeEasyClientBadRequest(resp) - if resp.status_code == 401: + if resp.status_code in [401, 403]: raise DnsMadeEasyClientUnauthorized() - if resp.status_code == 403: - raise DnsMadeEasyClientForbidden() if resp.status_code == 404: raise DnsMadeEasyClientNotFound() resp.raise_for_status() diff --git a/tests/fixtures/dnsmadeeasy-domains.json b/tests/fixtures/dnsmadeeasy-domains.json new file mode 100644 index 0000000..de7f7db --- /dev/null +++ b/tests/fixtures/dnsmadeeasy-domains.json @@ -0,0 +1,16 @@ +{ + "totalPages": 1, + "totalRecords": 1, + "data": [{ + "created": 1511740800000, + "folderId": 1990, + "gtdEnabled": false, + "pendingActionId": 0, + "updated": 1511766661574, + "processMulti": false, + "activeThirdParties": [], + "name": "unit.tests", + "id": 123123 + }], + "page": 0 +} \ No newline at end of file diff --git a/tests/fixtures/dnsmadeeasy-records.json b/tests/fixtures/dnsmadeeasy-records.json new file mode 100644 index 0000000..22fbc2f --- /dev/null +++ b/tests/fixtures/dnsmadeeasy-records.json @@ -0,0 +1,312 @@ +{ + "totalPages": 1, + "totalRecords": 21, + "data": [{ + "failover": false, + "monitor": false, + "sourceId": 123123, + "caaType": "issue", + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "issuerCritical": 0, + "ttl": 3600, + "source": 1, + "name": "", + "value": "\"ca.unit.tests\"", + "id": 11189874, + "type": "CAA" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 300, + "source": 1, + "name": "", + "value": "1.2.3.4", + "id": 11189875, + "type": "A" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 300, + "source": 1, + "name": "", + "value": "1.2.3.5", + "id": 11189876, + "type": "A" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "weight": 20, + "source": 1, + "name": "_srv._tcp", + "value": "foo-1.unit.tests.", + "id": 11189877, + "priority": 10, + "type": "SRV", + "port": 30 + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "weight": 20, + "source": 1, + "name": "_srv._tcp", + "value": "foo-2.unit.tests.", + "id": 11189878, + "priority": 12, + "type": "SRV", + "port": 30 + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "source": 1, + "name": "aaaa", + "value": "2601:644:500:e210:62f8:1dff:feb8:947a", + "id": 11189879, + "type": "AAAA" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 300, + "source": 1, + "name": "cname", + "value": "", + "id": 11189880, + "type": "CNAME" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 3600, + "source": 1, + "name": "included", + "value": "", + "id": 11189881, + "type": "CNAME" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "mxLevel": 30, + "ttl": 300, + "source": 1, + "name": "mx", + "value": "smtp-3.unit.tests.", + "id": 11189882, + "type": "MX" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "mxLevel": 20, + "ttl": 300, + "source": 1, + "name": "mx", + "value": "smtp-2.unit.tests.", + "id": 11189883, + "type": "MX" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "mxLevel": 10, + "ttl": 300, + "source": 1, + "name": "mx", + "value": "smtp-4.unit.tests.", + "id": 11189884, + "type": "MX" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "mxLevel": 40, + "ttl": 300, + "source": 1, + "name": "mx", + "value": "smtp-1.unit.tests.", + "id": 11189885, + "type": "MX" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "source": 1, + "name": "spf", + "value": "\"v=spf1 ip4:192.168.0.1/16-all\"", + "id": 11189886, + "type": "SPF" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "source": 1, + "name": "txt", + "value": "\"Bah bah black sheep\"", + "id": 11189887, + "type": "TXT" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "source": 1, + "name": "txt", + "value": "\"have you any wool.\"", + "id": 11189888, + "type": "TXT" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 600, + "source": 1, + "name": "txt", + "value": "\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\"", + "id": 11189889, + "type": "TXT" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 3600, + "source": 1, + "name": "under", + "value": "ns1.unit.tests.", + "id": 11189890, + "type": "NS" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 3600, + "source": 1, + "name": "under", + "value": "ns2", + "id": 11189891, + "type": "NS" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 300, + "source": 1, + "name": "www", + "value": "2.2.3.6", + "id": 11189892, + "type": "A" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 300, + "source": 1, + "name": "www.sub", + "value": "2.2.3.6", + "id": 11189893, + "type": "A" + }, { + "failover": false, + "monitor": false, + "sourceId": 123123, + "dynamicDns": false, + "failed": false, + "gtdLocation": "DEFAULT", + "hardLink": false, + "ttl": 300, + "source": 1, + "name": "ptr", + "value": "foo.bar.com.", + "id": 11189894, + "type": "PTR" + }], + "page": 0 +} \ No newline at end of file diff --git a/tests/test_octodns_provider_dnsmadeeasy.py b/tests/test_octodns_provider_dnsmadeeasy.py new file mode 100644 index 0000000..576b8f0 --- /dev/null +++ b/tests/test_octodns_provider_dnsmadeeasy.py @@ -0,0 +1,202 @@ +# +# +# + + +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from mock import Mock, call +from os.path import dirname, join +from requests import HTTPError +from requests_mock import ANY, mock as requests_mock +from unittest import TestCase + +from octodns.record import Record +from octodns.provider.dnsmadeeasy import DnsMadeEasyClientNotFound, \ + DnsMadeEasyProvider +from octodns.provider.yaml import YamlProvider +from octodns.zone import Zone + +import json + + +class TestDnsMadeEasyProvider(TestCase): + expected = Zone('unit.tests.', []) + source = YamlProvider('test', join(dirname(__file__), 'config')) + source.populate(expected) + + # Our test suite differs a bit, add our NS and remove the simple one + expected.add_record(Record.new(expected, 'under', { + 'ttl': 3600, + 'type': 'NS', + 'values': [ + 'ns1.unit.tests.', + 'ns2.unit.tests.', + ] + })) + for record in list(expected.records): + if record.name == 'sub' and record._type == 'NS': + expected._remove_record(record) + break + + def test_populate(self): + provider = DnsMadeEasyProvider('test', 'api', 'secret') + + # Bad auth + with requests_mock() as mock: + mock.get(ANY, status_code=401, + text='{"error": ["API key not found"]}') + + with self.assertRaises(Exception) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals('Unauthorized', ctx.exception.message) + + # Bad request + with requests_mock() as mock: + mock.get(ANY, status_code=400, + text='{"error": ["Rate limit exceeded"]}') + + with self.assertRaises(Exception) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals('\n - Rate limit exceeded', + ctx.exception.message) + + # General error + with requests_mock() as mock: + mock.get(ANY, status_code=502, text='Things caught fire') + + with self.assertRaises(HTTPError) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(502, ctx.exception.response.status_code) + + # Non-existant zone doesn't populate anything + with requests_mock() as mock: + mock.get(ANY, status_code=404, + text='') + + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(set(), zone.records) + + # No diffs == no changes + with requests_mock() as mock: + base = 'https://api.dnsmadeeasy.com/V2.0/dns/managed' + with open('tests/fixtures/dnsmadeeasy-domains.json') as fh: + mock.get('{}{}'.format(base, '/'), text=fh.read()) + with open('tests/fixtures/dnsmadeeasy-records.json') as fh: + mock.get('{}{}'.format(base, '/123123/records'), + text=fh.read()) + + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(13, len(zone.records)) + changes = self.expected.changes(zone, provider) + self.assertEquals(0, len(changes)) + + # 2nd populate makes no network calls/all from cache + again = Zone('unit.tests.', []) + provider.populate(again) + self.assertEquals(13, len(again.records)) + + # bust the cache + del provider._zone_records[zone.name] + + def test_apply(self): + # Create provider with sandbox enabled + provider = DnsMadeEasyProvider('test', 'api', 'secret', True) + + resp = Mock() + resp.json = Mock() + provider._client._request = Mock(return_value=resp) + + with open('tests/fixtures/dnsmadeeasy-domains.json') as fh: + domains = json.load(fh) + + # non-existant domain, create everything + resp.json.side_effect = [ + DnsMadeEasyClientNotFound, # no zone in populate + DnsMadeEasyClientNotFound, # no domain during apply + domains + ] + plan = provider.plan(self.expected) + + # No root NS, no ignored, no excluded, no unsupported + n = len(self.expected.records) - 5 + self.assertEquals(n, len(plan.changes)) + self.assertEquals(n, provider.apply(plan)) + + provider._client._request.assert_has_calls([ + # created the domain + call('POST', '/', data={'name': 'unit.tests'}), + # get all domains to build the cache + call('GET', '/'), + # created at least one of the record with expected data + call('POST', '/123123/records', data={ + 'name': '_srv._tcp', + 'weight': 20, + 'value': 'foo-1.unit.tests.', + 'priority': 10, + 'ttl': 600, + 'type': 'SRV', + 'port': 30 + }), + ]) + self.assertEquals(25, provider._client._request.call_count) + + provider._client._request.reset_mock() + + # delete 1 and update 1 + provider._client.records = Mock(return_value=[ + { + 'id': 11189897, + 'name': 'www', + 'value': '1.2.3.4', + 'ttl': 300, + 'type': 'A', + }, + { + 'id': 11189898, + 'name': 'www', + 'value': '2.2.3.4', + 'ttl': 300, + 'type': 'A', + }, + { + 'id': 11189899, + 'name': 'ttl', + 'value': '3.2.3.4', + 'ttl': 600, + 'type': 'A', + } + ]) + + # Domain exists, we don't care about return + resp.json.side_effect = ['{}'] + + wanted = Zone('unit.tests.', []) + wanted.add_record(Record.new(wanted, 'ttl', { + 'ttl': 300, + 'type': 'A', + 'value': '3.2.3.4' + })) + + plan = provider.plan(wanted) + self.assertEquals(2, len(plan.changes)) + self.assertEquals(2, provider.apply(plan)) + + # recreate for update, and deletes for the 2 parts of the other + provider._client._request.assert_has_calls([ + call('POST', '/123123/records', data={ + 'value': '3.2.3.4', + 'type': 'A', + 'name': 'ttl', + 'ttl': 300 + }), + call('DELETE', '/123123/records/11189899'), + call('DELETE', '/123123/records/11189897'), + call('DELETE', '/123123/records/11189898') + ], any_order=True)