| @ -1,291 +1,419 @@ | |||
| # | |||
| # | |||
| # | |||
| from __future__ import absolute_import, division, print_function, \ | |||
| unicode_literals | |||
| from __future__ import (absolute_import, division, print_function, | |||
| unicode_literals) | |||
| from operator import itemgetter | |||
| from os.path import dirname, join | |||
| from six import text_type | |||
| from suds import WebFault | |||
| from mock import patch | |||
| from unittest import TestCase | |||
| from unittest.mock import Mock, patch | |||
| from octodns.provider.transip import TransipProvider | |||
| from octodns.provider.transip import (DNSEntry, TransipConfigException, | |||
| TransipException, | |||
| TransipNewZoneException, TransipProvider, | |||
| _entries_for, _parse_to_fqdn) | |||
| from octodns.provider.yaml import YamlProvider | |||
| from octodns.zone import Zone | |||
| from transip.service.objects import DnsEntry | |||
| class MockFault(object): | |||
| faultstring = "" | |||
| faultcode = "" | |||
| def __init__(self, code, string, *args, **kwargs): | |||
| self.faultstring = string | |||
| self.faultcode = code | |||
| class MockResponse(object): | |||
| dnsEntries = [] | |||
| class MockDomainService(object): | |||
| def __init__(self, *args, **kwargs): | |||
| self.mockupEntries = [] | |||
| self.throw_auth_fault = False | |||
| from transip.exceptions import TransIPHTTPError | |||
| def mockup(self, records): | |||
| provider = TransipProvider('', '', '') | |||
| def make_expected(): | |||
| expected = Zone("unit.tests.", []) | |||
| source = YamlProvider("test", join(dirname(__file__), "config")) | |||
| source.populate(expected) | |||
| return expected | |||
| _dns_entries = [] | |||
| for record in records: | |||
| if record._type in provider.SUPPORTS: | |||
| entries_for = getattr(provider, | |||
| '_entries_for_{}'.format(record._type)) | |||
| # Root records have '@' as name | |||
| name = record.name | |||
| if name == '': | |||
| name = provider.ROOT_RECORD | |||
| def make_mock(): | |||
| zone = make_expected() | |||
| _dns_entries.extend(entries_for(name, record)) | |||
| # Turn Zone.records into TransIP DNSEntries | |||
| api_entries = [] | |||
| for record in zone.records: | |||
| if record._type in TransipProvider.SUPPORTS: | |||
| # Root records have '@' as name | |||
| name = record.name | |||
| if name == "": | |||
| name = TransipProvider.ROOT_RECORD | |||
| # Add a non-supported type | |||
| # so it triggers the "is supported" (transip.py:115) check and | |||
| # give 100% code coverage | |||
| _dns_entries.append( | |||
| DnsEntry('@', '3600', 'BOGUS', 'ns01.transip.nl.')) | |||
| api_entries.extend(_entries_for(name, record)) | |||
| self.mockupEntries = _dns_entries | |||
| # Append bogus entry so test for record type not being in SUPPORTS is | |||
| # executed. For 100% test coverage. | |||
| api_entries.append(DNSEntry("@", "3600", "BOGUS", "ns.transip.nl")) | |||
| # Skips authentication layer and returns the entries loaded by "Mockup" | |||
| def get_info(self, domain_name): | |||
| return zone, api_entries | |||
| if self.throw_auth_fault: | |||
| self.raiseInvalidAuth() | |||
| # Special 'domain' to trigger error | |||
| if str(domain_name) == str('notfound.unit.tests'): | |||
| self.raiseZoneNotFound() | |||
| def make_mock_empty(): | |||
| mock = Mock() | |||
| mock.return_value.domains.get.return_value.dns.list.return_value = [] | |||
| return mock | |||
| result = MockResponse() | |||
| result.dnsEntries = self.mockupEntries | |||
| return result | |||
| def set_dns_entries(self, domain_name, dns_entries): | |||
| # Special 'domain' to trigger error | |||
| if str(domain_name) == str('failsetdns.unit.tests'): | |||
| self.raiseSaveError() | |||
| return True | |||
| def raiseZoneNotFound(self): | |||
| fault = MockFault(str('102'), '102 is zone not found') | |||
| document = {} | |||
| raise WebFault(fault, document) | |||
| def raiseInvalidAuth(self): | |||
| fault = MockFault(str('200'), '200 is invalid auth') | |||
| document = {} | |||
| raise WebFault(fault, document) | |||
| def raiseSaveError(self): | |||
| fault = MockFault(str('200'), '202 random error') | |||
| document = {} | |||
| raise WebFault(fault, document) | |||
| def make_failing_mock(response_code): | |||
| mock = Mock() | |||
| mock.return_value.domains.get.side_effect = [ | |||
| TransIPHTTPError(str(response_code), response_code) | |||
| ] | |||
| return mock | |||
| class TestTransipProvider(TestCase): | |||
| bogus_key = str("""-----BEGIN RSA PRIVATE KEY----- | |||
| MIIEowIBAAKCAQEA0U5HGCkLrz423IyUf3u4cKN2WrNz1x5KNr6PvH2M/zxas+zB | |||
| elbxkdT3AQ+wmfcIvOuTmFRTHv35q2um1aBrPxVw+2s+lWo28VwIRttwIB1vIeWu | |||
| lSBnkEZQRLyPI2tH0i5QoMX4CVPf9rvij3Uslimi84jdzDfPFIh6jZ6C8nLipOTG | |||
| 0IMhge1ofVfB0oSy5H+7PYS2858QLAf5ruYbzbAxZRivS402wGmQ0d0Lc1KxraAj | |||
| kiMM5yj/CkH/Vm2w9I6+tLFeASE4ub5HCP5G/ig4dbYtqZMQMpqyAbGxd5SOVtyn | |||
| UHagAJUxf8DT3I8PyjEHjxdOPUsxNyRtepO/7QIDAQABAoIBAQC7fiZ7gxE/ezjD | |||
| 2n6PsHFpHVTBLS2gzzZl0dCKZeFvJk6ODJDImaeuHhrh7X8ifMNsEI9XjnojMhl8 | |||
| MGPzy88mZHugDNK0H8B19x5G8v1/Fz7dG5WHas660/HFkS+b59cfdXOugYiOOn9O | |||
| 08HBBpLZNRUOmVUuQfQTjapSwGLG8PocgpyRD4zx0LnldnJcqYCxwCdev+AAsPnq | |||
| ibNtOd/MYD37w9MEGcaxLE8wGgkv8yd97aTjkgE+tp4zsM4QE4Rag133tsLLNznT | |||
| 4Qr/of15M3NW/DXq/fgctyRcJjZpU66eCXLCz2iRTnLyyxxDC2nwlxKbubV+lcS0 | |||
| S4hbfd/BAoGBAO8jXxEaiybR0aIhhSR5esEc3ymo8R8vBN3ZMJ+vr5jEPXr/ZuFj | |||
| /R4cZ2XV3VoQJG0pvIOYVPZ5DpJM7W+zSXtJ/7bLXy4Bnmh/rc+YYgC+AXQoLSil | |||
| iD2OuB2xAzRAK71DVSO0kv8gEEXCersPT2i6+vC2GIlJvLcYbOdRKWGxAoGBAOAQ | |||
| aJbRLtKujH+kMdoMI7tRlL8XwI+SZf0FcieEu//nFyerTePUhVgEtcE+7eQ7hyhG | |||
| fIXUFx/wALySoqFzdJDLc8U8pTLhbUaoLOTjkwnCTKQVprhnISqQqqh/0U5u47IE | |||
| RWzWKN6OHb0CezNTq80Dr6HoxmPCnJHBHn5LinT9AoGAQSpvZpbIIqz8pmTiBl2A | |||
| QQ2gFpcuFeRXPClKYcmbXVLkuhbNL1BzEniFCLAt4LQTaRf9ghLJ3FyCxwVlkpHV | |||
| zV4N6/8hkcTpKOraL38D/dXJSaEFJVVuee/hZl3tVJjEEpA9rDwx7ooLRSdJEJ6M | |||
| ciq55UyKBSdt4KssSiDI2RECgYBL3mJ7xuLy5bWfNsrGiVvD/rC+L928/5ZXIXPw | |||
| 26oI0Yfun7ulDH4GOroMcDF/GYT/Zzac3h7iapLlR0WYI47xxGI0A//wBZLJ3QIu | |||
| krxkDo2C9e3Y/NqnHgsbOQR3aWbiDT4wxydZjIeXS3LKA2fl6Hyc90PN3cTEOb8I | |||
| hq2gRQKBgEt0SxhhtyB93SjgTzmUZZ7PiEf0YJatfM6cevmjWHexrZH+x31PB72s | |||
| fH2BQyTKKzoCLB1k/6HRaMnZdrWyWSZ7JKz3AHJ8+58d0Hr8LTrzDM1L6BbjeDct | |||
| N4OiVz1I3rbZGYa396lpxO6ku8yCglisL1yrSP6DdEUp66ntpKVd | |||
| -----END RSA PRIVATE KEY-----""") | |||
| def make_expected(self): | |||
| expected = Zone('unit.tests.', []) | |||
| source = YamlProvider('test', join(dirname(__file__), 'config')) | |||
| source.populate(expected) | |||
| return expected | |||
| @patch('octodns.provider.transip.TransipProvider._domain_service', | |||
| return_value=MockDomainService()) | |||
| def test_init(self, _): | |||
| # No key nor key_file | |||
| with self.assertRaises(Exception) as ctx: | |||
| TransipProvider('test', 'unittest') | |||
| bogus_key = "-----BEGIN RSA PRIVATE KEY-----Z-----END RSA PRIVATE KEY-----" | |||
| self.assertEquals( | |||
| str('Missing `key` or `key_file` parameter in config'), | |||
| str(ctx.exception)) | |||
| # With key | |||
| TransipProvider('test', 'unittest', key=self.bogus_key) | |||
| # With key_file | |||
| TransipProvider('test', 'unittest', key_file='/fake/path') | |||
| @patch("octodns.provider.transip.TransIP", make_mock_empty()) | |||
| def test_init(self): | |||
| with self.assertRaises(TransipConfigException) as ctx: | |||
| TransipProvider("test", "unittest") | |||
| @patch('suds.client.Client.__init__', new=lambda *args, **kwargs: None) | |||
| def test_domain_service(self): | |||
| # Special case smoke test for DomainService to get coverage | |||
| TransipProvider('test', 'unittest', key=self.bogus_key) | |||
| self.assertEquals( | |||
| "Missing `key` or `key_file` parameter in config", | |||
| str(ctx.exception), | |||
| ) | |||
| @patch('octodns.provider.transip.TransipProvider._domain_service', | |||
| return_value=MockDomainService()) | |||
| def test_populate(self, _): | |||
| _expected = self.make_expected() | |||
| # Those should work | |||
| TransipProvider("test", "unittest", key=self.bogus_key) | |||
| TransipProvider("test", "unittest", key_file="/fake/path") | |||
| @patch("octodns.provider.transip.TransIP", make_failing_mock(401)) | |||
| def test_populate_unauthenticated(self): | |||
| # Unhappy Plan - Not authenticated | |||
| # Live test against API, will fail in an unauthorized error | |||
| with self.assertRaises(WebFault) as ctx: | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| provider._client.throw_auth_fault = True | |||
| zone = Zone('unit.tests.', []) | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| zone = Zone("unit.tests.", []) | |||
| with self.assertRaises(TransipException): | |||
| provider.populate(zone, True) | |||
| self.assertEquals(str('WebFault'), | |||
| str(ctx.exception.__class__.__name__)) | |||
| self.assertEquals(str('200'), ctx.exception.fault.faultcode) | |||
| # No more auth problems | |||
| provider._client.throw_auth_fault = False | |||
| @patch("octodns.provider.transip.TransIP", make_failing_mock(404)) | |||
| def test_populate_new_zone_as_target(self): | |||
| # Unhappy Plan - Zone does not exists | |||
| # Will trigger an exception if provider is used as a target for a | |||
| # non-existing zone | |||
| with self.assertRaises(Exception) as ctx: | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| zone = Zone('notfound.unit.tests.', []) | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| zone = Zone("notfound.unit.tests.", []) | |||
| with self.assertRaises(TransipNewZoneException): | |||
| provider.populate(zone, True) | |||
| self.assertEquals(str('TransipNewZoneException'), | |||
| str(ctx.exception.__class__.__name__)) | |||
| self.assertEquals( | |||
| 'populate: (102) Transip used as target' + | |||
| ' for non-existing zone: notfound.unit.tests.', | |||
| text_type(ctx.exception)) | |||
| @patch("octodns.provider.transip.TransIP", make_mock_empty()) | |||
| def test_populate_new_zone_not_target(self): | |||
| # Happy Plan - Zone does not exists | |||
| # Won't trigger an exception if provider is NOT used as a target for a | |||
| # non-existing zone. | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| zone = Zone('notfound.unit.tests.', []) | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| zone = Zone("notfound.unit.tests.", []) | |||
| provider.populate(zone, False) | |||
| # Happy Plan - Populate with mockup records | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| provider._client.mockup(_expected.records) | |||
| zone = Zone('unit.tests.', []) | |||
| @patch("octodns.provider.transip.TransIP", make_failing_mock(404)) | |||
| def test_populate_zone_does_not_exist(self): | |||
| # Happy Plan - Zone does not exists | |||
| # Won't trigger an exception if provider is NOT used as a target for a | |||
| # non-existing zone. | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| zone = Zone("notfound.unit.tests.", []) | |||
| provider.populate(zone, False) | |||
| # Transip allows relative values for types like cname, mx. | |||
| # Test is these are correctly appended with the domain | |||
| provider._currentZone = zone | |||
| self.assertEquals("www.unit.tests.", provider._parse_to_fqdn("www")) | |||
| self.assertEquals("www.unit.tests.", | |||
| provider._parse_to_fqdn("www.unit.tests.")) | |||
| self.assertEquals("www.sub.sub.sub.unit.tests.", | |||
| provider._parse_to_fqdn("www.sub.sub.sub")) | |||
| self.assertEquals("unit.tests.", | |||
| provider._parse_to_fqdn("@")) | |||
| @patch("octodns.provider.transip.TransIP") | |||
| def test_populate_zone_exists_not_target(self, mock_client): | |||
| # Happy Plan - Populate | |||
| source_zone, api_records = make_mock() | |||
| mock_client.return_value.domains.get.return_value.dns.list. \ | |||
| return_value = api_records | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| zone = Zone("unit.tests.", []) | |||
| exists = provider.populate(zone, False) | |||
| self.assertTrue(exists, "populate should return True") | |||
| # Due to the implementation of Record._equality_tuple() we can't do a | |||
| # normal compare, as that ingores ttl's for example. We therefor use | |||
| # the __repr__ to compare. We do need to filter out `.geo` attributes | |||
| # that Transip doesn't support. | |||
| expected = set() | |||
| for r in source_zone.records: | |||
| if r._type in TransipProvider.SUPPORTS: | |||
| if hasattr(r, "geo"): | |||
| r.geo = None | |||
| expected.add(r.__repr__()) | |||
| self.assertEqual({r.__repr__() for r in zone.records}, expected) | |||
| @patch("octodns.provider.transip.TransIP", make_mock_empty()) | |||
| def test_populate_zone_exists_as_target(self): | |||
| # Happy Plan - Even if the zone has no records the zone should exist | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| zone = Zone('unit.tests.', []) | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| zone = Zone("unit.tests.", []) | |||
| exists = provider.populate(zone, True) | |||
| self.assertTrue(exists, 'populate should return true') | |||
| return | |||
| @patch('octodns.provider.transip.TransipProvider._domain_service', | |||
| return_value=MockDomainService()) | |||
| def test_plan(self, _): | |||
| _expected = self.make_expected() | |||
| # Test Happy plan, only create | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| plan = provider.plan(_expected) | |||
| self.assertEqual(15, plan.change_counts['Create']) | |||
| self.assertEqual(0, plan.change_counts['Update']) | |||
| self.assertEqual(0, plan.change_counts['Delete']) | |||
| return | |||
| @patch('octodns.provider.transip.TransipProvider._domain_service', | |||
| return_value=MockDomainService()) | |||
| def test_apply(self, _): | |||
| _expected = self.make_expected() | |||
| # Test happy flow. Create all supoorted records | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| plan = provider.plan(_expected) | |||
| self.assertEqual(15, len(plan.changes)) | |||
| changes = provider.apply(plan) | |||
| self.assertEqual(changes, len(plan.changes)) | |||
| self.assertTrue(exists, "populate should return True") | |||
| @patch("octodns.provider.transip.TransIP", make_mock_empty()) | |||
| def test_plan(self): | |||
| # Test happy plan, only create | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| plan = provider.plan(make_expected()) | |||
| self.assertIsNotNone(plan) | |||
| self.assertEqual(15, plan.change_counts["Create"]) | |||
| self.assertEqual(0, plan.change_counts["Update"]) | |||
| self.assertEqual(0, plan.change_counts["Delete"]) | |||
| @patch("octodns.provider.transip.TransIP") | |||
| def test_apply(self, client_mock): | |||
| # Test happy flow. Create all supported records | |||
| domain_mock = Mock() | |||
| client_mock.return_value.domains.get.return_value = domain_mock | |||
| domain_mock.dns.list.return_value = [] | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| plan = provider.plan(make_expected()) | |||
| self.assertIsNotNone(plan) | |||
| provider.apply(plan) | |||
| domain_mock.dns.replace.assert_called_once() | |||
| # These are the supported ones from tests/config/unit.test.yaml | |||
| expected_entries = [ | |||
| { | |||
| "name": "ignored", | |||
| "expire": 3600, | |||
| "type": "A", | |||
| "content": "9.9.9.9", | |||
| }, | |||
| { | |||
| "name": "@", | |||
| "expire": 3600, | |||
| "type": "CAA", | |||
| "content": "0 issue ca.unit.tests", | |||
| }, | |||
| { | |||
| "name": "sub", | |||
| "expire": 3600, | |||
| "type": "NS", | |||
| "content": "6.2.3.4.", | |||
| }, | |||
| { | |||
| "name": "sub", | |||
| "expire": 3600, | |||
| "type": "NS", | |||
| "content": "7.2.3.4.", | |||
| }, | |||
| { | |||
| "name": "spf", | |||
| "expire": 600, | |||
| "type": "SPF", | |||
| "content": "v=spf1 ip4:192.168.0.1/16-all", | |||
| }, | |||
| { | |||
| "name": "_srv._tcp", | |||
| "expire": 600, | |||
| "type": "SRV", | |||
| "content": "10 20 30 foo-1.unit.tests.", | |||
| }, | |||
| { | |||
| "name": "_srv._tcp", | |||
| "expire": 600, | |||
| "type": "SRV", | |||
| "content": "12 20 30 foo-2.unit.tests.", | |||
| }, | |||
| { | |||
| "name": "_pop3._tcp", | |||
| "expire": 600, | |||
| "type": "SRV", | |||
| "content": "0 0 0 .", | |||
| }, | |||
| { | |||
| "name": "_imap._tcp", | |||
| "expire": 600, | |||
| "type": "SRV", | |||
| "content": "0 0 0 .", | |||
| }, | |||
| { | |||
| "name": "txt", | |||
| "expire": 600, | |||
| "type": "TXT", | |||
| "content": "Bah bah black sheep", | |||
| }, | |||
| { | |||
| "name": "txt", | |||
| "expire": 600, | |||
| "type": "TXT", | |||
| "content": "have you any wool.", | |||
| }, | |||
| { | |||
| "name": "txt", | |||
| "expire": 600, | |||
| "type": "TXT", | |||
| "content": ( | |||
| "v=DKIM1;k=rsa;s=email;h=sha256;" | |||
| "p=A/kinda+of/long/string+with+numb3rs" | |||
| ), | |||
| }, | |||
| {"name": "@", "expire": 3600, "type": "NS", "content": "6.2.3.4."}, | |||
| {"name": "@", "expire": 3600, "type": "NS", "content": "7.2.3.4."}, | |||
| { | |||
| "name": "cname", | |||
| "expire": 300, | |||
| "type": "CNAME", | |||
| "content": "unit.tests.", | |||
| }, | |||
| { | |||
| "name": "excluded", | |||
| "expire": 3600, | |||
| "type": "CNAME", | |||
| "content": "unit.tests.", | |||
| }, | |||
| { | |||
| "name": "www.sub", | |||
| "expire": 300, | |||
| "type": "A", | |||
| "content": "2.2.3.6", | |||
| }, | |||
| { | |||
| "name": "included", | |||
| "expire": 3600, | |||
| "type": "CNAME", | |||
| "content": "unit.tests.", | |||
| }, | |||
| { | |||
| "name": "mx", | |||
| "expire": 300, | |||
| "type": "MX", | |||
| "content": "10 smtp-4.unit.tests.", | |||
| }, | |||
| { | |||
| "name": "mx", | |||
| "expire": 300, | |||
| "type": "MX", | |||
| "content": "20 smtp-2.unit.tests.", | |||
| }, | |||
| { | |||
| "name": "mx", | |||
| "expire": 300, | |||
| "type": "MX", | |||
| "content": "30 smtp-3.unit.tests.", | |||
| }, | |||
| { | |||
| "name": "mx", | |||
| "expire": 300, | |||
| "type": "MX", | |||
| "content": "40 smtp-1.unit.tests.", | |||
| }, | |||
| { | |||
| "name": "aaaa", | |||
| "expire": 600, | |||
| "type": "AAAA", | |||
| "content": "2601:644:500:e210:62f8:1dff:feb8:947a", | |||
| }, | |||
| {"name": "@", "expire": 300, "type": "A", "content": "1.2.3.4"}, | |||
| {"name": "@", "expire": 300, "type": "A", "content": "1.2.3.5"}, | |||
| {"name": "www", "expire": 300, "type": "A", "content": "2.2.3.6"}, | |||
| { | |||
| "name": "@", | |||
| "expire": 3600, | |||
| "type": "SSHFP", | |||
| "content": "1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49", | |||
| }, | |||
| { | |||
| "name": "@", | |||
| "expire": 3600, | |||
| "type": "SSHFP", | |||
| "content": "1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73", | |||
| }, | |||
| ] | |||
| # Unpack from the transip library magic structure... | |||
| seen_entries = [ | |||
| e.__dict__["_attrs"] | |||
| for e in domain_mock.dns.replace.mock_calls[0][1][0] | |||
| ] | |||
| self.assertEqual( | |||
| sorted(seen_entries, key=itemgetter("name", "type", "expire")), | |||
| sorted(expected_entries, key=itemgetter("name", "type", "expire")), | |||
| ) | |||
| @patch("octodns.provider.transip.TransIP") | |||
| def test_apply_unsupported(self, client_mock): | |||
| # This triggers the if supported statement to give 100% code coverage | |||
| domain_mock = Mock() | |||
| client_mock.return_value.domains.get.return_value = domain_mock | |||
| domain_mock.dns.list.return_value = [] | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| plan = provider.plan(make_expected()) | |||
| self.assertIsNotNone(plan) | |||
| # Test apply with only support for A records | |||
| provider.SUPPORTS = set(("A")) | |||
| provider.apply(plan) | |||
| seen_entries = [ | |||
| e.__dict__["_attrs"] | |||
| for e in domain_mock.dns.replace.mock_calls[0][1][0] | |||
| ] | |||
| expected_entries = [ | |||
| { | |||
| "name": "ignored", | |||
| "expire": 3600, | |||
| "type": "A", | |||
| "content": "9.9.9.9", | |||
| }, | |||
| { | |||
| "name": "www.sub", | |||
| "expire": 300, | |||
| "type": "A", | |||
| "content": "2.2.3.6", | |||
| }, | |||
| {"name": "@", "expire": 300, "type": "A", "content": "1.2.3.4"}, | |||
| {"name": "@", "expire": 300, "type": "A", "content": "1.2.3.5"}, | |||
| {"name": "www", "expire": 300, "type": "A", "content": "2.2.3.6"}, | |||
| ] | |||
| self.assertEquals( | |||
| sorted(seen_entries, key=itemgetter("name", "type", "expire")), | |||
| sorted(expected_entries, key=itemgetter("name", "type", "expire")), | |||
| ) | |||
| @patch("octodns.provider.transip.TransIP") | |||
| def test_apply_failure_on_not_found(self, client_mock): | |||
| # Test unhappy flow. Trigger 'not found error' in apply stage | |||
| # This should normally not happen as populate will capture it first | |||
| # but just in case. | |||
| changes = [] # reset changes | |||
| with self.assertRaises(Exception) as ctx: | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| plan = provider.plan(_expected) | |||
| plan.desired.name = 'notfound.unit.tests.' | |||
| changes = provider.apply(plan) | |||
| # Changes should not be set due to an Exception | |||
| self.assertEqual([], changes) | |||
| domain_mock = Mock() | |||
| domain_mock.dns.list.return_value = [] | |||
| client_mock.return_value.domains.get.side_effect = [ | |||
| domain_mock, | |||
| TransIPHTTPError("Not Found", 404), | |||
| ] | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| self.assertEquals(str('WebFault'), | |||
| str(ctx.exception.__class__.__name__)) | |||
| plan = provider.plan(make_expected()) | |||
| self.assertEquals(str('102'), ctx.exception.fault.faultcode) | |||
| with self.assertRaises(TransipException): | |||
| provider.apply(plan) | |||
| @patch("octodns.provider.transip.TransIP") | |||
| def test_apply_failure_on_error(self, client_mock): | |||
| # Test unhappy flow. Trigger a unrecoverable error while saving | |||
| _expected = self.make_expected() # reset expected | |||
| changes = [] # reset changes | |||
| domain_mock = Mock() | |||
| domain_mock.dns.list.return_value = [] | |||
| domain_mock.dns.replace.side_effect = [ | |||
| TransIPHTTPError("Not Found", 500) | |||
| ] | |||
| client_mock.return_value.domains.get.return_value = domain_mock | |||
| provider = TransipProvider("test", "unittest", self.bogus_key) | |||
| plan = provider.plan(make_expected()) | |||
| with self.assertRaises(Exception) as ctx: | |||
| provider = TransipProvider('test', 'unittest', self.bogus_key) | |||
| plan = provider.plan(_expected) | |||
| plan.desired.name = 'failsetdns.unit.tests.' | |||
| changes = provider.apply(plan) | |||
| with self.assertRaises(TransipException): | |||
| provider.apply(plan) | |||
| # Changes should not be set due to an Exception | |||
| self.assertEqual([], changes) | |||
| self.assertEquals(str('TransipException'), | |||
| str(ctx.exception.__class__.__name__)) | |||
| class TestParseFQDN(TestCase): | |||
| def test_parse_fqdn(self): | |||
| zone = Zone("unit.tests.", []) | |||
| self.assertEquals("www.unit.tests.", _parse_to_fqdn("www", zone)) | |||
| self.assertEquals( | |||
| "www.unit.tests.", _parse_to_fqdn("www.unit.tests.", zone) | |||
| ) | |||
| self.assertEquals( | |||
| "www.sub.sub.sub.unit.tests.", | |||
| _parse_to_fqdn("www.sub.sub.sub", zone), | |||
| ) | |||
| self.assertEquals("unit.tests.", _parse_to_fqdn("@", zone)) | |||