Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 17 additions & 30 deletions src/qodev_apollo_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
67 changes: 31 additions & 36 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,60 +795,55 @@ 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")

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")


# ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading