"""Functional tests for the SE-PC junction endpoints (MySQL).""" from unittest.mock import patch import pytest from abacus_contract import models from abacus_contract.constants import error from abacus_contract.tests.utils.factories import ( AccountContractFactory, ContractFactory, ReferenceSapProfitCenterFactory, ReferenceSigningEntityFactory, SigningEntitySapProfitCenterFactory, ) @pytest.mark.db('mysql') def test_post_signing_entity_profit_center_persists_row(fixture_client): """POST creates a live junction row visible in the DB.""" se = ReferenceSigningEntityFactory.create() pc = ReferenceSapProfitCenterFactory.create() res = fixture_client.post( '/signing-entity-profit-center/', json={ 'reference_signing_entity_id': se.reference_signing_entity_id, 'reference_sap_profit_center_id': pc.reference_sap_profit_center_id, }, ) assert res.status_code == 201, res.json new_id = res.json['signing_entity_sap_profit_center_id'] persisted = models.SigningEntitySapProfitCenter.query.get(new_id) assert persisted is not None assert persisted.deleted_at is None assert persisted.reference_signing_entity_id == se.reference_signing_entity_id assert persisted.reference_sap_profit_center_id == pc.reference_sap_profit_center_id @pytest.mark.db('mysql') def test_post_signing_entity_profit_center_duplicate_live_returns_409(fixture_client): """A live duplicate (SE, PC) is rejected with 409 and the existing row is untouched.""" existing = SigningEntitySapProfitCenterFactory.create() res = fixture_client.post( '/signing-entity-profit-center/', json={ 'reference_signing_entity_id': existing.reference_signing_entity_id, 'reference_sap_profit_center_id': existing.reference_sap_profit_center_id, }, ) assert res.status_code == 409, res.json refreshed = models.SigningEntitySapProfitCenter.query.get( existing.signing_entity_sap_profit_center_id ) assert refreshed.deleted_at is None @pytest.mark.db('mysql') def test_post_signing_entity_profit_center_restores_soft_deleted_match(fixture_client): """A soft-deleted match is restored in place (same id, deleted_at cleared).""" se = ReferenceSigningEntityFactory.create() pc = ReferenceSapProfitCenterFactory.create() old_row = SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) old_id = old_row.signing_entity_sap_profit_center_id models.SigningEntitySapProfitCenter.delete_by_id_or_error(old_id, soft_delete=True) res = fixture_client.post( '/signing-entity-profit-center/', json={ 'reference_signing_entity_id': se.reference_signing_entity_id, 'reference_sap_profit_center_id': pc.reference_sap_profit_center_id, }, ) assert res.status_code == 201, res.json assert res.json['signing_entity_sap_profit_center_id'] == old_id refreshed = models.SigningEntitySapProfitCenter.query.get(old_id) assert refreshed.deleted_at is None assert refreshed.deleted_by is None @pytest.mark.db('mysql') def test_post_signing_entity_profit_center_unknown_se_returns_400(fixture_client): """Non-existent SE -> 400.""" pc = ReferenceSapProfitCenterFactory.create() res = fixture_client.post( '/signing-entity-profit-center/', json={ 'reference_signing_entity_id': 99999999, 'reference_sap_profit_center_id': pc.reference_sap_profit_center_id, }, ) assert res.status_code == 400, res.json @pytest.mark.db('mysql') def test_post_signing_entity_profit_center_unknown_pc_returns_400(fixture_client): """Non-existent PC -> 400.""" se = ReferenceSigningEntityFactory.create() res = fixture_client.post( '/signing-entity-profit-center/', json={ 'reference_signing_entity_id': se.reference_signing_entity_id, 'reference_sap_profit_center_id': 99999999, }, ) assert res.status_code == 400, res.json @pytest.mark.db('mysql') def test_delete_signing_entity_profit_center_soft_deletes_row(fixture_client): """DELETE returns 204 and the row is soft-deleted in the DB.""" junction = SigningEntitySapProfitCenterFactory.create() junction_id = junction.signing_entity_sap_profit_center_id res = fixture_client.delete(f'/signing-entity-profit-center/{junction_id}/') assert res.status_code == 204, res.data refreshed = models.SigningEntitySapProfitCenter.query.get(junction_id) assert refreshed is not None assert refreshed.deleted_at is not None @pytest.mark.db('mysql') def test_delete_signing_entity_profit_center_with_active_contract_returns_409( fixture_client, create_mock_account ): """A contract referencing the pair blocks the delete with 409.""" se = ReferenceSigningEntityFactory.create() pc = ReferenceSapProfitCenterFactory.create() junction = SigningEntitySapProfitCenterFactory.create( reference_signing_entity=se, reference_sap_profit_center=pc ) contract = ContractFactory.create( reference_signing_entity=se, reference_sap_profit_center_id=pc.reference_sap_profit_center_id, ) AccountContractFactory.create(contract=contract) res = fixture_client.delete( f'/signing-entity-profit-center/{junction.signing_entity_sap_profit_center_id}/' ) assert res.status_code == 409, res.json refreshed = models.SigningEntitySapProfitCenter.query.get( junction.signing_entity_sap_profit_center_id ) assert refreshed.deleted_at is None @pytest.mark.db('mysql') def test_delete_signing_entity_profit_center_unknown_id_returns_404(fixture_client): """Unknown junction id -> 404.""" res = fixture_client.delete('/signing-entity-profit-center/99999999/') assert res.status_code == 404, res.json @pytest.mark.db('mysql') def test_delete_signing_entity_profit_center_already_soft_deleted_returns_404( fixture_client, ): """Re-DELETE on an already-soft-deleted row -> 404.""" junction = SigningEntitySapProfitCenterFactory.create() models.SigningEntitySapProfitCenter.delete_by_id_or_error( junction.signing_entity_sap_profit_center_id, soft_delete=True ) res = fixture_client.delete( f'/signing-entity-profit-center/{junction.signing_entity_sap_profit_center_id}/' ) assert res.status_code == 404, res.json @pytest.mark.db('mysql') def test_post_then_delete_then_post_recreates_via_restore(fixture_client): """End-to-end: create, delete (soft), create-again returns the same id (restore).""" se = ReferenceSigningEntityFactory.create() pc = ReferenceSapProfitCenterFactory.create() payload = { 'reference_signing_entity_id': se.reference_signing_entity_id, 'reference_sap_profit_center_id': pc.reference_sap_profit_center_id, } first = fixture_client.post('/signing-entity-profit-center/', json=payload) assert first.status_code == 201, first.json junction_id = first.json['signing_entity_sap_profit_center_id'] delete_res = fixture_client.delete(f'/signing-entity-profit-center/{junction_id}/') assert delete_res.status_code == 204, delete_res.data second = fixture_client.post('/signing-entity-profit-center/', json=payload) assert second.status_code == 201, second.json assert second.json['signing_entity_sap_profit_center_id'] == junction_id refreshed = models.SigningEntitySapProfitCenter.query.get(junction_id) assert refreshed.deleted_at is None @pytest.mark.db('mysql') def test_bulk_associate_signing_entities_success_mixed_insert_and_restore( fixture_client, ): """Test should successfully restore soft-deleted rows and insert brand new rows.""" mock_signing_entities = [ ReferenceSigningEntityFactory.create(with_junction_row=False), ReferenceSigningEntityFactory.create(with_junction_row=False), ReferenceSigningEntityFactory.create(with_junction_row=False), ] mock_profit_center = ReferenceSapProfitCenterFactory.create( display_name='Test Profit Center 1' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=mock_signing_entities[0], reference_sap_profit_center=mock_profit_center, deleted_by='Test User', deleted_at='2026-07-07', ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=mock_signing_entities[1], reference_sap_profit_center=mock_profit_center, deleted_by='Test User', deleted_at='2026-07-07', ) signing_entity_ids = [ signing_entity.reference_signing_entity_id for signing_entity in mock_signing_entities ] mock_profit_center_id = mock_profit_center.reference_sap_profit_center_id mock_request_body = {'reference_signing_entity_ids': signing_entity_ids} result = fixture_client.post( f'/reference-sap-profit-center/{mock_profit_center_id}/signing-entities/bulk', json=mock_request_body, ) assert result.status_code == 201 assert result.json == [ { 'reference_signing_entity_id': signing_entity_ids[2], 'reference_sap_profit_center_id': mock_profit_center_id, 'signing_entity_sap_profit_center_id': result.json[0][ 'signing_entity_sap_profit_center_id' ], 'created_at': result.json[0]['created_at'], 'deleted_at': None, }, { 'reference_signing_entity_id': signing_entity_ids[0], 'reference_sap_profit_center_id': mock_profit_center_id, 'signing_entity_sap_profit_center_id': result.json[1][ 'signing_entity_sap_profit_center_id' ], 'created_at': result.json[1]['created_at'], 'deleted_at': None, }, { 'reference_signing_entity_id': signing_entity_ids[1], 'reference_sap_profit_center_id': mock_profit_center_id, 'signing_entity_sap_profit_center_id': result.json[2][ 'signing_entity_sap_profit_center_id' ], 'created_at': result.json[2]['created_at'], 'deleted_at': None, }, ] @pytest.mark.db('mysql') def test_bulk_associate_signing_entities_conflict_active_row_exists(fixture_client): """Test should return a 409 error response when an active relationship already exists.""" mock_signing_entities = [ ReferenceSigningEntityFactory.create(with_junction_row=False), ReferenceSigningEntityFactory.create(with_junction_row=False), ] mock_profit_center = ReferenceSapProfitCenterFactory.create( display_name='Test Profit Center 1' ) SigningEntitySapProfitCenterFactory.create( reference_signing_entity=mock_signing_entities[0], reference_sap_profit_center=mock_profit_center, ) signing_entity_ids = [ signing_entity.reference_signing_entity_id for signing_entity in mock_signing_entities ] mock_profit_center_id = mock_profit_center.reference_sap_profit_center_id mock_request_body = {'reference_signing_entity_ids': signing_entity_ids} result = fixture_client.post( f'/reference-sap-profit-center/{mock_profit_center_id}/signing-entities/bulk', json=mock_request_body, ) assert result.status_code == 409 assert result.json[ 'message' ] == error.ERROR_SIGNING_ENTITY_PROFIT_CENTER_ALREADY_EXISTS.format( reference_signing_entity_id=signing_entity_ids[0], reference_sap_profit_center_id=mock_profit_center_id, ) @pytest.mark.db('mysql') def test_bulk_associate_signing_entities_raises_validation_error(fixture_client): """Test returns an error if signing entity id doesn't exist.""" mock_profit_center = ReferenceSapProfitCenterFactory.create( display_name='Test Profit Center 1' ) signing_entity_ids = [9999] mock_profit_center_id = mock_profit_center.reference_sap_profit_center_id mock_request_body = {'reference_signing_entity_ids': signing_entity_ids} result = fixture_client.post( f'/reference-sap-profit-center/{mock_profit_center_id}/signing-entities/bulk', json=mock_request_body, ) assert result.status_code == 400 assert ( result.json['message'] == 'One or more reference_signing_entity_ids are invalid.' ) @patch( 'abacus_contract.blueprints.signing_entity_sap_profit_center.flask_request.verify_rules_access_standalone' ) def test_bulk_associate_signing_entities_forbidden(mock_verify_rules, fixture_client): """Test that 401 is returned when the user is not authorized to create a signing entity sap profit center.""" mock_profit_center_id = 1 mock_request_body = {'reference_signing_entity_ids': [1, 2, 3]} mock_verify_rules.return_value = False result = fixture_client.post( f'/reference-sap-profit-center/{mock_profit_center_id}/signing-entities/bulk', json=mock_request_body, ) assert result.status_code == 401 assert result.json['message'] == 'Unauthorized' def test_bulk_associate_signing_entities_for_invalid_ids(fixture_client): """Test returns an error if invalid signing entity ids are passed.""" mock_profit_center = ReferenceSapProfitCenterFactory.create( display_name='Test Profit Center 1' ) signing_entity_ids = ['t1', 9999, 'test', '1111'] mock_profit_center_id = mock_profit_center.reference_sap_profit_center_id mock_request_body = {'reference_signing_entity_ids': signing_entity_ids} result = fixture_client.post( f'/reference-sap-profit-center/{mock_profit_center_id}/signing-entities/bulk', json=mock_request_body, ) assert result.status_code == 400 assert result.json['message'] == { 'reference_signing_entity_ids': { '0': ['Must be an integer greater or equal to 0.'], '2': ['Must be an integer greater or equal to 0.'], } }