"""Functional tests for Reference SAP Profit Center.""" from datetime import datetime, timezone from unittest.mock import patch import pytest from abacus_common_logic.constants.constants import SYSTEM_TIMEZONE from freezegun import freeze_time from abacus_contract import models from abacus_contract.tests.utils.factories import ( ReferenceSapProfitCenterFactory, ReferenceSigningEntityFactory, SigningEntitySapProfitCenterFactory, ) def test_get_reference_sap_profit_center_by_id(fixture_client): """Test getting an SAP Profit Center by ID.""" sap_profit_center = ReferenceSapProfitCenterFactory.create() sap_profit_center_id = sap_profit_center.reference_sap_profit_center_id res = fixture_client.get(f'/reference-sap-profit-center/{sap_profit_center_id}') assert res.status_code == 200 assert res.json == { 'business_group': sap_profit_center.business_group, 'company_code': sap_profit_center.company_code, 'display_name': sap_profit_center.display_name, 'profit_center': sap_profit_center.profit_center, 'reference_sap_profit_center_id': sap_profit_center_id, } # --- PUT /reference-sap-profit-center/ --- @pytest.mark.db('mysql') def test_put_reference_sap_profit_center_updates_display_name(fixture_client): """PUT writes the new display_name and a follow-up GET reflects it.""" pc = ReferenceSapProfitCenterFactory.create(display_name='Old') res = fixture_client.put( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}', json={'display_name': 'New'}, ) assert res.status_code == 200, res.json assert res.json['display_name'] == 'New' follow_up = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' ) assert follow_up.json['display_name'] == 'New' @pytest.mark.db('mysql') def test_put_reference_sap_profit_center_empty_returns_400(fixture_client): """PUT with empty display_name -> 400 via NonemptyString.""" pc = ReferenceSapProfitCenterFactory.create(display_name='Original') res = fixture_client.put( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}', json={'display_name': ''}, ) assert res.status_code == 400, res.json refreshed = models.ReferenceSapProfitCenter.query.get( pc.reference_sap_profit_center_id ) assert refreshed.display_name == 'Original' @pytest.mark.db('mysql') def test_put_reference_sap_profit_center_missing_field_returns_400(fixture_client): """PUT with body missing display_name -> 400 (required).""" pc = ReferenceSapProfitCenterFactory.create() res = fixture_client.put( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}', json={}, ) assert res.status_code == 400, res.json @pytest.mark.db('mysql') def test_put_reference_sap_profit_center_unknown_field_returns_400(fixture_client): """PUT with an extra field -> 400 via schema unknown=RAISE.""" pc = ReferenceSapProfitCenterFactory.create(profit_center='ORIG') res = fixture_client.put( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}', json={'display_name': 'New', 'profit_center': 'HACKED'}, ) assert res.status_code == 400, res.json refreshed = models.ReferenceSapProfitCenter.query.get( pc.reference_sap_profit_center_id ) assert refreshed.profit_center == 'ORIG' @pytest.mark.db('mysql') def test_put_reference_sap_profit_center_unknown_id_returns_404(fixture_client): """PUT on a non-existent id -> 404 (framework ItemView.put behavior).""" res = fixture_client.put( '/reference-sap-profit-center/99999999', json={'display_name': 'New'}, ) assert res.status_code == 404, res.data # --- GET /reference-sap-profit-center//signing-entities/ --- @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_returns_only_live(fixture_client): """Drawer endpoint surfaces live mappings with junction id + audit fields.""" pc = ReferenceSapProfitCenterFactory.create() live_se = ReferenceSigningEntityFactory.create( legal_name='Live Inc.', with_junction_row=False ) deleted_se = ReferenceSigningEntityFactory.create( legal_name='Was authorized', with_junction_row=False ) live_junction = SigningEntitySapProfitCenterFactory.create( reference_signing_entity=live_se, reference_sap_profit_center=pc ) soft_deleted_junction = SigningEntitySapProfitCenterFactory.create( reference_signing_entity=deleted_se, reference_sap_profit_center=pc ) models.SigningEntitySapProfitCenter.delete_by_id_or_error( soft_deleted_junction.signing_entity_sap_profit_center_id, soft_delete=True ) res = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' '/signing-entities/' ) assert res.status_code == 200, res.json assert res.json['total_count'] == 1 [item] = res.json['items'] assert ( item['signing_entity_sap_profit_center_id'] == live_junction.signing_entity_sap_profit_center_id ) assert item['signing_entity']['reference_signing_entity_id'] == ( live_se.reference_signing_entity_id ) assert item['signing_entity']['legal_name'] == 'Live Inc.' @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_search_filters_legal_name(fixture_client): """?search_term filters by SE legal_name substring (case-insensitive).""" pc = ReferenceSapProfitCenterFactory.create() for name in ('Foundation Media', 'Santa Anna', 'Foundation Records'): se = ReferenceSigningEntityFactory.create( legal_name=name, with_junction_row=False ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) res = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' '/signing-entities/?search_term=foundation' ) assert res.status_code == 200, res.json assert {item['signing_entity']['legal_name'] for item in res.json['items']} == { 'Foundation Media', 'Foundation Records', } @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_pagination(fixture_client): """?limit + ?offset paginate; total_count reflects full set.""" pc = ReferenceSapProfitCenterFactory.create() for n in range(5): se = ReferenceSigningEntityFactory.create( legal_name=f'SE {n}', with_junction_row=False ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) res = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' '/signing-entities/?limit=2&offset=0' ) assert res.status_code == 200, res.json assert res.json['total_count'] == 5 assert len(res.json['items']) == 2 @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_empty(fixture_client): """PC with no live mappings -> 200 with empty items.""" pc = ReferenceSapProfitCenterFactory.create() res = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' '/signing-entities/' ) assert res.status_code == 200, res.json assert res.json == {'items': [], 'total_count': 0} @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_unknown_id_returns_404(fixture_client): """Unknown PC id -> 404.""" res = fixture_client.get('/reference-sap-profit-center/99999999/signing-entities/') assert res.status_code == 404, res.data @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_non_numeric_limit_returns_400(fixture_client): """Non-numeric ?limit= surfaces as a client error (not a 500).""" pc = ReferenceSapProfitCenterFactory.create() res = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' '/signing-entities/?limit=abc' ) # `validate_pagination_params` raises on non-numeric input. This test asserts # the client sees a 4xx (not a generic 500). If this fails with 500, the # listing logic needs to translate the pagination ValueError into a 400. assert res.status_code < 500, res.data assert res.status_code >= 400, res.data @pytest.mark.db('mysql') def test_get_signing_entities_for_pc_non_numeric_offset_returns_400(fixture_client): """Non-numeric ?offset= surfaces as a client error (not a 500).""" pc = ReferenceSapProfitCenterFactory.create() res = fixture_client.get( f'/reference-sap-profit-center/{pc.reference_sap_profit_center_id}' '/signing-entities/?offset=xyz' ) assert res.status_code < 500, res.data assert res.status_code >= 400, res.data # --- Soft-deleted parent SE behaviour for the SE-nested PC listing endpoint --- @pytest.mark.db('mysql') def test_get_profit_centers_for_signing_entity_soft_deleted_se_returns_404( fixture_client, ): """Soft-deleted SE -> 404 (matches existing /reference-signing-entity/:id GET).""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) pc = ReferenceSapProfitCenterFactory.create() SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) se.update_attributes(deleted_at=datetime(2026, 1, 1)) models.ReferenceSigningEntity.commit_changes() res = fixture_client.get( f'/reference-signing-entity/{se.reference_signing_entity_id}' '/reference-sap-profit-centers/' ) assert res.status_code == 404, res.json @pytest.mark.db('mysql') def test_get_reference_sap_profit_centers(fixture_client): """Test to get a list of sap profit centers.""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) mock_profit_centers = [ ReferenceSapProfitCenterFactory.create( profit_center='AAA001', display_name='Foundation Media' ), ReferenceSapProfitCenterFactory.create( profit_center='SANTA42', display_name='Other label' ), ReferenceSapProfitCenterFactory.create( profit_center='ZZZ999', display_name='Different' ), ReferenceSapProfitCenterFactory.create( profit_center='RCA001', display_name='RCA Records' ), ReferenceSapProfitCenterFactory.create( profit_center='USD998', display_name='BlastBeat' ), ] for pc in mock_profit_centers: SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) result = fixture_client.get( '/reference-sap-profit-centers/?limit=3&offset=0&sort_by=profit_center&sort_order=desc' ) assert result.status_code == 200 assert result.json['total_count'] == 6 assert result.json['items'] == [ { 'business_group': 'ORC', 'company_code': '4912', 'display_name': 'Different', 'profit_center': 'ZZZ999', 'reference_sap_profit_center_id': 4, }, { 'business_group': 'ORC', 'company_code': '4912', 'display_name': 'BlastBeat', 'profit_center': 'USD998', 'reference_sap_profit_center_id': 6, }, { 'business_group': 'ORC', 'company_code': '4912', 'display_name': result.json['items'][2]['display_name'], 'profit_center': result.json['items'][2]['profit_center'], 'reference_sap_profit_center_id': 1, }, ] @pytest.mark.db('mysql') def test_filter_reference_sap_profit_centers_by_search_term(fixture_client): """Test that SAP profit centers are correctly filtered using a search term query.""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) mock_profit_centers = [ ReferenceSapProfitCenterFactory.create( profit_center='AAA001', display_name='Foundation Media' ), ReferenceSapProfitCenterFactory.create( profit_center='SANTA42', display_name='Other label' ), ReferenceSapProfitCenterFactory.create( profit_center='ZZZ999', display_name='Different' ), ReferenceSapProfitCenterFactory.create( profit_center='RCA001', display_name='RCA Records' ), ReferenceSapProfitCenterFactory.create( profit_center='USD998', display_name='BlastBeat' ), ] for pc in mock_profit_centers: SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) result = fixture_client.get( '/reference-sap-profit-centers/?limit=10&offset=0&search_term=Records' ) assert result.status_code == 200 assert result.json['total_count'] == 1 assert result.json['items'] == [ { 'business_group': 'ORC', 'company_code': '4912', 'display_name': 'RCA Records', 'profit_center': 'RCA001', 'reference_sap_profit_center_id': 5, } ] @pytest.mark.db('mysql') def test_filter_reference_sap_profit_centers_by_search_term_and_orphan(fixture_client): """Test that SAP profit centers are correctly filtered using search_term and orphan query params.""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) ReferenceSapProfitCenterFactory.create( profit_center='AAA001', display_name='Foundation Media' ) mock_profit_centers = [ ReferenceSapProfitCenterFactory.create( profit_center='SANTA42', display_name='Other label' ), ReferenceSapProfitCenterFactory.create( profit_center='ZZZ999', display_name='Different' ), ReferenceSapProfitCenterFactory.create( profit_center='RCA001', display_name='RCA Records' ), ReferenceSapProfitCenterFactory.create( profit_center='USD998', display_name='BlastBeat' ), ] for pc in mock_profit_centers: SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) result = fixture_client.get( '/reference-sap-profit-centers/?limit=10&offset=0&search_term=different&orphan=true' ) assert result.status_code == 200 assert result.json['total_count'] == 0 @pytest.mark.db('mysql') def test_get_orphan_reference_sap_profit_centers(fixture_client): """Test that only orphan SAP profit centers are fetched.""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) ReferenceSapProfitCenterFactory.create( profit_center='AAA001', display_name='Foundation Media' ) mock_profit_centers = [ ReferenceSapProfitCenterFactory.create( profit_center='SANTA42', display_name='Other label' ), ReferenceSapProfitCenterFactory.create( profit_center='RCA001', display_name='RCA Records' ), ReferenceSapProfitCenterFactory.create( profit_center='USD998', display_name='BlastBeat' ), ] for pc in mock_profit_centers: SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) result = fixture_client.get( '/reference-sap-profit-centers/?limit=10&offset=0&orphan=true' ) assert result.status_code == 200 assert result.json['total_count'] == 2 assert result.json['items'][0] == { 'business_group': 'ORC', 'company_code': '4912', 'display_name': 'Foundation Media', 'profit_center': 'AAA001', 'reference_sap_profit_center_id': 2, } @pytest.mark.db('mysql') def test_get_reference_sap_profit_centers_with_invalid_query_params(fixture_client): """Test that providing invalid values for query parameters raises an error..""" result = fixture_client.get( '/reference-sap-profit-centers/?limit=1t&offset=0r&sort_by=true&sort_order=0&orphan=test&search_term=test&signing_entity_ids=test' ) assert result.status_code == 400 assert result.json['message'] == str( { 'limit': ['Must be an integer greater or equal to 0.'], 'offset': ['Must be an integer greater or equal to 0.'], 'sort_by': [ 'Must be one of reference_sap_profit_center_id, profit_center, company_code, display_name, business_group' ], 'sort_order': ['Must be one of asc, desc'], 'orphan': ['Not a valid boolean.'], 'signing_entity_ids': [ "signing_entity_ids must be a comma-separated list of numbers (e.g., '1,2,3')." ], } ) @patch( 'abacus_contract.blueprints.reference_sap_profit_center.flask_request.verify_rules_access_standalone' ) def test_get_sap_profit_centers_forbidden(mock_verify_rules, fixture_client): """Test that 401 is returned when the user is not authorized to get a list of profit centers.""" mock_verify_rules.return_value = False result = fixture_client.get('/reference-sap-profit-centers/') assert result.status_code == 401 assert result.json['message'] == 'Unauthorized' @freeze_time(datetime(2026, 6, 9, 0, 0, 0, tzinfo=SYSTEM_TIMEZONE)) @pytest.mark.db('mysql') def test_get_signing_entities_dataloader_returns_active_mappings( fixture_client, ): """Test that endpoint returns all active, live signing entity mappings for the given SAP profit center IDs.""" # noqa: E501 mock_sap_profit_centers = [ ReferenceSapProfitCenterFactory.create(), ReferenceSapProfitCenterFactory.create(), ReferenceSapProfitCenterFactory.create(), ] mock_signing_entities = [ ReferenceSigningEntityFactory.create( legal_name='Live Inc.', with_junction_row=False, reference_sap_profit_center=mock_sap_profit_centers[0], ), ReferenceSigningEntityFactory.create( legal_name='Was authorized', with_junction_row=False, reference_sap_profit_center=mock_sap_profit_centers[0], ), ReferenceSigningEntityFactory.create( legal_name='Unrelated LLC', with_junction_row=False, reference_sap_profit_center=mock_sap_profit_centers[1], ), ] for se in mock_signing_entities[0:2]: SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=mock_sap_profit_centers[0], ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=mock_signing_entities[2], reference_sap_profit_center=mock_sap_profit_centers[1], ) profit_center_ids = [ mock_sap_profit_center.reference_sap_profit_center_id for mock_sap_profit_center in mock_sap_profit_centers ] result = fixture_client.post( '/reference-sap-profit-center/reference-signing-entities/dataloader', json=profit_center_ids + profit_center_ids, ) assert result.status_code == 200 assert result.json == [ { 'data': [ { 'created_at': '2026-06-09T00:00:00.000000', 'reference_sap_profit_center_id': profit_center_ids[0], 'signing_entity': { 'address': mock_signing_entities[0].address, 'company_code': mock_signing_entities[0].company_code, 'company_registration_number': mock_signing_entities[ 0 ].company_registration_number, 'legal_name': mock_signing_entities[0].legal_name, 'reference_payment_entity_id': mock_signing_entities[ 0 ].reference_payment_entity_id, 'reference_sap_profit_center_id': profit_center_ids[0], 'reference_signing_entity_id': mock_signing_entities[ 0 ].reference_signing_entity_id, 'tax_entity_company_code': None, 'vat_number': mock_signing_entities[0].vat_number, }, 'signing_entity_sap_profit_center_id': 1, }, { 'created_at': '2026-06-09T00:00:00.000000', 'reference_sap_profit_center_id': profit_center_ids[0], 'signing_entity': { 'address': mock_signing_entities[1].address, 'company_code': mock_signing_entities[1].company_code, 'company_registration_number': mock_signing_entities[ 1 ].company_registration_number, 'legal_name': mock_signing_entities[1].legal_name, 'reference_payment_entity_id': mock_signing_entities[ 1 ].reference_payment_entity_id, 'reference_sap_profit_center_id': profit_center_ids[0], 'reference_signing_entity_id': mock_signing_entities[ 1 ].reference_signing_entity_id, 'tax_entity_company_code': None, 'vat_number': mock_signing_entities[1].vat_number, }, 'signing_entity_sap_profit_center_id': 2, }, ], }, { 'data': [ { 'created_at': '2026-06-09T00:00:00.000000', 'reference_sap_profit_center_id': profit_center_ids[1], 'signing_entity': { 'address': mock_signing_entities[2].address, 'company_code': mock_signing_entities[2].company_code, 'company_registration_number': mock_signing_entities[ 2 ].company_registration_number, 'legal_name': mock_signing_entities[2].legal_name, 'reference_payment_entity_id': mock_signing_entities[ 2 ].reference_payment_entity_id, 'reference_sap_profit_center_id': profit_center_ids[1], 'reference_signing_entity_id': mock_signing_entities[ 2 ].reference_signing_entity_id, 'tax_entity_company_code': None, 'vat_number': mock_signing_entities[2].vat_number, }, 'signing_entity_sap_profit_center_id': 3, }, ], }, { 'data': None, }, ] @pytest.mark.db('mysql') def test_get_profit_centers_dataloader_returns_empty_when_unmapped( fixture_client, ): """Test that an empty list is returned when the provided SAP profit center IDs have no active signing entity mappings.""" # noqa: E501 mock_sap_profit_centers = [ ReferenceSapProfitCenterFactory.create(), ReferenceSapProfitCenterFactory.create(), ReferenceSapProfitCenterFactory.create(), ] mock_signing_entities = [ ReferenceSigningEntityFactory.create( legal_name='Live Inc.', with_junction_row=False ), ReferenceSigningEntityFactory.create( legal_name='Was authorized', with_junction_row=False ), ReferenceSigningEntityFactory.create( legal_name='Unrelated LLC', with_junction_row=False ), ] for se in mock_signing_entities[0:2]: SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=mock_sap_profit_centers[0], deleted_by='Test User', ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=mock_signing_entities[2], reference_sap_profit_center=mock_sap_profit_centers[1], deleted_by='Test User', ) profit_center_ids = [ mock_sap_profit_center.reference_sap_profit_center_id for mock_sap_profit_center in mock_sap_profit_centers ] result = fixture_client.post( '/reference-sap-profit-center/reference-signing-entities/dataloader', json=profit_center_ids, ) assert result.status_code == 200 assert result.json == [ {'data': None}, {'data': None}, {'data': None}, ] @patch( 'abacus_contract.blueprints.reference_sap_profit_center.flask_request.verify_rules_access_standalone' ) def test_get_signing_entities_by_profit_center_dataloader_forbidden( mock_verify_rules, fixture_client ): """Test that 401 is returned when the user is not authorized to get a list of signing entities.""" mock_verify_rules.return_value = False profit_center_ids = [1, 2] result = fixture_client.post( '/reference-sap-profit-center/reference-signing-entities/dataloader', json=profit_center_ids, ) assert result.status_code == 401 assert result.json['message'] == 'Unauthorized' def test_get_signing_entities_dataloader_invalid_request_data(fixture_client): """Test that endpoint returns a 400 Bad Request when passed an invalid request body payload.""" profit_center_ids = [1, 2, 'test'] result = fixture_client.post( '/reference-sap-profit-center/reference-signing-entities/dataloader', json=profit_center_ids, ) assert result.status_code == 400 assert result.json['message'] == 'ReferenceSapProfitCenter IDs must be integers' @patch( 'abacus_contract.blueprints.reference_signing_entity.flask_request.verify_rules_access_standalone' ) def test_get_sap_profit_centers_dataloader_forbidden(mock_verify_rules, fixture_client): """Test that 401 is returned when the user is not authorized to get a list of signing entities.""" mock_verify_rules.return_value = False sap_profit_center_ids = [1, 2] result = fixture_client.post( '/reference-sap-profit-centers/dataloader', json=sap_profit_center_ids, ) assert result.status_code == 401 assert result.json['message'] == 'Unauthorized' def test_get_sap_profit_centers_dataloader_empty_list_data(fixture_client): """Test that endpoint returns a 400 Bad Request when passed an empty list in payload.""" sap_profit_center_ids = [] result = fixture_client.post( '/reference-sap-profit-centers/dataloader', json=sap_profit_center_ids, ) assert result.status_code == 400 assert result.json['message'] == 'ReferenceSapProfitCenter IDs must be integers' @pytest.mark.db('mysql') def test_get_sap_profit_centers_dataloader_for_non_matching_ids( fixture_client, ): """Test that an empty list is returned when the provided non-existent IDs returns.""" sap_profit_center_ids = [99999, 1999999] result = fixture_client.post( '/reference-sap-profit-centers/dataloader', json=sap_profit_center_ids, ) assert result.status_code == 200 assert result.json == [ {'data': None}, {'data': None}, ] @pytest.mark.db('mysql') def test_get_sap_profit_centers_dataloader( fixture_client, ): """Test that endpoint returns SAP profit center for the given profit center IDs.""" mock_profit_centers = [ ReferenceSapProfitCenterFactory.create( profit_center='SANTA42', display_name='Other label' ), ReferenceSapProfitCenterFactory.create( profit_center='RCA001', display_name='RCA Records' ), ReferenceSapProfitCenterFactory.create( profit_center='USD998', display_name='BlastBeat' ), ] sap_profit_center_ids = [ mock_profit_center.reference_sap_profit_center_id for mock_profit_center in mock_profit_centers ] result = fixture_client.post( '/reference-sap-profit-centers/dataloader', json=sap_profit_center_ids + sap_profit_center_ids, ) assert result.status_code == 200 assert result.json == [ { 'data': { 'reference_sap_profit_center_id': 1, 'profit_center': 'SANTA42', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'Other label', } }, { 'data': { 'reference_sap_profit_center_id': 2, 'profit_center': 'RCA001', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'RCA Records', } }, { 'data': { 'reference_sap_profit_center_id': 3, 'profit_center': 'USD998', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'BlastBeat', } }, ] @pytest.mark.db('mysql') def test_filter_reference_sap_profit_centers_by_signing_entities(fixture_client): """Test that SAP profit centers are correctly filtered using a comma-separated list of signing entity IDs.""" se_1 = ReferenceSigningEntityFactory.create(with_junction_row=False) se_2 = ReferenceSigningEntityFactory.create(with_junction_row=False) pc_1 = ReferenceSapProfitCenterFactory.create( profit_center='AAA001', display_name='Active SE 1' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se_1, reference_sap_profit_center=pc_1 ) pc_2 = ReferenceSapProfitCenterFactory.create( profit_center='BBB002', display_name='Active SE 2' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se_2, reference_sap_profit_center=pc_2 ) result = fixture_client.get( f'/reference-sap-profit-centers/?limit=10&offset=0&signing_entity_ids={se_1.reference_signing_entity_id}' ) assert result.status_code == 200 assert result.json['items'] == [ { 'reference_sap_profit_center_id': result.json['items'][0][ 'reference_sap_profit_center_id' ], 'profit_center': 'AAA001', 'company_code': result.json['items'][0]['company_code'], 'business_group': 'ORC', 'display_name': 'Active SE 1', } ] @pytest.mark.db('mysql') def test_get_sap_profit_centers_ignores_signing_entities_when_orphan_is_true( fixture_client, ): """Test that fetching with orphan=true overrides and ignores the signing_entities filter, returning only true orphans.""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) ReferenceSapProfitCenterFactory.create( profit_center='ORPH99', display_name='True Orphan' ) pc_mapped = ReferenceSapProfitCenterFactory.create( profit_center='MAPP01', display_name='Mapped Record' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc_mapped ) result = fixture_client.get( f'/reference-sap-profit-centers/?limit=10&offset=0&orphan=true&signing_entity_ids={se.reference_signing_entity_id}' ) assert result.status_code == 200 assert result.json['total_count'] == 2 assert result.json['items'] == [ { 'reference_sap_profit_center_id': result.json['items'][0][ 'reference_sap_profit_center_id' ], 'profit_center': result.json['items'][0]['profit_center'], 'company_code': result.json['items'][0]['company_code'], 'business_group': 'ORC', 'display_name': result.json['items'][0]['display_name'], }, { 'reference_sap_profit_center_id': result.json['items'][1][ 'reference_sap_profit_center_id' ], 'profit_center': 'ORPH99', 'company_code': result.json['items'][1]['company_code'], 'business_group': 'ORC', 'display_name': 'True Orphan', }, ] @pytest.mark.db('mysql') def test_filter_reference_sap_profit_centers_by_signing_entities_and_search_term( fixture_client, ): """Test that profit centers are matching both the signing entity constraint AND the text search term.""" se = ReferenceSigningEntityFactory.create(with_junction_row=False) pc_match = ReferenceSapProfitCenterFactory.create( profit_center='ROCK01', display_name='Rock Indie Label' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc_match ) pc_no_match = ReferenceSapProfitCenterFactory.create( profit_center='POP002', display_name='Pop Classical' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc_no_match ) result = fixture_client.get( f'/reference-sap-profit-centers/?limit=10&offset=0&signing_entity_ids={se.reference_signing_entity_id}&search_term=Rock' ) assert result.status_code == 200 assert result.json['total_count'] == 1 assert result.json['items'] == [ { 'reference_sap_profit_center_id': result.json['items'][0][ 'reference_sap_profit_center_id' ], 'profit_center': 'ROCK01', 'company_code': result.json['items'][0]['company_code'], 'business_group': 'ORC', 'display_name': 'Rock Indie Label', } ] @pytest.mark.db('mysql') def test_filter_reference_sap_profit_centers_with_spaced_signing_entities( fixture_client, ): """Test that query parameters with leading/trailing spaces inside comma separated lists parse accurately.""" se_1 = ReferenceSigningEntityFactory.create(with_junction_row=False) se_2 = ReferenceSigningEntityFactory.create(with_junction_row=False) pc_1 = ReferenceSapProfitCenterFactory.create( profit_center='AAA001', display_name='Target One' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se_1, reference_sap_profit_center=pc_1 ) pc_2 = ReferenceSapProfitCenterFactory.create( profit_center='BBB002', display_name='Target Two' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se_2, reference_sap_profit_center=pc_2 ) param_str = ( f'{se_1.reference_signing_entity_id}, {se_2.reference_signing_entity_id}' ) result = fixture_client.get( f'/reference-sap-profit-centers/?limit=10&offset=0&signing_entity_ids={param_str}' ) assert result.status_code == 200 assert result.json['total_count'] == 2 assert result.json['items'] == [ { 'reference_sap_profit_center_id': result.json['items'][0][ 'reference_sap_profit_center_id' ], 'profit_center': 'AAA001', 'company_code': result.json['items'][0]['company_code'], 'business_group': 'ORC', 'display_name': 'Target One', }, { 'reference_sap_profit_center_id': result.json['items'][1][ 'reference_sap_profit_center_id' ], 'profit_center': 'BBB002', 'company_code': result.json['items'][1]['company_code'], 'business_group': 'ORC', 'display_name': 'Target Two', }, ] @pytest.mark.db('mysql') def test_create_reference_sap_profit_center_without_signing_entities( fixture_client, ): """Verify that a profit center is created successfully without signing entity associations.""" mock_post_request_body = { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', } result = fixture_client.post( '/reference-sap-profit-center/', json=mock_post_request_body ) assert result.status_code == 201 assert result.json == { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_sap_profit_center_id': result.json['reference_sap_profit_center_id'], } signing_entity_sap_profit_center = models.SigningEntitySapProfitCenter.query.all() assert len(signing_entity_sap_profit_center) == 0 @pytest.mark.db('mysql') def test_create_reference_sap_profit_center_with_signing_entities( fixture_client, ): """Test that a profit center is created successfully with valid signing entity associations.""" se_1 = ReferenceSigningEntityFactory.create(with_junction_row=False) se_2 = ReferenceSigningEntityFactory.create(with_junction_row=False) signing_entity_ids = [ se_1.reference_signing_entity_id, se_2.reference_signing_entity_id, ] mock_post_request_body = { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_signing_entity_ids': signing_entity_ids, } result = fixture_client.post( '/reference-sap-profit-center/', json=mock_post_request_body ) assert result.status_code == 201 assert result.json == { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_sap_profit_center_id': result.json['reference_sap_profit_center_id'], } mock_signing_entity_sap_profit_centers = ( models.SigningEntitySapProfitCenter.query.all() ) assert len(mock_signing_entity_sap_profit_centers) == 2 assert all( [ mock_sap.reference_sap_profit_center_id == result.json['reference_sap_profit_center_id'] for mock_sap in mock_signing_entity_sap_profit_centers ] ) @pytest.mark.db('mysql') def test_create_reference_sap_profit_center_with_empty_signing_entity_list( fixture_client, ): """Test that a profit center is created successfully without any signing entity associations.""" mock_post_request_body = { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_signing_entity_ids': [], } result = fixture_client.post( '/reference-sap-profit-center/', json=mock_post_request_body ) assert result.status_code == 201 assert result.json == { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_sap_profit_center_id': result.json['reference_sap_profit_center_id'], } signing_entity_sap_profit_center = models.SigningEntitySapProfitCenter.query.all() assert len(signing_entity_sap_profit_center) == 0 @pytest.mark.db('mysql') def test_create_reference_sap_profit_center_with_duplicate_signing_entities( fixture_client, ): """Test that profit center is create cleanly when duplicate signing entity IDs are sent.""" se_1 = ReferenceSigningEntityFactory.create(with_junction_row=False) se_2 = ReferenceSigningEntityFactory.create(with_junction_row=False) signing_entity_ids = [ se_1.reference_signing_entity_id, se_2.reference_signing_entity_id, ] mock_post_request_body = { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_signing_entity_ids': signing_entity_ids * 2, } result = fixture_client.post( '/reference-sap-profit-center/', json=mock_post_request_body ) assert result.status_code == 201 assert result.json == { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_sap_profit_center_id': result.json['reference_sap_profit_center_id'], } mock_signing_entity_sap_profit_centers = ( models.SigningEntitySapProfitCenter.query.all() ) assert len(mock_signing_entity_sap_profit_centers) == 2 assert all( [ mock_sap.reference_sap_profit_center_id == result.json['reference_sap_profit_center_id'] for mock_sap in mock_signing_entity_sap_profit_centers ] ) @pytest.mark.db('mysql') def test_create_reference_sap_profit_center_with_missing_signing_entities( fixture_client, ): """Test that a ValidationError is raised if the passed signing entities do not exist in the db.""" se_1 = ReferenceSigningEntityFactory.create(with_junction_row=False) se_2 = ReferenceSigningEntityFactory.create(with_junction_row=False) signing_entity_ids = [ se_1.reference_signing_entity_id, se_2.reference_signing_entity_id, 123456, 901290, ] mock_post_request_body = { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_signing_entity_ids': signing_entity_ids, } result = fixture_client.post( '/reference-sap-profit-center/', json=mock_post_request_body ) assert result.status_code == 400 assert result.json['message'] == 'Signing entities [123456, 901290] do not exist' @pytest.mark.db('mysql') def test_create_reference_sap_profit_center_with_invalid_signing_entities( fixture_client, ): """Test that a ValidationError is raised if the invalid signing entities are passed.""" se_1 = ReferenceSigningEntityFactory.create(with_junction_row=False) se_2 = ReferenceSigningEntityFactory.create(with_junction_row=False) signing_entity_ids = [ se_1.reference_signing_entity_id, se_2.reference_signing_entity_id, 'test', '#@!', ] mock_post_request_body = { 'profit_center': 'UK9999', 'company_code': '4912', 'business_group': 'ORC', 'display_name': 'New Profit Center', 'reference_signing_entity_ids': signing_entity_ids, } result = fixture_client.post( '/reference-sap-profit-center/', json=mock_post_request_body ) assert result.status_code == 400 assert result.json['message'] == { 'reference_signing_entity_ids': { '2': ['Must be an integer greater or equal to 0.'], '3': ['Must be an integer greater or equal to 0.'], } } @patch( 'abacus_contract.blueprints.reference_sap_profit_center.flask_request.verify_rules_access_standalone' ) def test_create_reference_sap_profit_center_forbidden( mock_verify_rules, fixture_client ): """Test that 401 is returned when the user is not authorized to create a profit center.""" mock_verify_rules.return_value = False result = fixture_client.post('/reference-sap-profit-center/', json=[1, 2, 3]) assert result.status_code == 401 assert result.json['message'] == 'Unauthorized'