"""Tests for Handler utils.""" from datetime import datetime import json from unittest.mock import patch import application import marshmallow from oto import response from oto import status as response_code from oto.adaptors.flask import flaskify import pytest from backend.constants import error as error_consts from backend.constants import field as field_consts from backend.constants import header as header_consts from backend.constants.error import OWS_PRODUCT_ERROR_CODE from backend.exceptions import RequestError from backend.exceptions import ValidationError from backend.utils import handlers as handlers_utils @handlers_utils.get_account_info def _test_account_info_handler(account_type, account_id): assert account_type in ( header_consts.GRASS_ACCOUNT_TYPE_VENDOR, header_consts.GRASS_ACCOUNT_TYPE_SUBACCOUNT) assert account_id and account_id.isdigit() and int(account_id) > 0 return flaskify(response.Response(message={})) def test_get_account_info_grass_headers(mocker, client_headers): """Test get account info using headers.""" with application.app.test_request_context(headers=client_headers): res = _test_account_info_handler() assert res.status_code == response_code.OK def test_get_account_info_query_string(mocker, account_query_params): """Test get account info using headers.""" with application.app.test_request_context( query_string=account_query_params): res = _test_account_info_handler() assert res.status_code == response_code.OK def test_get_account_info_no_account_data(mocker): """Test get account info returns error when not provided.""" with application.app.test_request_context(): res = _test_account_info_handler() res_body = json.loads(res.data.decode()) assert res.status_code == response_code.BAD_REQUEST assert res_body['code'] == error_consts.ERROR_ACCOUNT_DATA_CODE def test_get_account_info_duplicate( mocker, account_query_params, client_headers): """Verify duplicate data is not allowed.""" with application.app.test_request_context( headers=client_headers, query_string=account_query_params): res = _test_account_info_handler() res_body = json.loads(res.data.decode()) assert res.status_code == response_code.BAD_REQUEST assert res_body['code'] == error_consts.ERROR_ACCOUNT_DATA_CODE def test_get_account_info_partial( mocker, account_query_params, client_headers): """Test get account info using partial data.""" account_query_params.pop(field_consts.ACCOUNT_TYPE) with application.app.test_request_context( query_string=account_query_params): res = _test_account_info_handler() res_body = json.loads(res.data.decode()) assert res.status_code == response_code.BAD_REQUEST assert res_body['code'] == error_consts.ERROR_ACCOUNT_DATA_CODE client_headers.pop(header_consts.GRASS_ACCOUNT_ID) with application.app.test_request_context( headers=client_headers): res = _test_account_info_handler() res_body = json.loads(res.data.decode()) assert res.status_code == response_code.BAD_REQUEST assert res_body['code'] == error_consts.ERROR_ACCOUNT_DATA_CODE with application.app.test_request_context( headers=client_headers, query_string=account_query_params): res = _test_account_info_handler() res_body = json.loads(res.data.decode()) assert res.status_code == response_code.BAD_REQUEST assert res_body['code'] == error_consts.ERROR_ACCOUNT_DATA_CODE class _TestSchema(marshmallow.Schema): name = marshmallow.fields.Str(required=True) def test_parse_request_json(client_headers): """Test parsing and validating request JSON.""" request_data = {'name': 'Snowball'} with application.app.test_request_context( headers=client_headers, data=json.dumps(request_data)): data = handlers_utils.parse_request_json(schema=_TestSchema) assert data == request_data @pytest.mark.parametrize('data', [ b'true', b'null', b'\'\'', b'[]', b'', b'{"items": [}']) def test_parse_request_json_invalid_data(client_headers, data): """Test non-dict values returns a bad request.""" with application.app.test_request_context( headers=client_headers, data=data): with pytest.raises(RequestError): handlers_utils.parse_request_json(schema=_TestSchema) def test_parse_request_json_invalid_headers(): """Test missing header data returns a bad request.""" request_data = {'name': 'Snowball'} with application.app.test_request_context(data=json.dumps(request_data)): with pytest.raises(RequestError): handlers_utils.parse_request_json(schema=_TestSchema) def test_parse_request_json_missing_required_field(client_headers): """Test schema validator returns error.""" request_data = {} with application.app.test_request_context( headers=client_headers, data=json.dumps(request_data)): with pytest.raises(ValidationError): handlers_utils.parse_request_json(schema=_TestSchema) def test_explode_query_param_to_ids(mocker): """Test exploding query param to ids.""" key = 'test_key' query_string = {key: '1,2,3'} with application.app.test_request_context(query_string=query_string): ids = handlers_utils.explode_query_param_to_ids(key) assert ids == [1, 2, 3] def test_explode_query_param_to_ids_missing(mocker): """Test exploding query param to ids.""" key = 'test_key' query_string = {} with application.app.test_request_context(query_string=query_string): with pytest.raises(ValueError): handlers_utils.explode_query_param_to_ids(key) try: handlers_utils.explode_query_param_to_ids(key) except ValueError as e: assert str(e) == 'Field `test_key` is required' def test_explode_query_param_to_ids_not_int(mocker): """Test exploding query param to ids.""" key = 'test_key' query_string = {key: '1,b,3'} with application.app.test_request_context(query_string=query_string): with pytest.raises(ValueError): handlers_utils.explode_query_param_to_ids(key) def test_explode_query_param_to_ids_not_positive_int(mocker): """Test exploding query param to ids.""" key = 'test_key' query_string = {key: '1,0,3'} with application.app.test_request_context(query_string=query_string): with pytest.raises(ValueError): handlers_utils.explode_query_param_to_ids(key) def test_json_datetime_encoder_datetime(): """Test DatetimeEncoder converts datetime objects to strings correctly.""" test_datetime = datetime.utcnow() test_datetime_str = test_datetime.isoformat() result = handlers_utils.DatetimeEncoder().default(test_datetime) assert result == test_datetime_str def test_is_admin_request_legacy_oa_user(oa_client_headers): """Test _is_admin_request returns True for legacy oa: user.""" with application.app.test_request_context(headers=oa_client_headers): assert handlers_utils._is_admin_request() is True def test_is_admin_request_content_profile(content_profile_client_headers): """Test _is_admin_request returns True for content profile admin.""" with application.app.test_request_context( headers=content_profile_client_headers): assert handlers_utils._is_admin_request() is True def test_is_admin_request_regular_client(client_headers): """Test _is_admin_request returns False for regular client.""" with application.app.test_request_context(headers=client_headers): assert handlers_utils._is_admin_request() is False def test_is_admin_request_no_headers(): """Test _is_admin_request returns False when no relevant headers.""" with application.app.test_request_context(): assert handlers_utils._is_admin_request() is False def test_is_admin_request_profile_id_without_content_type(): """Test _is_admin_request returns False with profile_id but wrong type.""" headers = { header_consts.ORCHARD_PROFILE_ID: '789', header_consts.ORCHARD_PROFILE_TYPE: 'SomeOtherType', } with application.app.test_request_context(headers=headers): assert handlers_utils._is_admin_request() is False def test_is_admin_request_non_oa_user_id(): """Test _is_admin_request returns False for non-oa user id.""" headers = { header_consts.ORCHARD_USER_ID: 'vendor:123', } with application.app.test_request_context(headers=headers): assert handlers_utils._is_admin_request() is False @handlers_utils.get_account_info def _test_account_info_capture_handler(account_type, account_id, **kwargs): """Handler that returns account_type and account_id for inspection.""" return flaskify(response.Response( message={'account_type': account_type, 'account_id': account_id})) @patch('backend.utils.handlers.ows_product.get_product_by_product_id') def test_get_account_info_oa_user(mock_ows_product, oa_client_headers): """Test get account info using oa headers resolves to vendor.""" with application.app.test_request_context(headers=oa_client_headers): mock_ows_product.return_value = response.Response( message={ 'product_id': 152385, 'upc': 884385232818, 'vendor_id': 14010, 'subaccount_id': 0, }) res = _test_account_info_capture_handler(product_id=2341234) mock_ows_product.assert_called_once_with(2341234) res_body = json.loads(res.data.decode()) assert res.status_code == response_code.OK assert res_body['account_type'] == \ header_consts.GRASS_ACCOUNT_TYPE_VENDOR assert res_body['account_id'] == '14010' @patch('backend.utils.handlers.ows_product.get_product_by_product_id') def test_get_account_info_content_profile( mock_ows_product, content_profile_client_headers): """Test get account info using content profile resolves to vendor.""" with application.app.test_request_context( headers=content_profile_client_headers ): mock_ows_product.return_value = response.Response( message={ 'product_id': 152385, 'upc': 884385232818, 'vendor_id': 14010, 'subaccount_id': 0, }) res = _test_account_info_capture_handler(product_id=2341234) mock_ows_product.assert_called_once_with(2341234) res_body = json.loads(res.data.decode()) assert res.status_code == response_code.OK assert res_body['account_type'] == \ header_consts.GRASS_ACCOUNT_TYPE_VENDOR assert res_body['account_id'] == '14010' @patch('backend.utils.handlers.ows_product.get_product_by_product_id') def test_get_account_info_oa_user_subaccount( mock_ows_product, oa_client_headers): """Test get account info sets subaccount when subaccount_id is present.""" with application.app.test_request_context(headers=oa_client_headers): mock_ows_product.return_value = response.Response( message={ 'product_id': 152385, 'upc': 884385232818, 'vendor_id': 14010, 'subaccount_id': 5678, }) res = _test_account_info_capture_handler(product_id=2341234) mock_ows_product.assert_called_once_with(2341234) res_body = json.loads(res.data.decode()) assert res.status_code == response_code.OK assert res_body['account_type'] == \ header_consts.GRASS_ACCOUNT_TYPE_SUBACCOUNT assert res_body['account_id'] == '5678' @patch('backend.utils.handlers.ows_product.get_product_by_product_id') def test_get_account_info_admin_product_not_found( mock_ows_product, oa_client_headers): """Test get account info returns falsy response when product not found.""" with application.app.test_request_context(headers=oa_client_headers): error_response = response.create_error_response( code=OWS_PRODUCT_ERROR_CODE, message={}, status=404) mock_ows_product.return_value = error_response res = _test_account_info_capture_handler(product_id=9999) mock_ows_product.assert_called_once_with(9999) assert res == error_response @patch('backend.utils.handlers.ows_product.get_product_by_product_id') def test_get_account_info_content_profile_subaccount( mock_ows_product, content_profile_client_headers): """Test get account info with content profile sets subaccount.""" with application.app.test_request_context( headers=content_profile_client_headers, ): mock_ows_product.return_value = response.Response( message={ 'product_id': 152385, 'upc': 884385232818, 'vendor_id': 14010, 'subaccount_id': 999, }) res = _test_account_info_capture_handler(product_id=2341234) res_body = json.loads(res.data.decode()) mock_ows_product.assert_called_once_with(2341234) assert res.status_code == response_code.OK assert res_body['account_type'] == \ header_consts.GRASS_ACCOUNT_TYPE_SUBACCOUNT assert res_body['account_id'] == '999'