"""Test rest protocol.""" import asyncio import importlib import uuid from unittest.mock import MagicMock import pytest from owsrequest import request as owsrequest from tornado import httpclient from tornado.httputil import HTTPHeaders from grass import config from grass.logic.jwt_enabled_services import jwtEnabledServices from grass.protocols import rest @pytest.mark.parametrize( 'method_name', ['delete', 'post', 'put', 'patch', 'head', 'get'] ) async def test_method_call(monkeypatch, method_name): """All methods should point to request.""" response = MagicMock() service = MagicMock() service.resolve.return_value = 'http://theorchard.com:80/something/' handler = MagicMock() handler.name = 'ows-product' handler.get_correlation_id = MagicMock(return_value='correlation_id') handler.get_unique_call_id = MagicMock(return_value='10') authorization = str(uuid.uuid1()) class AsyncHTTPClient(MagicMock): def fetch(self, req, raise_error): assert req.headers.get('Authorization') == authorization assert req.headers.get('Correlation-Id') f = asyncio.Future() f.set_result(response) return f def configure(*args, **kwargs): pass class AsyncHTTPClientNoAuthorization(MagicMock): def fetch(self, req, raise_error): assert not req.headers.get('Authorization') assert req.headers.get('Correlation-Id') f = asyncio.Future() f.set_result(response) return f def configure(*args, **kwargs): pass # If service name not in the cache list monkeypatch.setattr(httpclient, 'AsyncHTTPClient', AsyncHTTPClient) monkeypatch.setattr(rest, 'on_fetch', MagicMock()) monkeypatch.setattr( rest, 'create_ows_authorization', MagicMock(autospec=rest.create_ows_authorization, return_value=authorization), ) cacheMock = MagicMock() cacheMock.getCache = ['ows-metadata'] monkeypatch.setattr(jwtEnabledServices, 'getInstance', cacheMock) monkeypatch.setattr( httpclient, 'HTTPRequest', MagicMock(wraps=httpclient.HTTPRequest) ) # set environment to prod to verify we generate auth headers monkeypatch.setattr(config, 'environment', config.PROD_ENVIRONMENT) await getattr(rest, method_name)( handler, service, 'something/', {'param1': 'param1_value'} ) rest.on_fetch.assert_called() handler.get_correlation_id.assert_called() rest.on_fetch.assert_called_once_with(handler, response) request_headers = httpclient.HTTPRequest.call_args[1]['headers'] httpclient.HTTPRequest.assert_called_with( 'http://theorchard.com:80/something/?param1=param1_value', headers={ 'Authorization': authorization, 'Correlation-Id': 'correlation_id.10', 'Host': 'theorchard.com:80', 'x-datadog-trace-id': request_headers['x-datadog-trace-id'], 'x-datadog-parent-id': request_headers['x-datadog-parent-id'], 'x-datadog-sampling-priority': request_headers[ 'x-datadog-sampling-priority' ], 'x-datadog-tags': request_headers['x-datadog-tags'], 'traceparent': request_headers['traceparent'], 'tracestate': request_headers['tracestate'], }, body=None, follow_redirects=False, allow_nonstandard_methods=True, method=method_name.upper(), request_timeout=60, ) rest.on_fetch.reset_mock() await rest.request(method_name.upper(), handler, service, 'something/') rest.on_fetch.assert_called() rest.on_fetch.assert_called_once_with(handler, response) request_headers = httpclient.HTTPRequest.call_args[1]['headers'] httpclient.HTTPRequest.assert_called_with( 'http://theorchard.com:80/something/', headers={ 'Authorization': authorization, 'Correlation-Id': 'correlation_id.10', 'Host': 'theorchard.com:80', 'x-datadog-trace-id': request_headers['x-datadog-trace-id'], 'x-datadog-parent-id': request_headers['x-datadog-parent-id'], 'x-datadog-sampling-priority': request_headers[ 'x-datadog-sampling-priority' ], 'x-datadog-tags': request_headers['x-datadog-tags'], 'traceparent': request_headers['traceparent'], 'tracestate': request_headers['tracestate'], }, body=None, follow_redirects=False, allow_nonstandard_methods=True, method=method_name.upper(), request_timeout=60, ) rest.on_fetch.reset_mock() # test that dev does not add authorization headers # but does set correlation-id and host monkeypatch.setattr(config, 'environment', config.DEV_ENVIRONMENT) monkeypatch.setattr(httpclient, 'AsyncHTTPClient', AsyncHTTPClientNoAuthorization) await rest.request(method_name.upper(), handler, service, 'something/') rest.on_fetch.assert_called() rest.on_fetch.assert_called_once_with(handler, response) request_headers = httpclient.HTTPRequest.call_args[1]['headers'] httpclient.HTTPRequest.assert_called_with( 'http://theorchard.com:80/something/', headers={ 'Correlation-Id': 'correlation_id.10', 'Host': 'theorchard.com:80', 'x-datadog-trace-id': request_headers['x-datadog-trace-id'], 'x-datadog-parent-id': request_headers['x-datadog-parent-id'], 'x-datadog-sampling-priority': request_headers[ 'x-datadog-sampling-priority' ], 'x-datadog-tags': request_headers['x-datadog-tags'], 'traceparent': request_headers['traceparent'], 'tracestate': request_headers['tracestate'], }, body=None, follow_redirects=False, allow_nonstandard_methods=True, method=method_name.upper(), request_timeout=60, ) rest.on_fetch.reset_mock() # Service name present in the cache list monkeypatch.setattr(httpclient, 'AsyncHTTPClient', AsyncHTTPClient) monkeypatch.setattr(rest, 'on_fetch', MagicMock()) cacheMock = MagicMock() cacheMock.getCache = ['ows-product'] monkeypatch.setattr(jwtEnabledServices, 'getInstance', cacheMock) monkeypatch.setattr( httpclient, 'HTTPRequest', MagicMock(wraps=httpclient.HTTPRequest) ) # set environment to prod to verify we generate auth headers monkeypatch.setattr(config, 'environment', config.PROD_ENVIRONMENT) await getattr(rest, method_name)( handler, service, 'something/', {'param1': 'param1_value'} ) rest.on_fetch.assert_called() handler.get_correlation_id.assert_called() rest.on_fetch.assert_called_once_with(handler, response) request_headers = httpclient.HTTPRequest.call_args[1]['headers'] httpclient.HTTPRequest.assert_called_with( 'http://theorchard.com:80/something/?param1=param1_value', headers={ 'Authorization': authorization, 'Correlation-Id': 'correlation_id.10', 'Host': 'theorchard.com:80', 'x-datadog-trace-id': request_headers['x-datadog-trace-id'], 'x-datadog-parent-id': request_headers['x-datadog-parent-id'], 'x-datadog-sampling-priority': request_headers[ 'x-datadog-sampling-priority' ], 'x-datadog-tags': request_headers['x-datadog-tags'], 'traceparent': request_headers['traceparent'], 'tracestate': request_headers['tracestate'], }, body=None, follow_redirects=False, allow_nonstandard_methods=True, method=method_name.upper(), request_timeout=60, ) rest.on_fetch.reset_mock() await rest.request(method_name.upper(), handler, service, 'something/') rest.on_fetch.assert_called() rest.on_fetch.assert_called_with(handler, response) request_headers = httpclient.HTTPRequest.call_args[1]['headers'] httpclient.HTTPRequest.assert_called_with( 'http://theorchard.com:80/something/', headers={ 'Authorization': authorization, 'Correlation-Id': 'correlation_id.10', 'Host': 'theorchard.com:80', 'x-datadog-trace-id': request_headers['x-datadog-trace-id'], 'x-datadog-parent-id': request_headers['x-datadog-parent-id'], 'x-datadog-sampling-priority': request_headers[ 'x-datadog-sampling-priority' ], 'x-datadog-tags': request_headers['x-datadog-tags'], 'traceparent': request_headers['traceparent'], 'tracestate': request_headers['tracestate'], }, body=None, follow_redirects=False, allow_nonstandard_methods=True, method=method_name.upper(), request_timeout=60, ) rest.on_fetch.reset_mock() # If service name in the cache list and autherization is not present monkeypatch.setattr(httpclient, 'AsyncHTTPClient', AsyncHTTPClient) monkeypatch.setattr(rest, 'on_fetch', MagicMock()) monkeypatch.setattr( rest, 'create_ows_authorization', MagicMock(autospec=rest.create_ows_authorization, return_value=authorization), ) cacheMock = MagicMock() cacheMock.getCache = ['ows-product'] monkeypatch.setattr(jwtEnabledServices, 'getInstance', cacheMock) monkeypatch.setattr( httpclient, 'HTTPRequest', MagicMock(wraps=httpclient.HTTPRequest) ) # set environment to prod to verify we generate auth headers monkeypatch.setattr(config, 'environment', config.PROD_ENVIRONMENT) await getattr(rest, method_name)( handler, service, 'something/', {'param1': 'param1_value'} ) rest.on_fetch.assert_called() handler.get_correlation_id.assert_called() rest.on_fetch.assert_called_once_with(handler, response) request_headers = httpclient.HTTPRequest.call_args[1]['headers'] httpclient.HTTPRequest.assert_called_with( 'http://theorchard.com:80/something/?param1=param1_value', headers={ 'Authorization': authorization, 'Correlation-Id': 'correlation_id.10', 'Host': 'theorchard.com:80', 'x-datadog-trace-id': request_headers['x-datadog-trace-id'], 'x-datadog-parent-id': request_headers['x-datadog-parent-id'], 'x-datadog-sampling-priority': request_headers[ 'x-datadog-sampling-priority' ], 'x-datadog-tags': request_headers['x-datadog-tags'], 'traceparent': request_headers['traceparent'], 'tracestate': request_headers['tracestate'], }, body=None, follow_redirects=False, allow_nonstandard_methods=True, method=method_name.upper(), request_timeout=60, ) def test_on_successful_fetch(): """Test the on fetch callback.""" request_handler = MagicMock() response = MagicMock() response.body = b'Data of the body' response.code = 200 response.error = None response.headers = HTTPHeaders( {'Content-Type': 'application/json', 'Random-Header': 'value'} ) response.headers.add('Set-Cookie', 'A=B') response.headers.add('Set-Cookie', 'C=D') rest.on_fetch(request_handler, response) request_handler.set_status.assert_called_with(response.code) request_handler.set_header.assert_any_call( 'Content-Type', response.headers.get('Content-Type') ) request_handler.add_header.assert_any_call('Set-Cookie', 'A=B') request_handler.add_header.assert_any_call('Set-Cookie', 'C=D') request_handler.write.assert_called_with(response.body) assert request_handler.finish.called assert request_handler.add_request_information.called def test_on_successful_fetch_with_empty_body(): """Test the on fetch callback with an emtpy body.""" request_handler = MagicMock() response = MagicMock() response.body = b'' response.code = 200 response.error = None response.headers = HTTPHeaders( {'Content-Type': 'application/json', 'Random-Header': 'value'} ) rest.on_fetch(request_handler, response) request_handler.set_status.assert_called_with(response.code) request_handler.set_header.assert_any_call( 'Content-Type', response.headers.get('Content-Type') ) assert not request_handler.write.called assert request_handler.finish.called def test_on_404_fetch(): """Test the on fetch callback on 404.""" request_handler = MagicMock() response = MagicMock() response.body = b'' response.code = 404 response.error = None response.headers = HTTPHeaders( {'Content-Type': 'application/json', 'Random-Header': 'value'} ) rest.on_fetch(request_handler, response) request_handler.set_status.assert_called_with(response.code) request_handler.set_header.assert_any_call( 'Content-Type', response.headers.get('Content-Type') ) request_handler.set_status.assert_called_with(response.code) assert request_handler.finish.called assert request_handler.add_request_information.called def test_on_error_fetch(): """Test the on fetch callback with an error.""" request_handler = MagicMock() response = MagicMock() response.error = True response.code = 500 response.body = b'' response.headers = HTTPHeaders( {'Content-Type': 'application/json', 'Random-Header': 'value'} ) rest.on_fetch(request_handler, response) request_handler.set_status.assert_called_with(response.code) assert request_handler.add_request_information.called test_url_1 = 'http://abc.def.com/p,a%2Ct%2ch%5C%20p\\ .ab/c?ef=123,a%2c&b%2C' test_url_2 = 'http://abc.def.com/p,a%2Ct%2ch\\ p\\ .ab/c?ef=123,a%2c&b%2C' test_url_3 = 'http://abc.def.com/p,a%2Ct%2ch%5C%20p%5C%20.ab/c?ef=123,a%2c&b%2C' authorization_q = 'ows-grass/{}:1053b7faf477499b316a64cafcd10780ebb0c9ba' ows_fred = 'ows-fred' ows_applesauce_bananas = 'ows-applesauce-bananas' @pytest.mark.parametrize( ('incoming_url', 'request_handler_name', 'expected_authorization'), [ (test_url_1, ows_fred, authorization_q.format(ows_fred)), (test_url_2, ows_fred, authorization_q.format(ows_fred)), (test_url_3, ows_fred, authorization_q.format(ows_fred)), ( test_url_1, ows_applesauce_bananas, authorization_q.format(ows_applesauce_bananas), ), ( test_url_2, ows_applesauce_bananas, authorization_q.format(ows_applesauce_bananas), ), ( test_url_3, ows_applesauce_bananas, authorization_q.format(ows_applesauce_bananas), ), ], ) def test_creating_ows_authorization( monkeypatch, incoming_url, request_handler_name, expected_authorization ): """Test the creation of the ows authorization.""" request_handler = MagicMock() request_handler.name = request_handler_name request1 = httpclient.HTTPRequest(incoming_url, method='GET') correlation_id = 'correlation-id' # get real owsrequest.create_authorization importlib.reload(owsrequest) monkeypatch.setattr( owsrequest.model, 'save_authorization', MagicMock(spec=owsrequest.model.save_authorization), ) monkeypatch.setattr( owsrequest.hmac, 'create_secret', MagicMock( spec=owsrequest.hmac.calculate, return_value='8eSMw3Ew8hOKj0B50HGLEc0dshiUO6', ), ) authorization = rest.create_ows_authorization( request_handler, request1, correlation_id ) assert expected_authorization == authorization