"""Tests for application handlers (applications.py).""" import json from typing import Any from unittest.mock import MagicMock, patch from flask.app import AppContext from flask.testing import FlaskClient from owsresponse import response from account.models import ( feature as feature_model, subaccount as subaccount_model, vendor as vendor_model, ) def test_get_applications_dataloader(fixture_client, monkeypatch): """Test get applications by IDs.""" url = '/applications/dataloader' result = fixture_client.post(url, json=['BANKING_TAX_APP', 'COLLABORATORS_APP', 'FAKE_APP']) assert result.status_code == 200 json_result = json.loads(result.data.decode('utf-8')) assert json_result == [ {'group_id': 'ACCOUNTING_APP_GROUP', 'application_id': 'BANKING_TAX_APP'}, {'group_id': 'ACCOUNTING_APP_GROUP', 'application_id': 'COLLABORATORS_APP'}, None, ] def test_get_roles_dataloader(fixture_client, monkeypatch): """Test get roles by IDs.""" url = '/roles/dataloader' result = fixture_client.post( url, json=['WORKSTATION_ANALYTICS_BASE_ROLE', 'BANKING_TAX_BASE_ROLE', 'FAKE_ROLE'], ) assert result.status_code == 200 json_result = json.loads(result.data.decode('utf-8')) assert json_result == [ { 'is_default': True, 'role_id': 'WORKSTATION_ANALYTICS_BASE_ROLE', 'application_id': 'WORKSTATION_ANALYTICS_APP', }, { 'is_default': True, 'role_id': 'BANKING_TAX_BASE_ROLE', 'application_id': 'BANKING_TAX_APP', }, None, ] @patch('account.connectors.mysql.session_scope', new_callable=MagicMock) @patch('account.models.ows_permissions.g') @patch('account.utils.api_utils.g', spec=['request_context']) @patch('owsrequest.request.process') def test_get_vendor_applications( mock_request: Any, mock_g_api: Any, mock_g: Any, mock_session_scope: Any, monkeypatch: Any, fixture_client: FlaskClient, app_context: AppContext, ) -> None: """Test get application by vendor uuid.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'tenants': [{'tenant_uuid': '1ee1ebda-42f2-4183-84d2-25df213a7db6', 'access': True}] } mock_request.return_value = mock_response mock_g_api.request_context.jwt_identity_id = 'test-identity-uuid' mock_g.request_context.authorization = 'Bearer token' mock_session = MagicMock() mock_session_scope.return_value.__enter__.return_value = mock_session mock_vendor = MagicMock( return_value=( { 'vendor_id': 34536, 'uuid': '1ee1ebda-42f2-4183-84d2-25df213a7db6', 'migrated_to_abacus': False, }, { 'id': 4, 'name': 'sme', }, ) ) monkeypatch.setattr(vendor_model, 'lookup_vendor_and_company_brand_by_uuid', mock_vendor) mock_features = MagicMock( return_value=[ {'feature_id': 1, 'feature_name': 'feature1'}, ] ) monkeypatch.setattr( feature_model, 'get_enabled_features_for_vendor_with_session', mock_features ) url = '/vendor/1ee1ebda-42f2-4183-84d2-25df213a7db6/applications' result = fixture_client.get(url) assert result.status_code == 200 json_result = json.loads(result.data.decode('utf-8')) assert 'applications' in json_result @patch('account.connectors.mysql.session_scope', new_callable=MagicMock) @patch('account.models.ows_permissions.g') @patch('account.utils.api_utils.g', spec=['request_context']) @patch('owsrequest.request.process') def test_post_vendor_applications( mock_request: Any, mock_g_api: Any, mock_g: Any, mock_session_scope: Any, monkeypatch: Any, fixture_client: FlaskClient, app_context: AppContext, ) -> None: """Test post application by vendor uuid.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'tenants': [ {'tenant_uuid': '1ee1ebda-42f2-4183-84d2-25df213a7db6', 'access': True}, {'tenant_uuid': '2ee1ebda-42f2-4183-84d2-25df213a7db7', 'access': True}, ] } mock_request.return_value = mock_response mock_g_api.request_context.jwt_identity_id = 'test-identity-uuid' mock_g.request_context.authorization = 'Bearer token' mock_session = MagicMock() mock_session_scope.return_value.__enter__.return_value = mock_session mock_vendors = MagicMock( return_value={ '1ee1ebda-42f2-4183-84d2-25df213a7db6': ( { 'vendor_id': 34536, 'uuid': '1ee1ebda-42f2-4183-84d2-25df213a7db6', 'migrated_to_abacus': True, }, { 'id': 4, 'name': 'sme', }, ), '2ee1ebda-42f2-4183-84d2-25df213a7db7': ( { 'vendor_id': 34537, 'uuid': '2ee1ebda-42f2-4183-84d2-25df213a7db7', 'migrated_to_abacus': True, }, { 'id': 1, 'name': 'knr', }, ), } ) monkeypatch.setattr(vendor_model, 'lookup_vendors_and_company_brands_by_uuids', mock_vendors) mock_features = MagicMock( return_value={ 34536: [{'feature_id': 1, 'feature_name': 'feature1'}], 34537: [{'feature_id': 1, 'feature_name': 'feature1'}], } ) monkeypatch.setattr( feature_model, 'get_enabled_features_for_vendors_with_session', mock_features ) url = '/vendors/applications/dataloader' vendor_uuids = ['1ee1ebda-42f2-4183-84d2-25df213a7db6', '2ee1ebda-42f2-4183-84d2-25df213a7db7'] result = fixture_client.post(url, json={'vendor_uuids': vendor_uuids}) assert result.status_code == 200 json_result = json.loads(result.data.decode('utf-8')) assert 'vendors' in json_result assert len(json_result['vendors']) == 2 assert 'applications' in json_result['vendors'][0] @patch('account.connectors.mysql.session_scope', new_callable=MagicMock) @patch('account.models.ows_permissions.g') @patch('account.utils.api_utils.g', spec=['request_context']) @patch('owsrequest.request.process') def test_post_subaccount_applications( mock_request: Any, mock_g_api: Any, mock_g: Any, mock_session_scope: Any, monkeypatch: Any, fixture_client: FlaskClient, app_context: AppContext, ) -> None: """Test post application by subaccount uuid.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'tenants': [ {'tenant_uuid': '11387154-0267-11ef-82c8-4a2888760684', 'access': True}, {'tenant_uuid': '3167fe36-0267-11ef-8440-4a2888760684', 'access': True}, ] } mock_request.return_value = mock_response mock_g_api.request_context.jwt_identity_id = 'test-identity-uuid' mock_g.request_context.authorization = 'Bearer token' mock_session = MagicMock() mock_session_scope.return_value.__enter__.return_value = mock_session mock_subaccounts = MagicMock( return_value={ '11387154-0267-11ef-82c8-4a2888760684': ( { 'subaccount_id': 62281, 'uuid': '11387154-0267-11ef-82c8-4a2888760684', }, { 'vendor_id': 21899, 'uuid': '69270e27-b666-4f60-b73e-11f6821a7b0a', 'migrated_to_abacus': False, }, { 'id': 2, 'name': 'theorchard', }, ), '3167fe36-0267-11ef-8440-4a2888760684': ( { 'subaccount_id': 78257, 'uuid': '3167fe36-0267-11ef-8440-4a2888760684', }, { 'vendor_id': 34536, 'uuid': '1ee1ebda-42f2-4183-84d2-25df213a7db6', 'migrated_to_abacus': True, }, { 'id': 2, 'name': 'theorchard', }, ), } ) monkeypatch.setattr( subaccount_model, 'lookup_subaccounts_and_vendors_and_company_brands_by_uuids', mock_subaccounts, ) mock_features = MagicMock( return_value={ 21899: [{'feature_id': 1, 'feature_name': 'feature1'}], 34536: [{'feature_id': 1, 'feature_name': 'feature1'}], } ) monkeypatch.setattr( feature_model, 'get_enabled_features_for_vendors_with_session', mock_features ) url = '/subaccounts/applications/dataloader' subaccount_uuids = [ '11387154-0267-11ef-82c8-4a2888760684', '3167fe36-0267-11ef-8440-4a2888760684', ] result = fixture_client.post(url, json={'subaccount_uuids': subaccount_uuids}) assert result.status_code == 200 json_result = json.loads(result.data.decode('utf-8')) assert 'subaccounts' in json_result assert len(json_result['subaccounts']) == 2 assert 'applications' in json_result['subaccounts'][0] @patch('account.connectors.mysql.session_scope', new_callable=MagicMock) @patch('account.models.ows_permissions.g') @patch('account.utils.api_utils.g', spec=['request_context']) @patch('owsrequest.request.process') def test_get_subaccount_applications( mock_request: Any, mock_g_api: Any, mock_g: Any, mock_session_scope: Any, monkeypatch: Any, fixture_client: FlaskClient, app_context: AppContext, ) -> None: """Test get application by subaccount uuid.""" mock_session = MagicMock() mock_session_scope.return_value.__enter__.return_value = mock_session mock_g_api.request_context.jwt_identity_id = 'test-identity-uuid' mock_g.request_context.authorization = 'Bearer token' mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'tenants': [{'tenant_uuid': '415af73a-8f57-4546-a485-19c65c185eee', 'access': True}] } mock_request.return_value = mock_response mock_subaccount = MagicMock( return_value=( {'subaccount_id': 78257, 'uuid': '415af73a-8f57-4546-a485-19c65c185eee'}, { 'vendor_id': 34536, 'uuid': '1ee1ebda-42f2-4183-84d2-25df213a7db6', 'migrated_to_abacus': True, }, {'id': 2, 'name': 'theorchard'}, ) ) monkeypatch.setattr( subaccount_model, 'lookup_subaccount_and_vendor_and_company_brand_by_uuid', mock_subaccount ) mock_features = MagicMock( return_value=[ {'feature_id': 1, 'feature_name': 'feature1'}, ] ) monkeypatch.setattr( feature_model, 'get_enabled_features_for_vendor_with_session', mock_features ) url = '/subaccount/415af73a-8f57-4546-a485-19c65c185eee/applications' result = fixture_client.get(url) assert result.status_code == 200 json_result = json.loads(result.data.decode('utf-8')) assert 'applications' in json_result @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') @patch('account.logic.application.collaborator.get_collaborator_applications') @patch('account.models.ows_permissions.g') @patch('account.utils.api_utils.g', spec=['request_context']) @patch('owsrequest.request.process') def test_get_collaborator_applications( mock_request: Any, mock_g_api: Any, mock_g: Any, get_apps_mock: Any, _, __, ___, fixture_client: FlaskClient, app_context: AppContext, ) -> None: """Test get application by collaborator uuid.""" collaborator_uuid = 'e864c3be-a6ea-4509-b1fe-4437d5a67500' mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'tenants': [{'tenant_uuid': collaborator_uuid, 'access': True}] } mock_request.return_value = mock_response mock_g_api.request_context.jwt_identity_id = 'test-identity-uuid' mock_g.request_context.authorization = 'Bearer token' app_info = response.Response( { 'applications': [ { 'application_id': 'CUSTOMER_ACCOUNTING_APP', 'roles': ['CUSTOMER_ACCOUNTING_BASE_ROLE'], 'url': 'https://accounting.theorchard.com', } ] } ) get_apps_mock.return_value = app_info result = fixture_client.get(f'/collaborator/{collaborator_uuid}/applications') assert result.status_code == 200 assert json.loads(result.data) == app_info.message get_apps_mock.assert_called_with(collaborator_uuid) @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') @patch('account.logic.application.label_participant.get_lp_applications') @patch('account.models.ows_permissions.g') @patch('account.utils.api_utils.g', spec=['request_context']) @patch('owsrequest.request.process') def test_get_label_participant_applications( mock_request: Any, mock_g_api: Any, mock_g: Any, get_apps_mock: Any, _, __, ___, fixture_client: FlaskClient, app_context: AppContext, ) -> None: """Test get application by label participant uuid.""" lp_uuid = '33897aeb-afba-474d-aaea-880b2b49aa04' mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = {'tenants': [{'tenant_uuid': lp_uuid, 'access': True}]} mock_request.return_value = mock_response mock_g_api.request_context.jwt_identity_id = 'test-identity-uuid' mock_g.request_context.authorization = 'Bearer token' app_info_dict = {'application_id': 'aaa', 'roles': ['🛼'], 'url': 'u.rl'} get_apps_mock.return_value = [app_info_dict] result = fixture_client.get(f'/label-participant/{lp_uuid}/applications') assert result.status_code == 200 assert json.loads(result.data) == {'applications': [app_info_dict]} get_apps_mock.assert_called_with(lp_uuid) # Tests for GET /internal/parent_company//applications @patch('account.utils.api_utils.g', spec=['request_context']) @patch('account.handlers.applications.authorization.pdp_authorize_manage_employee') def test_get_parent_company_applications_success( mock_pdp_authorize: MagicMock, mock_g: MagicMock, fixture_client, ): """Test successful retrieval of parent company applications.""" from account.constants.constants import ORCHARD_PARENT_COMPANY_UUID from account.models import application as app_model mock_g.request_context.jwt_identity_id = 'test-identity-uuid' mock_pdp_authorize.return_value = True result = fixture_client.get( f'/internal/parent_company/{ORCHARD_PARENT_COMPANY_UUID}/applications' ) assert result.status_code == 200 data = json.loads(result.get_data(as_text=True)) assert 'applications' in data assert len(data['applications']) == 2 # Check Insights app insights_app = next( (app for app in data['applications'] if app['application_id'] == app_model.INSIGHTS_APP), None, ) assert insights_app is not None assert insights_app['roles'] == [app_model.INSIGHTS_BASE_ROLE] assert insights_app['url'] == 'https://insights.theorchard.com' # Check Settings app settings_app = next( (app for app in data['applications'] if app['application_id'] == app_model.SETTINGS_APP), None, ) assert settings_app is not None assert settings_app['roles'] == [app_model.SETTINGS_BASE_ROLE] assert settings_app['url'] == 'https://settings.theorchard.com' mock_pdp_authorize.assert_called_once() @patch('account.utils.api_utils.g', spec=['request_context']) @patch('account.handlers.applications.authorization.pdp_authorize_manage_employee') def test_get_parent_company_applications_sme_success( mock_pdp_authorize: MagicMock, mock_g: MagicMock, fixture_client, ): """Test successful retrieval of SME parent company applications.""" from account.constants.constants import SME_PARENT_COMPANY_UUID from account.models import application as app_model mock_g.request_context.jwt_identity_id = 'test-identity-uuid' mock_pdp_authorize.return_value = True result = fixture_client.get(f'/internal/parent_company/{SME_PARENT_COMPANY_UUID}/applications') assert result.status_code == 200 data = json.loads(result.get_data(as_text=True)) assert 'applications' in data assert len(data['applications']) == 2 # Check that SME uses sme domain insights_app = next( (app for app in data['applications'] if app['application_id'] == app_model.INSIGHTS_APP), None, ) assert insights_app is not None assert insights_app['url'] == 'https://insights.sme.com' settings_app = next( (app for app in data['applications'] if app['application_id'] == app_model.SETTINGS_APP), None, ) assert settings_app is not None assert settings_app['url'] == 'https://settings.sme.com' mock_pdp_authorize.assert_called_once() @patch('account.utils.api_utils.g', spec=['request_context']) @patch('account.handlers.applications.authorization.pdp_authorize_manage_employee') def test_get_parent_company_applications_invalid_uuid( mock_pdp_authorize: MagicMock, mock_g: MagicMock, fixture_client, ): """Test 400 response for invalid parent company UUID.""" invalid_uuid = '00000000-0000-0000-0000-000000000000' mock_g.request_context.jwt_identity_id = 'test-identity-uuid' # Authorization is checked first, then UUID validation mock_pdp_authorize.return_value = True result = fixture_client.get(f'/internal/parent_company/{invalid_uuid}/applications') assert result.status_code == 400 data = json.loads(result.get_data(as_text=True)) assert data['code'] == 'invalid_input' assert data['message'] == 'Invalid parent company UUID' mock_pdp_authorize.assert_called_once() @patch('account.utils.api_utils.g', spec=['request_context']) @patch('account.handlers.applications.authorization.pdp_authorize_manage_employee') def test_get_parent_company_applications_forbidden( mock_pdp_authorize: MagicMock, mock_g: MagicMock, fixture_client, ): """Test 403 response when user lacks seat_can_administer_users role.""" from account.constants.constants import ORCHARD_PARENT_COMPANY_UUID mock_g.request_context.jwt_identity_id = 'test-identity-uuid' mock_pdp_authorize.return_value = False result = fixture_client.get( f'/internal/parent_company/{ORCHARD_PARENT_COMPANY_UUID}/applications' ) assert result.status_code == 403 data = json.loads(result.get_data(as_text=True)) assert data['code'] == 'authorization_error' assert data['message'] == 'Forbidden' mock_pdp_authorize.assert_called_once() @patch('account.utils.api_utils.g', spec=['request_context']) def test_get_parent_company_applications_missing_jwt( mock_g: MagicMock, fixture_client, ): """Test 401 response when JWT identity is missing.""" from account.constants.constants import ORCHARD_PARENT_COMPANY_UUID mock_g.request_context.jwt_identity_id = None result = fixture_client.get( f'/internal/parent_company/{ORCHARD_PARENT_COMPANY_UUID}/applications' ) assert result.status_code == 401 data = json.loads(result.get_data(as_text=True)) assert data['code'] == 'authorization_error' assert data['message'] == 'Request context has no identity uuid.'