Files
CIAgent/apps/api/tests/enrichment/test_ninjapear_provider.py
T
saksham 1a4c80958f Initial commit: CI Agent competitive-intelligence monitoring app
FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring,
source collection, LLM-based change analysis, enrichment, and account
security (Turnstile, escalating lockout, email verification).
2026-08-05 10:48:20 -04:00

187 lines
6.7 KiB
Python

"""NinjaPearProvider against the real API shape documented at
nubela.co/llms-full.txt: website-only company identification, and the
vendor's actual response field names (executives/employee_count,
total_funds_raised/funding_rounds, competitors keyed by website, three
separately-categorized customer/investor/partner arrays, first_name+domain
for work-email, x_profile_url for the person-profile endpoint)."""
from __future__ import annotations
import httpx
import pytest
import respx
from app.core.config import Settings
from app.enrichment.ninjapear import NinjaPearProvider
_SETTINGS = Settings(ninjapear_api_key="test-key")
_WEBSITE = "https://acme.example.com"
@pytest.mark.asyncio
async def test_get_company_details_parses_executives_and_employee_count():
payload = {
"description": "A payments company.",
"industry": "Fintech",
"founded_year": 2010,
"specialties": ["Payments", "APIs"],
"employee_count": "1001-5000",
"executives": [
{"name": "Jane Doe", "title": "CEO"},
{"name": "", "title": "Ignored - no name"},
],
}
with respx.mock:
respx.get("https://nubela.co/api/v1/company/details").mock(
return_value=httpx.Response(200, json=payload)
)
details = await NinjaPearProvider(_SETTINGS).get_company_details("Acme Corp", _WEBSITE)
assert details.industry == "Fintech"
assert details.employee_count_range == "1001-5000"
assert len(details.leadership_team) == 1
assert details.leadership_team[0].name == "Jane Doe"
@pytest.mark.asyncio
async def test_company_details_raises_without_a_website():
with pytest.raises(ValueError, match="website"):
await NinjaPearProvider(_SETTINGS).get_company_details("Acme Corp", None)
@pytest.mark.asyncio
async def test_get_funding_maps_total_funds_raised_and_funding_rounds():
# total_funds_raised/amount_usd are raw numbers, and each investor is
# an object (not a plain string) - matches the real API's shape.
payload = {
"total_funds_raised_usd": 500_000_000,
"funding_rounds": [
{
"round_type": "Series C",
"amount_usd": 200_000_000,
"date": "2024-01-01",
"investors": [{"name": "VC Co", "type": "company", "website": None}],
}
],
}
with respx.mock:
respx.get("https://nubela.co/api/v1/company/funding").mock(
return_value=httpx.Response(200, json=payload)
)
funding = await NinjaPearProvider(_SETTINGS).get_funding("Acme Corp", _WEBSITE)
assert funding.total_raised == "500000000"
assert funding.rounds[0].round_name == "Series C"
assert funding.rounds[0].amount == "200000000"
assert funding.rounds[0].investors == ["VC Co"]
@pytest.mark.asyncio
async def test_get_competitors_falls_back_to_website_when_no_name_given():
payload = {
"competitors": [{"website": "rival.example.com", "competition_reason": "Same market"}]
}
with respx.mock:
respx.get("https://nubela.co/api/v1/competitor/listing").mock(
return_value=httpx.Response(200, json=payload)
)
competitors = await NinjaPearProvider(_SETTINGS).get_competitors("Acme Corp", _WEBSITE)
assert competitors[0].name == "rival.example.com"
assert competitors[0].reason == "Same market"
@pytest.mark.asyncio
async def test_get_customers_merges_the_three_categorized_arrays():
payload = {
"customers": [{"name": "BigCo"}],
"investors": [{"name": "VC Fund"}],
"partner_platforms": [{"name": "PlatformCo"}],
}
with respx.mock:
respx.get("https://nubela.co/api/v1/customer/listing").mock(
return_value=httpx.Response(200, json=payload)
)
customers = await NinjaPearProvider(_SETTINGS).get_customers("Acme Corp", _WEBSITE)
by_relationship = {c.relationship: c.name for c in customers}
assert by_relationship == {"customer": "BigCo", "investor": "VC Fund", "partner": "PlatformCo"}
@pytest.mark.asyncio
async def test_get_products_joins_categories():
payload = {
"products": [
{"name": "Widget", "description": "A widget", "categories": ["Hardware", "IoT"]}
]
}
with respx.mock:
respx.get("https://nubela.co/api/v1/product/listing").mock(
return_value=httpx.Response(200, json=payload)
)
products = await NinjaPearProvider(_SETTINGS).get_products("Acme Corp", _WEBSITE)
assert products[0].name == "Widget"
assert products[0].category == "Hardware, IoT"
@pytest.mark.asyncio
async def test_get_updates_maps_title_and_source():
payload = {
"updates": [
{
"title": "We shipped a feature",
"source": "blog",
"url": "https://x",
"timestamp": "2026-01-01",
}
]
}
with respx.mock:
respx.get("https://nubela.co/api/v1/company/updates").mock(
return_value=httpx.Response(200, json=payload)
)
updates = await NinjaPearProvider(_SETTINGS).get_updates("Acme Corp", _WEBSITE)
assert updates[0].text == "We shipped a feature"
assert updates[0].type == "blog"
@pytest.mark.asyncio
async def test_get_work_email_sends_first_last_name_and_domain():
with respx.mock:
route = respx.get("https://nubela.co/api/v1/employee/work-email").mock(
return_value=httpx.Response(200, json={"work_email": "[email protected]"})
)
email = await NinjaPearProvider(_SETTINGS).get_work_email("Jane Doe", _WEBSITE)
assert email == "[email protected]"
sent_params = dict(route.calls[0].request.url.params)
assert sent_params == {"first_name": "Jane", "last_name": "Doe", "domain": "acme.example.com"}
@pytest.mark.asyncio
async def test_get_person_profile_hits_the_v2_endpoint_and_maps_x_profile_url():
with respx.mock:
respx.get("https://nubela.co/api/v2/employee/profile").mock(
return_value=httpx.Response(
200, json={"x_profile_url": "https://x.com/janedoe", "bio": "A bio."}
)
)
profile_url, bio = await NinjaPearProvider(_SETTINGS).get_person_profile(
"Jane Doe", _WEBSITE
)
assert profile_url == "https://x.com/janedoe"
assert bio == "A bio."
@pytest.mark.asyncio
async def test_a_provider_error_response_raises_rather_than_being_swallowed():
with respx.mock:
respx.get("https://nubela.co/api/v1/company/details").mock(
return_value=httpx.Response(429, text="rate limited")
)
with pytest.raises(httpx.HTTPStatusError):
await NinjaPearProvider(_SETTINGS).get_company_details("Acme Corp", _WEBSITE)