"""Integration tests for reference_sap_profit_center.""" import random import pytest import requests from abacus_contract.tests.integration.conftest import ows_abacus_contract_api_client from abacus_contract.tests.integration.consts.api import QA_BASE_URL from abacus_contract.tests.integration.utils import generic_helper @pytest.mark.jira('PLATFORM-4455') def test_reference_sap_profit_centers( auth_headers: dict[str, str], admin_headers: dict[str, str], ) -> None: """Test GET /reference-sap-profit-center/:id endpoint authorized.""" # Seed a (SE, PC, junction) tuple so the default-filtered list endpoint # (which excludes PCs without an active junction) returns at least one row. generic_helper.create_signing_entity_with_profit_center() # first fetch all the reference_sap_profit_centers with admin headers since # the readonly headers do not have access to this endpoint. endpoint = '{}/reference-sap-profit-centers'.format(QA_BASE_URL) res_get = requests.get(endpoint, headers=admin_headers) assert res_get.status_code == 200 items = res_get.json()['items'] # now test the endpoint with both admin and readonly headers and get a random item. random_item = random.choice(items) sap_profit_center_id = random_item['reference_sap_profit_center_id'] endpoint = '{}/reference-sap-profit-center/{}'.format( QA_BASE_URL, sap_profit_center_id ) res_get = requests.get(endpoint, headers=auth_headers) assert res_get.status_code == 200 assert res_get.json() == random_item @pytest.mark.jira('PLATFORM-4455') def test_reference_sap_profit_center_by_id_unauthorized( unauthorized_headers: dict[str, str], reference_sap_profit_center_fixtures ) -> None: """Test the GET /reference_sap_profit_center/:id endpoint unauthorized.""" endpoint = '{}/reference-sap-profit-center/1'.format(QA_BASE_URL) res_get = requests.get(endpoint, headers=unauthorized_headers) assert res_get.status_code == 403 expected = {'code': 'forbidden', 'message': 'User is forbidden'} assert res_get.json() == expected @pytest.mark.jira('ACC-10418') def test_put_reference_sap_profit_center_display_name( basic_headers: dict[str, str], ) -> None: """PUT /reference-sap-profit-center/:id updates display_name; GET reflects it.""" client = ows_abacus_contract_api_client(basic_headers) se_id, pc_id = ( generic_helper.create_signing_entity_and_profit_center_without_junction() ) new_label = f'Integration test label {se_id}' put_res = client.put_reference_sap_profit_center(pc_id, {'display_name': new_label}) assert put_res.status_code == 200, put_res.text assert put_res.json()['display_name'] == new_label get_res = requests.get( f'{QA_BASE_URL}/reference-sap-profit-center/{pc_id}', headers=basic_headers, ) assert get_res.status_code == 200, get_res.text assert get_res.json()['display_name'] == new_label @pytest.mark.jira('ACC-10438') def test_get_signing_entities_for_sap_profit_center( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-center/:id/signing-entities/ returns the live SE list.""" client = ows_abacus_contract_api_client(basic_headers) se_id, pc_id = generic_helper.create_signing_entity_with_profit_center() res = client.get_signing_entities_for_sap_profit_center(pc_id) assert res.status_code == 200, res.text items = res.json()['items'] matched = next( ( item for item in items if item['signing_entity']['reference_signing_entity_id'] == se_id ), None, ) assert matched is not None assert 'signing_entity_sap_profit_center_id' in matched assert 'created_at' in matched @pytest.mark.jira('ACC-10438') def test_get_signing_entities_for_sap_profit_center_unknown_id_returns_404( basic_headers: dict[str, str], ) -> None: """Unknown PC id -> 404.""" client = ows_abacus_contract_api_client(basic_headers) res = client.get_signing_entities_for_sap_profit_center(99999999) assert res.status_code == 404, res.text # --------------------------------------------------------------------------- # GET /reference-sap-profit-centers – list endpoint scenarios # --------------------------------------------------------------------------- @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_returns_items_and_total_count( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers returns 200 with items list and total_count.""" client = ows_abacus_contract_api_client(basic_headers) generic_helper.create_signing_entity_with_profit_center() res = client.get_reference_sap_profit_centers_list() assert res.status_code == 200, res.text body = res.json() assert 'items' in body, 'Response body must contain "items"' assert 'total_count' in body, 'Response body must contain "total_count"' assert isinstance(body['items'], list) assert isinstance(body['total_count'], int) assert body['total_count'] >= 1 assert len(body['items']) >= 1 # Verify expected fields on each returned item expected_fields = { 'reference_sap_profit_center_id', 'profit_center', 'company_code', 'business_group', 'display_name', } for item in body['items']: assert expected_fields <= item.keys(), ( f'Item is missing fields. Got: {set(item.keys())}' ) @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_pagination( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?limit=1&offset respects pagination params.""" client = ows_abacus_contract_api_client(basic_headers) # Seed two profit centers so pagination is non-trivial. generic_helper.create_signing_entity_with_profit_center() generic_helper.create_signing_entity_with_profit_center() res_all = client.get_reference_sap_profit_centers_list() assert res_all.status_code == 200, res_all.text total = res_all.json()['total_count'] assert total >= 2, 'Need at least 2 seeded items to test pagination' # limit=1 must return exactly one item res_page1 = client.get_reference_sap_profit_centers_list( params={'limit': 1, 'offset': 0} ) assert res_page1.status_code == 200, res_page1.text page1 = res_page1.json()['items'] assert len(page1) == 1 # offset=1 must skip the first item res_page2 = client.get_reference_sap_profit_centers_list( params={'limit': 1, 'offset': 1} ) assert res_page2.status_code == 200, res_page2.text page2 = res_page2.json()['items'] assert len(page2) == 1 assert ( page2[0]['reference_sap_profit_center_id'] != page1[0]['reference_sap_profit_center_id'] ) # total_count is consistent regardless of page size assert res_page1.json()['total_count'] == total @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_search_term_filters_results( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?search_term= filters by display_name / profit_center.""" client = ows_abacus_contract_api_client(basic_headers) _, pc_id = generic_helper.create_signing_entity_with_profit_center() # Retrieve the seeded PC so we know its exact display_name. res_by_id = requests.get( f'{QA_BASE_URL}/reference-sap-profit-center/{pc_id}', headers=basic_headers, ) assert res_by_id.status_code == 200, res_by_id.text display_name: str = res_by_id.json()['display_name'] # Use a substring that is unique to this record (the seeded name contains the PC code). search_term = display_name[:6] res = client.get_reference_sap_profit_centers_list( params={'search_term': search_term} ) assert res.status_code == 200, res.text items = res.json()['items'] assert any(item['reference_sap_profit_center_id'] == pc_id for item in items), ( f'Seeded PC {pc_id} not found in filtered results for search_term={search_term!r}' ) # All returned items should match the search term in either display_name or profit_center. for item in items: name_match = search_term.lower() in item['display_name'].lower() pc_match = search_term.lower() in item['profit_center'].lower() assert name_match or pc_match, ( f'Item {item} does not match search_term={search_term!r}' ) @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_orphan_filter( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?orphan=true returns only PCs without an active junction.""" client = ows_abacus_contract_api_client(basic_headers) # Orphan PC: has no signing_entity_sap_profit_center row. _, orphan_pc_id = ( generic_helper.create_signing_entity_and_profit_center_without_junction() ) # Non-orphan PC: has an active junction row. _, linked_pc_id = generic_helper.create_signing_entity_with_profit_center() res_orphan = client.get_reference_sap_profit_centers_list(params={'orphan': 'true'}) assert res_orphan.status_code == 200, res_orphan.text orphan_ids = { item['reference_sap_profit_center_id'] for item in res_orphan.json()['items'] } assert orphan_pc_id in orphan_ids, ( f'Orphan PC {orphan_pc_id} must appear when orphan=true' ) assert linked_pc_id not in orphan_ids, ( f'Linked PC {linked_pc_id} must NOT appear when orphan=true' ) res_non_orphan = client.get_reference_sap_profit_centers_list( params={'orphan': 'false'} ) assert res_non_orphan.status_code == 200, res_non_orphan.text non_orphan_ids = { item['reference_sap_profit_center_id'] for item in res_non_orphan.json()['items'] } assert linked_pc_id in non_orphan_ids, ( f'Linked PC {linked_pc_id} must appear when orphan=false' ) @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_signing_entity_ids_filter( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?signing_entity_ids= filters to that SE's PCs.""" client = ows_abacus_contract_api_client(basic_headers) se_id, pc_id = generic_helper.create_signing_entity_with_profit_center() # Seed a second unrelated (SE, PC) pair to confirm it is excluded. _, other_pc_id = generic_helper.create_signing_entity_with_profit_center() res = client.get_reference_sap_profit_centers_list( params={'signing_entity_ids': str(se_id)} ) assert res.status_code == 200, res.text returned_ids = { item['reference_sap_profit_center_id'] for item in res.json()['items'] } assert pc_id in returned_ids, ( f'PC {pc_id} linked to SE {se_id} must be in the filtered results' ) assert other_pc_id not in returned_ids, ( f'PC {other_pc_id} linked to a different SE must not appear' ) @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_sorting( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?sort_by=profit_center&sort_order=asc/desc is consistent.""" client = ows_abacus_contract_api_client(basic_headers) generic_helper.create_signing_entity_with_profit_center() generic_helper.create_signing_entity_with_profit_center() res_asc = client.get_reference_sap_profit_centers_list( params={'sort_by': 'profit_center', 'sort_order': 'asc'} ) assert res_asc.status_code == 200, res_asc.text items_asc = res_asc.json()['items'] res_desc = client.get_reference_sap_profit_centers_list( params={'sort_by': 'profit_center', 'sort_order': 'desc'} ) assert res_desc.status_code == 200, res_desc.text items_desc = res_desc.json()['items'] if len(items_asc) > 1: profit_centers_asc = [item['profit_center'] for item in items_asc] assert profit_centers_asc == sorted(profit_centers_asc), ( 'Items must be sorted ascending by profit_center' ) profit_centers_desc = [item['profit_center'] for item in items_desc] assert profit_centers_desc == sorted(profit_centers_desc, reverse=True), ( 'Items must be sorted descending by profit_center' ) @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_invalid_sort_by_returns_400( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?sort_by= returns 400.""" client = ows_abacus_contract_api_client(basic_headers) res = client.get_reference_sap_profit_centers_list( params={'sort_by': 'nonexistent_column'} ) assert res.status_code == 400, res.text @pytest.mark.jira('ACC-10415') def test_get_reference_sap_profit_centers_invalid_signing_entity_ids_returns_400( basic_headers: dict[str, str], ) -> None: """GET /reference-sap-profit-centers?signing_entity_ids= returns 400.""" client = ows_abacus_contract_api_client(basic_headers) res = client.get_reference_sap_profit_centers_list( params={'signing_entity_ids': 'not-a-number'} ) assert res.status_code == 400, res.text # --------------------------------------------------------------------------- # POST /reference-sap-profit-center/reference-signing-entities/dataloader # --------------------------------------------------------------------------- @pytest.mark.jira('ACC-10450') def test_post_signing_entities_dataloader_returns_live_se_mappings( basic_headers: dict[str, str], ) -> None: """Happy path: real DB rows flow through the full stack and arrive correctly shaped. Verifies that the HTTP layer, query, and serialization are wired together — logic edge-cases (deduplication, missing IDs, validation) are covered by the functional tests. """ client = ows_abacus_contract_api_client(basic_headers) se_id_1, pc_id_1 = generic_helper.create_signing_entity_with_profit_center() se_id_2, pc_id_2 = generic_helper.create_signing_entity_with_profit_center() res = client.post_signing_entities_by_profit_center_dataloader([pc_id_1, pc_id_2]) assert res.status_code == 200, res.text body = res.json() assert isinstance(body, list) assert len(body) == 2 # Per-PC data lists contain the expected signing entity IDs. se_ids_1 = { m['signing_entity']['reference_signing_entity_id'] for m in body[0]['data'] } se_ids_2 = { m['signing_entity']['reference_signing_entity_id'] for m in body[1]['data'] } assert se_id_1 in se_ids_1 assert se_id_2 in se_ids_2 @pytest.mark.jira('ACC-10450') def test_post_signing_entities_dataloader_unknown_pc_ids_return_data_none( basic_headers: dict[str, str], ) -> None: """Non-existent PC IDs produce {data: None} — confirms the real DB zero-row path.""" client = ows_abacus_contract_api_client(basic_headers) res = client.post_signing_entities_by_profit_center_dataloader([99999991, 99999992]) assert res.status_code == 200, res.text assert res.json() == [{'data': None}, {'data': None}] # --------------------------------------------------------------------------- # POST /reference-sap-profit-centers/dataloader # --------------------------------------------------------------------------- @pytest.mark.jira('ACC-10415') def test_post_sap_profit_centers_dataloader_returns_pc_data( basic_headers: dict[str, str], ) -> None: """Happy path: real PC rows flow through the full stack and arrive correctly shaped. Verifies HTTP routing → auth middleware → query → real DB → serialization are wired together — logic edge-cases are covered by the functional tests. """ client = ows_abacus_contract_api_client(basic_headers) _, pc_id_1 = ( generic_helper.create_signing_entity_and_profit_center_without_junction() ) _, pc_id_2 = ( generic_helper.create_signing_entity_and_profit_center_without_junction() ) res = client.post_sap_profit_centers_dataloader([pc_id_1, pc_id_2]) assert res.status_code == 200, res.text body = res.json() assert isinstance(body, list) assert len(body) == 2 returned_pc_ids = { entry['data']['reference_sap_profit_center_id'] for entry in body if entry.get('data') } assert pc_id_1 in returned_pc_ids assert pc_id_2 in returned_pc_ids @pytest.mark.jira('ACC-10415') def test_post_sap_profit_centers_dataloader_unknown_ids_return_data_none( basic_headers: dict[str, str], ) -> None: """Non-existent PC IDs produce {data: None} — confirms the real DB zero-row path.""" client = ows_abacus_contract_api_client(basic_headers) res = client.post_sap_profit_centers_dataloader([99999981, 99999982]) assert res.status_code == 200, res.text assert res.json() == [{'data': None}, {'data': None}] # --------------------------------------------------------------------------- # POST /reference-sap-profit-center/ # --------------------------------------------------------------------------- @pytest.mark.jira('ACC-10650') def test_post_reference_sap_profit_center(basic_headers: dict[str, str]) -> None: """Happy path: creating a PC persists it and it's retrievable via GET. Logic edge-cases (duplicate profit_center, unknown/invalid signing entity ids) are covered by the unit/functional tests. """ client = ows_abacus_contract_api_client(basic_headers) profit_center = f'IT{random.randint(100, 10**7)}' payload = { 'profit_center': profit_center, 'company_code': '4914', 'business_group': 'ORC', 'display_name': f'Integration test PC {profit_center}', } res = client.post_reference_sap_profit_center(payload) assert res.status_code == 201, res.text body = res.json() assert body['profit_center'] == payload['profit_center'] assert body['company_code'] == payload['company_code'] assert body['business_group'] == payload['business_group'] assert body['display_name'] == payload['display_name'] pc_id = body['reference_sap_profit_center_id'] get_res = requests.get( f'{QA_BASE_URL}/reference-sap-profit-center/{pc_id}', headers=basic_headers, ) assert get_res.status_code == 200, get_res.text assert get_res.json() == body @pytest.mark.jira('ACC-10650') def test_post_reference_sap_profit_center_with_signing_entity_ids( basic_headers: dict[str, str], ) -> None: """Happy path: creating a PC with signing_entity_ids links the junction rows.""" client = ows_abacus_contract_api_client(basic_headers) se_id, _ = generic_helper.create_signing_entity_and_profit_center_without_junction() profit_center = f'IT{random.randint(100, 10**7)}' payload = { 'profit_center': profit_center, 'company_code': '4914', 'business_group': 'ORC', 'display_name': f'Integration test PC {profit_center}', 'reference_signing_entity_ids': [se_id], } res = client.post_reference_sap_profit_center(payload) assert res.status_code == 201, res.text pc_id = res.json()['reference_sap_profit_center_id'] se_res = client.get_signing_entities_for_sap_profit_center(pc_id) assert se_res.status_code == 200, se_res.text linked_se_ids = { item['signing_entity']['reference_signing_entity_id'] for item in se_res.json()['items'] } assert se_id in linked_se_ids # --------------------------------------------------------------------------- # POST /reference-sap-profit-center//signing-entities/bulk # --------------------------------------------------------------------------- @pytest.mark.jira('ACC-10668') def test_post_bulk_associate_signing_entities(basic_headers: dict[str, str]) -> None: """Happy path: bulk-linking fresh SEs to a PC creates the junction rows. Edge cases (409 already-linked, 400 unknown/invalid SE ids, empty list, 401 forbidden, insert/restore ordering) are covered by the unit/functional tests. """ client = ows_abacus_contract_api_client(basic_headers) se_id_1, pc_id = ( generic_helper.create_signing_entity_and_profit_center_without_junction() ) se_id_2 = generic_helper.create_signing_entity_for_profit_center(pc_id) res = client.post_bulk_associate_signing_entities( pc_id, {'reference_signing_entity_ids': [se_id_1, se_id_2]} ) assert res.status_code == 201, res.text body = res.json() returned_se_ids = {item['reference_signing_entity_id'] for item in body} assert returned_se_ids == {se_id_1, se_id_2} assert all(item['reference_sap_profit_center_id'] == pc_id for item in body) assert ( generic_helper.get_signing_entity_profit_center_id(se_id_1, pc_id) is not None ) assert ( generic_helper.get_signing_entity_profit_center_id(se_id_2, pc_id) is not None )