"""Tests for subaccount handlers (subaccount.py).""" import json from unittest.mock import MagicMock, patch import pytest from owsrequest import flask_request from owsresponse import response from account.constants import error, header from account.logic import subaccount from account.utils import pagination from tests.unit.helpers import ( grass as grass_test_helper, subaccount as subaccount_test_helper, vendor as vendor_test_helper, ) from .conftest import fast_patch @pytest.mark.parametrize( ('url', 'status'), [('/subaccounts', None), ('/subaccounts?status=deactivated', 'deactivated')], ) def test_get_subaccounts( monkeypatch, fixture_client, url, fixture_vendor, fixture_subaccount_list, fixture_pagination, fixture_grass_account, fixture_ok_response, status, ): """Test routes that get list of subaccounts.""" fast_patch( monkeypatch, { pagination: dict(get_pagination=fixture_pagination), flask_request: dict( get_grass_headers=fixture_grass_account, verify_grass_access=fixture_ok_response, ), subaccount: dict(get_subaccounts=fixture_subaccount_list), }, ) result = fixture_client.get(url) subaccount.get_subaccounts.assert_called_with( fixture_vendor, status, page_offset=fixture_pagination.offset, page_limit=fixture_pagination.limit, ) assert result.status_code == 200 assert result.headers.get(header.CORRELATION_ID) assert json.loads(result.data.decode('utf-8')) == fixture_subaccount_list.message def test_get_subaccounts_with_vendor( monkeypatch, fixture_client, fixture_vendor, fixture_subaccount_list, fixture_pagination, fixture_grass_account, fixture_ok_response, ): """Test routes that get list of subaccounts.""" url = '/my_vendor/subaccounts' fast_patch( monkeypatch, { pagination: dict(get_pagination=fixture_pagination), flask_request: dict( get_grass_headers=fixture_grass_account, verify_grass_access=fixture_ok_response, ), subaccount: dict(get_subaccounts=fixture_subaccount_list), }, ) result = fixture_client.get(url) subaccount.get_subaccounts.assert_called_with( fixture_vendor, page_offset=fixture_pagination.offset, page_limit=fixture_pagination.limit, ) assert result.status_code == 200 assert result.headers.get(header.CORRELATION_ID) assert json.loads(result.data.decode('utf-8')) == fixture_subaccount_list.message @pytest.mark.parametrize( 'account_from_headers, expected', [ ( subaccount_test_helper.grass_subaccount_account( subaccount_test_helper.subaccount_response() ), 200, ), ( vendor_test_helper.fixture_grass_vendor_account(vendor_test_helper.fixture_vendor()), 400, ), (grass_test_helper.fixture_grass_account_none(), 400), ], ) def test_is_subaccount(monkeypatch, fixture_client, account_from_headers, expected): """Test route that checks if request is from a subaccount.""" fast_patch(monkeypatch, {flask_request: dict(get_grass_headers=account_from_headers)}) result = fixture_client.head('/subaccount') assert result.status_code == expected assert result.headers.get('Correlation-Id') def test_get_subaccount( monkeypatch, fixture_client, fixture_subaccount, fixture_grass_account, fixture_ok_response, ): """Test route that gets a specific subaccount.""" subaccount_id = str(fixture_subaccount.message.get('subaccount_id')) fast_patch( monkeypatch, { flask_request: dict( get_grass_headers=fixture_grass_account, verify_grass_access=fixture_ok_response, ), subaccount: dict(get_subaccount=fixture_subaccount), }, ) result = fixture_client.get('/subaccount/{0}'.format(subaccount_id)) subaccount.get_subaccount.assert_called_with(subaccount_id) assert result.status_code == 200 assert result.headers.get('Correlation-Id') assert json.loads(result.data.decode('utf-8')) == fixture_subaccount.message def test_get_subaccount_authorized_vendor( monkeypatch, fixture_client, fixture_subaccount, fixture_grass_vendor_account, fixture_ok_response, ): """Test route that gets a specific subaccount for authorized vendor.""" subaccount_id = str(fixture_subaccount.message.get('subaccount_id')) fast_patch( monkeypatch, { flask_request: dict( get_grass_headers=fixture_grass_vendor_account, verify_grass_access=fixture_ok_response, ), subaccount: dict( get_subaccount=fixture_subaccount, is_subaccount_for_vendor=fixture_subaccount, ), }, ) result = fixture_client.get('/subaccount/{0}'.format(subaccount_id)) subaccount.is_subaccount_for_vendor.assert_called_with( subaccount_id, fixture_grass_vendor_account[1], return_result=True ) assert result.status_code == 200 assert result.headers.get('Correlation-Id') assert json.loads(result.data.decode('utf-8')) == fixture_subaccount.message @pytest.mark.parametrize( 'url, method', [ ('/subaccount/{0}'.format('1'), 'get'), ('/sony/subaccount/{0}'.format('1'), 'head'), ], ) def test_get_subaccount_unauthorized_vendor( monkeypatch, fixture_client, url, method, fixture_subaccount, fixture_grass_vendor_account, fixture_ok_response, fixture_error_response, ): """Test route returns HTTP status error for unauthorized vendor.""" subaccount_id = str(fixture_subaccount.message.get('subaccount_id')) fast_patch( monkeypatch, { flask_request: dict( get_grass_headers=fixture_grass_vendor_account, verify_grass_access=fixture_ok_response, ), subaccount: dict(is_subaccount_for_vendor=fixture_error_response), }, ) test_method = getattr(fixture_client, method) result = test_method(url) if method == 'get': subaccount.is_subaccount_for_vendor.assert_called_with( subaccount_id, fixture_grass_vendor_account[1], return_result=True ) else: subaccount.is_subaccount_for_vendor.assert_called_with( subaccount_id, fixture_grass_vendor_account[1] ) subaccount.is_subaccount_for_vendor.assert_called_with( subaccount_id, fixture_grass_vendor_account[1] ) assert result.status_code != 200 assert result.headers.get('Correlation-Id') def test_is_subaccount_for_vendor( monkeypatch, fixture_client, fixture_subaccount, fixture_ok_response ): """Test route that checks if subaccount belongs to vendor.""" vendor_id = str(fixture_subaccount.message.get('vendor_id')) subaccount_id = str(fixture_subaccount.message.get('subaccount_id')) fast_patch( monkeypatch, { flask_request: dict(verify_grass_access=fixture_ok_response), subaccount: dict(is_subaccount_for_vendor=fixture_subaccount), }, ) result = fixture_client.head('/{0}/subaccount/{1}'.format(vendor_id, subaccount_id)) subaccount.is_subaccount_for_vendor.assert_called_with(subaccount_id, vendor_id) assert result.status_code == 200 assert result.headers.get('Correlation-Id') assert not result.data def test_get_subaccount_document(monkeypatch, fixture_client, fixture_subaccount_document): """Test route that gets a specific subaccount document.""" subaccount_id = fixture_subaccount_document['label_id'] fast_patch( monkeypatch, {subaccount: dict(get_subaccount_document=response.Response(fixture_subaccount_document))}, ) result = fixture_client.get('/subaccount/{0}/document'.format(subaccount_id)) subaccount.get_subaccount_document.assert_called_with(subaccount_id, False) assert result.status_code == 200 assert json.loads(result.data.decode('utf-8')) == fixture_subaccount_document def test_get_subaccount_document_with_tenant_uuids( monkeypatch, fixture_client, fixture_subaccount_document ): """Test route that gets a specific subaccount document.""" subaccount_id = fixture_subaccount_document['label_id'] fast_patch( monkeypatch, {subaccount: dict(get_subaccount_document=response.Response(fixture_subaccount_document))}, ) result = fixture_client.get( '/subaccount/{0}/document?with_tenant_uuids=1'.format(subaccount_id) ) subaccount.get_subaccount_document.assert_called_with(subaccount_id, True) assert result.status_code == 200 assert json.loads(result.data.decode('utf-8')) == fixture_subaccount_document def test_get_subaccount_document_for_rejected_grass_access( monkeypatch, fixture_client, fixture_subaccount_document ): """Test rejected GRASS access when fetching subaccount document.""" error_response = ( 'Direct access through ows-grass is blocked. ' 'Only non-ows-grass microservice-to-microservice' ' requests are allowed.' ) subaccount_id = fixture_subaccount_document['label_id'] result = fixture_client.get( '/subaccount/{0}/document'.format(subaccount_id), headers={ header.GRASS_ACCOUNT_TYPE: 'subaccount', header.GRASS_ACCOUNT_ID: subaccount_id, }, ) assert result.status_code == 400 assert result.data.decode('utf-8') == error_response def test_update_subaccount_status_success( db_fixture, fixture_client, valid_headers_for_vendor, fixture_subaccount_data_to_activate, ): """Test update subaccount by subaccount_id.""" result = fixture_client.put( '/subaccount/1/status', headers=valid_headers_for_vendor, json=fixture_subaccount_data_to_activate, ) assert result.status_code == 200 def test_update_subaccount_status_post_data_error( db_fixture, valid_headers_for_vendor, fixture_client ): """Test update subaccount when input data is invalid.""" subaccount_data = {'active': 'test_invalid'} result = fixture_client.put( '/subaccount/1/status', headers=valid_headers_for_vendor, json=subaccount_data, ) assert result.status_code == 400 def test_update_subaccount_status_grass_error( db_fixture, invalid_headers_for_vendor, fixture_client, fixture_subaccount_data_to_activate, ): """Test update subaccount when grass headers are invalid.""" result = fixture_client.put( '/subaccount/1/status', headers=invalid_headers_for_vendor, data=json.dumps(fixture_subaccount_data_to_activate), ) assert result.status_code == 400 def test_update_subaccount_status_ownership_error( db_fixture, headers, fixture_client, valid_headers_for_vendor, fixture_subaccount_data_to_activate, ): """Test when subaccount is not owned by grass vendor.""" result = fixture_client.put( '/subaccount/123/status', headers=valid_headers_for_vendor, json=fixture_subaccount_data_to_activate, ) error_result = json.loads(result.get_data(as_text=True)) assert error_result['code'] == error.ERROR_CODE_AUTHORIZATION assert result.status_code == 403 @pytest.mark.parametrize( ('headers', 'data', 'status'), [ # no headers - not allowed ({}, {'subaccount_name': 'test_subaccount_name', 'vendor_id': '1'}, 403), # profile headers, data validation failed. ( { 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': 123, 'Orchard-Identity-Id': 'uuid', 'Orchard-Identity-UUID': 'uuid', }, {'subaccount_name': 'test_subaccount_name'}, 400, ), # profile headers, data validation success. ( { 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': 123, 'Orchard-Identity-Id': 'uuid', 'Orchard-Identity-UUID': 'uuid', }, {'subaccount_name': 'test_subaccount_name', 'vendor_id': '1'}, 200, ), ], ) def test_create_subaccount(monkeypatch, headers, data, status, fixture_client, fixture_subaccount): """Test create_subaccount""" monkeypatch.setattr(subaccount, 'create_subaccount', MagicMock(return_value=fixture_subaccount)) result = fixture_client.post('/subaccount', json=data, headers=headers) assert result.status_code == status def test_get_subaccounts_names_by_subaccount_uuids( monkeypatch, fixture_client, app_context, ) -> None: """Test get vendors names by vendor_uuids.""" expected = { 'subaccounts': [ { 'uuid': '84e09dd0-9732-4538-926f-e2010caaa113', 'subaccount_id': 76247, 'name': 'Ash B.', }, ] } mock_get_subaccount_names = MagicMock(return_value=expected) monkeypatch.setattr(subaccount, 'get_subaccount_names', mock_get_subaccount_names) result = fixture_client.post( '/subaccounts/names/dataloader', json=[ '84e09dd0-9732-4538-926f-e2010caaa113', ], ) mock_get_subaccount_names.assert_called_once_with(['84e09dd0-9732-4538-926f-e2010caaa113']) assert json.loads(result.data) == expected def test_get_subaccounts_names_by_subaccount_uuids_exception( monkeypatch, fixture_client, app_context, ) -> None: """Test get subaccount names by subaccount_uuids handles exception.""" def mock_get_subaccount_names(*args, **kwargs): raise Exception('Mocked exception in get_subaccount_names') monkeypatch.setattr(subaccount, 'get_subaccount_names', mock_get_subaccount_names) result = fixture_client.post( '/subaccounts/names/dataloader', json=[ '84e09dd0-9732-4538-926f-e2010caaa113', ], ) assert json.loads(result.data) == { 'code': 'internal_error', 'message': ['Mocked exception in get_subaccount_names'], } @patch('account.handlers.subaccount.authorization_backend') @patch('account.handlers.subaccount.g') @patch('account.utils.api_utils.g') @patch('account.handlers.subaccount.identity') @patch('account.handlers.subaccount.subaccount') @patch('account.handlers.subaccount.SubaccountByUuidResourceGetter') def test_v2_delete_subaccount( mock_resource_getter: MagicMock, mock_subaccount_logic: MagicMock, mock_identity: MagicMock, mock_api_utils_g: MagicMock, mock_g: MagicMock, mock_authorization_backend: MagicMock, fixture_client: MagicMock, app_context, ) -> None: """Test DELETE /v2/subaccounts/.""" subaccount_uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' mock_authorization_backend.is_authorized.return_value = True mock_api_utils_g.request_context.jwt_identity_id = 'some-identity-uuid' mock_g.request_context.jwt_identity_id = 'some-identity-uuid' mock_identity.get_oa_user_id.return_value = 1234 mock_subaccount_logic.delete_subaccount.return_value = { 'subaccount_uuid': subaccount_uuid, 'date_deleted': None, } result = fixture_client.delete(f'/v2/subaccounts/{subaccount_uuid}') assert result.status_code == 200, result.text assert json.loads(result.get_data()) == {'subaccount_uuid': subaccount_uuid} mock_authorization_backend.is_authorized.assert_called_once_with( action='delete', resource_id=subaccount_uuid, resource_type='subaccount', resource_getter=mock_resource_getter.return_value, ) mock_subaccount_logic.delete_subaccount.assert_called_once_with(subaccount_uuid) @patch('account.handlers.subaccount.authorization_backend') @patch('account.utils.api_utils.g') @patch('account.handlers.subaccount.subaccount') def test_v2_delete_subaccount_forbidden( mock_subaccount_logic: MagicMock, mock_api_utils_g: MagicMock, mock_authorization_backend: MagicMock, fixture_client: MagicMock, app_context, ) -> None: """Test DELETE /v2/subaccounts/ returns 403 when not authorized.""" mock_api_utils_g.request_context.jwt_identity_id = 'some-identity-uuid' mock_authorization_backend.is_authorized.return_value = False result = fixture_client.delete('/v2/subaccounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890') assert result.status_code == 403, result.text mock_subaccount_logic.delete_subaccount.assert_not_called()