"""Test logic for subaccounts.""" from unittest.mock import MagicMock, patch from uuid import UUID import pytest from owsresponse import response from pytest import MonkeyPatch from account.constants import constants, error, pagination from account.logic import subaccount from account.models import subaccount as subaccount_model, vendor as vendor_model @pytest.fixture def fixture_subaccount(): """Fixture response for single subaccount.""" return response.Response( { 'subaccount_id': 1, 'vendor_id': 1, 'subaccount_name': 'Subaccount 1', 'description': 'Subaccount Description 1', 'date_deleted': None, } ) @pytest.fixture def fixture_subaccount_list(fixture_subaccount): """Fixture response for subaccount list.""" return response.Response( dict(items=[fixture_subaccount], pagination=dict(page_offset=0, page_limit=50)) ) @pytest.fixture def fixture_subaccount_count(): """Fixture response for subaccount count.""" return response.Response(1) @pytest.fixture def fixture_not_found_response(): """Fixture response for not found.""" return response.create_not_found_response() def test_get_subaccounts(monkeypatch, fixture_subaccount_list, fixture_subaccount_count): """Test get subaccounts returns paginated response.""" monkeypatch.setattr( subaccount_model, 'get_subaccounts', MagicMock(return_value=fixture_subaccount_list), ) monkeypatch.setattr( subaccount_model, 'get_subaccount_count', MagicMock(return_value=fixture_subaccount_count), ) result = subaccount.get_subaccounts(0, page_offset=0, page_limit=100) assert result.message.get('items') == fixture_subaccount_list.message.get('items') assert result.message.get('pagination').get('count') == fixture_subaccount_count.message @pytest.mark.parametrize('status', [(None), ('deactivated')]) def test_get_subaccounts_no_pagination( monkeypatch, fixture_subaccount_list, fixture_subaccount_count, status ): """Test default pagination values are set.""" monkeypatch.setattr( subaccount_model, 'get_subaccounts', MagicMock(return_value=fixture_subaccount_list), ) monkeypatch.setattr( subaccount_model, 'get_subaccount_count', MagicMock(return_value=fixture_subaccount_count), ) subaccount.get_subaccounts(vendor_id=1, status=status) subaccount_model.get_subaccounts.assert_called_with( 1, status, page_offset=pagination.PAGE_OFFSET_DEFAULT, page_limit=pagination.PAGE_LIMIT_DEFAULT, ) def test_get_subaccount(monkeypatch, fixture_subaccount): """Test subaccount is passed through.""" monkeypatch.setattr( subaccount_model, 'get_subaccount', MagicMock(return_value=fixture_subaccount) ) result = subaccount.get_subaccount(1) assert result == fixture_subaccount def test_is_subaccount_for_vendor_true(monkeypatch, fixture_subaccount): """Test check for subaccount where vendor is owner.""" monkeypatch.setattr( subaccount_model, 'get_subaccount_for_vendor', MagicMock(return_value=fixture_subaccount), ) result = subaccount.is_subaccount_for_vendor(1, 1) assert result def test_is_subaccount_for_vendor_false(monkeypatch, fixture_not_found_response): """Test error for subaccount where vendor is not owner.""" monkeypatch.setattr( subaccount_model, 'get_subaccount_for_vendor', MagicMock(return_value=fixture_not_found_response), ) result = subaccount.is_subaccount_for_vendor(1, 100) assert result.status == 403 def test_get_subaccount_document(monkeypatch, fixture_subaccount_document): """Test get subaccount document by id.""" monkeypatch.setattr( subaccount_model, 'get_subaccount_document_by_id', MagicMock(return_value=response.Response(fixture_subaccount_document)), ) result = subaccount.get_subaccount_document(2) assert result.message == fixture_subaccount_document subaccount_model.get_subaccount_document_by_id.assert_called_once_with(2, False) def test_get_subaccount_document_with_tenant_uuids(monkeypatch, fixture_subaccount_document): """Test get subaccount document by id.""" monkeypatch.setattr( subaccount_model, 'get_subaccount_document_by_id', MagicMock(return_value=response.Response(fixture_subaccount_document)), ) result = subaccount.get_subaccount_document(2, True) assert result.message == fixture_subaccount_document subaccount_model.get_subaccount_document_by_id.assert_called_once_with(2, True) def test_get_subaccount_document_for_no_results(monkeypatch): """Test get subaccount document by id for no results.""" subaccount_id = 2 not_found_response = response.create_not_found_response( 'Subaccount : {} not found.'.format(subaccount_id) ) monkeypatch.setattr( subaccount_model, 'get_subaccount_document_by_id', MagicMock(return_value=not_found_response), ) result = subaccount.get_subaccount_document(subaccount_id) assert result.message == not_found_response.message assert result.status == 404 def test_update_subaccount_status_success( monkeypatch, fixture_subaccount, fixture_subaccount_data_to_activate ): """Test update_subacocunt_status method.""" monkeypatch.setattr( subaccount_model, 'update_subaccount_status', MagicMock(return_value=fixture_subaccount), ) monkeypatch.setattr(subaccount, 'publish_subaccount_event', MagicMock()) result = subaccount.update_subaccount_status(1, fixture_subaccount_data_to_activate) assert result subaccount.publish_subaccount_event.assert_called_once() def test_update_subaccount_failure( monkeypatch, fixture_subaccount_id, fixture_subaccount, fixture_subaccount_data_to_activate, ): """Test update_subacocunt_status method if it failed to update status.""" monkeypatch.setattr( subaccount_model, 'update_subaccount_status', MagicMock( return_value=response.create_not_found_response( message=error.ERROR_MESSAGE_SUBACCOUNT_NOT_FOUND ) ), ) monkeypatch.setattr(subaccount, 'publish_subaccount_event', MagicMock()) result = subaccount.update_subaccount_status( fixture_subaccount_id, fixture_subaccount_data_to_activate ) assert not result assert result.status == 404 subaccount.publish_subaccount_event.assert_not_called() def test_create_subaccount_successful(monkeypatch): """Test create subaccount.""" data = { 'vendor_id': 1, 'subaccount_name': 'Subaccount 1', } created = response.Response( {'subaccount_id': 1, 'vendor_id': 1, 'subaccount_name': 'Subaccount 1'} ) monkeypatch.setattr( subaccount_model, 'create_subaccount', MagicMock(return_value=created), ) monkeypatch.setattr( vendor_model, 'get_vendor', MagicMock(return_value=response.Response({'vendor_id': 1})), ) monkeypatch.setattr(subaccount, 'publish_subaccount_event', MagicMock()) result = subaccount.create_subaccount(data) assert result == created subaccount.publish_subaccount_event.assert_called_once() def test_create_subaccount_vendor_not_found(monkeypatch): """Test create subaccount failed.""" data = { 'vendor_id': 1, 'subaccount_name': 'Subaccount 1', } monkeypatch.setattr( subaccount_model, 'create_subaccount', MagicMock(return_value=fixture_subaccount), ) monkeypatch.setattr( vendor_model, 'get_vendor', MagicMock(return_value=response.create_not_found_response()), ) monkeypatch.setattr(subaccount, 'publish_subaccount_event', MagicMock()) result = subaccount.create_subaccount(data) assert not result assert result.status == 404 subaccount.publish_subaccount_event.assert_not_called() def test_delete_subaccount_success(monkeypatch): """Test delete_subaccount publishes update event and returns uuid and date_deleted.""" import datetime subaccount_uuid = 'some-uuid-string' date_deleted = datetime.datetime(2024, 6, 15, 12, 0, 0) model_result = {'subaccount_uuid': subaccount_uuid, 'date_deleted': date_deleted} monkeypatch.setattr( subaccount_model, 'delete_subaccount_by_uuid', MagicMock(return_value=model_result), ) monkeypatch.setattr(subaccount, 'publish_subaccount_event', MagicMock()) result = subaccount.delete_subaccount(subaccount_uuid) assert result == model_result subaccount.publish_subaccount_event.assert_called_once_with(model_result, 'update') def test_publish_subaccount_event_serializes_dates(monkeypatch): """Test publish_subaccount_event serializes datetime values to ISO strings in the payload.""" import datetime mock_produce = MagicMock() monkeypatch.setattr(subaccount.kafka_producer, 'produce', mock_produce) date_deleted = datetime.datetime(2024, 6, 15, 12, 0, 0) event = {'subaccount_uuid': 'some-uuid', 'date_deleted': date_deleted} subaccount.publish_subaccount_event(event, 'update') mock_produce.assert_called_once() _, _, payload, _ = mock_produce.call_args.args assert payload['payload']['date_deleted'] == '2024-06-15T12:00:00' assert payload['payload']['subaccount_uuid'] == 'some-uuid' def test_delete_subaccount_already_deleted(monkeypatch): """Test delete_subaccount does not publish event when subaccount was already deleted.""" subaccount_uuid = 'some-uuid-string' model_result = {'subaccount_uuid': subaccount_uuid, 'date_deleted': None} monkeypatch.setattr( subaccount_model, 'delete_subaccount_by_uuid', MagicMock(return_value=model_result), ) monkeypatch.setattr(subaccount, 'publish_subaccount_event', MagicMock()) result = subaccount.delete_subaccount(subaccount_uuid) assert result == model_result subaccount.publish_subaccount_event.assert_not_called() @pytest.mark.parametrize( 'fetch_flags', [ ([]), ([constants.FETCH_TENANT_HIERARCHY]), ], ) @patch('account.logic.subaccount.format_for_dataloader') @patch('account.logic.subaccount.subaccount') @patch('account.logic.vendor.g', spec=['request_context']) def test_lookup_subaccounts_by_uuids( mock_g: MagicMock, mock_subaccount_model: MagicMock, mock_format_for_dataloader: MagicMock, fetch_flags: list[str], monkeypatch, ) -> None: """Test lookup subaccounts using uuids.""" uuids = [ UUID('5fe357ae-027d-11ef-8804-4a2888760683'), UUID('a8b1a5b6-ea42-4040-8baa-af75be70c80c'), ] uuids_str = [str(u) for u in uuids] expected = [None, None] mock_subaccount_model.lookup_subaccounts_by_uuids.return_value = response.Response([]) mock_format_for_dataloader.return_value = expected result = subaccount.lookup_subaccounts_by_uuids(uuids=uuids, fetch_flags=fetch_flags) assert result assert result.status == 200 assert result.message == {'subaccounts': expected} mock_subaccount_model.lookup_subaccounts_by_uuids.assert_called_once_with( uuids_str, fetch_flags, ) @pytest.mark.parametrize( 'fetch_flags', [ ([]), ([constants.FETCH_TENANT_HIERARCHY]), ], ) @patch('account.logic.subaccount.subaccount') @patch('account.logic.vendor.g', spec=['request_context']) def test_lookup_subaccounts_by_uuids_non_200( mock_g: MagicMock, mock_subaccount_model: MagicMock, fetch_flags: list[str], monkeypatch, ) -> None: """Test lookup subaccounts using uuids for a non-200 response.""" uuids = ['not.a.uuid1', 'not.a.uuid2'] mock_subaccount_model.lookup_subaccounts_by_uuids.return_value = response.Response( 'game over!', status=400 ) result = subaccount.lookup_subaccounts_by_uuids(uuids=uuids, fetch_flags=fetch_flags) assert not result assert result.status == 400 assert result.message == 'game over!' mock_subaccount_model.lookup_subaccounts_by_uuids.assert_called_once_with( uuids, fetch_flags, ) @pytest.mark.parametrize( 'fetch_flags', [ ([]), ([constants.FETCH_TENANT_HIERARCHY]), ], ) @patch('account.logic.subaccount.format_for_dataloader') @patch('account.logic.subaccount.subaccount') @patch('account.logic.vendor.g', spec=['request_context']) def test_lookup_subaccounts_by_subaccount_ids( mock_g: MagicMock, mock_subaccount_model: MagicMock, mock_format_for_dataloader: MagicMock, fetch_flags: list[str], monkeypatch, ) -> None: """Test lookup subaccounts using subaccount ids.""" subaccount_ids = ['75234', '88954'] expected = [None, None] mock_subaccount_model.lookup_subaccounts_by_subaccount_ids.return_value = response.Response([]) mock_format_for_dataloader.return_value = expected result = subaccount.lookup_subaccounts_by_subaccount_ids( subaccount_ids=subaccount_ids, fetch_flags=fetch_flags ) assert result assert result.status == 200 assert result.message == {'subaccounts': expected} mock_subaccount_model.lookup_subaccounts_by_subaccount_ids.assert_called_once_with( subaccount_ids, fetch_flags, ) def test_get_subaccount_names(monkeypatch: MonkeyPatch) -> None: """Test get subaccount names success in correct order.""" subaccount_uuids = ['three', 'two', 'one'] model_subaccount_response = [ {'subaccount_uuid': 'one', 'subaccount_name': 'Subaccount One', 'subaccount_id': 1}, {'subaccount_uuid': 'two', 'subaccount_name': 'Subaccount Two', 'subaccount_id': 2}, {'subaccount_uuid': 'three', 'subaccount_name': 'Subaccount Three', 'subaccount_id': 3}, ] expected_result = { 'subaccounts': [ {'uuid': 'three', 'name': 'Subaccount Three', 'subaccount_id': 3}, {'uuid': 'two', 'name': 'Subaccount Two', 'subaccount_id': 2}, {'uuid': 'one', 'name': 'Subaccount One', 'subaccount_id': 1}, ] } monkeypatch.setattr( subaccount_model, 'get_subaccount_names', MagicMock(return_value=model_subaccount_response), ) result = subaccount.get_subaccount_names(subaccount_uuids) assert result == expected_result subaccount_model.get_subaccount_names.assert_called_once_with(['three', 'two', 'one'])