diff --git a/src/qodev_apollo_api/client.py b/src/qodev_apollo_api/client.py index fcd9404..421b683 100644 --- a/src/qodev_apollo_api/client.py +++ b/src/qodev_apollo_api/client.py @@ -1179,29 +1179,20 @@ async def send_email_task( ) return EmailerMessage.model_validate(result.get("emailer_message", result)) - async def list_contact_calls(self, contact_id: str) -> list[Call]: - """List calls for a contact. - - Args: - contact_id: Contact ID - - Returns: - List of Call models - """ - result = await self._get(f"/contacts/{contact_id}/calls") - return [Call.model_validate(c) for c in result.get("calls", [])] - async def list_contact_tasks(self, contact_id: str) -> list[Task]: """List tasks for a contact. + Apollo removed the ``/contacts/{id}/tasks`` sub-resource route (now 404), + so this filters the tasks search by ``contact_ids`` instead. + Args: contact_id: Contact ID Returns: List of Task subclasses matching each task's type """ - result = await self._get(f"/contacts/{contact_id}/tasks") - return [resolve_task(t) for t in result.get("tasks", [])] + result = await self.search_tasks(contact_ids=[contact_id]) + return result.items # ======================================================================== # CALENDAR EVENTS @@ -1278,29 +1269,25 @@ async def get_conversation(self, conversation_id: str) -> ConversationDetail: # NEWS & JOBS # ======================================================================== - async def list_account_news(self, account_id: str) -> list[dict]: - """List news articles for an account. - - Args: - account_id: Account ID - - Returns: - List of news article dictionaries - """ - result = await self._get(f"/accounts/{account_id}/news") - return result.get("news", []) - async def list_account_jobs(self, account_id: str) -> list[dict]: """List job postings for an account. + Apollo removed the ``/accounts/{id}/job_postings`` sub-resource route (now + 404). Job postings live on the linked *organization*, so this resolves the + account's ``organization_id`` and reads ``/organizations/{org_id}/job_postings``. + Args: - account_id: Account ID + account_id: CRM account ID (its linked organization holds the postings). Returns: - List of job posting dictionaries + List of job posting dictionaries (empty if the account has no linked + organization). """ - result = await self._get(f"/accounts/{account_id}/job_postings") - return result.get("job_postings", []) + account = await self.get_account(account_id) + if not account.organization_id: + return [] + result = await self._get(f"/organizations/{account.organization_id}/job_postings") + return result.get("organization_job_postings", []) # ======================================================================== # USAGE & RATE LIMITS diff --git a/tests/test_client.py b/tests/test_client.py index 8d6f9be..0af81c9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -795,24 +795,14 @@ async def test_get_contact_stages(client: ApolloClient): client._client.request.assert_called_once_with("GET", "/contact_stages") -async def test_list_contact_calls(client: ApolloClient): - """Test GET /contacts/{id}/calls returns list[Call].""" - client._client.request.return_value = _make_response( - {"calls": [{"id": "call1"}, {"id": "call2"}]} - ) - - result = await client.list_contact_calls("c1") - - assert isinstance(result, list) - assert len(result) == 2 - assert all(isinstance(c, Call) for c in result) - client._client.request.assert_called_once_with("GET", "/contacts/c1/calls") - - async def test_list_contact_tasks(client: ApolloClient): - """Test GET /contacts/{id}/tasks returns list[Task].""" + """list_contact_tasks filters the tasks search by contact_ids (the old + /contacts/{id}/tasks route was removed by Apollo — now 404).""" client._client.request.return_value = _make_response( - {"tasks": [{"id": "t1", "type": "call"}, {"id": "t2", "type": "contact_action_item"}]} + { + "tasks": [{"id": "t1", "type": "call"}, {"id": "t2", "type": "contact_action_item"}], + "pagination": {"total_entries": 2}, + } ) result = await client.list_contact_tasks("c1") @@ -820,35 +810,40 @@ async def test_list_contact_tasks(client: ApolloClient): assert isinstance(result, list) assert len(result) == 2 assert all(isinstance(t, BaseTask) for t in result) - client._client.request.assert_called_once_with("GET", "/contacts/c1/tasks") + call_args = client._client.request.call_args + assert call_args[0] == ("POST", "/tasks/search") + assert call_args[1]["json"]["contact_ids"] == ["c1"] -async def test_list_account_news(client: ApolloClient): - """Test GET /accounts/{id}/news returns list[dict].""" - client._client.request.return_value = _make_response( - {"news": [{"title": "Big news"}, {"title": "Small news"}]} - ) +async def test_list_account_jobs(client: ApolloClient): + """list_account_jobs resolves the account's organization_id then reads + /organizations/{org_id}/job_postings (the old /accounts/{id}/job_postings + route was removed by Apollo — now 404).""" + client._client.request.side_effect = [ + _make_response({"account": {"id": "a1", "organization_id": "org9"}}), + _make_response( + {"organization_job_postings": [{"title": "Engineer"}, {"title": "Designer"}]} + ), + ] - result = await client.list_account_news("a1") + result = await client.list_account_jobs("a1") - assert isinstance(result, list) - assert len(result) == 2 - assert result[0]["title"] == "Big news" - client._client.request.assert_called_once_with("GET", "/accounts/a1/news") + assert [j["title"] for j in result] == ["Engineer", "Designer"] + assert client._client.request.call_args_list[0][0] == ("GET", "/accounts/a1") + assert client._client.request.call_args_list[1][0] == ( + "GET", + "/organizations/org9/job_postings", + ) -async def test_list_account_jobs(client: ApolloClient): - """Test GET /accounts/{id}/job_postings returns list[dict].""" - client._client.request.return_value = _make_response( - {"job_postings": [{"title": "Engineer"}, {"title": "Designer"}]} - ) +async def test_list_account_jobs_no_organization(client: ApolloClient): + """An account with no linked organization returns [] without a second call.""" + client._client.request.return_value = _make_response({"account": {"id": "a1"}}) result = await client.list_account_jobs("a1") - assert isinstance(result, list) - assert len(result) == 2 - assert result[0]["title"] == "Engineer" - client._client.request.assert_called_once_with("GET", "/accounts/a1/job_postings") + assert result == [] + client._client.request.assert_called_once_with("GET", "/accounts/a1") # ============================================================================ diff --git a/uv.lock b/uv.lock index d08fb05..e693ac7 100644 --- a/uv.lock +++ b/uv.lock @@ -407,7 +407,7 @@ wheels = [ [[package]] name = "qodev-apollo-api" -version = "0.3.2" +version = "0.4.0" source = { editable = "." } dependencies = [ { name = "httpx" },