"""Module implements plugin for pytest. It injects special fixture request_engine for all tests. It protects """ import re from unittest.mock import patch import pytest from owsrequest import request as ows_request from owsrequest import test_utils class OwsrequestMockException(Exception): """Exception raised when not expected request call occurs.""" class MockCallSpec: """Specifies mock that put on special method/path.""" def __init__(self, method, path, response, status=200): """Init mock spec. Args: method (str): HTTP method or '*' to match all. path (str): URI to match for this spec response (dict): response to request on this spec. status (int): HTTP response code for this spec """ self.method = method.upper() self.path_pattern = re.compile(path) self.status = status self.response = response if response is None: self.response = {} self.calls = [] @property def called(self): """Check if mock was called.""" return bool(self.calls) def match(self, method, path): """Check that mock spec matches method and path. Args: method (str): HTTP method to test this spec path (str): URI to test this spec Returns: bool: is spec matches method and path """ if self.method != '*' and self.method != method.upper(): return False return self.path_pattern.match(path) def __call__(self, method, path, **options): """Do actual call when mock is requested by owsrequest. Args: method (str): HTTP method this mock was called path (str): URI that called whis mock Returns: test_utils.MockOwsResponse: Mock response """ options['method'] = method options['path'] = path self.calls.append(options) if isinstance(self.response, BaseException): raise self.response if isinstance(self.response, type) and issubclass( self.response, BaseException): raise self.response() return test_utils.MockOwsResponse( status_code=self.status, json=self.response) class ServiceRouter: """Class provides service requests router.""" def __init__(self, name): """Init service router for specified service name. Args: name (str): Service name """ self.name = name self.specs = [] def __call__(self, method, path, **options): """Do call for service. Args: method (str): HTTP method to call path (str): URI to call options (dict): other request options """ for spec in self.specs: if spec.match(method, path): return spec(method, path, **options) raise OwsrequestMockException( 'Service {} called with unexpected request {} {}'.format( self.name, method, path)) def add_spec(self, method, path, response=None, status=200): """Add new mock spec to service. Args: method (str): Method for mock spec or '*' to match all. path (str): URI for mock spec. Can be regexp. response (dict): JSON for response or exception for side effect. status (int): HTTP status for mock response. Returns: MockCallSpec: mock for this spec. """ spec = MockCallSpec(method, path, response, status) self.specs.append(spec) return spec class RequestEngine: """Mock engine class.""" def __init__(self, fixture_request): """Init new mock engine. Args: fixture_request: request for fixture that creates engine. """ self.services = {} self.request = fixture_request def __getitem__(self, item): """Shortcut for accessing services.""" if item not in self.services: self.services[item] = ServiceRouter(item) return self.services[item] def process( self, application, environment, method, service_name, path, correlation_id=None, **options): """Function to replace request.process.""" service = self.services.get(service_name) # type: ServiceRouter if service is None: raise OwsrequestMockException( 'Service {} called, but it is not configured for this ' 'test'.format(service_name)) return service(method, path, correlation_id=correlation_id, **options) engine = None # type: RequestEngine def pytest_collection_modifyitems(session, config, items): """Hook that will inject owsrequest engine in all tests.""" for item in items: if 'no_owsrequest_patch' in item.keywords: continue if 'request_engine' not in item.fixturenames: item.fixturenames.append('request_engine') @pytest.fixture def request_engine(request): """Fixture mocking owsrequest engine.""" global request_engine request_engine = RequestEngine(request) with patch.object(ows_request, 'process', request_engine.process): yield request_engine request_engine = None