"""Test mode logic layer.""" import json import re from unittest.mock import MagicMock import pytest import requests from oto.response import ( Response, create_fatal_response, create_not_found_response, ) from reporting import config from reporting.constants import mode as mc from reporting.logic import mode from reporting.utils import mode as utils def test_generate_embed_src_no_params(): """Test generating a signed embed url with no parameters.""" report_token = 1 expected_base_url = '{}/{}/reports/{}'.format( mc.BASE_URI, mc.ORGANIZATION, report_token ) embed_src = mode.generate_embed_src(report_token, {}, False).message.get( 'signed_embed_src' ) assert expected_base_url in embed_src assert '/embed?' in embed_src assert '&signature=' in embed_src assert '×tamp=' in embed_src assert 'access_key=' in embed_src assert 'max_age={}'.format(mc.MAX_AGE_ONE_HOUR) in embed_src def test_generate_embed_src_with_params(): """Test params added to generated signed url.""" report_token = 1 params = {'param_limit': 50, 'param_vendor_id': 7123} expected_base_url = '{}/{}/reports/{}'.format( mc.BASE_URI, mc.ORGANIZATION, report_token ) embed_src = mode.generate_embed_src( report_token, params, False ).message.get('signed_embed_src') assert expected_base_url in embed_src assert 'max_age={}'.format(mc.MAX_AGE_ONE_HOUR) in embed_src assert '¶m_limit=50' in embed_src assert '¶m_vendor_id=7123' in embed_src def test_generate_embed_src_orders_params_in_url(): """Test params ordered alphabetically in signed url.""" report_token = 1 params = {'param_limit': 50, 'param_vendor_id': 7123} sort_order_regex = re.compile( 'access_key=.+&max_age=.+¶m_limit=.+¶m_vendor_id=.+×tamp=.+' ) # noqa embed_src = mode.generate_embed_src( report_token, params, False ).message.get('signed_embed_src') assert sort_order_regex.search(embed_src) is not None def test_generate_embed_src_orders_params_url_encoded(): """Test params are url encoded in %xx format in signed url.""" report_token = 1 params = {'param_label_name': 'Live* fast+die_young(!)'} embed_src = mode.generate_embed_src( report_token, params, False ).message.get('signed_embed_src') assert 'param_label_name=Live%2A%20fast%2Bdie_young%28%21%29' in embed_src def test_generate_embed_src_can_up_cache_time(): """Test params are url encoded in %xx format in signed url.""" report_token = 1 params = {'param_label_name': 'Live* fast+die_young(!)'} cache = True embed_src = mode.generate_embed_src( report_token, params, cache ).message.get('signed_embed_src') assert 'max_age={}'.format(mc.MAX_AGE_ONE_DAY) in embed_src def test_generate_embed_src_with_custom_cache_time(): """Test to generate signed embed url with custom cache time.""" report_token = 1 params = {} expected_base_url = '{}/{}/reports/{}'.format( mc.BASE_URI, mc.ORGANIZATION, report_token ) embed_src = mode.generate_embed_src( report_token, params, False, 3600 ).message.get('signed_embed_src') assert expected_base_url in embed_src assert 'max_age={}'.format(mc.MAX_AGE_ONE_HOUR) in embed_src def test_report_run_status_succeeded(mocker): """Test run status returns 200 when state is 'succeeded'.""" base_uri = 'https://modeanalytics.com/api/theorchard' report_token = '0916aeab9453' report_run_token = '2428701e9a74' expected_response = {'name': 'Retailer View', 'content': []} report_url = '{}/reports/{}/runs/{}'.format( base_uri, report_token, report_run_token ) mode_response = MagicMock( status_code=200, json=lambda: {'state': mc.REPORT_RUN_COMPLETE} ) mocker.patch.object(requests, 'get', return_value=mode_response) mocker.patch.object( mode, 'get_report_data', return_value=Response(status=200, message=expected_response), ) report_run_status = mode.report_run_status(report_token, report_run_token) requests.get.assert_called_with(report_url, auth=config.MODE_API_AUTH_KEYS) assert report_run_status assert report_run_status.message.get('report_data') is expected_response def test_report_run_status_enqueuing(mocker): """Test run status returns 204 when report state has not completed.""" base_uri = 'https://modeanalytics.com/api/theorchard' report_token = '0916aeab9453' report_run_token = '2428701e9a74' report_url = '{}/reports/{}/runs/{}'.format( base_uri, report_token, report_run_token ) mode_response = MagicMock( status_code=200, json=lambda: {'state': mc.REPORT_RUN_RUNNING} ) mocker.patch.object(requests, 'get', return_value=mode_response) report_run_status = mode.report_run_status(report_token, report_run_token) requests.get.assert_called_with(report_url, auth=config.MODE_API_AUTH_KEYS) assert report_run_status.status == 204 def test_report_run_status_not_found(mocker): """Test report run status is 404 when Mode report is not found.""" report_token = '0916aeab9453' report_run_token = '2428701e9a74' mode_response = MagicMock(status_code=404) mocker.patch.object(requests, 'get', return_value=mode_response) report_run_status = mode.report_run_status(report_token, report_run_token) assert report_run_status.status == 404 assert ( report_run_status.errors.get('message') == 'The report or run was not found.' ) def test_report_run_status_failure(mocker): """Test run status is 500 when Mode sends any response but 200.""" report_token = '0916aeab9453' report_run_token = '2428701e9a74' mode_response = MagicMock(status_code=500) mocker.patch.object(requests, 'get', return_value=mode_response) report_run_status = mode.report_run_status(report_token, report_run_token) assert report_run_status.status == 500 assert ( report_run_status.errors.get('message') == 'There was a problem performing your request.' ) def test_initiate_report_run(mocker): """Test initiating a report run for a given report id and parameters.""" report_token = 'report_01' run_token = 'run_token_number_9' params = {'param_limit': 100, 'param_vendor_id': 7123} query_params = 'param_limit=100¶m_vendor_id=7123' expected_request_url = '{base_uri}/{report_token}?{query_params}'.format( base_uri=mc.MODE_REPORTS_URL, report_token=report_token, query_params=query_params, ) location_header = '{base_uri}/{report_token}/runs/{run_token}'.format( base_uri=mc.MODE_REPORTS_URL, report_token=report_token, run_token=run_token, ) mock_response = MagicMock( status_code=302, headers={'Location': location_header} ) mocker.patch.object(requests, 'get', return_value=mock_response) run_response = mode.initiate_report_run(report_token, params) requests.get.assert_called_with( expected_request_url, auth=config.MODE_API_AUTH_KEYS, allow_redirects=False, ) assert run_response.status == 200 assert run_response.message == {'run_token': run_token} def test_initiate_report_run_not_found(mocker): """Test initiating a non-existent report run returns 404 response.""" mocker.patch.object( requests, 'get', return_value=MagicMock(status_code=404) ) run_response = mode.initiate_report_run('some_id', {}) assert run_response.status == 404 assert run_response.errors.get('message') == 'No report found' def test_initiate_report_run_fatal_error(mocker): """Test initiating a mode api failure returns error response.""" mocker.patch.object( requests, 'get', return_value=MagicMock(status_code=500) ) run_response = mode.initiate_report_run('some_id', {}) error_message = 'There was a problem completing your request' assert run_response.status == 500 assert run_response.errors.get('message') == error_message def test_get_space_token_success(mocker, mode_spaces): """Test getting Mode space token from api return 200 response & token.""" space_name = '{}_workstation_physical'.format(config.ENVIRONMENT) mocker.patch.object( requests, 'get', return_value=MagicMock(status_code=200, json=lambda: mode_spaces), ) mocker.patch.object(json, 'loads', return_value=mode_spaces) spaces = mode_spaces['_embedded']['spaces'] current_space = utils.dict_from_collection(spaces, space_name) space_token_response = mode.get_space_token(space_name) assert space_token_response.message.get( 'space_token' ) == current_space.get('token') @pytest.mark.parametrize( 'space_name, mode_status_code, expected_status_code, response_message', [ ( '{}_workstation_physical'.format(config.ENVIRONMENT), 500, 500, 'There was a problem completing your request.', ), ( '{}_workstation_physical'.format(config.ENVIRONMENT), 404, 404, 'The requested space was not found.', ), ], ) def test_get_space_token_failure( mocker, space_name, mode_status_code, expected_status_code, response_message, ): """Test initiating a mode api failure returns error response.""" mocker.patch.object( requests, 'get', return_value=MagicMock(status_code=mode_status_code) ) space_token_response = mode.get_space_token(space_name) assert space_token_response.status == expected_status_code assert space_token_response.errors.get('message') == response_message def test_get_report_token_success(mocker, mode_reports): """Test getting Mode space token from api return 200 response & token.""" space_name = 'workstation_physical' report_name = 'product_view_report' env_report_name = utils.prefix_with_environment('product_view_report') mocker.patch.object( mode, 'get_space_token', return_value=MagicMock( status_code=200, json=lambda: {'space_token': '3a17f1133a4b'} ), ) mocker.patch.object( requests, 'get', return_value=MagicMock(status_code=200, json=lambda: mode_reports), ) mocker.patch.object(json, 'loads', return_value=mode_reports) reports = mode_reports['_embedded']['reports'] expected_report = utils.dict_from_collection(reports, env_report_name) report_token_response = mode.get_report_token(space_name, report_name) assert report_token_response.message.get( 'report_token' ) == expected_report.get('token') @pytest.mark.parametrize( ( 'space_name', 'report_name', 'space_token_status_code', 'mode_status_code', 'expected_status_code', 'response_message', ), [ ( '{}_workstation_physical'.format(config.ENVIRONMENT), '{}_product_view_report'.format(config.ENVIRONMENT), 200, 500, 500, 'There was a problem completing your request.', ), ( '{}_workstation_physical'.format(config.ENVIRONMENT), '{}_product_view_report'.format(config.ENVIRONMENT), 200, 404, 404, 'The requested space was not found.', ), ( 'XXXXXX', 'YYYYYY', 200, 404, 404, 'The requested space was not found.', ), ], ) def test_get_report_token_failure( mocker, space_name, report_name, space_token_status_code, mode_status_code, expected_status_code, response_message, ): """Test getting Mode space token from api failure states.""" mocker.patch.object( mode, 'get_space_token', return_value=MagicMock( status_code=space_token_status_code, json=lambda: {'space_token': '3a17f1133a4b'}, ), ) mocker.patch.object( requests, 'get', return_value=MagicMock(status_code=mode_status_code) ) space_token_response = mode.get_report_token(space_name, report_name) assert space_token_response.status == expected_status_code assert space_token_response.errors.get('message') == response_message test_get_report_data_data = [ ( Response(status=200, message={'query_runs': [1, 2]}), Response( status=200, message={ 'query': { 'name': 'Retailer View', 'content': [], 'columns': [], } }, ), True, Response( status=200, message={'name': 'Retailer View', 'content': [], 'columns': []}, ), 200, {'name': 'Retailer View', 'content': [], 'columns': []}, ), ( create_not_found_response(), create_not_found_response(), False, create_not_found_response(), 404, None, ), ( create_fatal_response(), create_not_found_response(), True, create_not_found_response(), 500, None, ), ( Response(status=200, message={'query_runs': [1, 2]}), create_not_found_response(), False, create_not_found_response(), 404, None, ), ( Response(status=200, message={'query_runs': [1, 2]}), create_fatal_response(), False, create_not_found_response(), 500, None, ), ( Response(status=200, message={'query_runs': [1, 2]}), Response(status=200, message={'query': {'name': 'Retailer View'}}), False, create_not_found_response(), 404, None, ), ( Response(status=200, message={'query_runs': [1, 2]}), Response(status=200, message={'query': {'name': 'Retailer View'}}), True, create_fatal_response(), 500, None, ), ] @pytest.mark.parametrize( ( 'query_runs_response,' 'query_response,' 'is_report_query_response,' 'get_content_from_query_run_response,' 'expected_status_code,' 'expected_payload' ), test_get_report_data_data, ) def test_get_report_data( query_runs_response, query_response, is_report_query_response, get_content_from_query_run_response, expected_status_code, expected_payload, mocker, ): """Test fetching a report's filters.""" report_token = '3b594976c097' run_token = '92de96196071' mocker.patch.object( mode, 'get_query_runs', return_value=query_runs_response ) mocker.patch.object( mode, 'get_query_from_query_run', return_value=query_response ) mocker.patch.object( utils, 'is_report_query', return_value=is_report_query_response ) mocker.patch.object( mode, 'get_content_from_query_run', return_value=get_content_from_query_run_response, ) filter_response = mode.get_report_data(report_token, run_token) assert filter_response.status == expected_status_code assert filter_response.message == expected_payload test_get_query_runs_data = [ ( 'http://api.io', MagicMock( status_code=200, json=lambda: {'_embedded': {'query_runs': [{}, {}]}}, ), 200, {'query_runs': [{}, {}]}, ), ('http://api.io', MagicMock(status_code=404), 404, None), ('http://api.io', MagicMock(status_code=500), 500, None), ( 'http://api.io', MagicMock( status_code=200, json=lambda: {'_embedded': {'query_runs': []}} ), 404, None, ), ] @pytest.mark.parametrize( ( 'utils_api_url_response,' 'mode_response,' 'expected_status_code,' 'expected_payload' ), test_get_query_runs_data, ) def test_get_query_runs( utils_api_url_response, mode_response, expected_status_code, expected_payload, mocker, ): """Test fetching report_run's query_runs.""" report_token = '3b594976c097' run_token = '92de96196071' mocker.patch.object(utils, 'api_url', return_value=utils_api_url_response) mocker.patch.object(requests, 'get', return_value=mode_response) query_runs_response = mode.get_query_runs(report_token, run_token) assert query_runs_response.status == expected_status_code assert query_runs_response.message == expected_payload test_get_query_from_query_run_data = [ ( 'http://api.io/query', MagicMock(status_code=200, json=lambda: {}), 200, {'query': {}}, ), ('http://api.io/query', MagicMock(status_code=404), 404, None), ('http://api.io/query', MagicMock(status_code=500), 500, None), ] @pytest.mark.parametrize( ( 'utils_query_url_from_query_run_response,' 'mode_response,' 'expected_status_code,' 'expected_payload' ), test_get_query_from_query_run_data, ) def test_get_query_from_query_run( utils_query_url_from_query_run_response, mode_response, expected_status_code, expected_payload, mocker, ): """Test fetching report_run's query_runs.""" mocker.patch.object( utils, 'api_url', return_value=utils_query_url_from_query_run_response ) mocker.patch.object(requests, 'get', return_value=mode_response) query_runs_response = mode.get_query_from_query_run( {'_links': {'query': {'href': 'http://api.com/query/1234'}}} ) assert query_runs_response.status == expected_status_code assert query_runs_response.message == expected_payload test_get_content_from_query_run_data = [ ( Response(status=200, message={'query_run_result': {}}), 'http://api.io/json', MagicMock(status_code=200, text='{}', json=lambda: {}), 200, {'content': {}, 'columns': []}, ), ( create_not_found_response(), 'http://api.io/json', MagicMock(status_code=200, text='{}', json=lambda: {}), 404, None, ), ( create_fatal_response(), 'http://api.io/json', MagicMock(status_code=200, text='{}', json=lambda: {}), 500, None, ), ( Response(status=200, message={'query_run_result': {}}), 'http://api.io/json', MagicMock(status_code=404), 404, None, ), ( Response(status=200, message={'query_run_result': {}}), 'http://api.io/json', MagicMock(status_code=500), 500, None, ), ] @pytest.mark.parametrize( ( 'get_query_run_result_from_query_run_response,' 'utils_json_url_from_query_result_response,' 'mode_response,' 'expected_status_code,' 'expected_payload' ), test_get_content_from_query_run_data, ) def test_get_content_from_query_run( get_query_run_result_from_query_run_response, utils_json_url_from_query_result_response, mode_response, expected_status_code, expected_payload, mocker, ): """Test fetching query_run's result content.""" mocker.patch.object( mode, 'get_query_run_result_from_query_run', return_value=get_query_run_result_from_query_run_response, ) mocker.patch.object( utils, 'json_url_from_query_result', return_value=utils_json_url_from_query_result_response, ) mocker.patch.object(requests, 'get', return_value=mode_response) query_run_content_response = mode.get_content_from_query_run({}) assert query_run_content_response.status == expected_status_code assert query_run_content_response.message == expected_payload test_get_query_run_result_from_query_run_data = [ ( 'http://api.io/results', MagicMock(status_code=200, json=lambda: {}), 200, {'query_run_result': {}}, ), ('http://api.io/results', MagicMock(status_code=404), 404, None), ('http://api.io/results', MagicMock(status_code=500), 500, None), ] @pytest.mark.parametrize( ( 'utils_query_run_result_url_from_query_run_response,' 'mode_response,' 'expected_status_code,' 'expected_payload' ), test_get_query_run_result_from_query_run_data, ) def test_get_query_run_result_from_query_run( utils_query_run_result_url_from_query_run_response, mode_response, expected_status_code, expected_payload, mocker, ): """Test fetching query_run's results info.""" mocker.patch.object( utils, 'query_run_result_url_from_query_run', return_value=utils_query_run_result_url_from_query_run_response, ) mocker.patch.object(requests, 'get', return_value=mode_response) query_run_results_response = mode.get_query_run_result_from_query_run({}) assert query_run_results_response.status == expected_status_code assert query_run_results_response.message == expected_payload