1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
import importlib
import pkgutil
from argparse import ArgumentParser
from re import Pattern
from types import ModuleType
from typing import List, Union, cast
from unittest import mock
import pytest
def pytest_addoption(parser):
"""Standard pytest hook invoked to add options to pytest CLI"""
parser.addoption(
"--xfail-providers-with-missing-deps",
action="store_true",
help="Skip tests on providers with optional dependencies",
)
def pytest_runtest_setup(item):
"""Standard pytest hook invoked before each test execution"""
try:
skip_providers_with_optdeps = getattr(
item.config.option, "xfail_providers_with_missing_deps"
)
except AttributeError:
pass
else:
if skip_providers_with_optdeps:
from lexicon._private.discovery import find_providers
providers = find_providers()
skip = [
available
for provider, available in providers.items()
if provider in item.parent.name.lower()
]
if skip and not skip[0]:
pytest.xfail(
"Test expected to fail because --skip-providers-with-missing-deps "
"is set and provider has missing required dependencies."
)
@pytest.fixture(scope="session")
def mock_provider():
"""
Create a fake provider module, and mock relevant
functions to make it appear as a real module.
"""
from lexicon.interfaces import Provider as BaseProvider
class Provider(BaseProvider):
"""
Fake provider to simulate the provider resolution from configuration,
and to have execution traces when lexicon client is invoked
"""
@staticmethod
def get_nameservers() -> Union[List[str], List[Pattern]]:
return cast(List[str], [])
@staticmethod
def configure_parser(parser: ArgumentParser) -> None:
pass
def authenticate(self):
print("Authenticate action")
def create_record(self, rtype, name, content):
return {
"action": "create",
"domain": self.domain,
"type": rtype,
"name": name,
"content": content,
}
def list_records(self, rtype=None, name=None, content=None):
return {
"action": "list",
"domain": self.domain,
"type": rtype,
"name": name,
"content": content,
}
def update_record(self, identifier=None, rtype=None, name=None, content=None):
return {
"action": "update",
"domain": self.domain,
"identifier": identifier,
"type": rtype,
"name": name,
"content": content,
}
def delete_record(self, identifier=None, rtype=None, name=None, content=None):
return {
"action": "delete",
"domain": self.domain,
"identifier": identifier,
"type": rtype,
"name": name,
"content": content,
}
def _request(self, action="GET", url="/", data=None, query_params=None):
# Not use for tests
pass
original_iter = pkgutil.iter_modules
original_import = importlib.import_module
with mock.patch("lexicon._private.discovery.pkgutil.iter_modules") as mock_iter:
with mock.patch(
"lexicon._private.discovery.importlib.import_module"
) as mock_import:
def return_iter(path):
"""
This will include an adhoc fakeprovider module
to the normal return of pkgutil.iter_modules.
"""
modules = list(original_iter(path))
modules.append((None, "fakeprovider", None))
return modules
mock_iter.side_effect = return_iter
def return_import(module_name):
"""
This will return a adhoc fakeprovider module if necessary,
or fallback to the normal return of importlib.import_module.
"""
if module_name == "lexicon._private.providers.fakeprovider":
module = ModuleType("lexicon._private.providers.fakeprovider")
setattr(module, "Provider", Provider)
return module
return original_import(module_name)
mock_import.side_effect = return_import
yield
|