"""Tests for connectors.abacus — AbacusClient.""" import pytest from connectors.abacus import AbacusClient from connectors.errors import ApiError from connectors.graphql import GraphQLError from schemas import ( AbacusContractInput, AbacusContractLifecycleInput, AbacusContractLifecycleScheduleInput, AbacusContractWithLifecyclesInput, ContractType, PeriodType, ProcessingStatus, RenewalType, ) class FakeGraphQLClient: """Records queries and returns canned responses for testing AbacusClient.""" def __init__(self): self.queries: list[tuple[str, dict | None]] = [] self._responses: list[dict | Exception] = [] def add_response(self, response: dict | Exception): self._responses.append(response) def query(self, query: str, variables: dict | None = None) -> dict: self.queries.append((query, variables)) response = self._responses.pop(0) if isinstance(response, Exception): raise response return response def _make_gql_input(name: str = 'Test') -> AbacusContractWithLifecyclesInput: lifecycle = AbacusContractLifecycleInput(lifecycle_term_start='2026-01-01') return AbacusContractWithLifecyclesInput( contract=AbacusContractInput( account_id=1, contract_name=name, contract_type=ContractType.DISTRIBUTION, ), lifecycle=lifecycle, lifecycle_schedules=[ AbacusContractLifecycleScheduleInput( renewal_type=RenewalType.CONTINUOUSLY_ACTIVE, termination_notice_detail_interval=30, termination_notice_detail_type=PeriodType.DAY, contract_lifecycle=lifecycle, ) ], ) class TestGetReferenceSigningEntities: def test_success(self): gql = FakeGraphQLClient() gql.add_response( { 'abacusReferenceSigningEntities': { 'items': [ { 'referenceSigningEntityId': 1, 'legalName': 'Acme', 'companyCode': '0001', 'address': None, 'companyRegistrationNumber': None, 'vatNumber': None, } ] } } ) client = AbacusClient(gql) entities = client.get_reference_signing_entities() assert len(entities) == 1 assert entities[0].legal_name == 'Acme' assert entities[0].reference_signing_entity_id == 1 def test_api_error_propagates(self): gql = FakeGraphQLClient() gql.add_response(ApiError('connection refused')) client = AbacusClient(gql) with pytest.raises(ApiError): client.get_reference_signing_entities() def test_graphql_error_propagates(self): gql = FakeGraphQLClient() gql.add_response(GraphQLError([{'message': 'Unauthorized'}])) client = AbacusClient(gql) with pytest.raises(GraphQLError): client.get_reference_signing_entities() def test_empty_items_returns_empty_list(self): gql = FakeGraphQLClient() gql.add_response({'abacusReferenceSigningEntities': {'items': []}}) client = AbacusClient(gql) entities = client.get_reference_signing_entities() assert entities == [] class TestGetRunControllers: def test_single_page(self): gql = FakeGraphQLClient() gql.add_response( { 'abacusRunControllers': { 'items': [ { 'runControllerId': 10, 'runControllerName': 'ctrl-a', 'contractType': 'distribution', } ] } } ) client = AbacusClient(gql) controllers = client.get_run_controllers() assert len(controllers) == 1 assert controllers[0].run_controller_id == 10 assert controllers[0].run_controller_name == 'ctrl-a' def test_pagination_multiple_pages(self): gql = FakeGraphQLClient() # First page: 100 items (full page) page1_items = [ { 'runControllerId': i, 'runControllerName': f'ctrl-{i}', 'contractType': 'distribution', } for i in range(100) ] gql.add_response({'abacusRunControllers': {'items': page1_items}}) # Second page: 50 items (partial page → stops) page2_items = [ { 'runControllerId': 100 + i, 'runControllerName': f'ctrl-{100 + i}', 'contractType': 'distribution', } for i in range(50) ] gql.add_response({'abacusRunControllers': {'items': page2_items}}) client = AbacusClient(gql) controllers = client.get_run_controllers() assert len(controllers) == 150 # Verify correct offsets were used _, vars1 = gql.queries[0] _, vars2 = gql.queries[1] assert vars1['offset'] == 0 assert vars2['offset'] == 100 def test_pagination_stops_on_empty_page(self): gql = FakeGraphQLClient() gql.add_response( { 'abacusRunControllers': { 'items': [ { 'runControllerId': 1, 'runControllerName': 'a', 'contractType': 'distribution', } ] * 100 } } ) gql.add_response({'abacusRunControllers': {'items': []}}) client = AbacusClient(gql) controllers = client.get_run_controllers() assert len(controllers) == 100 assert len(gql.queries) == 2 def test_api_error_propagates(self): gql = FakeGraphQLClient() gql.add_response(ApiError('timeout')) client = AbacusClient(gql) with pytest.raises(ApiError): client.get_run_controllers() class TestCreateContractWithLifecycles: def test_success(self): gql = FakeGraphQLClient() gql.add_response( { 'abacusCreateContractWithLifecycles': { 'contractId': 42, 'contractName': 'Test', 'contractType': 'distribution', 'contractStatus': 'active', 'runControllerId': None, 'isExcludedFromAccountingRun': False, 'isPrimaryContract': True, 'executionDate': None, } } ) client = AbacusClient(gql) result = client.create_contract_with_lifecycles(_make_gql_input()) assert result.status == ProcessingStatus.SUCCESS assert result.data.contract_id == 42 assert result.data.contract_name == 'Test' def test_api_error_returns_error_result(self): gql = FakeGraphQLClient() gql.add_response(ApiError('connection refused')) client = AbacusClient(gql) result = client.create_contract_with_lifecycles(_make_gql_input()) assert result.status == ProcessingStatus.ERROR assert 'connection refused' in result.error def test_graphql_error_returns_error_result(self): gql = FakeGraphQLClient() gql.add_response(GraphQLError([{'message': 'Account not found'}])) client = AbacusClient(gql) result = client.create_contract_with_lifecycles(_make_gql_input()) assert result.status == ProcessingStatus.ERROR assert 'Account not found' in result.error def test_sends_serialized_input(self): gql = FakeGraphQLClient() gql.add_response( { 'abacusCreateContractWithLifecycles': { 'contractId': 1, 'contractName': 'Test', 'contractType': 'distribution', } } ) client = AbacusClient(gql) client.create_contract_with_lifecycles(_make_gql_input('My Contract')) _, variables = gql.queries[0] contract = variables['input']['contract'] assert contract['contractName'] == 'My Contract' assert contract['contractType'] == 'distribution' class TestAttachRunController: def test_success(self): gql = FakeGraphQLClient() gql.add_response( { 'abacusUpdateRunControllerContracts': { 'contractId': 42, 'runControllerContractId': 1, 'runControllerId': 10, } } ) client = AbacusClient(gql) result = client.attach_run_controller(42, 10) assert result.contract_id == 42 assert result.run_controller_id == 10 def test_api_error_propagates(self): gql = FakeGraphQLClient() gql.add_response(ApiError('timeout')) client = AbacusClient(gql) with pytest.raises(ApiError): client.attach_run_controller(42, 10) def test_graphql_error_propagates(self): gql = FakeGraphQLClient() gql.add_response(GraphQLError([{'message': 'Not found'}])) client = AbacusClient(gql) with pytest.raises(GraphQLError): client.attach_run_controller(42, 10)