Implement black formatting

This commit is contained in:
Ross McFarland
2022-07-04 12:27:39 -07:00
parent 392d8b516f
commit e116d26eec
101 changed files with 6403 additions and 5490 deletions
+6 -6
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from shutil import rmtree
from tempfile import mkdtemp
@@ -15,7 +19,6 @@ from octodns.provider.yaml import YamlProvider
class SimpleSource(object):
def __init__(self, id='test'):
pass
@@ -78,13 +81,11 @@ class DynamicProvider(object):
class NoSshFpProvider(SimpleProvider):
def supports(self, record):
return record._type != 'SSHFP'
class TemporaryDirectory(object):
def __init__(self, delete_on_exit=True):
self.delete_on_exit = delete_on_exit
@@ -100,7 +101,6 @@ class TemporaryDirectory(object):
class WantsConfigProcessor(BaseProcessor):
def __init__(self, name, some_config):
super(WantsConfigProcessor, self).__init__(name)
+6 -6
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
@@ -11,11 +15,8 @@ from octodns.equality import EqualityTupleMixin
class TestEqualityTupleMixin(TestCase):
def test_basics(self):
class Simple(EqualityTupleMixin):
def __init__(self, a, b, c):
self.a = a
self.b = b
@@ -60,7 +61,6 @@ class TestEqualityTupleMixin(TestCase):
self.assertTrue(one >= same)
def test_not_implemented(self):
class MissingMethod(EqualityTupleMixin):
pass
+189 -142
View File
@@ -2,15 +2,23 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from os import environ
from os.path import dirname, join
from octodns import __VERSION__
from octodns.manager import _AggregateTarget, MainThreadExecutor, Manager, \
ManagerException
from octodns.manager import (
_AggregateTarget,
MainThreadExecutor,
Manager,
ManagerException,
)
from octodns.processor.base import BaseProcessor
from octodns.record import Create, Delete, Record
from octodns.yaml import safe_load
@@ -19,8 +27,14 @@ from octodns.zone import Zone
from unittest import TestCase
from unittest.mock import MagicMock, patch
from helpers import DynamicProvider, GeoProvider, NoSshFpProvider, \
PlannableProvider, SimpleProvider, TemporaryDirectory
from helpers import (
DynamicProvider,
GeoProvider,
NoSshFpProvider,
PlannableProvider,
SimpleProvider,
TemporaryDirectory,
)
config_dir = join(dirname(__file__), 'config')
@@ -30,7 +44,6 @@ def get_config_filename(which):
class TestManager(TestCase):
def test_missing_provider_class(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('missing-provider-class.yaml')).sync()
@@ -43,14 +56,16 @@ class TestManager(TestCase):
def test_bad_provider_class_module(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-provider-class-module.yaml')) \
.sync()
Manager(
get_config_filename('bad-provider-class-module.yaml')
).sync()
self.assertTrue('Unknown provider class' in str(ctx.exception))
def test_bad_provider_class_no_module(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-provider-class-no-module.yaml')) \
.sync()
Manager(
get_config_filename('bad-provider-class-no-module.yaml')
).sync()
self.assertTrue('Unknown provider class' in str(ctx.exception))
def test_missing_provider_config(self):
@@ -66,141 +81,160 @@ class TestManager(TestCase):
def test_missing_source(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['missing.sources.'])
Manager(get_config_filename('provider-problems.yaml')).sync(
['missing.sources.']
)
self.assertTrue('missing sources' in str(ctx.exception))
def test_missing_targets(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['missing.targets.'])
Manager(get_config_filename('provider-problems.yaml')).sync(
['missing.targets.']
)
self.assertTrue('missing targets' in str(ctx.exception))
def test_unknown_source(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['unknown.source.'])
Manager(get_config_filename('provider-problems.yaml')).sync(
['unknown.source.']
)
self.assertTrue('unknown source' in str(ctx.exception))
def test_unknown_target(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['unknown.target.'])
Manager(get_config_filename('provider-problems.yaml')).sync(
['unknown.target.']
)
self.assertTrue('unknown target' in str(ctx.exception))
def test_bad_plan_output_class(self):
with self.assertRaises(ManagerException) as ctx:
name = 'bad-plan-output-missing-class.yaml'
Manager(get_config_filename(name)).sync()
self.assertEqual('plan_output bad is missing class',
str(ctx.exception))
self.assertEqual('plan_output bad is missing class', str(ctx.exception))
def test_bad_plan_output_config(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-plan-output-config.yaml')).sync()
self.assertEqual('Incorrect plan_output config for bad',
str(ctx.exception))
self.assertEqual(
'Incorrect plan_output config for bad', str(ctx.exception)
)
def test_source_only_as_a_target(self):
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['not.targetable.'])
self.assertTrue('does not support targeting' in
str(ctx.exception))
Manager(get_config_filename('provider-problems.yaml')).sync(
['not.targetable.']
)
self.assertTrue('does not support targeting' in str(ctx.exception))
def test_always_dry_run(self):
with TemporaryDirectory() as tmpdir:
environ['YAML_TMP_DIR'] = tmpdir.dirname
tc = Manager(get_config_filename('always-dry-run.yaml')) \
.sync(dry_run=False)
tc = Manager(get_config_filename('always-dry-run.yaml')).sync(
dry_run=False
)
# only the stuff from subzone, unit.tests. is always-dry-run
self.assertEqual(3, tc)
def test_simple(self):
with TemporaryDirectory() as tmpdir:
environ['YAML_TMP_DIR'] = tmpdir.dirname
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False)
tc = Manager(get_config_filename('simple.yaml')).sync(dry_run=False)
self.assertEqual(28, tc)
# try with just one of the zones
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, eligible_zones=['unit.tests.'])
tc = Manager(get_config_filename('simple.yaml')).sync(
dry_run=False, eligible_zones=['unit.tests.']
)
self.assertEqual(22, tc)
# the subzone, with 2 targets
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, eligible_zones=['subzone.unit.tests.'])
tc = Manager(get_config_filename('simple.yaml')).sync(
dry_run=False, eligible_zones=['subzone.unit.tests.']
)
self.assertEqual(6, tc)
# and finally the empty zone
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, eligible_zones=['empty.'])
tc = Manager(get_config_filename('simple.yaml')).sync(
dry_run=False, eligible_zones=['empty.']
)
self.assertEqual(0, tc)
# Again with force
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, force=True)
tc = Manager(get_config_filename('simple.yaml')).sync(
dry_run=False, force=True
)
self.assertEqual(28, tc)
# Again with max_workers = 1
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
.sync(dry_run=False, force=True)
tc = Manager(
get_config_filename('simple.yaml'), max_workers=1
).sync(dry_run=False, force=True)
self.assertEqual(28, tc)
# Include meta
tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
include_meta=True) \
.sync(dry_run=False, force=True)
tc = Manager(
get_config_filename('simple.yaml'),
max_workers=1,
include_meta=True,
).sync(dry_run=False, force=True)
self.assertEqual(33, tc)
def test_eligible_sources(self):
with TemporaryDirectory() as tmpdir:
environ['YAML_TMP_DIR'] = tmpdir.dirname
# Only allow a target that doesn't exist
tc = Manager(get_config_filename('simple.yaml')) \
.sync(eligible_sources=['foo'])
tc = Manager(get_config_filename('simple.yaml')).sync(
eligible_sources=['foo']
)
self.assertEqual(0, tc)
def test_eligible_targets(self):
with TemporaryDirectory() as tmpdir:
environ['YAML_TMP_DIR'] = tmpdir.dirname
# Only allow a target that doesn't exist
tc = Manager(get_config_filename('simple.yaml')) \
.sync(eligible_targets=['foo'])
tc = Manager(get_config_filename('simple.yaml')).sync(
eligible_targets=['foo']
)
self.assertEqual(0, tc)
def test_aliases(self):
with TemporaryDirectory() as tmpdir:
environ['YAML_TMP_DIR'] = tmpdir.dirname
# Alias zones with a valid target.
tc = Manager(get_config_filename('simple-alias-zone.yaml')) \
.sync()
tc = Manager(get_config_filename('simple-alias-zone.yaml')).sync()
self.assertEqual(0, tc)
# Alias zone with an invalid target.
with self.assertRaises(ManagerException) as ctx:
tc = Manager(get_config_filename('unknown-source-zone.yaml')) \
.sync()
self.assertEqual('Invalid alias zone alias.tests.: source zone '
'does-not-exists.tests. does not exist',
str(ctx.exception))
tc = Manager(
get_config_filename('unknown-source-zone.yaml')
).sync()
self.assertEqual(
'Invalid alias zone alias.tests.: source zone '
'does-not-exists.tests. does not exist',
str(ctx.exception),
)
# Alias zone that points to another alias zone.
with self.assertRaises(ManagerException) as ctx:
tc = Manager(get_config_filename('alias-zone-loop.yaml')) \
.sync()
self.assertEqual('Invalid alias zone alias-loop.tests.: source '
'zone alias.tests. is an alias zone',
str(ctx.exception))
tc = Manager(get_config_filename('alias-zone-loop.yaml')).sync()
self.assertEqual(
'Invalid alias zone alias-loop.tests.: source '
'zone alias.tests. is an alias zone',
str(ctx.exception),
)
# Sync an alias without the zone it refers to
with self.assertRaises(ManagerException) as ctx:
tc = Manager(get_config_filename('simple-alias-zone.yaml')) \
.sync(eligible_zones=["alias.tests."])
self.assertEqual('Zone alias.tests. cannot be sync without zone '
'unit.tests. sinced it is aliased',
str(ctx.exception))
tc = Manager(
get_config_filename('simple-alias-zone.yaml')
).sync(eligible_zones=["alias.tests."])
self.assertEqual(
'Zone alias.tests. cannot be sync without zone '
'unit.tests. sinced it is aliased',
str(ctx.exception),
)
def test_compare(self):
with TemporaryDirectory() as tmpdir:
@@ -223,9 +257,9 @@ class TestManager(TestCase):
self.assertEqual(23, len(changes))
# Compound sources with varying support
changes = manager.compare(['in', 'nosshfp'],
['dump'],
'unit.tests.')
changes = manager.compare(
['in', 'nosshfp'], ['dump'], 'unit.tests.'
)
self.assertEqual(22, len(changes))
with self.assertRaises(ManagerException) as ctx:
@@ -249,8 +283,9 @@ class TestManager(TestCase):
# exception
with self.assertRaises(AttributeError) as ctx:
at.FOO
self.assertEqual('_AggregateTarget object has no attribute FOO',
str(ctx.exception))
self.assertEqual(
'_AggregateTarget object has no attribute FOO', str(ctx.exception)
)
self.assertFalse(_AggregateTarget([simple, simple]).SUPPORTS_GEO)
self.assertFalse(_AggregateTarget([simple, geo]).SUPPORTS_GEO)
@@ -263,15 +298,19 @@ class TestManager(TestCase):
self.assertTrue(_AggregateTarget([dynamic, dynamic]).SUPPORTS_DYNAMIC)
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'sshfp', {
'ttl': 60,
'type': 'SSHFP',
'value': {
'algorithm': 1,
'fingerprint_type': 1,
'fingerprint': 'abcdefg',
record = Record.new(
zone,
'sshfp',
{
'ttl': 60,
'type': 'SSHFP',
'value': {
'algorithm': 1,
'fingerprint_type': 1,
'fingerprint': 'abcdefg',
},
},
})
)
self.assertTrue(simple.supports(record))
self.assertFalse(nosshfp.supports(record))
self.assertTrue(_AggregateTarget([simple, simple]).supports(record))
@@ -283,8 +322,9 @@ class TestManager(TestCase):
manager = Manager(get_config_filename('simple.yaml'))
with self.assertRaises(ManagerException) as ctx:
manager.dump('unit.tests.', tmpdir.dirname, False, False,
'nope')
manager.dump(
'unit.tests.', tmpdir.dirname, False, False, 'nope'
)
self.assertEqual('Unknown source: nope', str(ctx.exception))
manager.dump('unit.tests.', tmpdir.dirname, False, False, 'in')
@@ -292,8 +332,9 @@ class TestManager(TestCase):
# make sure this fails with an IOError and not a KeyError when
# trying to find sub zones
with self.assertRaises(IOError):
manager.dump('unknown.zone.', tmpdir.dirname, False, False,
'in')
manager.dump(
'unknown.zone.', tmpdir.dirname, False, False, 'in'
)
def test_dump_empty(self):
with TemporaryDirectory() as tmpdir:
@@ -312,8 +353,7 @@ class TestManager(TestCase):
manager = Manager(get_config_filename('simple-split.yaml'))
with self.assertRaises(ManagerException) as ctx:
manager.dump('unit.tests.', tmpdir.dirname, False, True,
'nope')
manager.dump('unit.tests.', tmpdir.dirname, False, True, 'nope')
self.assertEqual('Unknown source: nope', str(ctx.exception))
manager.dump('unit.tests.', tmpdir.dirname, False, True, 'in')
@@ -321,41 +361,46 @@ class TestManager(TestCase):
# make sure this fails with an OSError and not a KeyError when
# trying to find sub zones
with self.assertRaises(OSError):
manager.dump('unknown.zone.', tmpdir.dirname, False, True,
'in')
manager.dump('unknown.zone.', tmpdir.dirname, False, True, 'in')
def test_validate_configs(self):
Manager(get_config_filename('simple-validate.yaml')).validate_configs()
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('missing-sources.yaml')) \
.validate_configs()
Manager(
get_config_filename('missing-sources.yaml')
).validate_configs()
self.assertTrue('missing sources' in str(ctx.exception))
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
.validate_configs()
Manager(
get_config_filename('unknown-provider.yaml')
).validate_configs()
self.assertTrue('unknown source' in str(ctx.exception))
# Alias zone using an invalid source zone.
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('unknown-source-zone.yaml')) \
.validate_configs()
Manager(
get_config_filename('unknown-source-zone.yaml')
).validate_configs()
self.assertTrue('does not exist' in str(ctx.exception))
# Alias zone that points to another alias zone.
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('alias-zone-loop.yaml')) \
.validate_configs()
Manager(
get_config_filename('alias-zone-loop.yaml')
).validate_configs()
self.assertTrue('is an alias zone' in str(ctx.exception))
# Valid config file using an alias zone.
Manager(get_config_filename('simple-alias-zone.yaml')) \
.validate_configs()
Manager(
get_config_filename('simple-alias-zone.yaml')
).validate_configs()
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('unknown-processor.yaml')) \
.validate_configs()
Manager(
get_config_filename('unknown-processor.yaml')
).validate_configs()
self.assertTrue('unknown processor' in str(ctx.exception))
def test_get_zone(self):
@@ -366,8 +411,9 @@ class TestManager(TestCase):
self.assertTrue('missing ending dot' in str(ctx.exception))
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('simple.yaml')) \
.get_zone('unknown-zone.tests.')
Manager(get_config_filename('simple.yaml')).get_zone(
'unknown-zone.tests.'
)
self.assertTrue('Unknown zone name' in str(ctx.exception))
def test_populate_lenient_fallback(self):
@@ -377,7 +423,6 @@ class TestManager(TestCase):
manager = Manager(get_config_filename('simple.yaml'))
class NoLenient(SimpleProvider):
def populate(self, zone):
pass
@@ -385,14 +430,12 @@ class TestManager(TestCase):
manager._populate_and_plan('unit.tests.', [], [NoLenient()], [])
class OtherType(SimpleProvider):
def populate(self, zone, lenient=False):
raise TypeError('something else')
# This will blow up, we don't fallback for source
with self.assertRaises(TypeError) as ctx:
manager._populate_and_plan('unit.tests.', [], [OtherType()],
[])
manager._populate_and_plan('unit.tests.', [], [OtherType()], [])
self.assertEqual('something else', str(ctx.exception))
def test_plan_processors_fallback(self):
@@ -402,23 +445,19 @@ class TestManager(TestCase):
manager = Manager(get_config_filename('simple.yaml'))
class NoProcessors(SimpleProvider):
def plan(self, zone):
pass
# This should be ok, we'll fall back to not passing it
manager._populate_and_plan('unit.tests.', [], [],
[NoProcessors()])
manager._populate_and_plan('unit.tests.', [], [], [NoProcessors()])
class OtherType(SimpleProvider):
def plan(self, zone, processors):
raise TypeError('something else')
# This will blow up, we don't fallback for source
with self.assertRaises(TypeError) as ctx:
manager._populate_and_plan('unit.tests.', [], [],
[OtherType()])
manager._populate_and_plan('unit.tests.', [], [], [OtherType()])
self.assertEqual('something else', str(ctx.exception))
@patch('octodns.manager.Manager._get_named_class')
@@ -429,8 +468,9 @@ class TestManager(TestCase):
mock.return_value = (plan_output_class_mock, 'ignored', 'ignored')
fh_mock = MagicMock()
Manager(get_config_filename('plan-output-filehandle.yaml')
).sync(plan_output_fh=fh_mock)
Manager(get_config_filename('plan-output-filehandle.yaml')).sync(
plan_output_fh=fh_mock
)
# Since we only care about the fh kwarg, and different _PlanOutputs are
# are free to require arbitrary kwargs anyway, we concern ourselves
@@ -449,18 +489,22 @@ class TestManager(TestCase):
with self.assertRaises(ManagerException) as ctx:
# This zone specifies a non-existent processor
manager.sync(['bad.unit.tests.'])
self.assertTrue('Zone bad.unit.tests., unknown processor: '
'doesnt-exist' in str(ctx.exception))
self.assertTrue(
'Zone bad.unit.tests., unknown processor: '
'doesnt-exist' in str(ctx.exception)
)
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('processors-missing-class.yaml'))
self.assertTrue('Processor no-class is missing class' in
str(ctx.exception))
self.assertTrue(
'Processor no-class is missing class' in str(ctx.exception)
)
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('processors-wants-config.yaml'))
self.assertTrue('Incorrect processor config for wants-config' in
str(ctx.exception))
self.assertTrue(
'Incorrect processor config for wants-config' in str(ctx.exception)
)
def test_processors(self):
manager = Manager(get_config_filename('simple.yaml'))
@@ -468,23 +512,21 @@ class TestManager(TestCase):
targets = [PlannableProvider('prov')]
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
# muck with sources
class MockProcessor(BaseProcessor):
def process_source_zone(self, zone, sources):
zone = zone.copy()
zone.add_record(record)
return zone
mock = MockProcessor('mock')
plans, zone = manager._populate_and_plan('unit.tests.', [mock], [],
targets)
plans, zone = manager._populate_and_plan(
'unit.tests.', [mock], [], targets
)
# Our mock was called and added the record
self.assertEqual(record, list(zone.records)[0])
# We got a create for the thing added to the expected state (source)
@@ -492,15 +534,15 @@ class TestManager(TestCase):
# muck with targets
class MockProcessor(BaseProcessor):
def process_target_zone(self, zone, target):
zone = zone.copy()
zone.add_record(record)
return zone
mock = MockProcessor('mock')
plans, zone = manager._populate_and_plan('unit.tests.', [mock], [],
targets)
plans, zone = manager._populate_and_plan(
'unit.tests.', [mock], [], targets
)
# No record added since it's target this time
self.assertFalse(zone.records)
# We got a delete for the thing added to the existing state (target)
@@ -508,7 +550,6 @@ class TestManager(TestCase):
# muck with plans
class MockProcessor(BaseProcessor):
def process_target_zone(self, zone, target):
zone = zone.copy()
zone.add_record(record)
@@ -519,8 +560,9 @@ class TestManager(TestCase):
plans.changes.pop(0)
mock = MockProcessor('mock')
plans, zone = manager._populate_and_plan('unit.tests.', [mock], [],
targets)
plans, zone = manager._populate_and_plan(
'unit.tests.', [mock], [], targets
)
# We planned a delete again, but this time removed it from the plan, so
# no plans
self.assertFalse(plans)
@@ -534,23 +576,28 @@ class TestManager(TestCase):
dummy_module = DummyModule()
# use importlib.metadata.version
self.assertTrue(__VERSION__,
manager._try_version('octodns',
module=dummy_module,
version='1.2.3'))
self.assertTrue(
__VERSION__,
manager._try_version(
'octodns', module=dummy_module, version='1.2.3'
),
)
# use module
self.assertTrue(manager._try_version('doesnt-exist',
module=dummy_module))
self.assertTrue(
manager._try_version('doesnt-exist', module=dummy_module)
)
# fall back to version, preferred over module
self.assertEqual('1.2.3', manager._try_version('doesnt-exist',
module=dummy_module,
version='1.2.3', ))
self.assertEqual(
'1.2.3',
manager._try_version(
'doesnt-exist', module=dummy_module, version='1.2.3'
),
)
class TestMainThreadExecutor(TestCase):
def test_success(self):
mte = MainThreadExecutor()
+92 -78
View File
@@ -2,15 +2,25 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from io import StringIO
from logging import getLogger
from unittest import TestCase
from octodns.provider.plan import Plan, PlanHtml, PlanLogger, PlanMarkdown, \
RootNsChange, TooMuchChange
from octodns.provider.plan import (
Plan,
PlanHtml,
PlanLogger,
PlanMarkdown,
RootNsChange,
TooMuchChange,
)
from octodns.record import Create, Delete, Record, Update
from octodns.zone import Zone
@@ -19,32 +29,41 @@ from helpers import SimpleProvider
simple = SimpleProvider()
zone = Zone('unit.tests.', [])
existing = Record.new(zone, 'a', {
'ttl': 300,
'type': 'A',
# This matches the zone data above, one to swap, one to leave
'values': ['1.1.1.1', '2.2.2.2'],
})
new = Record.new(zone, 'a', {
'geo': {
'AF': ['5.5.5.5'],
'NA-US': ['6.6.6.6']
existing = Record.new(
zone,
'a',
{
'ttl': 300,
'type': 'A',
# This matches the zone data above, one to swap, one to leave
'values': ['1.1.1.1', '2.2.2.2'],
},
'ttl': 300,
'type': 'A',
# This leaves one, swaps ones, and adds one
'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'],
}, simple)
create = Create(Record.new(zone, 'b', {
'ttl': 60,
'type': 'CNAME',
'value': 'foo.unit.tests.'
}, simple))
create2 = Create(Record.new(zone, 'c', {
'ttl': 60,
'type': 'CNAME',
'value': 'foo.unit.tests.'
}))
)
new = Record.new(
zone,
'a',
{
'geo': {'AF': ['5.5.5.5'], 'NA-US': ['6.6.6.6']},
'ttl': 300,
'type': 'A',
# This leaves one, swaps ones, and adds one
'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'],
},
simple,
)
create = Create(
Record.new(
zone,
'b',
{'ttl': 60, 'type': 'CNAME', 'value': 'foo.unit.tests.'},
simple,
)
)
create2 = Create(
Record.new(
zone, 'c', {'ttl': 60, 'type': 'CNAME', 'value': 'foo.unit.tests.'}
)
)
update = Update(existing, new)
delete = Delete(new)
changes = [create, create2, delete, update]
@@ -55,23 +74,18 @@ plans = [
class TestPlanSortsChanges(TestCase):
def test_plan_sorts_changes_pass_to_it(self):
# we aren't worried about the details of the sorting, that's tested in
# test_octodns_record's TestChanges. We just want to make sure that the
# changes are sorted at all.
zone = Zone('unit.tests.', [])
record_a_1 = Record.new(zone, '1', {
'type': 'A',
'ttl': 30,
'value': '1.2.3.4',
})
record_a_1 = Record.new(
zone, '1', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'}
)
create_a_1 = Create(record_a_1)
record_a_2 = Record.new(zone, '2', {
'type': 'A',
'ttl': 30,
'value': '1.2.3.4',
})
record_a_2 = Record.new(
zone, '2', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'}
)
create_a_2 = Create(record_a_2)
# passed in reverse of expected order
@@ -80,16 +94,13 @@ class TestPlanSortsChanges(TestCase):
class TestPlanLogger(TestCase):
def test_invalid_level(self):
with self.assertRaises(Exception) as ctx:
PlanLogger('invalid', 'not-a-level')
self.assertEqual('Unsupported level: not-a-level', str(ctx.exception))
def test_create(self):
class MockLogger(object):
def __init__(self):
self.out = StringIO()
@@ -99,8 +110,10 @@ class TestPlanLogger(TestCase):
log = MockLogger()
PlanLogger('logger').run(log, plans)
out = log.out.getvalue()
self.assertTrue('Summary: Creates=2, Updates=1, '
'Deletes=1, Existing Records=0' in out)
self.assertTrue(
'Summary: Creates=2, Updates=1, '
'Deletes=1, Existing Records=0' in out
)
class TestPlanHtml(TestCase):
@@ -115,8 +128,10 @@ class TestPlanHtml(TestCase):
out = StringIO()
PlanHtml('html').run(plans, fh=out)
out = out.getvalue()
self.assertTrue(' <td colspan=6>Summary: Creates=2, Updates=1, '
'Deletes=1, Existing Records=0</td>' in out)
self.assertTrue(
' <td colspan=6>Summary: Creates=2, Updates=1, '
'Deletes=1, Existing Records=0</td>' in out
)
class TestPlanMarkdown(TestCase):
@@ -139,7 +154,6 @@ class TestPlanMarkdown(TestCase):
class HelperPlan(Plan):
def __init__(self, *args, min_existing=0, **kwargs):
super().__init__(*args, **kwargs)
self.MIN_EXISTING_RECORDS = min_existing
@@ -147,26 +161,18 @@ class HelperPlan(Plan):
class TestPlanSafety(TestCase):
existing = Zone('unit.tests.', [])
record_1 = Record.new(existing, '1', data={
'type': 'A',
'ttl': 42,
'value': '1.2.3.4',
})
record_2 = Record.new(existing, '2', data={
'type': 'A',
'ttl': 42,
'value': '1.2.3.4',
})
record_3 = Record.new(existing, '3', data={
'type': 'A',
'ttl': 42,
'value': '1.2.3.4',
})
record_4 = Record.new(existing, '4', data={
'type': 'A',
'ttl': 42,
'value': '1.2.3.4',
})
record_1 = Record.new(
existing, '1', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'}
)
record_2 = Record.new(
existing, '2', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'}
)
record_3 = Record.new(
existing, '3', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'}
)
record_4 = Record.new(
existing, '4', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'}
)
def test_too_many_updates(self):
existing = self.existing.copy()
@@ -267,11 +273,15 @@ class TestPlanSafety(TestCase):
plan.raise_if_unsafe()
# Add a change to a non-root NS record, we're OK
ns_record = Record.new(existing, 'sub', data={
'type': 'NS',
'ttl': 43,
'values': ('ns1.unit.tests.', 'ns1.unit.tests.'),
})
ns_record = Record.new(
existing,
'sub',
data={
'type': 'NS',
'ttl': 43,
'values': ('ns1.unit.tests.', 'ns1.unit.tests.'),
},
)
changes.append(Delete(ns_record))
plan = HelperPlan(existing, None, changes, True)
plan.raise_if_unsafe()
@@ -279,11 +289,15 @@ class TestPlanSafety(TestCase):
changes.pop(-1)
# Delete the root NS record and we get an unsafe
root_ns_record = Record.new(existing, '', data={
'type': 'NS',
'ttl': 43,
'values': ('ns3.unit.tests.', 'ns4.unit.tests.'),
})
root_ns_record = Record.new(
existing,
'',
data={
'type': 'NS',
'ttl': 43,
'values': ('ns3.unit.tests.', 'ns4.unit.tests.'),
},
)
changes.append(Delete(root_ns_record))
plan = HelperPlan(existing, None, changes, True)
with self.assertRaises(RootNsChange) as ctx:
+53 -50
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
@@ -13,46 +17,43 @@ from octodns.zone import Zone
zone = Zone('unit.tests.', [])
records = {
'root-unowned': Record.new(zone, '_acme-challenge', {
'ttl': 30,
'type': 'TXT',
'value': 'magic bit',
}),
'sub-unowned': Record.new(zone, '_acme-challenge.sub-unowned', {
'ttl': 30,
'type': 'TXT',
'value': 'magic bit',
}),
'not-txt': Record.new(zone, '_acme-challenge.not-txt', {
'ttl': 30,
'type': 'AAAA',
'value': '::1',
}),
'not-acme': Record.new(zone, 'not-acme', {
'ttl': 30,
'type': 'TXT',
'value': 'Hello World!',
}),
'managed': Record.new(zone, '_acme-challenge.managed', {
'ttl': 30,
'type': 'TXT',
'value': 'magic bit',
}),
'owned': Record.new(zone, '_acme-challenge.owned', {
'ttl': 30,
'type': 'TXT',
'values': ['*octoDNS*', 'magic bit'],
}),
'going-away': Record.new(zone, '_acme-challenge.going-away', {
'ttl': 30,
'type': 'TXT',
'values': ['*octoDNS*', 'magic bit'],
}),
'root-unowned': Record.new(
zone,
'_acme-challenge',
{'ttl': 30, 'type': 'TXT', 'value': 'magic bit'},
),
'sub-unowned': Record.new(
zone,
'_acme-challenge.sub-unowned',
{'ttl': 30, 'type': 'TXT', 'value': 'magic bit'},
),
'not-txt': Record.new(
zone,
'_acme-challenge.not-txt',
{'ttl': 30, 'type': 'AAAA', 'value': '::1'},
),
'not-acme': Record.new(
zone, 'not-acme', {'ttl': 30, 'type': 'TXT', 'value': 'Hello World!'}
),
'managed': Record.new(
zone,
'_acme-challenge.managed',
{'ttl': 30, 'type': 'TXT', 'value': 'magic bit'},
),
'owned': Record.new(
zone,
'_acme-challenge.owned',
{'ttl': 30, 'type': 'TXT', 'values': ['*octoDNS*', 'magic bit']},
),
'going-away': Record.new(
zone,
'_acme-challenge.going-away',
{'ttl': 30, 'type': 'TXT', 'values': ['*octoDNS*', 'magic bit']},
),
}
class TestAcmeMangingProcessor(TestCase):
def test_process_zones(self):
acme = AcmeMangingProcessor('acme')
@@ -64,11 +65,10 @@ class TestAcmeMangingProcessor(TestCase):
source.add_record(records['managed'])
got = acme.process_source_zone(source)
self.assertEqual([
'_acme-challenge.managed',
'_acme-challenge.not-txt',
'not-acme',
], sorted([r.name for r in got.records]))
self.assertEqual(
['_acme-challenge.managed', '_acme-challenge.not-txt', 'not-acme'],
sorted([r.name for r in got.records]),
)
managed = None
for record in got.records:
if record.name.endswith('managed'):
@@ -93,10 +93,13 @@ class TestAcmeMangingProcessor(TestCase):
existing.add_record(records['going-away'])
got = acme.process_target_zone(existing)
self.assertEqual([
'_acme-challenge.going-away',
'_acme-challenge.managed',
'_acme-challenge.not-txt',
'_acme-challenge.owned',
'not-acme'
], sorted([r.name for r in got.records]))
self.assertEqual(
[
'_acme-challenge.going-away',
'_acme-challenge.managed',
'_acme-challenge.not-txt',
'_acme-challenge.owned',
'not-acme',
],
sorted([r.name for r in got.records]),
)
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestAwsAcmMangingProcessor(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.processor.awsacm import AwsAcmMangingProcessor
AwsAcmMangingProcessor
+29 -41
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
@@ -13,37 +17,20 @@ from octodns.zone import Zone
zone = Zone('unit.tests.', [])
for record in [
Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
}),
Record.new(zone, 'aaaa', {
'ttl': 30,
'type': 'AAAA',
'value': '::1',
}),
Record.new(zone, 'txt', {
'ttl': 30,
'type': 'TXT',
'value': 'Hello World!',
}),
Record.new(zone, 'a2', {
'ttl': 30,
'type': 'A',
'value': '2.3.4.5',
}),
Record.new(zone, 'txt2', {
'ttl': 30,
'type': 'TXT',
'value': 'That will do',
}),
Record.new(zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}),
Record.new(zone, 'aaaa', {'ttl': 30, 'type': 'AAAA', 'value': '::1'}),
Record.new(
zone, 'txt', {'ttl': 30, 'type': 'TXT', 'value': 'Hello World!'}
),
Record.new(zone, 'a2', {'ttl': 30, 'type': 'A', 'value': '2.3.4.5'}),
Record.new(
zone, 'txt2', {'ttl': 30, 'type': 'TXT', 'value': 'That will do'}
),
]:
zone.add_record(record)
class TestTypeAllowListFilter(TestCase):
def test_basics(self):
filter_a = TypeAllowlistFilter('only-a', set(('A')))
@@ -56,35 +43,36 @@ class TestTypeAllowListFilter(TestCase):
filter_txt = TypeAllowlistFilter('only-txt', ['TXT'])
got = filter_txt.process_target_zone(zone.copy())
self.assertEqual(['txt', 'txt2'],
sorted([r.name for r in got.records]))
self.assertEqual(['txt', 'txt2'], sorted([r.name for r in got.records]))
filter_a_aaaa = TypeAllowlistFilter('only-aaaa', set(('A', 'AAAA')))
got = filter_a_aaaa.process_target_zone(zone.copy())
self.assertEqual(['a', 'a2', 'aaaa'],
sorted([r.name for r in got.records]))
self.assertEqual(
['a', 'a2', 'aaaa'], sorted([r.name for r in got.records])
)
class TestTypeRejectListFilter(TestCase):
def test_basics(self):
filter_a = TypeRejectlistFilter('not-a', set(('A')))
got = filter_a.process_source_zone(zone.copy())
self.assertEqual(['aaaa', 'txt', 'txt2'],
sorted([r.name for r in got.records]))
self.assertEqual(
['aaaa', 'txt', 'txt2'], sorted([r.name for r in got.records])
)
filter_aaaa = TypeRejectlistFilter('not-aaaa', ('AAAA',))
got = filter_aaaa.process_source_zone(zone.copy())
self.assertEqual(['a', 'a2', 'txt', 'txt2'],
sorted([r.name for r in got.records]))
self.assertEqual(
['a', 'a2', 'txt', 'txt2'], sorted([r.name for r in got.records])
)
filter_txt = TypeRejectlistFilter('not-txt', ['TXT'])
got = filter_txt.process_target_zone(zone.copy())
self.assertEqual(['a', 'a2', 'aaaa'],
sorted([r.name for r in got.records]))
self.assertEqual(
['a', 'a2', 'aaaa'], sorted([r.name for r in got.records])
)
filter_a_aaaa = TypeRejectlistFilter('not-a-aaaa', set(('A', 'AAAA')))
got = filter_a_aaaa.process_target_zone(zone.copy())
self.assertEqual(['txt', 'txt2'],
sorted([r.name for r in got.records]))
self.assertEqual(['txt', 'txt2'], sorted([r.name for r in got.records]))
+36 -53
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
@@ -17,57 +21,40 @@ from helpers import PlannableProvider
zone = Zone('unit.tests.', [])
records = {}
for record in [
Record.new(zone, '', {
'ttl': 30,
'type': 'A',
'values': [
'1.2.3.4',
'5.6.7.8',
],
}),
Record.new(zone, 'the-a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
}),
Record.new(zone, 'the-aaaa', {
'ttl': 30,
'type': 'AAAA',
'value': '::1',
}),
Record.new(zone, 'the-txt', {
'ttl': 30,
'type': 'TXT',
'value': 'Hello World!',
}),
Record.new(zone, '*', {
'ttl': 30,
'type': 'A',
'value': '4.3.2.1',
}),
Record.new(
zone, '', {'ttl': 30, 'type': 'A', 'values': ['1.2.3.4', '5.6.7.8']}
),
Record.new(zone, 'the-a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}),
Record.new(zone, 'the-aaaa', {'ttl': 30, 'type': 'AAAA', 'value': '::1'}),
Record.new(
zone, 'the-txt', {'ttl': 30, 'type': 'TXT', 'value': 'Hello World!'}
),
Record.new(zone, '*', {'ttl': 30, 'type': 'A', 'value': '4.3.2.1'}),
]:
records[record.name] = record
zone.add_record(record)
class TestOwnershipProcessor(TestCase):
def test_process_source_zone(self):
ownership = OwnershipProcessor('ownership')
got = ownership.process_source_zone(zone.copy())
self.assertEqual([
'',
'*',
'_owner.a',
'_owner.a._wildcard',
'_owner.a.the-a',
'_owner.aaaa.the-aaaa',
'_owner.txt.the-txt',
'the-a',
'the-aaaa',
'the-txt',
], sorted([r.name for r in got.records]))
self.assertEqual(
[
'',
'*',
'_owner.a',
'_owner.a._wildcard',
'_owner.a.the-a',
'_owner.aaaa.the-aaaa',
'_owner.txt.the-txt',
'the-a',
'the-aaaa',
'the-txt',
],
sorted([r.name for r in got.records]),
)
found = False
for record in got.records:
@@ -101,11 +88,9 @@ class TestOwnershipProcessor(TestCase):
# Something extra exists and doesn't have ownership TXT, leave it
# alone, we don't own it.
extra_a = Record.new(zone, 'extra-a', {
'ttl': 30,
'type': 'A',
'value': '4.4.4.4',
})
extra_a = Record.new(
zone, 'extra-a', {'ttl': 30, 'type': 'A', 'value': '4.4.4.4'}
)
plan.existing.add_record(extra_a)
# If we'd done a "real" plan we'd have a delete for the extra thing.
plan.changes.append(Delete(extra_a))
@@ -130,11 +115,9 @@ class TestOwnershipProcessor(TestCase):
the_a = records['the-a']
plan.existing.add_record(the_a)
name = f'{ownership.txt_name}.a.the-a'
the_a_ownership = Record.new(zone, name, {
'ttl': 30,
'type': 'TXT',
'value': ownership.txt_value,
})
the_a_ownership = Record.new(
zone, name, {'ttl': 30, 'type': 'TXT', 'value': ownership.txt_value}
)
plan.existing.add_record(the_a_ownership)
plan.changes.append(Delete(the_a))
plan.changes.append(Delete(the_a_ownership))
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestAzureShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.azuredns import AzureProvider
AzureProvider
+273 -262
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from logging import getLogger
from unittest import TestCase
@@ -27,8 +31,12 @@ class HelperProvider(BaseProvider):
id = 'test'
strict_supports = False
def __init__(self, extra_changes=[], apply_disabled=False,
include_change_callback=None):
def __init__(
self,
extra_changes=[],
apply_disabled=False,
include_change_callback=None,
):
self.__extra_changes = extra_changes
self.apply_disabled = apply_disabled
self.include_change_callback = include_change_callback
@@ -39,8 +47,9 @@ class HelperProvider(BaseProvider):
return True
def _include_change(self, change):
return not self.include_change_callback or \
self.include_change_callback(change)
return not self.include_change_callback or self.include_change_callback(
change
)
def _extra_changes(self, **kwargs):
return self.__extra_changes
@@ -50,7 +59,6 @@ class HelperProvider(BaseProvider):
class TrickyProcessor(BaseProcessor):
def __init__(self, name, add_during_process_target_zone):
super(TrickyProcessor, self).__init__(name)
self.add_during_process_target_zone = add_during_process_target_zone
@@ -73,20 +81,22 @@ class TrickyProcessor(BaseProcessor):
class TestBaseProvider(TestCase):
def test_base_provider(self):
with self.assertRaises(NotImplementedError) as ctx:
BaseProvider('base')
self.assertEqual('Abstract base class, log property missing',
str(ctx.exception))
self.assertEqual(
'Abstract base class, log property missing', str(ctx.exception)
)
class HasLog(BaseProvider):
log = getLogger('HasLog')
with self.assertRaises(NotImplementedError) as ctx:
HasLog('haslog')
self.assertEqual('Abstract base class, SUPPORTS_GEO property missing',
str(ctx.exception))
self.assertEqual(
'Abstract base class, SUPPORTS_GEO property missing',
str(ctx.exception),
)
class HasSupportsGeo(HasLog):
SUPPORTS_GEO = False
@@ -94,15 +104,18 @@ class TestBaseProvider(TestCase):
zone = Zone('unit.tests.', ['sub'])
with self.assertRaises(NotImplementedError) as ctx:
HasSupportsGeo('hassupportsgeo').populate(zone)
self.assertEqual('Abstract base class, SUPPORTS property missing',
str(ctx.exception))
self.assertEqual(
'Abstract base class, SUPPORTS property missing', str(ctx.exception)
)
class HasSupports(HasSupportsGeo):
SUPPORTS = set(('A',))
with self.assertRaises(NotImplementedError) as ctx:
HasSupports('hassupports').populate(zone)
self.assertEqual('Abstract base class, populate method missing',
str(ctx.exception))
self.assertEqual(
'Abstract base class, populate method missing', str(ctx.exception)
)
# SUPPORTS_DYNAMIC has a default/fallback
self.assertFalse(HasSupports('hassupports').SUPPORTS_DYNAMIC)
@@ -111,44 +124,51 @@ class TestBaseProvider(TestCase):
class HasSupportsDyanmic(HasSupports):
SUPPORTS_DYNAMIC = True
self.assertTrue(HasSupportsDyanmic('hassupportsdynamic')
.SUPPORTS_DYNAMIC)
self.assertTrue(
HasSupportsDyanmic('hassupportsdynamic').SUPPORTS_DYNAMIC
)
class HasPopulate(HasSupports):
def populate(self, zone, target=False, lenient=False):
zone.add_record(Record.new(zone, '', {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}), lenient=lenient)
zone.add_record(Record.new(zone, 'going', {
'ttl': 60,
'type': 'A',
'value': '3.4.5.6'
}), lenient=lenient)
zone.add_record(Record.new(zone, 'foo.sub', {
'ttl': 61,
'type': 'A',
'value': '4.5.6.7'
}), lenient=lenient)
zone.add_record(
Record.new(
zone, '', {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
),
lenient=lenient,
)
zone.add_record(
Record.new(
zone,
'going',
{'ttl': 60, 'type': 'A', 'value': '3.4.5.6'},
),
lenient=lenient,
)
zone.add_record(
Record.new(
zone,
'foo.sub',
{'ttl': 61, 'type': 'A', 'value': '4.5.6.7'},
),
lenient=lenient,
)
zone.add_record(Record.new(zone, '', {
'ttl': 60,
'type': 'A',
'value': '1.2.3.4'
}))
zone.add_record(
Record.new(zone, '', {'ttl': 60, 'type': 'A', 'value': '1.2.3.4'})
)
self.assertTrue(HasSupports('hassupportsgeo')
.supports(list(zone.records)[0]))
self.assertTrue(
HasSupports('hassupportsgeo').supports(list(zone.records)[0])
)
plan = HasPopulate('haspopulate').plan(zone)
self.assertEqual(3, len(plan.changes))
with self.assertRaises(NotImplementedError) as ctx:
HasPopulate('haspopulate').apply(plan)
self.assertEqual('Abstract base class, _apply method missing',
str(ctx.exception))
self.assertEqual(
'Abstract base class, _apply method missing', str(ctx.exception)
)
def test_plan(self):
ignored = Zone('unit.tests.', [])
@@ -157,11 +177,9 @@ class TestBaseProvider(TestCase):
provider = HelperProvider([])
self.assertEqual(None, provider.plan(ignored))
record = Record.new(ignored, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
ignored, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
provider = HelperProvider([Create(record)])
plan = provider.plan(ignored)
self.assertTrue(plan)
@@ -171,7 +189,6 @@ class TestBaseProvider(TestCase):
ignored = Zone('unit.tests.', [])
class OldApiProvider(HelperProvider):
def _process_desired_zone(self, desired):
return desired
@@ -180,7 +197,6 @@ class TestBaseProvider(TestCase):
self.assertEqual(None, provider.plan(ignored))
class OtherTypeErrorProvider(HelperProvider):
def _process_desired_zone(self, desired, exists=False):
raise TypeError('foo')
@@ -193,18 +209,20 @@ class TestBaseProvider(TestCase):
zone = Zone('unit.tests.', [])
# supported
supported = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
supported = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
zone.add_record(supported)
# not supported
not_supported = Record.new(zone, 'aaaa', {
'ttl': 30,
'type': 'AAAA',
'value': '2601:644:500:e210:62f8:1dff:feb8:947a',
})
not_supported = Record.new(
zone,
'aaaa',
{
'ttl': 30,
'type': 'AAAA',
'value': '2601:644:500:e210:62f8:1dff:feb8:947a',
},
)
zone.add_record(not_supported)
provider = HelperProvider()
plan = provider.plan(zone)
@@ -215,11 +233,9 @@ class TestBaseProvider(TestCase):
def test_plan_with_processors(self):
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
provider = HelperProvider()
# Processor that adds a record to the zone, which planning will then
# delete since it won't know anything about it
@@ -233,11 +249,9 @@ class TestBaseProvider(TestCase):
self.assertEqual(zone.name, tricky.existing.name)
# Chain of processors happen one after the other
other = Record.new(zone, 'b', {
'ttl': 30,
'type': 'A',
'value': '5.6.7.8',
})
other = Record.new(
zone, 'b', {'ttl': 30, 'type': 'A', 'value': '5.6.7.8'}
)
# Another processor will add its record, thus 2 deletes
another = TrickyProcessor('tricky', [other])
plan = provider.plan(zone, processors=[tricky, another])
@@ -253,11 +267,9 @@ class TestBaseProvider(TestCase):
def test_plan_with_root_ns(self):
zone = Zone('unit.tests.', [])
record = Record.new(zone, '', {
'ttl': 30,
'type': 'NS',
'value': '1.2.3.4.',
})
record = Record.new(
zone, '', {'ttl': 30, 'type': 'NS', 'value': '1.2.3.4.'}
)
zone.add_record(record)
# No root NS support, no change, thus no plan
@@ -273,11 +285,9 @@ class TestBaseProvider(TestCase):
def test_apply(self):
ignored = Zone('unit.tests.', [])
record = Record.new(ignored, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
ignored, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
provider = HelperProvider([Create(record)], apply_disabled=True)
plan = provider.plan(ignored)
provider.apply(plan)
@@ -288,11 +298,9 @@ class TestBaseProvider(TestCase):
def test_include_change(self):
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
zone.add_record(record)
provider = HelperProvider([], include_change_callback=lambda c: False)
plan = provider.plan(zone)
@@ -300,7 +308,6 @@ class TestBaseProvider(TestCase):
self.assertFalse(plan)
def test_plan_order_of_operations(self):
class MockProvider(BaseProvider):
log = getLogger('mock-provider')
SUPPORTS = set(('A',))
@@ -327,8 +334,10 @@ class TestBaseProvider(TestCase):
self.assertFalse(provider.plan(zone))
# ensure the calls were made in the expected order, populate comes
# first, then desired, then existing
self.assertEqual(['populate', '_process_desired_zone',
'_process_existing_zone'], provider.calls)
self.assertEqual(
['populate', '_process_desired_zone', '_process_existing_zone'],
provider.calls,
)
def test_process_desired_zone(self):
provider = HelperProvider('test')
@@ -336,11 +345,11 @@ class TestBaseProvider(TestCase):
# SUPPORTS_MULTIVALUE_PTR
provider.SUPPORTS_MULTIVALUE_PTR = False
zone1 = Zone('unit.tests.', [])
record1 = Record.new(zone1, 'ptr', {
'type': 'PTR',
'ttl': 3600,
'values': ['foo.com.', 'bar.com.'],
})
record1 = Record.new(
zone1,
'ptr',
{'type': 'PTR', 'ttl': 3600, 'values': ['foo.com.', 'bar.com.']},
)
zone1.add_record(record1)
zone2 = provider._process_desired_zone(zone1.copy())
@@ -355,23 +364,19 @@ class TestBaseProvider(TestCase):
# SUPPORTS_DYNAMIC
provider.SUPPORTS_DYNAMIC = False
zone1 = Zone('unit.tests.', [])
record1 = Record.new(zone1, 'a', {
'dynamic': {
'pools': {
'one': {
'values': [{
'value': '1.1.1.1',
}],
},
record1 = Record.new(
zone1,
'a',
{
'dynamic': {
'pools': {'one': {'values': [{'value': '1.1.1.1'}]}},
'rules': [{'pool': 'one'}],
},
'rules': [{
'pool': 'one',
}],
'type': 'A',
'ttl': 3600,
'values': ['2.2.2.2'],
},
'type': 'A',
'ttl': 3600,
'values': ['2.2.2.2'],
})
)
self.assertTrue(record1.dynamic)
zone1.add_record(record1)
@@ -393,18 +398,17 @@ class TestBaseProvider(TestCase):
zone2 = provider._process_desired_zone(zone1.copy())
record2 = list(zone2.records)[0]
self.assertEqual(
record2.dynamic.pools['one'].data['values'][0]['status'],
'obey'
record2.dynamic.pools['one'].data['values'][0]['status'], 'obey'
)
# SUPPORTS_ROOT_NS
provider.SUPPORTS_ROOT_NS = False
zone1 = Zone('unit.tests.', [])
record1 = Record.new(zone1, '', {
'type': 'NS',
'ttl': 3600,
'values': ['foo.com.', 'bar.com.'],
})
record1 = Record.new(
zone1,
'',
{'type': 'NS', 'ttl': 3600, 'values': ['foo.com.', 'bar.com.']},
)
zone1.add_record(record1)
zone2 = provider._process_desired_zone(zone1.copy())
@@ -421,11 +425,11 @@ class TestBaseProvider(TestCase):
# SUPPORTS_ROOT_NS
provider.SUPPORTS_ROOT_NS = False
zone1 = Zone('unit.tests.', [])
record1 = Record.new(zone1, '', {
'type': 'NS',
'ttl': 3600,
'values': ['foo.com.', 'bar.com.'],
})
record1 = Record.new(
zone1,
'',
{'type': 'NS', 'ttl': 3600, 'values': ['foo.com.', 'bar.com.']},
)
zone1.add_record(record1)
zone2 = provider._process_existing_zone(zone1.copy(), zone1)
@@ -444,42 +448,38 @@ class TestBaseProvider(TestCase):
# Creates are safe when existing records is under MIN_EXISTING_RECORDS
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
Plan(zone, zone, [Create(record) for i in range(10)], True) \
.raise_if_unsafe()
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
Plan(
zone, zone, [Create(record) for i in range(10)], True
).raise_if_unsafe()
def test_safe_min_existing_creates(self):
# Creates are safe when existing records is over MIN_EXISTING_RECORDS
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
Plan(zone, zone, [Create(record) for i in range(10)], True) \
.raise_if_unsafe()
Plan(
zone, zone, [Create(record) for i in range(10)], True
).raise_if_unsafe()
def test_safe_no_existing(self):
# existing records fewer than MIN_EXISTING_RECORDS is safe
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
updates = [Update(record, record), Update(record, record)]
Plan(zone, zone, updates, True).raise_if_unsafe()
@@ -488,22 +488,23 @@ class TestBaseProvider(TestCase):
# MAX_SAFE_UPDATE_PCENT+1 fails when more
# than MIN_EXISTING_RECORDS exist
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
changes = [Update(record, record)
for i in range(int(Plan.MIN_EXISTING_RECORDS *
Plan.MAX_SAFE_UPDATE_PCENT) + 1)]
changes = [
Update(record, record)
for i in range(
int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_UPDATE_PCENT) + 1
)
]
with self.assertRaises(UnsafePlan) as ctx:
Plan(zone, zone, changes, True).raise_if_unsafe()
@@ -514,21 +515,22 @@ class TestBaseProvider(TestCase):
# MAX_SAFE_UPDATE_PCENT is safe when more
# than MIN_EXISTING_RECORDS exist
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
changes = [Update(record, record)
for i in range(int(Plan.MIN_EXISTING_RECORDS *
Plan.MAX_SAFE_UPDATE_PCENT))]
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
changes = [
Update(record, record)
for i in range(
int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_UPDATE_PCENT)
)
]
Plan(zone, zone, changes, True).raise_if_unsafe()
@@ -536,22 +538,23 @@ class TestBaseProvider(TestCase):
# MAX_SAFE_DELETE_PCENT+1 fails when more
# than MIN_EXISTING_RECORDS exist
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
changes = [Delete(record)
for i in range(int(Plan.MIN_EXISTING_RECORDS *
Plan.MAX_SAFE_DELETE_PCENT) + 1)]
changes = [
Delete(record)
for i in range(
int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_DELETE_PCENT) + 1
)
]
with self.assertRaises(UnsafePlan) as ctx:
Plan(zone, zone, changes, True).raise_if_unsafe()
@@ -562,77 +565,78 @@ class TestBaseProvider(TestCase):
# MAX_SAFE_DELETE_PCENT is safe when more
# than MIN_EXISTING_RECORDS exist
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
changes = [Delete(record)
for i in range(int(Plan.MIN_EXISTING_RECORDS *
Plan.MAX_SAFE_DELETE_PCENT))]
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
changes = [
Delete(record)
for i in range(
int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_DELETE_PCENT)
)
]
Plan(zone, zone, changes, True).raise_if_unsafe()
def test_safe_updates_min_existing_override(self):
safe_pcent = .4
safe_pcent = 0.4
# 40% + 1 fails when more
# than MIN_EXISTING_RECORDS exist
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
changes = [Update(record, record)
for i in range(int(Plan.MIN_EXISTING_RECORDS *
safe_pcent) + 1)]
changes = [
Update(record, record)
for i in range(int(Plan.MIN_EXISTING_RECORDS * safe_pcent) + 1)
]
with self.assertRaises(UnsafePlan) as ctx:
Plan(zone, zone, changes, True,
update_pcent_threshold=safe_pcent).raise_if_unsafe()
Plan(
zone, zone, changes, True, update_pcent_threshold=safe_pcent
).raise_if_unsafe()
self.assertTrue('Too many updates' in str(ctx.exception))
def test_safe_deletes_min_existing_override(self):
safe_pcent = .4
safe_pcent = 0.4
# 40% + 1 fails when more
# than MIN_EXISTING_RECORDS exist
zone = Zone('unit.tests.', [])
record = Record.new(zone, 'a', {
'ttl': 30,
'type': 'A',
'value': '1.2.3.4',
})
record = Record.new(
zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}
)
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, str(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
}))
zone.add_record(
Record.new(
zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'}
)
)
changes = [Delete(record)
for i in range(int(Plan.MIN_EXISTING_RECORDS *
safe_pcent) + 1)]
changes = [
Delete(record)
for i in range(int(Plan.MIN_EXISTING_RECORDS * safe_pcent) + 1)
]
with self.assertRaises(UnsafePlan) as ctx:
Plan(zone, zone, changes, True,
delete_pcent_threshold=safe_pcent).raise_if_unsafe()
Plan(
zone, zone, changes, True, delete_pcent_threshold=safe_pcent
).raise_if_unsafe()
self.assertTrue('Too many deletes' in str(ctx.exception))
@@ -649,9 +653,9 @@ class TestBaseProvider(TestCase):
# Should log and not expect
normal.supports_warn_or_except('Hello World!', 'Goodbye')
normal.log.warning.assert_called_once()
normal.log.warning.assert_has_calls([
call('%s; %s', 'Hello World!', 'Goodbye')
])
normal.log.warning.assert_has_calls(
[call('%s; %s', 'Hello World!', 'Goodbye')]
)
strict = MinimalProvider(strict_supports=True)
# Should log and not expect
@@ -662,7 +666,6 @@ class TestBaseProvider(TestCase):
class TestBaseProviderSupportsRootNs(TestCase):
class Provider(BaseProvider):
log = getLogger('Provider')
@@ -684,33 +687,33 @@ class TestBaseProviderSupportsRootNs(TestCase):
return False
zone = Zone('unit.tests.', [])
a_record = Record.new(zone, 'ptr', {
'type': 'A',
'ttl': 3600,
'values': ['1.2.3.4', '2.3.4.5'],
})
ns_record = Record.new(zone, 'sub', {
'type': 'NS',
'ttl': 3600,
'values': ['ns2.foo.com.', 'ns2.bar.com.'],
})
a_record = Record.new(
zone,
'ptr',
{'type': 'A', 'ttl': 3600, 'values': ['1.2.3.4', '2.3.4.5']},
)
ns_record = Record.new(
zone,
'sub',
{'type': 'NS', 'ttl': 3600, 'values': ['ns2.foo.com.', 'ns2.bar.com.']},
)
no_root = zone.copy()
no_root.add_record(a_record)
no_root.add_record(ns_record)
root_ns_record = Record.new(zone, '', {
'type': 'NS',
'ttl': 3600,
'values': ['ns1.foo.com.', 'ns1.bar.com.'],
})
root_ns_record = Record.new(
zone,
'',
{'type': 'NS', 'ttl': 3600, 'values': ['ns1.foo.com.', 'ns1.bar.com.']},
)
has_root = no_root.copy()
has_root.add_record(root_ns_record)
other_root_ns_record = Record.new(zone, '', {
'type': 'NS',
'ttl': 3600,
'values': ['ns4.foo.com.', 'ns4.bar.com.'],
})
other_root_ns_record = Record.new(
zone,
'',
{'type': 'NS', 'ttl': 3600, 'values': ['ns4.foo.com.', 'ns4.bar.com.']},
)
different_root = no_root.copy()
different_root.add_record(other_root_ns_record)
@@ -733,8 +736,10 @@ class TestBaseProviderSupportsRootNs(TestCase):
provider.strict_supports = True
with self.assertRaises(SupportsException) as ctx:
provider.plan(self.has_root)
self.assertEqual('test: root NS record not supported for unit.tests.',
str(ctx.exception))
self.assertEqual(
'test: root NS record not supported for unit.tests.',
str(ctx.exception),
)
def test_supports_root_ns_false_different(self):
# provider has a non-matching existing record
@@ -754,8 +759,10 @@ class TestBaseProviderSupportsRootNs(TestCase):
provider.strict_supports = True
with self.assertRaises(SupportsException) as ctx:
provider.plan(self.has_root)
self.assertEqual('test: root NS record not supported for unit.tests.',
str(ctx.exception))
self.assertEqual(
'test: root NS record not supported for unit.tests.',
str(ctx.exception),
)
def test_supports_root_ns_false_missing(self):
# provider has an existing record
@@ -792,8 +799,10 @@ class TestBaseProviderSupportsRootNs(TestCase):
provider.strict_supports = True
with self.assertRaises(SupportsException) as ctx:
provider.plan(self.has_root)
self.assertEqual('test: root NS record not supported for unit.tests.',
str(ctx.exception))
self.assertEqual(
'test: root NS record not supported for unit.tests.',
str(ctx.exception),
)
def test_supports_root_ns_false_create_zone_missing(self):
# provider has no existing records (create)
@@ -889,8 +898,9 @@ class TestBaseProviderSupportsRootNs(TestCase):
# we'll get a plan that creates everything, including it
self.assertTrue(plan)
self.assertEqual(3, len(plan.changes))
change = [c for c in plan.changes
if c.new.name == '' and c.new._type == 'NS'][0]
change = [
c for c in plan.changes if c.new.name == '' and c.new._type == 'NS'
][0]
self.assertFalse(change.existing)
self.assertEqual(self.root_ns_record, change.new)
@@ -900,8 +910,9 @@ class TestBaseProviderSupportsRootNs(TestCase):
plan = provider.plan(self.has_root)
self.assertTrue(plan)
self.assertEqual(3, len(plan.changes))
change = [c for c in plan.changes
if c.new.name == '' and c.new._type == 'NS'][0]
change = [
c for c in plan.changes if c.new.name == '' and c.new._type == 'NS'
][0]
self.assertFalse(change.existing)
self.assertEqual(self.root_ns_record, change.new)
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestCloudflareShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.cloudflare import CloudflareProvider
CloudflareProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestConstellixShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.constellix import ConstellixProvider
ConstellixProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestDigitalOceanShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.digitalocean import DigitalOceanProvider
DigitalOceanProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestDnsimpleShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.dnsimple import DnsimpleProvider
DnsimpleProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestDnsMadeEasyShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.dnsmadeeasy import DnsMadeEasyProvider
DnsMadeEasyProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestDynShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.dyn import DynProvider
DynProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestEasyDnsShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.easydns import EasyDnsProvider
EasyDnsProvider
+8 -3
View File
@@ -2,20 +2,25 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
# Just for coverage
import octodns.provider.fastdns
# Quell warnings
octodns.provider.fastdns
class TestAkamaiShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.edgedns import AkamaiProvider
AkamaiProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestEtcHostsShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.etc_hosts import EtcHostsProvider
EtcHostsProvider
+8 -3
View File
@@ -2,20 +2,25 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
# Just for coverage
import octodns.provider.fastdns
# Quell warnings
octodns.provider.fastdns
class TestGandiShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.gandi import GandiProvider
GandiProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestGCoreShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.gcore import GCoreProvider
GCoreProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestGoogleCloudShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.googlecloud import GoogleCloudProvider
GoogleCloudProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestHetznerShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.hetzner import HetznerProvider
HetznerProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestMythicBeastsShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.mythicbeasts import MythicBeastsProvider
MythicBeastsProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestNs1Provider(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.ns1 import Ns1Provider
Ns1Provider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestOvhShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.ovh import OvhProvider
OvhProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestPowerDnsShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.powerdns import PowerDnsProvider
PowerDnsProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestRackspaceShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.rackspace import RackspaceProvider
RackspaceProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestRoute53Provider(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.route53 import Route53Provider
Route53Provider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestSelectelShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.selectel import SelectelProvider
SelectelProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestTransipShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.transip import TransipProvider
TransipProvider
+7 -3
View File
@@ -2,15 +2,19 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
class TestUltraShim(TestCase):
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.ultra import UltraProvider
UltraProvider
+119 -70
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from os import makedirs
from os.path import basename, dirname, isdir, isfile, join
@@ -13,15 +17,17 @@ from yaml.constructor import ConstructorError
from octodns.record import Create
from octodns.provider.base import Plan
from octodns.provider.yaml import _list_all_yaml_files, \
SplitYamlProvider, YamlProvider
from octodns.provider.yaml import (
_list_all_yaml_files,
SplitYamlProvider,
YamlProvider,
)
from octodns.zone import SubzoneRecordException, Zone
from helpers import TemporaryDirectory
class TestYamlProvider(TestCase):
def test_provider(self):
source = YamlProvider('test', join(dirname(__file__), 'config'))
@@ -57,8 +63,9 @@ class TestYamlProvider(TestCase):
# We add everything
plan = target.plan(zone)
self.assertEqual(22, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEqual(
22, len([c for c in plan.changes if isinstance(c, Create)])
)
self.assertFalse(isfile(yaml_file))
# Now actually do it
@@ -67,8 +74,9 @@ class TestYamlProvider(TestCase):
# Dynamic plan
plan = target.plan(dynamic_zone)
self.assertEqual(6, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEqual(
6, len([c for c in plan.changes if isinstance(c, Create)])
)
self.assertFalse(isfile(dynamic_yaml_file))
# Apply it
self.assertEqual(6, target.apply(plan))
@@ -79,8 +87,10 @@ class TestYamlProvider(TestCase):
target.populate(reloaded)
self.assertDictEqual(
{'included': ['test']},
[x for x in reloaded.records
if x.name == 'included'][0]._octodns)
[x for x in reloaded.records if x.name == 'included'][
0
]._octodns,
)
# manually copy over the root since it will have been ignored
# when things were written out
@@ -90,8 +100,9 @@ class TestYamlProvider(TestCase):
# A 2nd sync should still create everything
plan = target.plan(zone)
self.assertEqual(22, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEqual(
22, len([c for c in plan.changes if isinstance(c, Create)])
)
with open(yaml_file) as fh:
data = safe_load(fh.read())
@@ -100,7 +111,7 @@ class TestYamlProvider(TestCase):
roots = sorted(data.pop(''), key=lambda r: r['type'])
self.assertTrue('values' in roots[0]) # A
self.assertTrue('geo' in roots[0]) # geo made the trip
self.assertTrue('value' in roots[1]) # CAA
self.assertTrue('value' in roots[1]) # CAA
self.assertTrue('values' in roots[2]) # SSHFP
# these are stored as plural 'values'
@@ -162,8 +173,9 @@ class TestYamlProvider(TestCase):
self.assertEqual([], list(data.keys()))
def test_empty(self):
source = YamlProvider('test', join(dirname(__file__), 'config'),
supports_root_ns=False)
source = YamlProvider(
'test', join(dirname(__file__), 'config'), supports_root_ns=False
)
zone = Zone('empty.', [])
@@ -172,34 +184,41 @@ class TestYamlProvider(TestCase):
self.assertEqual(0, len(zone.records))
def test_unsorted(self):
source = YamlProvider('test', join(dirname(__file__), 'config'),
supports_root_ns=False)
source = YamlProvider(
'test', join(dirname(__file__), 'config'), supports_root_ns=False
)
zone = Zone('unordered.', [])
with self.assertRaises(ConstructorError):
source.populate(zone)
source = YamlProvider('test', join(dirname(__file__), 'config'),
enforce_order=False, supports_root_ns=False)
source = YamlProvider(
'test',
join(dirname(__file__), 'config'),
enforce_order=False,
supports_root_ns=False,
)
# no exception
source.populate(zone)
self.assertEqual(2, len(zone.records))
def test_subzone_handling(self):
source = YamlProvider('test', join(dirname(__file__), 'config'),
supports_root_ns=False)
source = YamlProvider(
'test', join(dirname(__file__), 'config'), supports_root_ns=False
)
# If we add `sub` as a sub-zone we'll reject `www.sub`
zone = Zone('unit.tests.', ['sub'])
with self.assertRaises(SubzoneRecordException) as ctx:
source.populate(zone)
self.assertEqual('Record www.sub.unit.tests. is under a managed '
'subzone', str(ctx.exception))
self.assertEqual(
'Record www.sub.unit.tests. is under a managed ' 'subzone',
str(ctx.exception),
)
class TestSplitYamlProvider(TestCase):
def test_list_all_yaml_files(self):
yaml_files = ('foo.yaml', '1.yaml', '$unit.tests.yaml')
all_files = ('something', 'else', '1', '$$', '-f') + yaml_files
@@ -223,19 +242,21 @@ class TestSplitYamlProvider(TestCase):
def test_zone_directory(self):
source = SplitYamlProvider(
'test', join(dirname(__file__), 'config/split'),
extension='.tst')
'test', join(dirname(__file__), 'config/split'), extension='.tst'
)
zone = Zone('unit.tests.', [])
self.assertEqual(
join(dirname(__file__), 'config/split', 'unit.tests.tst'),
source._zone_directory(zone))
source._zone_directory(zone),
)
def test_apply_handles_existing_zone_directory(self):
with TemporaryDirectory() as td:
provider = SplitYamlProvider('test', join(td.dirname, 'config'),
extension='.tst')
provider = SplitYamlProvider(
'test', join(td.dirname, 'config'), extension='.tst'
)
makedirs(join(td.dirname, 'config', 'does.exist.tst'))
zone = Zone('does.exist.', [])
@@ -245,8 +266,8 @@ class TestSplitYamlProvider(TestCase):
def test_provider(self):
source = SplitYamlProvider(
'test', join(dirname(__file__), 'config/split'),
extension='.tst')
'test', join(dirname(__file__), 'config/split'), extension='.tst'
)
zone = Zone('unit.tests.', [])
dynamic_zone = Zone('dynamic.tests.', [])
@@ -267,14 +288,15 @@ class TestSplitYamlProvider(TestCase):
directory = join(td.dirname, 'sub', 'dir')
zone_dir = join(directory, 'unit.tests.tst')
dynamic_zone_dir = join(directory, 'dynamic.tests.tst')
target = SplitYamlProvider('test', directory,
extension='.tst',
supports_root_ns=False)
target = SplitYamlProvider(
'test', directory, extension='.tst', supports_root_ns=False
)
# We add everything
plan = target.plan(zone)
self.assertEqual(17, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEqual(
17, len([c for c in plan.changes if isinstance(c, Create)])
)
self.assertFalse(isdir(zone_dir))
# Now actually do it
@@ -282,8 +304,9 @@ class TestSplitYamlProvider(TestCase):
# Dynamic plan
plan = target.plan(dynamic_zone)
self.assertEqual(5, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEqual(
5, len([c for c in plan.changes if isinstance(c, Create)])
)
self.assertFalse(isdir(dynamic_zone_dir))
# Apply it
self.assertEqual(5, target.apply(plan))
@@ -294,8 +317,10 @@ class TestSplitYamlProvider(TestCase):
target.populate(reloaded)
self.assertDictEqual(
{'included': ['test']},
[x for x in reloaded.records
if x.name == 'included'][0]._octodns)
[x for x in reloaded.records if x.name == 'included'][
0
]._octodns,
)
# manually copy over the root since it will have been ignored
# when things were written out
@@ -305,8 +330,9 @@ class TestSplitYamlProvider(TestCase):
# A 2nd sync should still create everything
plan = target.plan(zone)
self.assertEqual(17, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEqual(
17, len([c for c in plan.changes if isinstance(c, Create)])
)
yaml_file = join(zone_dir, '$unit.tests.yaml')
self.assertTrue(isfile(yaml_file))
@@ -315,13 +341,19 @@ class TestSplitYamlProvider(TestCase):
roots = sorted(data.pop(''), key=lambda r: r['type'])
self.assertTrue('values' in roots[0]) # A
self.assertTrue('geo' in roots[0]) # geo made the trip
self.assertTrue('value' in roots[1]) # CAA
self.assertTrue('value' in roots[1]) # CAA
self.assertTrue('values' in roots[2]) # SSHFP
# These records are stored as plural "values." Check each file to
# ensure correctness.
for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt',
'urlfwd'):
for record_name in (
'_srv._tcp',
'mx',
'naptr',
'sub',
'txt',
'urlfwd',
):
yaml_file = join(zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
@@ -329,8 +361,16 @@ class TestSplitYamlProvider(TestCase):
self.assertTrue('values' in data.pop(record_name))
# These are stored as singular "value." Again, check each file.
for record_name in ('aaaa', 'cname', 'dname', 'included', 'ptr',
'spf', 'www.sub', 'www'):
for record_name in (
'aaaa',
'cname',
'dname',
'included',
'ptr',
'spf',
'www.sub',
'www',
):
yaml_file = join(zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
@@ -339,8 +379,7 @@ class TestSplitYamlProvider(TestCase):
# Again with the plural, this time checking dynamic.tests.
for record_name in ('a', 'aaaa', 'real-ish-a'):
yaml_file = join(
dynamic_zone_dir, f'{record_name}.yaml')
yaml_file = join(dynamic_zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
data = safe_load(fh.read())
@@ -360,8 +399,8 @@ class TestSplitYamlProvider(TestCase):
def test_empty(self):
source = SplitYamlProvider(
'test', join(dirname(__file__), 'config/split'),
extension='.tst')
'test', join(dirname(__file__), 'config/split'), extension='.tst'
)
zone = Zone('empty.', [])
@@ -371,8 +410,8 @@ class TestSplitYamlProvider(TestCase):
def test_unsorted(self):
source = SplitYamlProvider(
'test', join(dirname(__file__), 'config/split'),
extension='.tst')
'test', join(dirname(__file__), 'config/split'), extension='.tst'
)
zone = Zone('unordered.', [])
@@ -382,35 +421,46 @@ class TestSplitYamlProvider(TestCase):
zone = Zone('unordered.', [])
source = SplitYamlProvider(
'test', join(dirname(__file__), 'config/split'),
extension='.tst', enforce_order=False)
'test',
join(dirname(__file__), 'config/split'),
extension='.tst',
enforce_order=False,
)
# no exception
source.populate(zone)
self.assertEqual(2, len(zone.records))
def test_subzone_handling(self):
source = SplitYamlProvider(
'test', join(dirname(__file__), 'config/split'),
extension='.tst')
'test', join(dirname(__file__), 'config/split'), extension='.tst'
)
# If we add `sub` as a sub-zone we'll reject `www.sub`
zone = Zone('unit.tests.', ['sub'])
with self.assertRaises(SubzoneRecordException) as ctx:
source.populate(zone)
self.assertEqual('Record www.sub.unit.tests. is under a managed '
'subzone', str(ctx.exception))
self.assertEqual(
'Record www.sub.unit.tests. is under a managed ' 'subzone',
str(ctx.exception),
)
class TestOverridingYamlProvider(TestCase):
def test_provider(self):
config = join(dirname(__file__), 'config')
override_config = join(dirname(__file__), 'config', 'override')
base = YamlProvider('base', config, populate_should_replace=False,
supports_root_ns=False)
override = YamlProvider('test', override_config,
populate_should_replace=True,
supports_root_ns=False)
base = YamlProvider(
'base',
config,
populate_should_replace=False,
supports_root_ns=False,
)
override = YamlProvider(
'test',
override_config,
populate_should_replace=True,
supports_root_ns=False,
)
zone = Zone('dynamic.tests.', [])
@@ -428,9 +478,8 @@ class TestOverridingYamlProvider(TestCase):
got = {r.name: r for r in zone.records}
self.assertEqual(7, len(got))
# 'a' was replaced with a generic record
self.assertEqual({
'ttl': 3600,
'values': ['4.4.4.4', '5.5.5.5']
}, got['a'].data)
self.assertEqual(
{'ttl': 3600, 'values': ['4.4.4.4', '5.5.5.5']}, got['a'].data
)
# And we have the new one
self.assertTrue('added' in got)
+2906 -3002
View File
File diff suppressed because it is too large Load Diff
+69 -38
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
@@ -11,7 +15,6 @@ from octodns.record.geo import GeoCodes
class TestRecordGeoCodes(TestCase):
def test_validate(self):
prefix = 'xyz '
@@ -21,53 +24,81 @@ class TestRecordGeoCodes(TestCase):
self.assertEqual([], GeoCodes.validate('NA-US-OR', prefix))
# Just plain bad
self.assertEqual(['xyz invalid geo code "XX-YY-ZZ-AA"'],
GeoCodes.validate('XX-YY-ZZ-AA', prefix))
self.assertEqual(['xyz unknown continent code "X-Y-Z"'],
GeoCodes.validate('X-Y-Z', prefix))
self.assertEqual(['xyz unknown continent code "XXX-Y-Z"'],
GeoCodes.validate('XXX-Y-Z', prefix))
self.assertEqual(
['xyz invalid geo code "XX-YY-ZZ-AA"'],
GeoCodes.validate('XX-YY-ZZ-AA', prefix),
)
self.assertEqual(
['xyz unknown continent code "X-Y-Z"'],
GeoCodes.validate('X-Y-Z', prefix),
)
self.assertEqual(
['xyz unknown continent code "XXX-Y-Z"'],
GeoCodes.validate('XXX-Y-Z', prefix),
)
# Bad continent
self.assertEqual(['xyz unknown continent code "XX"'],
GeoCodes.validate('XX', prefix))
self.assertEqual(
['xyz unknown continent code "XX"'], GeoCodes.validate('XX', prefix)
)
# Bad continent good country
self.assertEqual(['xyz unknown continent code "XX-US"'],
GeoCodes.validate('XX-US', prefix))
self.assertEqual(
['xyz unknown continent code "XX-US"'],
GeoCodes.validate('XX-US', prefix),
)
# Bad continent good country and province
self.assertEqual(['xyz unknown continent code "XX-US-OR"'],
GeoCodes.validate('XX-US-OR', prefix))
self.assertEqual(
['xyz unknown continent code "XX-US-OR"'],
GeoCodes.validate('XX-US-OR', prefix),
)
# Bad country, good continent
self.assertEqual(['xyz unknown country code "NA-XX"'],
GeoCodes.validate('NA-XX', prefix))
self.assertEqual(
['xyz unknown country code "NA-XX"'],
GeoCodes.validate('NA-XX', prefix),
)
# Bad country, good continent and state
self.assertEqual(['xyz unknown country code "NA-XX-OR"'],
GeoCodes.validate('NA-XX-OR', prefix))
self.assertEqual(
['xyz unknown country code "NA-XX-OR"'],
GeoCodes.validate('NA-XX-OR', prefix),
)
# Good country, good continent, but bad match
self.assertEqual(['xyz unknown country code "NA-GB"'],
GeoCodes.validate('NA-GB', prefix))
self.assertEqual(
['xyz unknown country code "NA-GB"'],
GeoCodes.validate('NA-GB', prefix),
)
# Bad province code, good continent and country
self.assertEqual(['xyz unknown province code "NA-US-XX"'],
GeoCodes.validate('NA-US-XX', prefix))
self.assertEqual(
['xyz unknown province code "NA-US-XX"'],
GeoCodes.validate('NA-US-XX', prefix),
)
def test_parse(self):
self.assertEqual({
'continent_code': 'NA',
'country_code': None,
'province_code': None,
}, GeoCodes.parse('NA'))
self.assertEqual({
'continent_code': 'NA',
'country_code': 'US',
'province_code': None,
}, GeoCodes.parse('NA-US'))
self.assertEqual({
'continent_code': 'NA',
'country_code': 'US',
'province_code': 'CA',
}, GeoCodes.parse('NA-US-CA'))
self.assertEqual(
{
'continent_code': 'NA',
'country_code': None,
'province_code': None,
},
GeoCodes.parse('NA'),
)
self.assertEqual(
{
'continent_code': 'NA',
'country_code': 'US',
'province_code': None,
},
GeoCodes.parse('NA-US'),
)
self.assertEqual(
{
'continent_code': 'NA',
'country_code': 'US',
'province_code': 'CA',
},
GeoCodes.parse('NA-US-CA'),
)
def test_country_to_code(self):
self.assertEqual('NA-US', GeoCodes.country_to_code('US'))
+29 -19
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
import dns.zone
from dns.exception import DNSException
@@ -13,8 +17,12 @@ from shutil import copyfile
from unittest import TestCase
from unittest.mock import patch
from octodns.source.axfr import AxfrSource, AxfrSourceZoneTransferFailed, \
ZoneFileSource, ZoneFileSourceLoadFailure
from octodns.source.axfr import (
AxfrSource,
AxfrSourceZoneTransferFailed,
ZoneFileSource,
ZoneFileSourceLoadFailure,
)
from octodns.zone import Zone
from octodns.record import ValidationError
@@ -22,17 +30,15 @@ from octodns.record import ValidationError
class TestAxfrSource(TestCase):
source = AxfrSource('test', 'localhost')
forward_zonefile = dns.zone.from_file('./tests/zones/unit.tests.tst',
'unit.tests', relativize=False)
forward_zonefile = dns.zone.from_file(
'./tests/zones/unit.tests.tst', 'unit.tests', relativize=False
)
@patch('dns.zone.from_xfr')
def test_populate(self, from_xfr_mock):
got = Zone('unit.tests.', [])
from_xfr_mock.side_effect = [
self.forward_zonefile,
DNSException
]
from_xfr_mock.side_effect = [self.forward_zonefile, DNSException]
self.source.populate(got)
self.assertEqual(16, len(got.records))
@@ -40,8 +46,7 @@ class TestAxfrSource(TestCase):
with self.assertRaises(AxfrSourceZoneTransferFailed) as ctx:
zone = Zone('unit.tests.', [])
self.source.populate(zone)
self.assertEqual('Unable to Perform Zone Transfer',
str(ctx.exception))
self.assertEqual('Unable to Perform Zone Transfer', str(ctx.exception))
class TestZoneFileSource(TestCase):
@@ -65,8 +70,10 @@ class TestZoneFileSource(TestCase):
# It did so we need to skip this test, that means windows won't
# have full code coverage, but skipping the test is going out of
# our way enough for a os-specific/oddball case.
self.skipTest('Unable to create unit.tests. (ending with .) so '
'skipping default filename testing.')
self.skipTest(
'Unable to create unit.tests. (ending with .) so '
'skipping default filename testing.'
)
source = ZoneFileSource('test', './tests/zones')
# Load zonefiles without a specified file extension
@@ -97,16 +104,19 @@ class TestZoneFileSource(TestCase):
with self.assertRaises(ZoneFileSourceLoadFailure) as ctx:
zone = Zone('invalid.zone.', [])
self.source.populate(zone)
self.assertEqual('The DNS zone has no NS RRset at its origin.',
str(ctx.exception))
self.assertEqual(
'The DNS zone has no NS RRset at its origin.', str(ctx.exception)
)
# Records are not to RFC (lenient=False)
with self.assertRaises(ValidationError) as ctx:
zone = Zone('invalid.records.', [])
self.source.populate(zone)
self.assertEqual('Invalid record _invalid.invalid.records.\n'
' - invalid name for SRV record',
str(ctx.exception))
self.assertEqual(
'Invalid record _invalid.invalid.records.\n'
' - invalid name for SRV record',
str(ctx.exception),
)
# Records are not to RFC, but load anyhow (lenient=True)
invalid = Zone('invalid.records.', [])
-1
View File
@@ -7,7 +7,6 @@ from octodns.zone import Zone
class TestEnvVarSource(TestCase):
def test_read_variable(self):
envvar = 'OCTODNS_TEST_ENVIRONMENT_VARIABLE'
source = EnvVarSource('testid', envvar, 'recordname', ttl=120)
+117 -134
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
@@ -24,103 +28,96 @@ class TestTinyDnsFileSource(TestCase):
expected = Zone('example.com.', [])
for name, data in (
('', {
'type': 'A',
'ttl': 30,
'values': ['10.2.3.4', '10.2.3.5'],
}),
('', {
'type': 'NS',
'ttl': 3600,
'values': ['ns1.ns.com.', 'ns2.ns.com.'],
}),
('sub', {
'type': 'NS',
'ttl': 30,
'values': ['ns3.ns.com.', 'ns4.ns.com.'],
}),
('www', {
'type': 'A',
'ttl': 3600,
'value': '10.2.3.6',
}),
('cname', {
'type': 'CNAME',
'ttl': 3600,
'value': 'www.example.com.',
}),
('some-host-abc123', {
'type': 'A',
'ttl': 1800,
'value': '10.2.3.7',
}),
('has-dup-def123', {
'type': 'A',
'ttl': 3600,
'value': '10.2.3.8',
}),
('www.sub', {
'type': 'A',
'ttl': 3600,
'value': '1.2.3.4',
}),
('has-dup-def456', {
'type': 'A',
'ttl': 3600,
'value': '10.2.3.8',
}),
('', {
'type': 'MX',
'ttl': 3600,
'values': [{
'preference': 10,
'exchange': 'smtp-1-host.example.com.',
}, {
'preference': 20,
'exchange': 'smtp-2-host.example.com.',
}]
}),
('smtp', {
'type': 'MX',
'ttl': 1800,
'values': [{
'preference': 30,
'exchange': 'smtp-1-host.example.com.',
}, {
'preference': 40,
'exchange': 'smtp-2-host.example.com.',
}]
}),
('', {
'type': 'TXT',
'ttl': 300,
'value': 'test TXT',
}),
('colon', {
'type': 'TXT',
'ttl': 300,
'value': 'test : TXT',
}),
('nottl', {
'type': 'TXT',
'ttl': 3600,
'value': 'nottl test TXT',
}),
('ipv6-3', {
'type': 'AAAA',
'ttl': 300,
'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5742',
}),
('ipv6-6', {
'type': 'AAAA',
'ttl': 3600,
'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5743',
}),
('semicolon', {
'type': 'TXT',
'ttl': 300,
'value': 'v=DKIM1\\; k=rsa\\; p=blah',
}),
('', {'type': 'A', 'ttl': 30, 'values': ['10.2.3.4', '10.2.3.5']}),
(
'',
{
'type': 'NS',
'ttl': 3600,
'values': ['ns1.ns.com.', 'ns2.ns.com.'],
},
),
(
'sub',
{
'type': 'NS',
'ttl': 30,
'values': ['ns3.ns.com.', 'ns4.ns.com.'],
},
),
('www', {'type': 'A', 'ttl': 3600, 'value': '10.2.3.6'}),
(
'cname',
{'type': 'CNAME', 'ttl': 3600, 'value': 'www.example.com.'},
),
(
'some-host-abc123',
{'type': 'A', 'ttl': 1800, 'value': '10.2.3.7'},
),
('has-dup-def123', {'type': 'A', 'ttl': 3600, 'value': '10.2.3.8'}),
('www.sub', {'type': 'A', 'ttl': 3600, 'value': '1.2.3.4'}),
('has-dup-def456', {'type': 'A', 'ttl': 3600, 'value': '10.2.3.8'}),
(
'',
{
'type': 'MX',
'ttl': 3600,
'values': [
{
'preference': 10,
'exchange': 'smtp-1-host.example.com.',
},
{
'preference': 20,
'exchange': 'smtp-2-host.example.com.',
},
],
},
),
(
'smtp',
{
'type': 'MX',
'ttl': 1800,
'values': [
{
'preference': 30,
'exchange': 'smtp-1-host.example.com.',
},
{
'preference': 40,
'exchange': 'smtp-2-host.example.com.',
},
],
},
),
('', {'type': 'TXT', 'ttl': 300, 'value': 'test TXT'}),
('colon', {'type': 'TXT', 'ttl': 300, 'value': 'test : TXT'}),
('nottl', {'type': 'TXT', 'ttl': 3600, 'value': 'nottl test TXT'}),
(
'ipv6-3',
{
'type': 'AAAA',
'ttl': 300,
'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5742',
},
),
(
'ipv6-6',
{
'type': 'AAAA',
'ttl': 3600,
'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5743',
},
),
(
'semicolon',
{
'type': 'TXT',
'ttl': 300,
'value': 'v=DKIM1\\; k=rsa\\; p=blah',
},
),
):
record = Record.new(expected, name, data)
expected.add_record(record)
@@ -135,11 +132,7 @@ class TestTinyDnsFileSource(TestCase):
expected = Zone('asdf.subtest.com.', [])
for name, data in (
('a3', {
'type': 'A',
'ttl': 3600,
'values': ['10.2.3.7'],
}),
('a3', {'type': 'A', 'ttl': 3600, 'values': ['10.2.3.7']}),
):
record = Record.new(expected, name, data)
expected.add_record(record)
@@ -154,16 +147,8 @@ class TestTinyDnsFileSource(TestCase):
expected = Zone('sub-asdf.subtest.com.', [])
for name, data in (
('a1', {
'type': 'A',
'ttl': 3600,
'values': ['10.2.3.5'],
}),
('a2', {
'type': 'A',
'ttl': 3600,
'values': ['10.2.3.6'],
}),
('a1', {'type': 'A', 'ttl': 3600, 'values': ['10.2.3.5']}),
('a2', {'type': 'A', 'ttl': 3600, 'values': ['10.2.3.6']}),
):
record = Record.new(expected, name, data)
expected.add_record(record)
@@ -178,26 +163,24 @@ class TestTinyDnsFileSource(TestCase):
expected = Zone('3.2.10.in-addr.arpa.', [])
for name, data in (
('10', {
'type': 'PTR',
'ttl': 3600,
'value': 'a-ptr.example.com.'
}),
('11', {
'type': 'PTR',
'ttl': 30,
'value': 'a-ptr-2.example.com.'
}),
('8', {
'type': 'PTR',
'ttl': 3600,
'value': 'has-dup-def123.example.com.'
}),
('7', {
'type': 'PTR',
'ttl': 1800,
'value': 'some-host-abc123.example.com.'
}),
('10', {'type': 'PTR', 'ttl': 3600, 'value': 'a-ptr.example.com.'}),
('11', {'type': 'PTR', 'ttl': 30, 'value': 'a-ptr-2.example.com.'}),
(
'8',
{
'type': 'PTR',
'ttl': 3600,
'value': 'has-dup-def123.example.com.',
},
),
(
'7',
{
'type': 'PTR',
'ttl': 1800,
'value': 'some-host-abc123.example.com.',
},
),
):
record = Record.new(expected, name, data)
expected.add_record(record)
+33 -33
View File
@@ -2,8 +2,12 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from io import StringIO
from unittest import TestCase
@@ -13,58 +17,54 @@ from octodns.yaml import safe_dump, safe_load
class TestYaml(TestCase):
def test_stuff(self):
self.assertEqual({
1: 'a',
2: 'b',
'3': 'c',
10: 'd',
'11': 'e',
}, safe_load('''
self.assertEqual(
{1: 'a', 2: 'b', '3': 'c', 10: 'd', '11': 'e'},
safe_load(
'''
1: a
2: b
'3': c
10: d
'11': e
'''))
'''
),
)
self.assertEqual({
'*.1.2': 'a',
'*.2.2': 'b',
'*.10.1': 'c',
'*.11.2': 'd',
}, safe_load('''
self.assertEqual(
{'*.1.2': 'a', '*.2.2': 'b', '*.10.1': 'c', '*.11.2': 'd'},
safe_load(
'''
'*.1.2': 'a'
'*.2.2': 'b'
'*.10.1': 'c'
'*.11.2': 'd'
'''))
'''
),
)
with self.assertRaises(ConstructorError) as ctx:
safe_load('''
safe_load(
'''
'*.2.2': 'b'
'*.1.2': 'a'
'*.11.2': 'd'
'*.10.1': 'c'
''')
self.assertTrue('keys out of order: expected *.1.2 got *.2.2 at' in
ctx.exception.problem)
'''
)
self.assertTrue(
'keys out of order: expected *.1.2 got *.2.2 at'
in ctx.exception.problem
)
buf = StringIO()
safe_dump({
'*.1.1': 42,
'*.11.1': 43,
'*.2.1': 44,
}, buf)
self.assertEqual("---\n'*.1.1': 42\n'*.2.1': 44\n'*.11.1': 43\n",
buf.getvalue())
safe_dump({'*.1.1': 42, '*.11.1': 43, '*.2.1': 44}, buf)
self.assertEqual(
"---\n'*.1.1': 42\n'*.2.1': 44\n'*.11.1': 43\n", buf.getvalue()
)
# hex sorting isn't ideal, not treated as hex, this make sure we don't
# change the behavior
buf = StringIO()
safe_dump({
'45a03129': 42,
'45a0392a': 43,
}, buf)
safe_dump({'45a03129': 42, '45a0392a': 43}, buf)
self.assertEqual("---\n45a0392a: 43\n45a03129: 42\n", buf.getvalue())
+108 -96
View File
@@ -2,21 +2,35 @@
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from unittest import TestCase
from octodns.record import ARecord, AaaaRecord, Create, Delete, NsRecord, \
Record, Update
from octodns.zone import DuplicateRecordException, InvalidNodeException, \
SubzoneRecordException, Zone
from octodns.record import (
ARecord,
AaaaRecord,
Create,
Delete,
NsRecord,
Record,
Update,
)
from octodns.zone import (
DuplicateRecordException,
InvalidNodeException,
SubzoneRecordException,
Zone,
)
from helpers import SimpleProvider
class TestZone(TestCase):
def test_lowering(self):
zone = Zone('UniT.TEsTs.', [])
self.assertEqual('unit.tests.', zone.name)
@@ -47,8 +61,9 @@ class TestZone(TestCase):
# Can't add record with same name & type
with self.assertRaises(DuplicateRecordException) as ctx:
zone.add_record(a)
self.assertEqual('Duplicate record a.unit.tests., type A',
str(ctx.exception))
self.assertEqual(
'Duplicate record a.unit.tests., type A', str(ctx.exception)
)
self.assertEqual(zone.records, set([a]))
# can add duplicate with replace=True
@@ -108,7 +123,6 @@ class TestZone(TestCase):
update.__repr__()
def test_unsupporting(self):
class NoAaaaProvider(object):
id = 'no-aaaa'
SUPPORTS_GEO = False
@@ -144,21 +158,21 @@ class TestZone(TestCase):
# NS for exactly the sub is allowed
zone = Zone('unit.tests.', set(['sub', 'barred']))
record = Record.new(zone, 'sub', {
'ttl': 3600,
'type': 'NS',
'values': ['1.2.3.4.', '2.3.4.5.'],
})
record = Record.new(
zone,
'sub',
{'ttl': 3600, 'type': 'NS', 'values': ['1.2.3.4.', '2.3.4.5.']},
)
zone.add_record(record)
self.assertEqual(set([record]), zone.records)
# non-NS for exactly the sub is rejected
zone = Zone('unit.tests.', set(['sub', 'barred']))
record = Record.new(zone, 'sub', {
'ttl': 3600,
'type': 'A',
'values': ['1.2.3.4', '2.3.4.5'],
})
record = Record.new(
zone,
'sub',
{'ttl': 3600, 'type': 'A', 'values': ['1.2.3.4', '2.3.4.5']},
)
with self.assertRaises(SubzoneRecordException) as ctx:
zone.add_record(record)
self.assertTrue('not of type NS', str(ctx.exception))
@@ -168,11 +182,11 @@ class TestZone(TestCase):
# NS for something below the sub is rejected
zone = Zone('unit.tests.', set(['sub', 'barred']))
record = Record.new(zone, 'foo.sub', {
'ttl': 3600,
'type': 'NS',
'values': ['1.2.3.4.', '2.3.4.5.'],
})
record = Record.new(
zone,
'foo.sub',
{'ttl': 3600, 'type': 'NS', 'values': ['1.2.3.4.', '2.3.4.5.']},
)
with self.assertRaises(SubzoneRecordException) as ctx:
zone.add_record(record)
self.assertTrue('under a managed sub-zone', str(ctx.exception))
@@ -182,11 +196,11 @@ class TestZone(TestCase):
# A for something below the sub is rejected
zone = Zone('unit.tests.', set(['sub', 'barred']))
record = Record.new(zone, 'foo.bar.sub', {
'ttl': 3600,
'type': 'A',
'values': ['1.2.3.4', '2.3.4.5'],
})
record = Record.new(
zone,
'foo.bar.sub',
{'ttl': 3600, 'type': 'A', 'values': ['1.2.3.4', '2.3.4.5']},
)
with self.assertRaises(SubzoneRecordException) as ctx:
zone.add_record(record)
self.assertTrue('under a managed sub-zone', str(ctx.exception))
@@ -199,21 +213,21 @@ class TestZone(TestCase):
zone_ignored = Zone('unit.tests.', [])
zone_missing = Zone('unit.tests.', [])
normal = Record.new(zone_normal, 'www', {
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
normal = Record.new(
zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'}
)
zone_normal.add_record(normal)
ignored = Record.new(zone_ignored, 'www', {
'octodns': {
'ignored': True
ignored = Record.new(
zone_ignored,
'www',
{
'octodns': {'ignored': True},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
)
zone_ignored.add_record(ignored)
provider = SimpleProvider()
@@ -229,16 +243,12 @@ class TestZone(TestCase):
def test_cname_coexisting(self):
zone = Zone('unit.tests.', [])
a = Record.new(zone, 'www', {
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
cname = Record.new(zone, 'www', {
'ttl': 60,
'type': 'CNAME',
'value': 'foo.bar.com.',
})
a = Record.new(
zone, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'}
)
cname = Record.new(
zone, 'www', {'ttl': 60, 'type': 'CNAME', 'value': 'foo.bar.com.'}
)
# add cname to a
zone.add_record(a)
@@ -262,21 +272,21 @@ class TestZone(TestCase):
zone_excluded = Zone('unit.tests.', [])
zone_missing = Zone('unit.tests.', [])
normal = Record.new(zone_normal, 'www', {
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
normal = Record.new(
zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'}
)
zone_normal.add_record(normal)
excluded = Record.new(zone_excluded, 'www', {
'octodns': {
'excluded': ['test']
excluded = Record.new(
zone_excluded,
'www',
{
'octodns': {'excluded': ['test']},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
)
zone_excluded.add_record(excluded)
provider = SimpleProvider()
@@ -295,21 +305,21 @@ class TestZone(TestCase):
zone_included = Zone('unit.tests.', [])
zone_missing = Zone('unit.tests.', [])
normal = Record.new(zone_normal, 'www', {
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
normal = Record.new(
zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'}
)
zone_normal.add_record(normal)
included = Record.new(zone_included, 'www', {
'octodns': {
'included': ['test']
included = Record.new(
zone_included,
'www',
{
'octodns': {'included': ['test']},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
)
zone_included.add_record(included)
provider = SimpleProvider()
@@ -328,21 +338,21 @@ class TestZone(TestCase):
zone_included = Zone('unit.tests.', [])
zone_missing = Zone('unit.tests.', [])
normal = Record.new(zone_normal, 'www', {
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
normal = Record.new(
zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'}
)
zone_normal.add_record(normal)
included = Record.new(zone_included, 'www', {
'octodns': {
'included': ['not-here']
included = Record.new(
zone_included,
'www',
{
'octodns': {'included': ['not-here']},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
},
'ttl': 60,
'type': 'A',
'value': '9.9.9.9',
})
)
zone_included.add_record(included)
provider = SimpleProvider()
@@ -420,18 +430,20 @@ class TestZone(TestCase):
# No root NS yet
self.assertFalse(zone.root_ns)
non_root_ns = NsRecord(zone, 'sub', {'ttl': 42, 'values': (
'ns1.unit.tests.',
'ns2.unit.tests.',
)})
non_root_ns = NsRecord(
zone,
'sub',
{'ttl': 42, 'values': ('ns1.unit.tests.', 'ns2.unit.tests.')},
)
zone.add_record(non_root_ns)
# No root NS yet b/c this was a sub
self.assertFalse(zone.root_ns)
root_ns = NsRecord(zone, '', {'ttl': 42, 'values': (
'ns3.unit.tests.',
'ns4.unit.tests.',
)})
root_ns = NsRecord(
zone,
'',
{'ttl': 42, 'values': ('ns3.unit.tests.', 'ns4.unit.tests.')},
)
zone.add_record(root_ns)
# Now we have a root NS
self.assertEqual(root_ns, zone.root_ns)