Files
treg/tests/test_routing.py
T
Jason ZhouandClaude Opus 5.5 fd5a2c0033 fix: stop billing empty answers, make /admin/errors read-only, add treg --json call (#675)
* fix(billing): stop charging for empty and company-blind answers

Per-result searches settled at the requested page size even when the
vendor returned nothing: count the billed rows for CompanyEnrich (2-credit
minimum), Findymail employees, Icypeas bulk (FOUND only), TheCompaniesAPI
search (and simplified=true, which is free) and Serpstat (error envelopes
free, 1-credit minimum on an empty result). Icypeas profile-URL misses
settle at zero through an expect rule.

Routing: Prospeo 400 NO_RESULTS is a declared miss, not a caller fault;
the CompanyEnrich scroll adapter is removed so one empty question is not
routed twice; people.search declares scoping: [company_domain], so a
title-only provider is dropped from a company-scoped plan instead of
billing title-matched strangers once the others miss.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMPFwsMRowa1X7kXc5nZyw

* fix(admin): make GET /admin/errors read-only; purge evidence from a worker

Loading the errors page ran a platform-wide UPDATE blanking failed-call
evidence past the 14-day window. The purge moves to
application/evidence_retention.py behind 'treg-worker admin
purge-evidence', in bounded id batches. The view withholds evidence past
the window itself, so an unscheduled purge never widens what it shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMPFwsMRowa1X7kXc5nZyw

* feat(cli): treg --json call prints one parseable envelope

With --json, call prints {"result": <body>, "_treg": {http_status,
call_id, charged_micro|reserved_micro, ...}} on one line and nothing on
stderr, so a script that merges the streams still parses every answer.
Default output and --await are unchanged. Agent docs say to use it when
parsing, and to check a few results before looping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMPFwsMRowa1X7kXc5nZyw

* fix(billing): price counted rows at their credits and cap them at the hold

For a credit-priced per_result row the frozen unit is one provider credit,
so counting rows alone under-billed CompanyEnrich (2 credits a person, and
its 2-credit minimum) and Icypeas reverse-email bulk (10 a hit). Rows whose
catalog unit names an input entity reserve per thing asked about, so a row
count is capped at the hold: counting may lower a bill, never raise it.
simplified=true is free only on TheCompaniesAPI endpoints that declare it.

Also: a malformed contract scoping list fails the catalog load; --json
call keeps stderr silent on the WAF base64 retry; AGENTS.md records the
evidence-retention purge as the second callrecord writer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMPFwsMRowa1X7kXc5nZyw

* fix(billing): bill Icypeas company scrapes at the company rate; never exceed a zero hold

Also corrects the AGENTS.md callrecord-writer note and the scoping
comment (a bad list empties routing rather than failing the catalog).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMPFwsMRowa1X7kXc5nZyw

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-25 20:48:54 +10:00

2595 lines
145 KiB
Python

"""Capability routing — first-party routed endpoints (docs/CAPABILITY-ROUTING-PLAN.md).
`treg.people.email.find` picks a child and runs it through the ordinary call use case."""
from __future__ import annotations
import asyncio
import json
from pathlib import Path
import pytest
from httpx import AsyncClient
from sqlmodel import select
from treg import audit
from treg.domain import money as ledger
from treg.application.call import route as call_route
from treg.application.call import service as call_service
from treg.application.call.types import UpstreamResponse
from treg.config import get_settings
from treg.infra.db import session_maker
from treg.domain.catalog import store as catalog_store
from treg.domain.catalog.routing import paths as P
from treg.domain.catalog.routing.contracts import canonical_identity
from treg.domain.catalog.routing.plan import Candidate, cost_at, rank
from treg.infra.catalog_observations import CachedEndpointObservationReader
from treg.models import CallRecord, Hold, LedgerEntry
from test_marketplace_call import _balance, platform_on # noqa: F401
ROUTED = "treg.people.email.find"
@pytest.fixture
def enrichment_on(monkeypatch, platform_on):
for p in ("HUNTER", "TOMBA", "LEADMAGIC", "LEADSFORGE", "FINDYMAIL", "AVIATO", "FIBER_AI"):
monkeypatch.setenv(f"TREG_PLATFORM_KEY_{p}", f"PLATFORM-{p}-KEY")
monkeypatch.setenv("TREG_PLATFORM_KEY_TOMBA_SECRET", "PLATFORM-TOMBA-SECRET")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def enrichment_with_miss_declarers_on(monkeypatch, enrichment_on):
"""enrichment_on plus the two providers whose miss is a 4xx with a body (prospeo 400 NO_MATCH)
or a plain 4xx (limadata 404) — without them in TREG_PLATFORM_PROVIDERS a prospeo/limadata
test never runs the child and passes vacuously."""
for p in ("PROSPEO", "LIMADATA"):
monkeypatch.setenv(f"TREG_PLATFORM_KEY_{p}", f"PLATFORM-{p}-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,prospeo,limadata")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def enrichment_with_quickenrich_on(monkeypatch, platform_on):
"""Like enrichment_on but includes QuickEnrich - the cheapest provider for phone/email lookups."""
for p in ("HUNTER", "TOMBA", "LEADMAGIC", "LEADSFORGE", "FINDYMAIL", "AVIATO", "FIBER_AI", "QUICKENRICH"):
monkeypatch.setenv(f"TREG_PLATFORM_KEY_{p}", f"PLATFORM-{p}-KEY")
monkeypatch.setenv("TREG_PLATFORM_KEY_TOMBA_SECRET", "PLATFORM-TOMBA-SECRET")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,quickenrich")
get_settings.cache_clear()
yield
get_settings.cache_clear()
def _relay_by_provider(answers: dict[str, list[tuple[int, dict]]], seen: list):
"""A fake upstream keyed by the vendor host: each provider answers its scripted list in order."""
async def _relay(request, upstream_url, tool, secrets, client, drop_params=None, force_identity=False):
provider = next((p for p in answers if p != "*" and p in upstream_url), "*") # "*": any other vendor
body = b""
async for chunk in request.body_stream():
body += chunk
seen.append((provider, request.method, dict(request.query_items), json.loads(body) if body else None))
status, doc = answers[provider].pop(0)
payload = json.dumps(doc).encode()
async def _s():
yield payload
async def _c():
return None
return UpstreamResponse(status, ((b"content-type", b"application/json"),), _s(), _c)
return _relay
# ---- pure ------------------------------------------------------------------------------------
def test_expression_language():
doc = {"data": {"email": "a@x.io", "score": 80, "verification": {"status": "valid"}}, "emails": [{"email": "e", "type": "work"}], "none": []}
assert P.evaluate("data.email", doc) == "a@x.io"
assert P.evaluate("data.score / 100", doc) == 0.8
assert P.evaluate("data.verification.status == 'valid'", doc) is True
assert P.evaluate("data.email == null", doc) is False and P.evaluate("data.missing == null", doc) is True
assert P.evaluate("none == []", doc) is True and P.evaluate("emails == []", doc) is False
assert P.evaluate("emails[0].email", doc) == "e" and P.evaluate("emails[3].email", doc) is None
assert P.evaluate("coalesce(data.missing, data.email)", doc) == "a@x.io"
assert P.evaluate("split_first(data.name)", {"data": {"name": "Patrick Collison"}}) == "Patrick"
assert P.evaluate("split_last(data.name)", {"data": {"name": "Patrick"}}) is None
assert P.evaluate("join(a, b)", {"a": "Patrick", "b": "Collison"}) == "Patrick Collison"
with pytest.raises(ValueError):
P.evaluate("nope(a)", doc)
def test_admission_only_contract_verifies_adapters_but_generates_no_routed_row():
"""`routed: false` (contracts.yaml): the influencers.club raw/profile/full tiers are one provider
at three prices, so their contract exists for cache result admission only — the adapters must
verify (that is what `has_result_rules` reads), and no `treg.creators.profile` row may appear."""
cat = catalog_store.load()
for cap in ("creators.profile", "creators.analytics", "creators.enrich.by_email"):
assert cat.contracts[cap].routed is False
assert "treg." + cap not in cat.by_id
assert cat.contracts["people.email.find"].routed is True
tiers = ["influencersclub.creators.enrich." + t for t in ("raw", "profile", "full", "analytics", "email")]
assert all(cat.adapters[eid].verified and not cat.adapters[eid].verify_note for eid in tiers)
# ≥ 2 verified children of one capability would have generated a row on a routed contract
assert len([e for e in cat.for_capability("creators.profile") if cat.adapters[e["id"]].verified]) >= 2
def test_every_shipped_adapter_round_trips_its_fixture():
cat = catalog_store.load()
bad = {eid: a.verify_note for eid, a in cat.adapters.items() if not a.verified}
assert bad == {}, bad
ep = cat.by_id[ROUTED]
assert ep["kind"] == "routed" and ep["provider"] == "treg" and len(ep["routed_children"]) >= 8
assert cat.platform_eligible(ep) and ep["cost_range_usd"][0] < ep["cost_range_usd"][1]
# a hand-verified round trip on the plan's worked example
ad = cat.adapters["leadsforge.people.email.find"]
q, b = ad.to_upstream({"first_name": "Patrick", "last_name": "Collison", "domain": "stripe.com", "full_name": "Patrick Collison"})
assert b == {"firstName": "Patrick", "lastName": "Collison", "companyDomain": "stripe.com"} and q == {}
assert ad.from_upstream({"email": "p@stripe.com", "status": "succeeded"}) == {"email": "p@stripe.com"}
assert ad.is_miss({"email": None}) and not ad.is_miss({"email": "x"})
def test_openmart_tools_are_direct_only_not_routed():
cat = catalog_store.load()
assert "openmart.companies.search" not in cat.adapters
assert "openmart.companies.search" not in cat.by_id["treg.companies.search"]["routed_children"]
assert cat.platform_eligible(cat.by_id["openmart.companies.search"])
assert "openmart.companies.enrich" not in cat.by_id["treg.companies.enrich"]["routed_children"]
def test_tavily_routes_synchronous_web_tools_and_keeps_crawl_direct():
cat = catalog_store.load()
routed = {
"tavily.web.search": "treg.web.search",
"tavily.web.extract": "treg.web.extract",
"tavily.web.map": "treg.web.map",
}
for child, parent in routed.items():
assert cat.adapters[child].verified
assert child in cat.by_id[parent]["routed_children"]
assert "tavily.web.crawl" not in cat.adapters
assert "treg.web.crawl" not in cat.by_id or (
"tavily.web.crawl" not in cat.by_id["treg.web.crawl"]["routed_children"]
)
assert cat.platform_eligible(cat.by_id["tavily.web.crawl"])
def test_serper_routes_search_and_single_page_extract_only():
cat = catalog_store.load()
routed = {
"serper.web.search": "treg.web.search",
"serper.web.extract": "treg.web.extract",
}
for child, parent in routed.items():
assert cat.adapters[child].verified
assert child in cat.by_id[parent]["routed_children"]
assert cat.platform_eligible(cat.by_id[child])
direct = set(ep["id"] for ep in cat.for_provider("serper")) - set(routed)
assert not direct & set(cat.adapters)
def test_fetchin_linkedin_adapters_are_verified_and_routed():
cat = catalog_store.load()
routed = {
"fetchinio.linkedin.user.profile": "treg.linkedin.user.profile",
"fetchinio.linkedin.company.profile": "treg.linkedin.company.profile",
"fetchinio.linkedin.user.posts": "treg.linkedin.user.posts",
"fetchinio.linkedin.post.comments": "treg.linkedin.post.comments",
"fetchinio.linkedin.post.reactions": "treg.linkedin.post.reactions",
}
for child, parent in routed.items():
assert cat.adapters[child].verified
assert not cat.adapters[child].verify_note
assert child in cat.by_id[parent]["routed_children"]
# The new posts route is genuinely comparative, not a synthetic one-provider wrapper.
assert {
"aviato.linkedin.user.posts",
"fetchinio.linkedin.user.posts",
"harvestapi.linkedin.user.posts",
} <= set(cat.by_id["treg.linkedin.user.posts"]["routed_children"])
# Fetchin has no provider-neutral contracts for these provider-native operations.
assert "fetchinio.linkedin.user.reactions" not in cat.adapters
assert "fetchinio.linkedin.post.engagement" not in cat.adapters
profile = cat.adapters["fetchinio.linkedin.user.profile"]
assert profile.is_miss({"id": "profile-id", "firstName": None, "lastName": None})
assert not profile.is_miss({"id": "profile-id", "firstName": "Ada", "lastName": None})
async def test_fetchin_member_posts_route_uses_adapter_and_settles(
clients: AsyncClient, platform_on, monkeypatch,
):
monkeypatch.setenv("TREG_PLATFORM_KEY_FETCHINIO", "PLATFORM-FETCHINIO")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "fetchinio")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"fetchin.io": [(200, {
"posts": [{"id": "urn:li:activity:1", "text": "hello"}],
"paginationToken": "next-page",
"hasMore": True,
})],
}, seen))
before = await _balance(clients)
response = await clients.post(
"/call/treg.linkedin.user.posts",
json={"linkedin_handle": "satyanadella", "limit": 1},
headers={"X-Treg-Route-Prefer": "fetchinio"},
)
assert response.status_code == 200, response.text
data = response.json()
assert data["_treg"]["served_by"] == "fetchinio.linkedin.user.posts"
assert data["output"]["posts"] == [{"id": "urn:li:activity:1", "text": "hello"}]
assert data["output"]["next_cursor"] == "next-page"
assert data["output"]["has_more"] is True
assert seen == [("fetchin.io", "GET", {
"profileUrlOrUrn": "https://www.linkedin.com/in/satyanadella", "count": "1",
}, None)]
assert before - await _balance(clients) == 1_500
get_settings.cache_clear()
async def test_tavily_routed_empty_search_is_a_paid_miss_then_falls_through(
clients, monkeypatch,
):
monkeypatch.setenv("TREG_PLATFORM_KEY_TAVILY", "PLATFORM-TAVILY")
monkeypatch.setenv("TREG_PLATFORM_KEY_EXA", "PLATFORM-EXA")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "tavily,exa")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"tavily": [(200, {"results": [], "usage": {"credits": 1}})],
"exa": [(200, {
"results": [{"title": "Example", "url": "https://example.com"}],
"costDollars": {"total": 0.007},
})],
}, seen))
before = await _balance(clients)
response = await clients.post(
"/call/treg.web.search",
json={"q": "example query", "limit": 3},
headers={"X-Treg-Route-Prefer": "tavily,exa"},
)
assert response.status_code == 200, response.text
data = response.json()
assert data["_treg"]["served_by"] == "exa.web.search"
assert [attempt["outcome"] for attempt in data["_treg"]["tried"]] == ["miss", "hit"]
assert [attempt["charged_micro"] for attempt in data["_treg"]["tried"]] == [8_000, 7_000]
assert data["_treg"]["charged_micro"] == 15_000
assert before - await _balance(clients) == 15_000
assert [row[0] for row in seen] == ["tavily", "exa"]
assert seen[0][3] == {
"query": "example query", "max_results": 3,
"search_depth": "basic", "include_usage": True,
}
async with session_maker() as db:
assert (await db.execute(select(Hold))).scalars().all() == []
entries = (await db.execute(select(LedgerEntry))).scalars().all()
call_id = response.headers["X-Treg-Call-Id"]
assert {entry.call_id for entry in entries if entry.kind == "settle"} == {
call_id + ":r0", call_id + ":r1",
}
get_settings.cache_clear()
def test_dropleads_routing_surface_contains_only_verified_single_record_tools():
catalog = catalog_store.load()
expected = {
"dropleads.people.email.find",
"dropleads.people.phone.find",
"dropleads.people.email.verify",
"dropleads.people.search",
"dropleads.people.enrich",
"dropleads.companies.search",
"dropleads.companies.enrich",
}
assert {eid for eid in expected if catalog.adapters[eid].verified} == expected
assert not any("bulk" in eid or eid.endswith(".count") for eid in expected)
def test_dropleads_country_filters_use_each_upstream_schema():
catalog = catalog_store.load()
_, people_body = catalog.adapters["dropleads.people.search"].to_upstream({
"company_domain": "example.com", "country": "US",
})
_, company_body = catalog.adapters["dropleads.companies.search"].to_upstream({
"domain": "example.com", "country": "US",
})
assert people_body["filters"]["countries"] == ["United States"]
assert company_body["filters"]["countries"] == {"include": ["United States"]}
def test_prospeo_routing_surface_uses_fixed_single_record_modes():
catalog = catalog_store.load()
expected = {
"prospeo.people.email.find",
"prospeo.people.phone.find",
"prospeo.people.enrich",
"prospeo.people.search",
"prospeo.companies.enrich",
"prospeo.companies.search",
}
assert {eid for eid in expected if catalog.adapters[eid].verified} == expected
assert not any("bulk" in eid or "suggestions" in eid for eid in expected)
_, email_body = catalog.adapters["prospeo.people.email.find"].to_upstream({
"full_name": "Jane Doe", "domain": "example.com",
})
assert email_body == {
"data": {"full_name": "Jane Doe", "company_website": "example.com"},
"only_verified_email": True,
"enrich_mobile": False,
"only_verified_mobile": False,
}
_, phone_body = catalog.adapters["prospeo.people.phone.find"].to_upstream({
"linkedin_url": "https://www.linkedin.com/in/example",
})
assert phone_body["enrich_mobile"] is True
assert phone_body["only_verified_mobile"] is True
def test_aiark_routing_surface_uses_verified_bounded_adapters():
catalog = catalog_store.load()
expected = {
"aiark.people.search",
"aiark.companies.search",
"aiark.people.email.find",
"aiark.people.phone.find",
"aiark.people.enrich",
}
assert {eid for eid in expected if catalog.adapters[eid].verified} == expected
_, people = catalog.adapters["aiark.people.search"].to_upstream({
"company_domain": "example.com",
})
_, companies = catalog.adapters["aiark.companies.search"].to_upstream({
"domain": "example.com",
})
bounded = {
"account": {"domain": {"any": {"include": ["example.com"]}}},
"page": 0,
"size": 1,
}
assert people == bounded
assert companies == bounded
def test_aiark_finders_treat_present_but_empty_outputs_as_misses():
catalog = catalog_store.load()
email = catalog.adapters["aiark.people.email.find"]
phone = catalog.adapters["aiark.people.phone.find"]
assert email.is_miss({"data": None})
assert email.is_miss({"data": {"email": {"output": []}}})
assert not email.is_miss({"data": {"email": {"output": [{
"address": "jane@example.com",
}]}}})
assert phone.is_miss({"data": None})
assert phone.is_miss({"data": {"data": [[]]}})
assert not phone.is_miss({"data": {"data": [["+15550101000"]]}})
def test_limadata_routing_surface_contains_only_its_verified_adapters():
catalog = catalog_store.load()
expected = {
"limadata.people.email.find.name",
"limadata.people.email.find.linkedin",
"limadata.people.email.verify",
"limadata.people.phone.find",
"limadata.companies.enrich",
}
assert {
eid for eid, adapter in catalog.adapters.items()
if eid.startswith("limadata.") and adapter.verified
} == expected
assert "limadata.people.email.find.name" in catalog.by_id[
"treg.people.email.find"
]["routed_children"]
assert "limadata.people.email.verify" in catalog.by_id[
"treg.people.email.verify"
]["routed_children"]
assert "limadata.people.phone.find" in catalog.by_id[
"treg.people.phone.find"
]["routed_children"]
assert "limadata.people.enrich" not in catalog.adapters
assert "limadata.people.enrich" not in catalog.by_id[
"treg.people.enrich"
]["routed_children"]
def test_wiza_routing_surface_uses_bounded_single_record_searches():
catalog = catalog_store.load()
expected = {
"wiza.people.search",
"wiza.companies.search",
"wiza.companies.enrich",
}
assert {eid for eid in expected if catalog.adapters[eid].verified} == expected
_, people_body = catalog.adapters["wiza.people.search"].to_upstream({"title": "Founder"})
_, company_body = catalog.adapters["wiza.companies.search"].to_upstream({
"technology": "amazon-web-services",
})
assert people_body == {"filters": {"job_title": [{"v": "Founder", "s": "i"}]}, "size": 1}
assert company_body == {
"filters": {"technologies": [{"v": "amazon-web-services", "s": "i"}]},
"size": 1,
}
def test_identity_variants_derive_and_never_cross():
contract = catalog_store.load().contracts["people.email.find"]
ident, variant = canonical_identity(contract, {"full_name": "Patrick Collison", "domain": "stripe.com"})
assert variant == ("domain", "full_name") and ident["first_name"] == "Patrick" and ident["last_name"] == "Collison"
ident, variant = canonical_identity(contract, {"first_name": "Patrick", "last_name": "Collison", "domain": "stripe.com"})
assert ident["full_name"] == "Patrick Collison"
ident, variant = canonical_identity(contract, {"linkedin_url": "https://www.linkedin.com/in/x"})
assert variant == ("linkedin_url",) and "domain" not in ident
assert canonical_identity(contract, {"full_name": "Patrick Collison"})[1] is None
def test_cost_at_and_ranking_math():
assert cost_at({"usd": 0.0038, "type": "per_result", "per": 1}, {"limit": 10}) == 38_000
assert cost_at({"usd": 0.0044, "type": "per_result", "per": 25}, {"limit": 10}) == 110_000, "lusha: 1 credit per 25 rows, minimum 1"
assert cost_at({"usd": 0.0044, "type": "per_result", "per": 25}, {"limit": 40}) == 220_000
assert cost_at({"usd": 0.005, "type": "per_call"}, {"limit": 10}) == 5_000
assert cost_at({"usd": None}, {}) is None
ep = lambda i, t="per_success": {"id": i, "provider": i.split(".")[0], "cost": {"type": t}}
a = Candidate(ep("a.x"), None, ("domain",), "platform", 24_500, hit_rate=0.4, ok_rate=None, p50_ms=100, last_ok_days=1)
b = Candidate(ep("b.x", "per_call"), None, ("domain",), "platform", 20_000, hit_rate=0.8, ok_rate=None, p50_ms=100, last_ok_days=1)
own = Candidate(ep("c.x"), None, ("domain",), "credential", 0, hit_rate=None, ok_rate=None, p50_ms=None, last_ok_days=None)
anonymous = Candidate(ep("d.x"), None, ("domain",), "anonymous", 0, hit_rate=None, ok_rate=None, p50_ms=None, last_ok_days=None)
assert a.expected_cost_per_hit == pytest.approx(24_500), "per-success: billed only on a hit → price per hit"
assert b.expected_cost_per_hit == pytest.approx(25_000), "per-call at 80% hit rate: 20000/0.8"
assert [c.endpoint["id"] for c in rank([a, b, anonymous, own])] == ["c.x", "d.x", "a.x", "b.x"]
assert [c.endpoint["id"] for c in rank([a, b], prefer=["b"])] == ["b.x", "a.x"]
assert [c.endpoint["id"] for c in rank([a, b], exclude=["a"])] == ["b.x"]
a.exhausted = True
assert [c.endpoint["id"] for c in rank([a, b])] == ["b.x"]
async def test_concurrent_routed_plans_share_one_cached_observation_refresh(
clients: AsyncClient, enrichment_on, monkeypatch,
):
from treg.domain.catalog import stats
class Source:
calls = 0
async def get_many(self, endpoint_ids):
self.calls += 1
return {
endpoint_id: {
"samples": 20, "ok_rate": 1.0, "p50_ms": 20, "p95_ms": 40,
"last_ok_days": 0, "hit_rate": 0.5, "hit_samples": 20,
}
for endpoint_id in endpoint_ids
}
async def request_time_aggregate(*args, **kwargs):
raise AssertionError("routed planning must not aggregate CallRecord on the request path")
source = Source()
reader = CachedEndpointObservationReader(source)
monkeypatch.setattr(call_route, "_endpoint_observation_reader", reader, raising=False)
monkeypatch.setattr(stats, "observed", request_time_aggregate)
ep = catalog_store.load().by_id[ROUTED]
class _Org:
id = 1
class _Caller:
org_id = 1
org = _Org()
try:
plans = await asyncio.gather(*(
call_route.build_plan(
ep, {"full_name": "Patrick Collison", "domain": "stripe.com"},
_Caller(), call_route.RouteOptions.from_headers(lambda key: None),
)
for _ in range(20)
))
await reader.wait_for_idle()
finally:
await reader.aclose()
assert all(plan.candidates for plan in plans)
assert source.calls == 1
async def test_routed_plan_keeps_per_success_hit_fallback_from_the_cache(
clients: AsyncClient, enrichment_on, monkeypatch,
):
from treg import api as A
from treg.domain.catalog import stats
endpoint_id = "tomba.people.email.find"
async with session_maker() as db:
for cost_micro in (8_900, 8_900, 0):
db.add(CallRecord(
org_id=1, user_email="a@b.c", tool_name=endpoint_id, method="GET", path="/x",
status_code=200, endpoint_id=endpoint_id, cost_observed_micro=cost_micro, hit=None,
))
await db.commit()
monkeypatch.setattr(stats, "MIN_HIT_SAMPLES", 3)
reader = A.app.state.endpoint_observation_reader
assert await reader.get_many([endpoint_id]) == {}
await reader.wait_for_idle()
warm = await reader.get_many([endpoint_id])
assert warm[endpoint_id]["hit_rate"] == pytest.approx(2 / 3, abs=1e-3)
async def request_time_aggregate(*args, **kwargs):
raise AssertionError("the routed plan must use the warm observation cache")
monkeypatch.setattr(stats, "observed", request_time_aggregate)
monkeypatch.setattr(call_route, "_endpoint_observation_reader", reader, raising=False)
ep = catalog_store.load().by_id[ROUTED]
class _Org:
id = 1
class _Caller:
org_id = 1
org = _Org()
plan = await call_route.build_plan(
ep, {"full_name": "Patrick Collison", "domain": "stripe.com"},
_Caller(), call_route.RouteOptions.from_headers(lambda key: None),
)
tomba = next(candidate for candidate in plan.candidates if candidate.endpoint["id"] == endpoint_id)
assert tomba.hit_rate == pytest.approx(2 / 3, abs=1e-3)
# ---- the call path ---------------------------------------------------------------------------
@pytest.mark.parametrize("routed", [False, True])
@pytest.mark.parametrize("verdict", ["valid", "invalid"])
async def test_tomba_verification_keeps_email_in_query_and_settles(
clients: AsyncClient, enrichment_on, monkeypatch, routed, verdict,
):
email = "person+tag@example.com"
payload = {"data": {"email": {"status": verdict, "score": 99}}}
seen = []
async def relay(request, upstream_url, tool, secrets, client, drop_params=None, **kwargs):
seen.append(upstream_url)
assert upstream_url == "https://api.tomba.io/v1/email-verifier"
assert request.method == "GET"
assert dict(request.query_items) == {"email": email}
assert "email" not in (drop_params or ())
async def body():
yield json.dumps(payload).encode()
async def close():
pass
return UpstreamResponse(200, ((b"content-type", b"application/json"),), body(), close)
monkeypatch.setattr(call_service, "relay", relay)
before = await _balance(clients)
if routed:
response = await clients.post(
"/call/treg.people.email.verify", json={"email": email},
headers={"X-Treg-Route-Prefer": "tomba"},
)
else:
response = await clients.get("/call/tomba.people.email.verify", params={"email": email})
assert response.status_code == 200, response.text
assert len(seen) == 1
if routed:
doc = response.json()
assert doc["raw"] == payload
assert doc["output"] == {"valid": verdict == "valid", "status": verdict, "score": 99}
assert doc["_treg"]["served_by"] == "tomba.people.email.verify"
assert doc["_treg"]["outcome"] == "hit", "an invalid verdict is still a verification answer"
else:
assert response.json() == payload
assert int(response.headers["X-Treg-Cost-Micro"]) == 8_900
assert before - await _balance(clients) == 8_900
async with session_maker() as db:
assert (await db.execute(select(Hold))).scalars().all() == []
async def test_routed_call_runs_the_cheapest_child_and_returns_output_raw_and_provenance(clients: AsyncClient, enrichment_on, monkeypatch):
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [(200, {"data": {"email": "patrick@stripe.com", "score": 99, "first_name": "Patrick", "last_name": "Collison",
"verification": {"status": "valid"}}})]}, seen))
before = await _balance(clients)
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 200, r.text
d = r.json()
assert d["output"] == {"email": "patrick@stripe.com", "confidence": 0.99, "first_name": "Patrick", "last_name": "Collison", "verified": True}
assert d["raw"]["data"]["score"] == 99, "the winning provider's body, verbatim"
assert d["_treg"]["served_by"] == "tomba.people.email.find" and d["_treg"]["outcome"] == "hit"
assert "advice" not in d["_treg"], "the provider vouched for the mailbox — nothing to add"
assert r.headers["X-Treg-Served-By"] == "tomba.people.email.find" and r.headers["X-Treg-Providers-Tried"] == "tomba"
assert seen == [("tomba", "GET", {"domain": "stripe.com", "full_name": "Patrick Collison"}, None)]
charged = int(r.headers["X-Treg-Cost-Micro"])
assert charged == 8_900 and before - await _balance(clients) == charged, "tomba's price, nothing else"
assert d["_treg"]["charged_micro"] == charged
async with session_maker() as db:
assert (await db.execute(select(Hold))).scalars().all() == []
entries = (await db.execute(select(LedgerEntry))).scalars().all()
assert {e.call_id for e in entries if e.kind == "settle"} == {r.headers["X-Treg-Call-Id"] + ":r0"}
await audit.drain()
rows = (await clients.get("/calls")).json()
kinds = {(x["tool_name"], x.get("credential_tier")) for x in rows}
assert (ROUTED, "routed") in kinds and ("tomba.people.email.find", "platform") in kinds
async def test_an_unverified_hit_carries_verify_advice(clients: AsyncClient, enrichment_on, monkeypatch):
"""A found address the provider did not vouch for (Tomba's verification status is not `valid`
— the catch-all shape that bounced for a recruiting team on 2026-09-06) is still a HIT and
still billed, but the answer says so in `_treg.advice` and points at the verify endpoint. A
suggestion, not a chained call: the balance moves by the find alone."""
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [(200, {"data": {"email": "alan@pruittstructures.com", "score": 96,
"verification": {"status": "accept_all"}}})]}, []))
before = await _balance(clients)
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Alan Marquez", "domain": "pruittstructures.com"})
assert r.status_code == 200, r.text
d = r.json()
assert d["output"]["email"] == "alan@pruittstructures.com" and d["output"]["verified"] is False
assert d["_treg"]["outcome"] == "hit"
assert "treg.people.email.verify" in d["_treg"]["advice"]
assert before - await _balance(clients) == int(r.headers["X-Treg-Cost-Micro"]) == 8_900, "the find, nothing chained"
async def test_a_people_search_hit_always_carries_verify_advice(clients: AsyncClient, enrichment_on, monkeypatch):
"""Search rows are directory listings: a row's email is found, not confirmed deliverable. The
contract has no `verified` output, so the advice attaches to every hit — Hunter domain-search
rows with `verification: null` were 73 of one team's 79 bounces (2026-09-08). Still a
suggestion: one child call, the find's price, nothing chained."""
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"hunter": [(200, {"data": {"emails": [{"value": "info@royalfarms.com", "type": "generic", "confidence": 10,
"verification": {"date": None, "status": None}}]},
"meta": {"results": 1}})],
"*": [(200, {"persons": []})] * 12}, []))
before = await _balance(clients)
r = await clients.post("/call/treg.people.search", json={"company_domain": "royalfarms.com", "limit": 10})
assert r.status_code == 200, r.text
d = r.json()
assert d["_treg"]["served_by"] == "hunter.companies.emails" and d["_treg"]["outcome"] == "hit"
assert d["output"]["people"][0]["verification"]["status"] is None, "the row's own field, untouched"
assert "treg.people.email.verify" in d["_treg"]["advice"] and "directory" in d["_treg"]["advice"]
assert before - await _balance(clients) == int(r.headers["X-Treg-Cost-Micro"]), "the find, nothing chained"
async def test_error_on_the_first_child_falls_back_to_the_second(clients: AsyncClient, enrichment_on, monkeypatch):
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [(503, {"error": "down"})],
"findymail": [(200, {"contact": {"name": "Patrick Collison", "email": "patrick@stripe.com"}})]}, seen))
before = await _balance(clients)
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 200, r.text
d = r.json()
assert [t["outcome"] for t in d["_treg"]["tried"]] == ["error", "hit"]
assert d["_treg"]["served_by"] == "findymail.search.name" and d["output"]["email"] == "patrick@stripe.com"
assert r.headers["X-Treg-Providers-Tried"] == "tomba,findymail"
assert before - await _balance(clients) == 19_800, "the failed child released its hold; only findymail charged"
assert seen[1] == ("findymail", "POST", {}, {"name": "Patrick Collison", "domain": "stripe.com"})
async def test_child_capability_pin_refusal_falls_back_to_the_pinned_provider(
clients: AsyncClient, enrichment_on, monkeypatch,
):
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
pinned = await clients.post(
f"/orgs/{org_id}/pins",
json={"capability": "people.email.find", "provider": "hunter"},
)
assert pinned.status_code == 200, pinned.text
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"hunter": [(200, {"data": {
"email": "patrick@stripe.com", "score": 90,
"verification": {"status": "valid"},
}})],
}, seen))
response = await clients.post(
f"/call/{ROUTED}",
json={"full_name": "Patrick Collison", "domain": "stripe.com"},
headers={"X-Treg-Route-Prefer": "tomba,hunter"},
)
assert response.status_code == 200, response.text
doc = response.json()
assert doc["_treg"]["served_by"] == "hunter.people.email.find"
assert [attempt["outcome"] for attempt in doc["_treg"]["tried"]] == ["error", "hit"]
assert doc["_treg"]["tried"][0]["endpoint_id"] == "tomba.people.email.find"
assert [provider for provider, *_ in seen] == ["hunter"]
async def test_platform_vendor_401_falls_back_to_the_next_provider(
clients: AsyncClient, enrichment_on, monkeypatch,
):
findymail = catalog_store.load().by_id["findymail.search.name"]
monkeypatch.setitem(findymail, "cost", {**findymail["cost"], "type": "per_call"})
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"tomba": [(401, {"error": "invalid platform key"})],
"findymail": [(200, {"contact": {
"name": "Patrick Collison", "email": "patrick@stripe.com",
}})],
}, seen))
response = await clients.post(
f"/call/{ROUTED}",
json={"full_name": "Patrick Collison", "domain": "stripe.com"},
headers={
"X-Treg-Route-Prefer": "tomba,findymail",
"X-Treg-Route-Exclude": "hunter,leadmagic,leadsforge,aviato,fiber-ai",
},
)
assert response.status_code == 200, response.text
doc = response.json()
assert doc["_treg"]["served_by"] == "findymail.search.name"
assert [attempt["outcome"] for attempt in doc["_treg"]["tried"]] == ["error", "hit"]
assert [provider for provider, *_ in seen] == ["tomba", "findymail"]
async def test_routed_insufficient_balance_still_stops_before_fallback(
clients: AsyncClient, enrichment_on, monkeypatch,
):
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
await ledger.reserve(db, org_id, "drain routed balance", 1_000_000)
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"tomba": [(200, {"data": {"email": "should-not-run@example.com"}})],
}, seen))
response = await clients.post(
f"/call/{ROUTED}",
json={"full_name": "Patrick Collison", "domain": "stripe.com"},
headers={"X-Treg-Route-Prefer": "tomba,findymail"},
)
assert response.status_code == 402
assert response.json()["detail"]["error"] == "insufficient_balance"
assert seen == []
async def test_waterfall_is_on_by_default_can_be_turned_off_and_respects_max_cost(clients: AsyncClient, enrichment_on, monkeypatch):
miss_tomba = (200, {"data": {"email": None, "score": None, "verification": {"status": None}}})
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"tomba": [miss_tomba]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Nobody Here", "domain": "stripe.com"},
headers={"X-Treg-Route-Waterfall": "0"})
assert r.status_code == 200 and r.json()["_treg"]["outcome"] == "miss" and r.json()["output"]["email"] is None
assert r.headers["X-Treg-Route-Outcome"] == "miss" and len(seen) == 1, "waterfall off: stop at the first miss"
# waterfall (the default): miss → next cheapest → hit; skips a candidate that would breach the ceiling
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [miss_tomba], "findymail": [(200, {"contact": {"name": "N H", "email": None}})],
"hunter": [(200, {"data": {"email": "n@stripe.com", "score": 50, "verification": {"status": "valid"}}})]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Nobody Here", "domain": "stripe.com"},
headers={"X-Treg-Route-Max-Cost": "0.08"})
assert r.status_code == 200, r.text
tried = r.json()["_treg"]["tried"]
assert [t["outcome"] for t in tried] == ["miss", "miss", "hit"] and r.json()["_treg"]["served_by"] == "hunter.people.email.find"
assert [p for p, *_ in seen] == ["tomba", "findymail", "hunter"]
assert r.json()["_treg"]["charged_micro"] == 24_500, "misses on per-success providers are free; only the hit is billed"
assert [t["charged_micro"] for t in tried] == [0, 0, 24_500]
# a ceiling the third candidate would breach stops the waterfall there
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [miss_tomba], "findymail": [(200, {"contact": {"name": "N H", "email": None}})],
"hunter": [(200, {"data": {"email": None, "score": None}})]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Nobody Here", "domain": "stripe.com"},
headers={"X-Treg-Route-Max-Cost": "0.02"})
assert r.status_code == 200 and r.json()["_treg"]["outcome"] == "miss"
# free misses do not consume the ceiling, but hunter (2.45¢ > 2¢) and everything dearer is skipped
assert [p for p, *_ in seen] == ["tomba", "findymail"]
assert all(t["outcome"] in ("miss", "skipped") for t in r.json()["_treg"]["tried"]) and r.json()["_treg"]["charged_micro"] == 0
async def test_max_cost_below_the_cheapest_refuses_before_any_call(clients: AsyncClient, enrichment_on, monkeypatch):
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"tomba": [(200, {})]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "P C", "domain": "stripe.com"}, headers={"X-Treg-Route-Max-Cost": "0.001"})
assert r.status_code == 402 and r.json()["detail"]["error"] == "route_max_cost" and seen == []
async with session_maker() as db:
assert (await db.execute(select(Hold))).scalars().all() == []
async def test_identity_no_provider_accepts_is_422_naming_variants(clients: AsyncClient, enrichment_on):
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison"})
assert r.status_code == 422 and r.json()["detail"]["error"] == "identity_incomplete"
assert ["domain", "full_name"] in r.json()["detail"]["variants"]
async def test_caller_fault_on_a_child_stops_and_own_key_ranks_first(clients: AsyncClient, enrichment_on, monkeypatch):
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"hunter": [(200, {"data": {"email": "p@stripe.com", "score": 90, "verification": {"status": "valid"}}})]}, seen))
await clients.post("/secrets", json={"name": "hunter", "value": "MY-HUNTER-KEY"}) # tier 2 for hunter
before = await _balance(clients)
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 200 and r.json()["_treg"]["served_by"] == "hunter.people.email.find"
assert r.json()["_treg"]["tier"] == "credential" and await _balance(clients) == before, "own key: first, and free"
# a vendor 4xx on the child goes on ONLY to providers that bill nothing for a rejected request
# (per_success / free): tomba is per_success, so it is asked; when it rejects too, the caller gets
# route_caller_fault naming both — and no paid-per-call provider was ever asked.
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"hunter": [(400, {"errors": [{"details": "bad"}]})], "tomba": [(400, {"error": "bad"})], "*": [(400, {"error": "bad"})]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 400 and r.json()["detail"]["error"] == "route_caller_fault", r.text
assert [p for p, *_ in seen][:2] == ["hunter", "tomba"] and len(seen) == 3, "at most two fallbacks, then the 4xx is the caller's"
outcomes = {t["endpoint_id"]: t["outcome"] for t in r.json()["detail"]["tried"]}
assert outcomes["hunter.people.email.find"] == "error" and outcomes["tomba.people.email.find"] == "error"
# a scraper's "please retry" 400 (tikhub, live 2026-08-28) is why: the next free-on-failure provider answers
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"hunter": [(400, {"errors": [{"details": "bad"}]})],
"tomba": [(200, {"data": {"email": "p@stripe.com", "score": 90, "verification": {"status": "valid"}}})]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 200 and r.json()["_treg"]["served_by"] == "tomba.people.email.find", r.text
async def test_a_2xx_without_the_required_core_is_a_miss_not_a_hit(clients: AsyncClient, enrichment_on, monkeypatch):
"""A 200 whose body lacks the contract's required field (a null result under a success envelope)
is a MISS: the waterfall goes on, and the verdict/hit-rate never counts it as answered."""
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"hunter": [(200, {"data": {"email": None, "score": None}})],
"tomba": [(200, {"data": {"email": "p@stripe.com", "score": 90, "verification": {"status": "valid"}}})]}, seen))
await clients.post("/secrets", json={"name": "hunter", "value": "MY-HUNTER-KEY"})
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 200, r.text
outcomes = {t["endpoint_id"]: t["outcome"] for t in r.json()["_treg"]["tried"]}
assert outcomes["hunter.people.email.find"] == "miss" and r.json()["_treg"]["served_by"] == "tomba.people.email.find"
async def test_catalog_get_on_the_routed_endpoint_shows_the_plan(clients: AsyncClient, enrichment_on):
r = await clients.get(f"/catalog/endpoints/{ROUTED}")
assert r.status_code == 200, r.text
d = r.json()
assert d["endpoint"]["kind"] == "routed" and d["routing"]["contract"]["identity"]
# the same job from unrouted providers is named here too — the search page points at this row
also = {a["endpoint_id"] for a in d["routing"]["also"]}
assert also.isdisjoint(d["endpoint"]["routed_children"]) and all(i.endswith("email.find") or "." in i for i in also)
plan = d["routing"]["plan"]
assert plan and plan[0]["usd"] <= plan[-1]["usd"] and plan[0]["accepts"]
assert "hit_rate" not in plan[0], "unmeasured says nothing rather than nulls"
assert {c["endpoint_id"] for c in plan} <= set(d["endpoint"]["routed_children"] if "routed_children" in d["endpoint"] else [c["endpoint_id"] for c in plan])
async def test_idempotent_replay_of_a_routed_call_never_calls_a_provider_twice(clients: AsyncClient, enrichment_on, monkeypatch):
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [(200, {"data": {"email": "p@stripe.com", "score": 90, "verification": {"status": "valid"}}})]}, seen))
h = {"Idempotency-Key": "route-1"}
r1 = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"}, headers=h)
r2 = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"}, headers=h)
assert r1.status_code == 200 and r2.status_code == 200 and r2.headers.get("X-Treg-Idempotent-Replay") == "true"
assert r2.json() == r1.json() and len(seen) == 1
@pytest.mark.parametrize(
("terminal", "expected_status", "expected_error"),
[
((400, {"message": "invalid email"}), 400, "route_caller_fault"),
((503, {"message": "provider down"}), 502, "route_failed"),
],
)
async def test_idempotent_replay_preserves_a_routed_failure_after_partial_charge(
clients: AsyncClient, enrichment_on, monkeypatch, terminal, expected_status, expected_error,
):
routed = "treg.people.email.verify"
tomba_miss = (200, {"data": {"email": {"status": None, "score": None}}})
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [tomba_miss, tomba_miss], "leadmagic": [terminal, terminal]},
seen,
))
headers = {
"Idempotency-Key": "route-partially-charged-failure",
"X-Treg-Route-Prefer": "tomba,leadmagic",
"X-Treg-Route-Exclude": "hunter",
}
before = await _balance(clients)
r1 = await clients.post(f"/call/{routed}", json={"email": "bad@example.com"}, headers=headers)
r2 = await clients.post(f"/call/{routed}", json={"email": "bad@example.com"}, headers=headers)
assert r1.status_code == expected_status and r1.json()["detail"]["error"] == expected_error
assert r2.status_code == r1.status_code and r2.json() == r1.json()
assert r2.headers.get("X-Treg-Idempotent-Replay") == "true"
assert r1.headers["X-Treg-Cost-Micro"] == r2.headers["X-Treg-Cost-Micro"] == "8900"
assert before - await _balance(clients) == 8_900
assert [provider for provider, *_ in seen] == ["tomba", "leadmagic"]
def test_a_per_success_miss_settles_at_zero_when_the_adapter_can_tell():
"""Live 2026-08-28: the first waterfall charged tomba, findymail and leadsforge for misses the
catalog calls free. The adapter's `miss` predicate is the missing knowledge."""
from test_marketplace_call import _mk
from treg.application.call import settle as A
miss_tomba = b'{"data": {"email": null, "score": null, "first_name": "Z", "verification": {"status": null}}}'
assert A._observed_cost_micro(_mk("tomba", endpoint_id="tomba.people.email.find", cost_type="per_success"), miss_tomba) == 0
hit_tomba = b'{"data": {"email": "z@x.io", "score": 90}}'
assert A._observed_cost_micro(_mk("tomba", endpoint_id="tomba.people.email.find", cost_type="per_success"), hit_tomba) is None, "a hit still settles at the estimate"
assert A._observed_cost_micro(_mk("findymail", endpoint_id="findymail.search.name", cost_type="per_success"), b'{"contact": {"email": null}}') == 0
assert A._observed_cost_micro(_mk("leadsforge", endpoint_id="leadsforge.people.email.find", cost_type="per_success"), b'{"email": null, "status": "failed"}') == 0
assert A._observed_cost_micro(_mk("leadsforge", endpoint_id="leadsforge.people.email.find", cost_type="per_call"), b'{"email": null}') is None, "per_call bills the call"
assert A._observed_cost_micro(_mk("tomba", endpoint_id="tomba.companies.emails.count", cost_type="per_success"), b'{"data": {}}') is None, "no adapter → no opinion"
async def test_discovery_puts_the_routed_parent_first_and_its_children_under_it(clients: AsyncClient):
r = await clients.get("/catalog/search", params={"q": "find work email"})
rows = r.json()["results"]
ids = [x["id"] for x in rows]
parent = ids.index(ROUTED)
kids = [i for i, x in enumerate(rows) if x["capability"] == "people.email.find" and x["id"] != ROUTED]
assert kids and parent < min(kids), "the routed parent leads its capability group"
assert kids == list(range(parent + 1, parent + 1 + len(kids))), "children sit right under the parent"
assert rows[parent]["routed_children"] and any("ROUTED" in h for h in r.json()["hints"])
p = await clients.get("/catalog/platforms/people")
group = next(c for c in p.json()["capabilities"] if c["id"] == "people.email.find")
assert group["endpoints"][0]["id"] == ROUTED
from treg.domain.catalog.store import group_routed
plain = [{"id": "a", "capability": "x", "kind": "data"}, {"id": "b", "capability": "y", "kind": "data"}]
assert group_routed(plain) == plain, "no routed row → order untouched"
async def test_hit_verdict_is_recorded_and_becomes_a_hit_rate(clients: AsyncClient, enrichment_on, monkeypatch):
from treg.domain.catalog import stats
from treg.models import CallRecord
hit = (200, {"data": {"email": "p@stripe.com", "score": 90, "verification": {"status": "valid"}}})
miss = (200, {"data": {"email": None, "score": None, "verification": {"status": None}}})
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"tomba": [hit, miss, hit]}, seen))
for _ in range(3):
assert (await clients.get("/call/tomba.people.email.find?full_name=P%20C&domain=stripe.com")).status_code == 200
await audit.drain()
async with session_maker() as db:
rows = (await db.execute(select(CallRecord).where(CallRecord.endpoint_id == "tomba.people.email.find"))).scalars().all()
assert sorted(r.hit for r in rows) == [False, True, True], "the verdict, never the body"
# below the floor → None; the floor is about evidence, not a bug
assert (await stats.observed(db, ["tomba.people.email.find"]))["tomba.people.email.find"]["hit_rate"] is None
monkeypatch.setattr(stats, "MIN_HIT_SAMPLES", 3)
s = (await stats.observed(db, ["tomba.people.email.find"], per_success={"tomba.people.email.find"}))["tomba.people.email.find"]
assert s["hit_rate"] == pytest.approx(2 / 3, abs=1e-3) and s["hit_samples"] == 3
# historical rows without a verdict: a per-success 2xx with cost_observed 0 is a miss, > 0 a hit
for r in rows:
r.hit = None
r.cost_observed_micro = 8_900 if r.status_code == 200 and "x" else 0
rows[0].cost_observed_micro = 0
await db.commit()
s = (await stats.observed(db, ["tomba.people.email.find"], per_success={"tomba.people.email.find"}))["tomba.people.email.find"]
assert s["hit_samples"] == 3 and s["hit_rate"] == pytest.approx(2 / 3, abs=1e-3)
s = (await stats.observed(db, ["tomba.people.email.find"]))["tomba.people.email.find"]
assert s["hit_samples"] == 0, "the zero-cost fallback applies to per-success endpoints only"
# the plan reads it: with a measured hit rate the confidence flips from unmeasured to measured
monkeypatch.setattr(stats, "MIN_HIT_SAMPLES", 3)
# the catalog reads observations through the process cache: a cold entry answers nothing and
# refreshes in the background, so warm it the way test_endpoint_stats does
from treg import api as A
monkeypatch.setattr(call_route, "_endpoint_observation_reader", A.app.state.endpoint_observation_reader)
await clients.get(f"/catalog/endpoints/{ROUTED}")
await A.app.state.endpoint_observation_reader.wait_for_idle()
r = await clients.get(f"/catalog/endpoints/{ROUTED}")
tomba = next(c for c in r.json()["routing"]["plan"] if c["endpoint_id"] == "tomba.people.email.find")
assert tomba["hit_rate"] == pytest.approx(2 / 3, abs=1e-3) and tomba["usd_per_hit"] == pytest.approx(0.0089, abs=1e-4)
async def test_a_registered_tool_for_a_provider_ranks_first_and_is_free(clients: AsyncClient, enrichment_on, monkeypatch):
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"hunter": [(200, {"data": {"email": "p@stripe.com", "score": 90, "verification": {"status": "valid"}}})]}, seen))
sid = (await clients.post("/secrets", json={"name": "my-hunter", "value": "OWN-HUNTER"})).json()["id"]
r = await clients.post("/tools", json={"name": "our-hunter", "base_url": "https://api.hunter.io/v2", "secret_id": sid})
assert r.status_code == 200, r.text
before = await _balance(clients)
r = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert r.status_code == 200, r.text
assert r.json()["_treg"]["served_by"] == "hunter.people.email.find" and r.json()["_treg"]["tier"] == "tool"
assert await _balance(clients) == before and r.json()["_treg"]["charged_micro"] == 0
async def test_mcp_search_shows_the_routed_parent_first_with_its_children(clients: AsyncClient):
from treg import mcp as M
out = await M._catalog_search_impl("find work email", 12, surface=M._TEAM_SURFACE) if "surface" in M._catalog_search_impl.__code__.co_varnames else await M._catalog_search_impl("find work email", 12)
ids = [r["endpoint_id"] for r in out["results"]]
parent = ids.index(ROUTED)
kids = [i for i, r in enumerate(out["results"]) if r["provider"] != "treg" and r["endpoint_id"].split(".", 1)[1] in ("people.email.find", "people.email.find.linkedin", "search.name")]
assert kids and parent < min(kids)
assert out["results"][parent]["routed"].startswith("treg picks among")
def test_filters_reach_adapters_through_in_expr_and_array_bodies():
cat = catalog_store.load()
contract = cat.contracts["google.keywords.ideas"]
req, variant = canonical_identity(contract, {"keyword": "coffee"})
assert variant == ("keyword",) and req["country"] == "us" and req["limit"] == 20, "filter defaults ride with the identity"
req, _ = canonical_identity(contract, {"keyword": "coffee", "country": "GB", "limit": 5})
q, b = cat.adapters["dataforseo.google.keywords.ideas"].to_upstream(req)
assert b == [{"keyword": "coffee", "location_code": 2826, "language_code": "en", "limit": 5}], "task list body, GB → 2826"
q, b = cat.adapters["seranking.google.keywords.ideas"].to_upstream(req)
assert q == {"keyword": "coffee", "source": "uk", "limit": "5"}
q, b = cat.adapters["serpapi.google.keywords.ideas"].to_upstream(req)
assert q == {"q": "coffee", "gl": "gb", "hl": "en", "engine": "google_autocomplete"}
q, b = cat.adapters["tomba.people.email.verify"].to_upstream({"email": "a@b.io"})
assert q == {"email": "a@b.io"}, "Tomba verification requires the email query parameter"
assert cat.by_id["tomba.people.email.verify"]["path"] == "/v1/email-verifier"
assert cost_at({"usd": 0.00179, "type": "per_result", "per": 1}, req) == 8_950, "priced at the requested limit"
ep = cat.by_id["treg.google.keywords.ideas"]
assert ep["input"]["body"]["country"]["note"].startswith("filter — default 'us'")
async def test_a_keyless_provider_is_dropped_at_planning_not_failed_at_call_time(clients: AsyncClient, enrichment_on, monkeypatch):
"""Live 2026-08-28: exa is platform-eligible but this deployment held no exa key; the child's
'no credential' 404 aborted the routed call. Planning must drop it and name why."""
from treg.application.call.route import RouteOptions, build_plan
cat = catalog_store.load()
ep = cat.by_id["treg.people.email.find"]
monkeypatch.setenv("TREG_PLATFORM_KEY_AVIATO", "") # aviato stays eligible, but keyless
get_settings.cache_clear()
class _Org: id = 1
class _Caller: org_id = 1; org = _Org()
plan = await build_plan(ep, {"linkedin_url": "https://www.linkedin.com/in/x"}, _Caller(), RouteOptions.from_headers(lambda k: None))
assert "aviato.people.email.find" not in [c.endpoint["id"] for c in plan.candidates]
assert any(d["endpoint_id"] == "aviato.people.email.find" and "no aviato key" in d["why"] for d in plan.dropped)
def test_a_contract_may_set_its_own_default_ceiling():
from treg.application.call.route import RouteOptions, DEFAULT_MAX_COST_MICRO
cat = catalog_store.load()
assert cat.contracts["people.search"].default_max_cost_usd is None, "the $1 default covers every current ladder"
assert RouteOptions.from_headers(lambda k: None, 500_000).max_cost_micro == 500_000
assert RouteOptions.from_headers(lambda k: None).max_cost_micro == DEFAULT_MAX_COST_MICRO
assert RouteOptions.from_headers(lambda k: "0.02" if k == "x-treg-route-max-cost" else None, 500_000).max_cost_micro == 20_000
async def test_routed_call_and_access_name_the_providers_dropped_for_this_deployment(clients: AsyncClient, enrichment_on, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_AVIATO", "")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [(200, {"data": {"email": None, "score": None, "verification": {"status": None}}})],
"findymail": [(200, {"contact": {"name": "x", "email": None}})],
"leadsforge": [(200, {"email": None, "status": "failed"})]}, seen))
r = await clients.post(f"/call/{ROUTED}", json={"linkedin_url": "https://www.linkedin.com/in/x"}, headers={"X-Treg-Route-Max-Cost": "0.03"})
assert r.status_code == 200 and r.json()["_treg"]["outcome"] == "miss"
dropped = r.json()["_treg"]["dropped"]
assert any(d["endpoint_id"] == "aviato.people.email.find" and "no aviato key" in d["why"] for d in dropped)
hunter = next(d for d in dropped if d["endpoint_id"] == "hunter.people.email.find")
assert hunter == {"endpoint_id": "hunter.people.email.find", "why": "needs {domain, full_name} | {domain, first_name, last_name}"}
a = await clients.get(f"/catalog/endpoints/{ROUTED}/access")
assert a.status_code == 200 and a.json()["tier"] == "routed" and a.json()["detail"].startswith("routed — ")
assert "aviato.people.email.find" in a.json()["detail"]
def test_a_caller_may_send_everything_it_knows_and_each_provider_gets_only_its_variant():
cat = catalog_store.load()
contract = cat.contracts["people.phone.find"]
everything = {"email": "p@stripe.com", "linkedin_url": "https://www.linkedin.com/in/p", "full_name": "Patrick Collison", "domain": "stripe.com"}
ident, variant = canonical_identity(contract, everything)
assert ident["first_name"] == "Patrick"
from treg.domain.catalog.routing.contracts import adapter_accepts
tomba = cat.adapters["tomba.people.phone.find"]
v = adapter_accepts(tomba, ident)
q, b = tomba.to_upstream(ident, v)
assert q == {"email": "p@stripe.com"}, "tomba insists on exactly one identifier — only the matched variant is sent"
lf = cat.adapters["leadsforge.people.phone.find"]
q, b = lf.to_upstream(ident, adapter_accepts(lf, ident))
assert b == {"firstName": "Patrick", "lastName": "Collison", "companyDomain": "stripe.com"}, "derived names, and not the LinkedIn URL"
# filters always travel, whatever the variant
kw = cat.contracts["google.keywords.ideas"]
req, v = canonical_identity(kw, {"keyword": "coffee", "country": "de"})
q, b = cat.adapters["seranking.google.keywords.ideas"].to_upstream(req, v)
assert q == {"keyword": "coffee", "source": "de", "limit": "20"}
# every adapter still verifies with the change
assert all(a.verified for a in cat.adapters.values())
def test_rank_prefers_the_candidate_that_uses_more_of_the_identity():
"""Given {company_domain, title}, a title-aware provider outranks a cheaper domain-only one —
the cheaper answer would be to a different question (the whole company)."""
from treg.domain.catalog.routing.plan import Candidate, rank
def cand(eid, variant, price):
return Candidate(endpoint={"id": eid, "provider": eid.split(".")[0], "cost": {"type": "per_result"}}, adapter=None,
variant=variant, tier="platform", price_micro=price, hit_rate=None, ok_rate=None, p50_ms=None, last_ok_days=None)
free_domain = cand("hunter.x.multi-domain-search", ("company_domain",), 0)
title_aware = cand("icypeas.people.search", ("company_domain", "title"), 380)
dearer_title = cand("companyenrich.people.search", ("company_domain", "title"), 19_600)
given = {"company_domain", "title"}
assert [c.endpoint["id"] for c in rank([free_domain, dearer_title, title_aware], given=given)] == [
"icypeas.people.search", "companyenrich.people.search", "hunter.x.multi-domain-search"]
# a key the caller did NOT send (reached via derive) earns nothing: price decides again
assert rank([free_domain, title_aware], given={"company_domain"})[0] is free_domain
# …but a variant DERIVED from what the caller sent covers it: {first,last,domain} from a supplied
# full_name is as specific as {full_name, domain}, so the cheaper of the two (hunter) leads
derive = {"first_name": "split_first(full_name)", "last_name": "split_last(full_name)"}
hunter = cand("hunter.people.email.find", ("first_name", "last_name", "domain"), 4_900)
apollo = cand("apollo.people.enrich", ("full_name", "domain"), 26_000)
assert rank([apollo, hunter], given={"full_name", "domain"}, derive=derive)[0] is hunter
def test_a_provider_that_cannot_express_a_supplied_filter_ranks_last_among_equals():
"""Live 2026-08-29: `{q, title, location: London, country: GB}` went to the cheapest candidate,
which had no place for either geo filter, and returned people in Bengaluru and San Francisco —
reported as a hit. Cheapness must not buy an answer to a looser question."""
from treg.domain.catalog.routing.plan import Candidate, ignored_filters, rank
def cand(eid, price, ignored=()):
return Candidate(endpoint={"id": eid, "provider": eid.split(".")[0], "cost": {"type": "per_result"}},
adapter=None, variant=("q",), tier="platform", price_micro=price, hit_rate=None,
ok_rate=None, p50_ms=None, last_ok_days=None, ignored=ignored)
geo_blind = cand("aviato.people.search", 2_500, ignored=("country", "location"))
geo_aware = cand("icypeas.people.search", 5_700)
assert [c.endpoint["id"] for c in rank([geo_blind, geo_aware], given={"q"})] == [
"icypeas.people.search", "aviato.people.search"], "the dearer provider that honours the filters leads"
# still reachable when it is the only candidate, and price still decides among equals
assert rank([geo_blind], given={"q"})[0] is geo_blind
assert rank([geo_blind, cand("z.people.search", 9_000, ignored=("country", "location"))], given={"q"})[0] is geo_blind
# and the set itself is read off the adapter's input map, not guessed
cat = catalog_store.load()
contract = cat.contracts["people.search"]
ident = {"q": "backend engineers", "country": "GB", "location": "London, United Kingdom", "limit": 15}
assert "country" in ignored_filters(cat.adapters["aviato.people.search"], contract, ident)
assert ignored_filters(cat.adapters["icypeas.people.search"], contract, ident) == (), \
"icypeas is the only people.search adapter that maps geo — the rule must float it to the top"
# the full_name variant has exactly two candidates and neither mapped `country` — so a GT search
# went to New York and was billed (voice-ai-outbound, 2026-09-03). aviato's simple search takes
# country NAMES (live 2026-09-04: `Guatemala` → 84,145 rows, `GT` → 0), hence country_name().
simple = cat.adapters["aviato.people.search.simple"]
by_name, _ = canonical_identity(contract, {"full_name": "Carlos Lopez", "country": "GT", "limit": 5})
assert ignored_filters(simple, contract, by_name) == ()
q, _ = simple.to_upstream(by_name, ("full_name",))
assert q["country"] == "Guatemala", q # a query value travels as one string, never a list repr
assert "country" not in simple.to_upstream({**by_name, "country": None}, ("full_name",))[0]
async def test_the_geo_aware_child_wins_a_filtered_search_and_the_answer_says_what_was_dropped(
clients: AsyncClient, enrichment_on, monkeypatch
):
"""End to end on the real people.search ladder: the caller sends geo, so the child that maps it
is called even though a CHEAPER one is callable — and when the winner drops a filter, the
envelope and a header say so, where a caller will see it (not buried in `tried[]`).
Live 2026-08-29 this went to aviato ($0.0025, maps neither `country` nor `location`) and came
back with people in Bengaluru and San Francisco for a London brief, reported as a hit."""
monkeypatch.setenv("TREG_PLATFORM_KEY_ICYPEAS", "PLATFORM-ICYPEAS-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,icypeas")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, {"leads": [{"firstname": "Aleksei", "lastname": "Strizhak", "lastJobTitle": "Senior Backend Engineer",
"address": "London Area, United Kingdom", "profileUrl": "https://linkedin.com/in/as"}]})],
"*": [(200, {"persons": []})] * 12}, seen))
r = await clients.post("/call/treg.people.search",
json={"q": "backend engineer", "title": "Backend Engineer",
"location": "London, United Kingdom", "country": "GB", "limit": 15})
assert r.status_code == 200, r.text
d = r.json()
assert d["_treg"]["served_by"] == "icypeas.people.search", \
"the child that maps country/location leads, though the cheaper geo-blind aviato is callable"
assert seen == [("icypeas", "POST", {}, {"query": {"currentJobTitle": {"include": ["Backend Engineer"]},
"location": {"include": ["London, United Kingdom"]}},
"pagination": {"size": 15}})], \
"one call, and the geography actually reached the provider"
assert "ignored_filters" not in d["_treg"] and "X-Treg-Ignored-Filters" not in r.headers
# …and when the winner cannot express a filter, the answer says which — envelope, header, attempt
seen2 = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"*": [(200, {"items": [{"fullName": "Ada L", "URLs": {"linkedin": "linkedin.com/in/al"}}], "count": {"value": 1}})] * 12}, seen2))
r2 = await clients.post("/call/treg.people.search", json={"q": "backend engineer", "country": "GB", "limit": 15})
assert r2.status_code == 200, r2.text
d2 = r2.json()
assert d2["_treg"]["served_by"] == "aviato.people.search", "no geo-aware child answers a {q}-only brief here"
assert d2["_treg"]["ignored_filters"] == ["country"], "the caller sent it; aviato has no place for it"
assert r2.headers["X-Treg-Ignored-Filters"] == "country"
assert d2["_treg"]["tried"][-1]["ignored_filters"] == ["country"], "same set in all three places"
async def test_routed_discovery_is_a_runtime_switch(clients: AsyncClient, enrichment_on, monkeypatch):
"""`TREG_ROUTED_DISCOVERY=off` stops search LEADING with `treg.<capability>` — nothing else.
The endpoints stay callable, priced and reachable by id; only the steering goes away, so a
deployment can answer "should every agent be pointed at the router by default" with traffic
instead of an argument, and can undo it without a redeploy."""
q = {"q": "find someone's work email from their name and company", "limit": 8}
on = (await clients.get("/catalog/search", params=q)).json()
ids_on = [r["id"] for r in on["results"]]
assert any(i.startswith("treg.") for i in ids_on), "steering on: the routed row is in the page"
monkeypatch.setenv("TREG_ROUTED_DISCOVERY", "off")
get_settings.cache_clear()
off = (await clients.get("/catalog/search", params=q)).json()
ids_off = [r["id"] for r in off["results"]]
assert not any(i.startswith("treg.") for i in ids_off), \
"steering off: search looks as it did before routing shipped"
assert ids_off, "and it still returns the providers themselves"
# every OTHER discovery surface follows the same switch, or the deployment contradicts itself
plat = (await clients.get("/catalog/platforms/people")).json()
flat = json.dumps(plat)
assert "treg.people." not in flat, "browse view: no routed row while steering is off"
for path in ("/skill.md", "/llms.txt"):
body = (await clients.get(path)).text
assert "Routed endpoints" not in body, f"{path} must not teach what search hides"
assert "<!--routed" not in body, f"{path} leaked a marker"
assert "provider_capacity_unavailable" in body, f"{path} lost the unrelated overflow guidance"
# …but the endpoint is untouched: still callable, still priced, still found by id
r = await clients.get("/catalog/endpoints/treg.people.email.find")
assert r.status_code == 200 and r.json()["endpoint"]["kind"] == "routed"
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"tomba": [(200, {"data": {"email": "p@stripe.com", "score": 99, "first_name": "Patrick",
"last_name": "Collison", "verification": {"status": "valid"}}})]}, []))
call = await clients.post(f"/call/{ROUTED}", json={"full_name": "Patrick Collison", "domain": "stripe.com"})
assert call.status_code == 200 and call.json()["_treg"]["served_by"] == "tomba.people.email.find"
get_settings.cache_clear()
def test_keywords_are_a_filter_so_they_reach_every_provider_that_can_express_them():
"""The brief's SUBSTANCE lives in its keywords. As identity they would be dropped whenever
another variant matched — icypeas matches {title}, so a `q` carrying "microservices" never
reached it and the search degenerated to title+location (bench 2026-08-29: "football scouting
analysts" reached the provider as title="Football Analyst" and scored 0 qualified of 15)."""
cat = catalog_store.load()
contract = cat.contracts["people.search"]
assert "keywords" in contract.filters and "keywords" not in {k for v in contract.identity for k in v}
ident, variant = canonical_identity(contract, {
"q": "backend developers with microservices", "title": "Backend Engineer",
"location": "London, United Kingdom", "country": "GB",
"keywords": ["microservices", "architecture"], "limit": 15})
from treg.domain.catalog.routing.contracts import adapter_accepts
icy = cat.adapters["icypeas.people.search"]
_, body = icy.to_upstream(ident, adapter_accepts(icy, ident))
assert body["query"]["keyword"]["include"] == ["microservices", "architecture"], \
"icypeas takes them natively — the whole point of the contract field"
exa = cat.adapters["exa.people.search"]
_, body = exa.to_upstream(ident, adapter_accepts(exa, ident))
assert "microservices" in body["query"], "a semantic provider gets them folded into the query"
# and a provider with nowhere to put them says so, which ranks it down (PR #254)
from treg.domain.catalog.routing.plan import ignored_filters
assert "keywords" in ignored_filters(cat.adapters["aviato.people.search"], contract, ident)
async def test_a_thin_hit_does_not_end_the_waterfall_when_the_caller_set_min_results(
clients: AsyncClient, enrichment_on, monkeypatch
):
"""`X-Treg-Route-Min-Results: 3` — one row is not an answer to "find me candidates". The
router keeps going and the FULLEST answer wins; without it the first non-empty body stops the
search (bench 2026-08-29: the hand-written policy's `if len(rows) < 3 -> fall through` was the
single behaviour the routed path could not express)."""
monkeypatch.setenv("TREG_PLATFORM_KEY_ICYPEAS", "PLATFORM-ICYPEAS-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,icypeas")
get_settings.cache_clear()
thin = {"leads": [{"firstname": "Solo", "lastname": "Row", "profileUrl": "https://linkedin.com/in/s"}]}
full = {"items": [{"fullName": f"P{i}", "URLs": {"linkedin": f"linkedin.com/in/p{i}"}} for i in range(9)],
"count": {"value": 9}}
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, thin)], "*": [(200, full)] * 12}, seen))
body = {"q": "backend engineer", "title": "Backend Engineer",
"location": "London, United Kingdom", "country": "GB", "limit": 15}
r = await clients.post("/call/treg.people.search", json=body, headers={"X-Treg-Route-Min-Results": "3"})
assert r.status_code == 200, r.text
d = r.json()
assert [t["outcome"] for t in d["_treg"]["tried"]][0] == "weak", "1 row < 3 is not an answer"
assert d["_treg"]["served_by"] != "icypeas.people.search" and len(d["output"]["people"]) == 9
# default (min_results 1): the same thin answer ends the search, as before
seen2 = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, thin)], "*": [(200, full)] * 12}, seen2))
r2 = await clients.post("/call/treg.people.search", json=body)
assert r2.json()["_treg"]["served_by"] == "icypeas.people.search" and len(seen2) == 1
# and when NOBODY clears the bar, the fullest weak answer is still returned, not a miss
seen3 = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, thin)], "*": [(200, {"items": [{"fullName": "Two"}, {"fullName": "Rows"}],
"count": {"value": 2}})] * 12}, seen3))
r3 = await clients.post("/call/treg.people.search", json=body, headers={"X-Treg-Route-Min-Results": "5"})
assert r3.status_code == 200 and len(r3.json()["output"]["people"]) == 2, "best effort beats nothing"
async def test_the_weak_hit_fallback_is_bounded_like_the_error_fallback(
clients: AsyncClient, enrichment_on, monkeypatch
):
"""Some briefs HAVE only one right answer ("who runs engineering at X"), so no provider ever
clears min_results and an unbounded rule pays the whole ladder on every call. Measured on the
bench's deterministic set: 12.7x ($1.76 -> $22.35 over 28 queries) for answers already correct.
At most MAX_WEAK_FALLBACKS extra providers are asked, then the fullest answer is returned."""
monkeypatch.setenv("TREG_PLATFORM_KEY_ICYPEAS", "PLATFORM-ICYPEAS-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,icypeas")
get_settings.cache_clear()
# Every provider answers ONE row in ITS OWN shape — a real thin HIT, not a miss (a miss does
# not consume the bound, and must not: the bound is about paying for thin answers).
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, {"leads": [{"firstname": "One", "lastname": "Row"}], "total": 1})] * 4,
"leadsforge": [(200, {"leads": [{"firstName": "One", "lastName": "Row"}]})] * 4,
"leadmagic": [(200, {"data": [{"full_name": "One Row"}], "total_count": 1})] * 4,
"hunter": [(200, {"data": {"emails": [{"value": "one@stripe.com"}]}, "meta": {"results": 1}})] * 4,
"*": [(200, {"items": [{"fullName": "One Row"}], "count": {"value": 1}})] * 8}, seen))
# {company_domain} has the deepest ladder, so the BOUND is what stops this, not running out
body = {"company_domain": "stripe.com", "limit": 15}
r = await clients.post("/call/treg.people.search", json=body,
headers={"X-Treg-Route-Min-Results": "5"}) # nothing will ever clear 5
assert r.status_code == 200, r.text
d = r.json()
attempts = [t for t in d["_treg"]["tried"] if t["outcome"] == "weak"]
assert len(attempts) == call_route.MAX_WEAK_FALLBACKS + 1, \
f"the first ask plus at most {call_route.MAX_WEAK_FALLBACKS} more, not the whole ladder"
assert len(seen) == len(attempts), "and no provider beyond the bound was ever called"
assert len(d["output"]["people"]) == 1, "and the caller still gets the answer that exists"
async def test_merge_unions_the_rows_the_caller_already_paid_for(
clients: AsyncClient, enrichment_on, monkeypatch
):
"""`X-Treg-Route-Merge: 1` — a list answer is the one shape a union makes sense for, and the
caller is charged for EVERY attempt already (`charged_micro` sums them), so returning only the
winner's rows throws away results the team bought. Bench 2026-08-29: a people.search that fell
through returned the fullest single provider's rows, never icypeas' 5 plus exa's 10."""
monkeypatch.setenv("TREG_PLATFORM_KEY_ICYPEAS", "PLATFORM-ICYPEAS-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,icypeas")
get_settings.cache_clear()
icy = {"leads": [{"firstname": "Ada", "lastname": "L", "profileUrl": "https://linkedin.com/in/ada"},
{"firstname": "Bo", "lastname": "M", "profileUrl": "https://linkedin.com/in/bo"}], "total": 2}
# one row OVERLAPS on the profile url (different casing/scheme), one is new
other = {"items": [{"fullName": "Ada L", "URLs": {"linkedin": "www.linkedin.com/in/Ada/"}},
{"fullName": "Cy N", "URLs": {"linkedin": "linkedin.com/in/cy"}}], "count": {"value": 2}}
body = {"q": "backend engineer", "title": "Backend Engineer", "limit": 15}
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, icy)], "*": [(200, other)] * 8}, seen))
r = await clients.post("/call/treg.people.search", json=body,
headers={"X-Treg-Route-Min-Results": "5", "X-Treg-Route-Merge": "1"})
assert r.status_code == 200, r.text
d = r.json()
people = d["output"]["people"]
keys = sorted(call_route._row_key(p) for p in people)
assert keys == ["linkedin.com/in/ada", "linkedin.com/in/bo", "linkedin.com/in/cy"], \
"the union of both providers, and Ada — who both returned, spelled differently — appears ONCE"
assert len(people) == 3, "3 distinct people from two answers of 2 rows each"
assert len(d["_treg"]["merged_from"]) >= 2 and "X-Treg-Merged-From" in r.headers
assert d["_treg"]["charged_micro"] == int(r.headers["X-Treg-Cost-Micro"]), \
"merging changes no money: the sum over attempts is what it always was"
# without the header the winner's rows alone come back — the pre-existing contract
seen2 = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"icypeas": [(200, icy)], "*": [(200, other)] * 8}, seen2))
r2 = await clients.post("/call/treg.people.search", json=body,
headers={"X-Treg-Route-Min-Results": "5"})
assert "merged_from" not in r2.json()["_treg"] and len(r2.json()["output"]["people"]) == 2
def test_a_per_success_endpoint_with_no_adapter_settles_on_the_providers_own_success_rule():
"""The adapter's `miss` predicate covers routed children; 1330 of 1517 per_success endpoints
have no adapter, and they are the SCRAPERS — whose failure mode is an HTTP 200 carrying an
error code. Those providers publish a success rule and the catalog records it as `expect`,
which until now only `scripts/catalog_verify.py` read.
Live 2026-08-29: `justoneapi.x.linkedin-search-user-v1` answered
`{"code": 301, "message": "COLLECT FAILED, SEND REQUEST AGAIN"}` — free on the vendor's own
published terms ("only a code-0 response is billed") — and treg settled $0.0295 against the
caller."""
from test_marketplace_call import _mk
from treg.application.call import settle as A
cat = catalog_store.load()
eid = "justoneapi.x.linkedin-search-user-v1"
ep = cat.by_id[eid]
assert (ep.get("cost") or {}).get("type") == "per_success" and cat.adapters.get(eid) is None, \
"the shape this rule exists for: priced per success, no adapter to ask"
assert ep.get("expect") == {"json_path": "code", "equals": 0}, "the loader must carry the rule"
mk = _mk("justoneapi", endpoint_id=eid, cost_type="per_success")
fail = b'{"code": 301, "data": null, "message": "COLLECT FAILED, SEND REQUEST AGAIN"}'
assert A._observed_cost_micro(mk, fail) == 0, "a vendor-side failure the vendor does not bill"
ok = b'{"code": 0, "data": {"users": [{"name": "Ada"}]}}'
assert A._observed_cost_micro(mk, ok) is None, "a real hit still settles at the estimate"
# a nested rule form (dataforseo's task envelope) reads the same way — picking one that also
# has no adapter, since an adapter's own predicate takes precedence when there is one
dfs = [e for e in cat.by_id.values()
if (e.get("expect") or {}).get("json_path") == "tasks.0.status_code"
and cat.adapters.get(e["id"]) is None
and (e.get("cost") or {}).get("type") == "per_success"]
if dfs:
m2 = _mk(dfs[0]["provider"], endpoint_id=dfs[0]["id"], cost_type="per_success")
assert A._observed_cost_micro(m2, b'{"tasks": [{"status_code": 40501}]}') == 0
assert A._observed_cost_micro(m2, b'{"tasks": [{"status_code": 20000}]}') is None
async def test_a_declared_miss_status_is_a_miss_not_a_caller_fault(clients: AsyncClient, enrichment_on, monkeypatch):
"""aviato answers HTTP 404 `Not Found` for a person it has no record of. The endpoint's YAML says
so (`miss: {status: 404}`), and the router must read it: before this a waterfall in which the
other providers all missed ended in a 502 `route_failed` (live 2026-09-03, voice-ai-outbound —
768 of 1,824 phone.find 502s in 30 days had no failure but an aviato 404), when the honest
answer was a 200 miss."""
routed = "treg.people.phone.find"
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"aviato": [(404, {"message": "Not Found"})],
"tomba": [(200, {"data": {"e164_format": None}})],
"leadmagic": [(200, {"mobile_number": None, "credits_consumed": 0})],
"findymail": [(200, {"phone": None})],
"leadsforge": [(200, {"phoneNumber": None})]}, seen))
before = await _balance(clients)
r = await clients.post(f"/call/{routed}", json={"linkedin_url": "https://www.linkedin.com/in/nobody-here"})
assert r.status_code == 200, r.text
assert r.headers["X-Treg-Route-Outcome"] == "miss" and r.json()["_treg"]["served_by"] is None
outcomes = {t["endpoint_id"]: t["outcome"] for t in r.json()["_treg"]["tried"]}
assert outcomes["aviato.people.phone.find"] == "miss" and len(seen) == 5, "the 404 is a miss; every provider was still asked"
assert await _balance(clients) == before, "nobody found anything, nothing was charged"
# an UNDECLARED 4xx keeps its meaning: a vendor rejecting the request is still the caller's fault
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider(
{"aviato": [(422, {"message": "bad identifier"})], "*": [(422, {"message": "bad"})] * 4}, seen))
r = await clients.post(f"/call/{routed}", json={"linkedin_url": "https://www.linkedin.com/in/nobody-here"},
headers={"X-Treg-Route-Prefer": "aviato"})
assert r.status_code == 422 and r.json()["detail"]["error"] == "route_caller_fault", r.text
# and with the waterfall off, the declared 404 alone is the (free) miss the caller asked for
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"aviato": [(404, {"message": "Not Found"})]}, seen))
r = await clients.post(f"/call/{routed}", json={"linkedin_url": "https://www.linkedin.com/in/nobody-here"},
headers={"X-Treg-Route-Prefer": "aviato", "X-Treg-Route-Waterfall": "0"})
assert r.status_code == 200 and r.headers["X-Treg-Route-Outcome"] == "miss" and len(seen) == 1, r.text
assert r.json()["output"]["phone"] is None
def test_every_declared_miss_status_names_its_meaning():
"""`miss: {status, means}` is agent-facing (`endpoint_view`) and router-facing: both halves
are required. The router honours a 4xx only — a `status: 200` block is documentation for the
agent (tikhub answers 200 with a null body for an unknown id) and the adapter's own predicate
decides that case, so `_miss_status` must never turn a success into a miss."""
cat = catalog_store.load()
declared = {e["id"]: e["miss"] for e in cat.by_id.values() if e.get("miss")}
assert "aviato.people.phone.find" in declared and "hunter.people.enrich" in declared
for eid, m in declared.items():
assert isinstance(m, dict) and m.get("status") is not None and m.get("means"), eid
assert int(m["status"]) < 500, f"{eid}: a 5xx is never 'asked and answered'"
assert call_route._miss_status(cat.by_id["aviato.people.phone.find"]) == 404
assert call_route._miss_status(cat.by_id["tikhub.x.reddit-app-fetch-post-comments"]) is None
assert call_route._miss_status({"id": "x"}) is None
async def test_lusha_is_the_last_rung_of_the_phone_waterfall_and_settles_on_its_own_bill(clients: AsyncClient, enrichment_on, monkeypatch):
"""Guatemala, 2026-09-03: 7 phones in 44 across tomba/aviato/leadmagic/findymail/leadsforge.
Lusha's native direct-dial data remains the last rung — dearest per hit (6 credits), so it ranks
after AI Ark, Dropleads, Prospeo, and the cheaper providers; a miss is free and a matched profile
with no number costs the 1-credit search, both read off `billing.creditsCharged`."""
monkeypatch.setenv("TREG_PLATFORM_KEY_LUSHA", "PLATFORM-LUSHA-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,lusha")
get_settings.cache_clear()
routed = "treg.people.phone.find"
plan = (await clients.get(f"/catalog/endpoints/{routed}")).json()["routing"]["plan"]
assert plan[-1]["endpoint_id"] == "lusha.people.phone.find" and len(plan) == 13, [c["endpoint_id"] for c in plan]
def misses():
return {"aviato": [(404, {"message": "Not Found"})], "tomba": [(200, {"data": {"e164_format": None}})],
"leadmagic": [(200, {"mobile_number": None, "credits_consumed": 0})],
"findymail": [(200, {"phone": None})], "leadsforge": [(200, {"phoneNumber": None})]}
seen = []
hit = {"requestId": "r", "results": [{"id": "v1.x", "fullName": "Ana Perez",
"phones": [{"number": "+502 5555 0100", "type": "mobile", "doNotCall": False, "countryIso2": "GT"}]}],
"billing": {"creditsCharged": 6, "resultsReturned": 1}}
monkeypatch.setattr(call_service, "relay", _relay_by_provider({**misses(), "lusha": [(200, hit)]}, seen))
# a Lusha attempt RESERVES the 6-credit hit price (~$0.75): on the $1.00 signup grant a team gets
# one attempt, so fund the second call here rather than let the reserve mask the miss rule
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
await ledger.grant(db, org_id, amount_micro=5_000_000, kind="test-funding", once=False)
await db.commit()
before = await _balance(clients)
r = await clients.post(f"/call/{routed}", json={"full_name": "Ana Perez", "domain": "acme.gt"})
assert r.status_code == 200 and r.json()["_treg"]["served_by"] == "lusha.people.phone.find", r.text
assert r.json()["output"] == {"phone": "+502 5555 0100", "line_type": "mobile", "country_code": "GT"}
# {full_name, domain} is accepted by two rungs only (leadsforge, lusha); the four that need a
# LinkedIn URL or an email are not candidates for this identity at all
assert [p for p, *_ in seen] == ["leadsforge", "lusha"], "asked last, after every cheaper candidate missed"
body = seen[-1][3]
assert body == {"contacts": [{"firstName": "Ana", "lastName": "Perez", "companyDomain": "acme.gt"}], "reveal": ["phones"]}
rate = catalog_store.load().credit_rates["lusha"]
assert before - await _balance(clients) == int(6 * rate * 1_000_000 + 0.5), "the bill is Lusha's own creditsCharged"
# a matched profile with no number is a MISS that still cost the 1-credit search
seen.clear()
no_number = {"requestId": "r", "results": [{"id": "v1.x", "fullName": "Ana Perez", "partialProfile": False}],
"billing": {"creditsCharged": 1, "resultsReturned": 1}, "status": "partial", "statusReason": "WATERFALL_NOT_CONFIGURED"}
monkeypatch.setattr(call_service, "relay", _relay_by_provider({**misses(), "lusha": [(200, no_number)]}, seen))
before = await _balance(clients)
r = await clients.post(f"/call/{routed}", json={"full_name": "Ana Perez", "domain": "acme.gt"})
assert r.status_code == 200 and r.headers["X-Treg-Route-Outcome"] == "miss" and r.json()["output"]["phone"] is None, r.text
assert before - await _balance(clients) == int(1 * rate * 1_000_000 + 0.5)
get_settings.cache_clear()
async def test_quickenrich_is_cheapest_phone_provider_and_respects_max_cost(clients: AsyncClient, enrichment_with_quickenrich_on, monkeypatch):
"""QuickEnrich is the cheapest phone provider (~$0.0048) and must be considered when max-cost is
set above its price. Regression for feedback #136/#128: customers got 402s because the router
was treating a more expensive provider as cheapest when QuickEnrich was not in PLATFORM_PROVIDERS."""
routed = "treg.people.phone.find"
plan = (await clients.get(f"/catalog/endpoints/{routed}")).json()["routing"]["plan"]
prices = [(c["endpoint_id"], c["usd"]) for c in plan]
quickenrich_entry = next((p for p in prices if "quickenrich" in p[0]), None)
assert quickenrich_entry is not None, f"QuickEnrich must be in the phone waterfall: {prices}"
assert quickenrich_entry[1] == min(p[1] for p in prices if p[1]), f"QuickEnrich must be the cheapest: {prices}"
seen = []
hit = {'success': True, 'data': {'employee_phone': '+15550100100', 'employee_phone_type': 'mobile'},
'meta': {'credits_used': 1}}
monkeypatch.setattr(call_service, "relay", _relay_by_provider({'quickenrich': [(200, hit)]}, seen))
before = await _balance(clients)
# max_cost=$0.01 is above QuickEnrich (~$0.0048) but below every other provider
r = await clients.post(f"/call/{routed}", json={"linkedin_url": "https://www.linkedin.com/in/example"},
headers={"X-Treg-Route-Max-Cost": "0.01"})
assert r.status_code == 200, f"Should succeed with QuickEnrich: {r.text}"
assert r.json()["_treg"]["served_by"] == "quickenrich.people.phone.find"
assert r.json()["output"]["phone"] == "+15550100100"
assert [p for p, *_ in seen] == ["quickenrich"], "Only QuickEnrich should be called"
charged = before - await _balance(clients)
assert charged == 4834, f"QuickEnrich should charge 1 credit = $0.004834 = 4834 micro: got {charged}"
async def test_max_cost_below_cheapest_refuses_before_any_call_for_phone(clients: AsyncClient, enrichment_with_quickenrich_on, monkeypatch):
"""When max-cost is below even the cheapest provider (QuickEnrich), the call is refused with
route_max_cost error before any provider is asked, naming the cheapest candidate."""
routed = "treg.people.phone.find"
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({}, seen))
r = await clients.post(f"/call/{routed}", json={"linkedin_url": "https://www.linkedin.com/in/example"},
headers={"X-Treg-Route-Max-Cost": "0.001"}) # $0.001 < QuickEnrich's $0.0048
assert r.status_code == 402, r.text
d = r.json()["detail"]
assert d["error"] == "route_max_cost"
assert "quickenrich" in d["message"], f"Error should name QuickEnrich as cheapest: {d['message']}"
assert seen == [], "No provider should be called when max-cost is below the cheapest"
async def test_capped_signal_when_max_cost_truncates_waterfall(clients: AsyncClient, enrichment_with_quickenrich_on, monkeypatch):
"""Feedback #131: When max-cost stops the waterfall early, the result should indicate that more
expensive providers were skipped (capped=true). This lets callers distinguish an exhaustive miss
from one truncated by budget - they can raise their ceiling if they need to try all providers."""
routed = "treg.people.phone.find"
miss = {'success': False, 'message': 'No data found for this profile'}
monkeypatch.setattr(call_service, "relay", _relay_by_provider({'quickenrich': [(200, miss)]}, []))
r = await clients.post(f"/call/{routed}", json={"linkedin_url": "https://www.linkedin.com/in/example"},
headers={"X-Treg-Route-Max-Cost": "0.01"}) # ~$0.01 allows QuickEnrich only
assert r.status_code == 200, r.text
treg = r.json()["_treg"]
assert treg["outcome"] == "miss"
assert treg.get("capped") is True, f"Expected capped=true when waterfall truncated: {treg}"
assert r.headers.get("X-Treg-Route-Capped") == "true", "Expected X-Treg-Route-Capped header"
skipped = [t for t in treg["tried"] if t["outcome"] == "skipped" and "would exceed" in t.get("detail", "")]
assert skipped, f"Expected some providers skipped due to cost: {treg['tried']}"
async def test_strict_filters_refuses_a_looser_answer_instead_of_billing_it(clients: AsyncClient, enrichment_on, monkeypatch):
"""voice-ai-outbound, 2026-09-03: `{full_name, country: GT}` went to a candidate that ignored
the country and was billed for people in New York. Opt-in, the caller is refused instead —
unbilled, told which filter, and what identity a filter-aware provider would take."""
monkeypatch.setenv("TREG_PLATFORM_KEY_CRUSTDATA", "PLATFORM-CRUSTDATA-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "hunter,tomba,leadmagic,leadsforge,findymail,aviato,fiber-ai,crustdata")
get_settings.cache_clear()
# This regression compares Crustdata with Aviato, independently of other catalog additions.
cat = catalog_store.load()
for eid in cat.by_id["treg.people.search"]["routed_children"]:
if not eid.startswith(("crustdata.", "aviato.")):
monkeypatch.delitem(cat.adapters, eid)
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"*": [(200, {"profiles": [{"name": "Someone"}], "total_count": 1})] * 3}, seen))
before = await _balance(clients)
# crustdata's people.search takes full_name and nothing geographic: with the header it is dropped
r = await clients.post("/call/treg.people.search", json={"full_name": "Carlos Lopez", "country": "GT", "limit": 3},
headers={"X-Treg-Route-Strict-Filters": "1", "X-Treg-Route-Exclude": "aviato"})
assert r.status_code == 422 and r.json()["detail"]["error"] == "no_route_candidate", r.text
d = r.json()["detail"]
assert seen == [] and await _balance(clients) == before, "refused before any provider was asked; nothing billed"
assert any(x["endpoint_id"] == "crustdata.people.search" and x.get("strict") and "country" in x["why"] for x in d["dropped"]), d
assert "X-Treg-Route-Strict-Filters" in d["message"] and "full_name" in d["message"]
# without the header the same call goes out, is billed, and says what it ignored
r = await clients.post("/call/treg.people.search", json={"full_name": "Carlos Lopez", "country": "GT", "limit": 3},
headers={"X-Treg-Route-Exclude": "aviato"})
assert r.status_code == 200 and r.headers["X-Treg-Ignored-Filters"] == "country" and r.json()["_treg"]["ignored_filters"] == ["country"], r.text
assert len(seen) == 1
# and a candidate that CAN express the filter is unaffected by the header (aviato's simple search maps country)
seen.clear()
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"aviato": [(200, {"items": [{"fullName": "Carlos Lopez", "location": "Guatemala"}], "totalResults": 1})]}, seen))
r = await clients.post("/call/treg.people.search", json={"full_name": "Carlos Lopez", "country": "GT", "limit": 3},
headers={"X-Treg-Route-Strict-Filters": "1"})
assert r.status_code == 200 and r.json()["_treg"]["served_by"] == "aviato.people.search.simple", r.text
assert "X-Treg-Ignored-Filters" not in r.headers and seen[0][2]["country"] == "Guatemala"
get_settings.cache_clear()
@pytest.mark.parametrize("capability", ["people.email.find", "people.phone.find"])
def test_leadsforge_requires_both_name_parts_or_linkedin(capability):
from treg.domain.catalog.routing.contracts import adapter_accepts
cat = catalog_store.load()
adapter = cat.adapters["leadsforge." + capability]
contract = cat.contracts[capability]
incomplete, _ = canonical_identity(contract, {"full_name": "Jason", "domain": "example.com"})
assert adapter_accepts(adapter, incomplete) is None, "Do not send a company-only request"
complete, _ = canonical_identity(contract, {"full_name": "Test Person", "domain": "example.com"})
query, body = adapter.to_upstream(complete, adapter_accepts(adapter, complete))
assert query == {}
assert body == {"firstName": "Test", "lastName": "Person", "companyDomain": "example.com"}
linkedin, _ = canonical_identity(contract, {"linkedin_url": "https://www.linkedin.com/in/test-person"})
assert adapter.to_upstream(linkedin, adapter_accepts(adapter, linkedin))[1] == {
"linkedinURL": "https://www.linkedin.com/in/test-person"}
def test_successful_contact_lookup_is_not_mailbox_verification():
cat = catalog_store.load()
for endpoint, response in [
("leadsforge.people.email.find", {"email": "test@example.com", "status": "succeeded"}),
("fiber-ai.people.contacts.reveal", {"output": {"profile": {
"success": True, "emails": [{"email": "test@example.com"}]}}}),
]:
output = cat.adapters[endpoint].from_upstream(response)
assert output["email"] == "test@example.com"
assert "verified" not in output, "Successful enrichment is not a deliverability verdict"
@pytest.mark.parametrize("result,valid,miss", [
("ok", True, False), ("invalid", False, False), ("disposable", False, False),
("catch_all", False, False), ("unknown", False, False), ("unverified", False, False),
])
def test_millionverifier_verdicts(result, valid, miss):
cat = catalog_store.load()
eid = "millionverifier.people.email.verify"
assert eid in cat.by_id["treg.people.email.verify"]["routed_children"]
assert cat.platform_eligible(cat.by_id[eid])
assert not cat.platform_eligible(cat.by_id["millionverifier.account.usage"])
adapter = cat.adapters[eid]
assert adapter.verified
doc = {"result": result, "quality": "good" if valid else "bad", "error": ""}
assert adapter.from_upstream(doc) == {"valid": valid, "status": result}
assert adapter.is_miss(doc) is miss
assert adapter.is_miss({"result": "error", "error": "invalid_api_key"})
assert adapter.is_miss({})
@pytest.mark.parametrize("result,valid", [
("deliverable", True),
("risky", False),
("undeliverable", False),
("unknown", False),
])
def test_bounceban_verdicts_join_existing_email_verification_route(result, valid):
cat = catalog_store.load()
eid = "bounceban.people.email.verify"
routed = cat.by_id["treg.people.email.verify"]["routed_children"]
assert eid in routed
assert "bounceban.people.email.verify.waterfall" not in routed
assert cat.platform_eligible(cat.by_id[eid])
for blocked in (
"bounceban.people.email.verify.waterfall",
"bounceban.account.usage",
):
assert not cat.platform_eligible(cat.by_id[blocked])
adapter = cat.adapters[eid]
assert adapter.verified
doc = {"status": "success", "result": result, "score": 99}
assert adapter.from_upstream(doc) == {"valid": valid, "status": result, "score": 99}
assert not adapter.is_miss(doc)
assert adapter.is_miss({"id": "task", "status": "verifying"})
def test_zerobounce_verdicts_join_existing_email_verification_route():
cat = catalog_store.load()
eid = "zerobounce.people.email.verify"
assert eid in cat.by_id["treg.people.email.verify"]["routed_children"]
assert cat.platform_eligible(cat.by_id[eid])
adapter = cat.adapters[eid]
assert adapter.verified
assert adapter.is_miss({"status": "unknown"})
assert adapter.is_miss({})
for status, valid in (("valid", True), ("invalid", False), ("catch-all", False),
("spamtrap", False), ("abuse", False), ("do_not_mail", False)):
doc = {"status": status}
assert not adapter.is_miss(doc)
assert adapter.from_upstream(doc) == {"valid": valid, "status": status}
def test_zerobounce_expensive_discovery_tools_stay_out_of_automatic_routing():
cat = catalog_store.load()
assert "zerobounce.people.email.find" not in cat.by_id["treg.people.email.find"]["routed_children"]
assert "zerobounce.people.email.find" not in cat.adapters
assert "zerobounce.companies.email_pattern" not in cat.adapters
async def test_zerobounce_serves_existing_email_verification_route(clients, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_ZEROBOUNCE", "PLATFORM-ZEROBOUNCE")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "zerobounce")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"zerobounce": [(200, {"status": "valid"})],
}, seen))
try:
response = await clients.post(
"/call/treg.people.email.verify", json={"email": "valid@example.com"})
assert response.status_code == 200, response.text
body = response.json()
assert body["output"] == {"valid": True, "status": "valid"}
assert body["_treg"]["served_by"] == "zerobounce.people.email.verify"
assert [row[0] for row in seen] == ["zerobounce"]
finally:
get_settings.cache_clear()
async def test_bounceban_serves_existing_email_verification_route(clients, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_BOUNCEBAN", "PLATFORM-BOUNCEBAN")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "bounceban")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"bounceban": [(200, {
"id": "task", "status": "success", "result": "risky", "score": 62,
"credits_consumed": 1, "credits_remaining": 9996,
})],
}, seen))
before = await _balance(clients)
response = await clients.post(
"/call/treg.people.email.verify", json={"email": "dev@bounceban.com"})
assert response.status_code == 200, response.text
data = response.json()
assert data["output"]["valid"] is False
assert data["output"]["status"] == "risky"
assert data["output"]["score"] == 62
assert data["_treg"]["served_by"] == "bounceban.people.email.verify"
assert before - await _balance(clients) == 4_000
assert [row[0] for row in seen] == ["bounceban"]
get_settings.cache_clear()
async def test_bounceban_routed_pending_result_is_a_paid_miss_then_falls_through(
clients, monkeypatch,
):
monkeypatch.setenv("TREG_PLATFORM_KEY_BOUNCEBAN", "PLATFORM-BOUNCEBAN")
monkeypatch.setenv("TREG_PLATFORM_KEY_TOMBA", "PLATFORM-TOMBA-KEY")
monkeypatch.setenv("TREG_PLATFORM_KEY_TOMBA_SECRET", "PLATFORM-TOMBA-SECRET")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "bounceban,tomba")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"bounceban": [(200, {
"id": "task", "status": "verifying", "try_again_at": 1789516800,
})],
"tomba": [(200, {
"data": {"email": {"status": "valid", "score": 99}},
})],
}, seen))
before = await _balance(clients)
response = await clients.post(
"/call/treg.people.email.verify",
json={"email": "dev@bounceban.com"},
headers={"X-Treg-Route-Prefer": "bounceban,tomba"},
)
assert response.status_code == 200, response.text
data = response.json()
assert data["_treg"]["served_by"] == "tomba.people.email.verify"
assert [attempt["outcome"] for attempt in data["_treg"]["tried"]] == ["miss", "hit"]
assert [attempt["charged_micro"] for attempt in data["_treg"]["tried"]] == [4_000, 8_900]
assert data["_treg"]["charged_micro"] == 12_900
assert before - await _balance(clients) == 12_900
assert [row[0] for row in seen] == ["bounceban", "tomba"]
await audit.drain()
async with session_maker() as db:
rows = (await db.execute(select(CallRecord).where(
CallRecord.provider == "bounceban"))).scalars().all()
assert len(rows) == 1
assert rows[0].hit is False
assert rows[0].cost_charged_micro == 4_000
get_settings.cache_clear()
async def test_millionverifier_error_falls_through_unbilled(clients, enrichment_on, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_MILLIONVERIFIER", "PLATFORM-MV-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "millionverifier,leadmagic")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"millionverifier": [(200, {"result": "error", "error": "Apikey not found"})],
"leadmagic": [(200, {"email_status": "valid", "credits_consumed": 0.25})],
}, seen))
response = await clients.post("/call/treg.people.email.verify", json={"email": "support@millionverifier.com"},
headers={"X-Treg-Route-Prefer": "millionverifier,leadmagic"})
assert response.status_code == 200, response.text
data = response.json()
assert data["_treg"]["served_by"] == "leadmagic.people.email.verify"
assert data["_treg"]["tried"][0]["outcome"] == "miss"
await audit.drain()
async with session_maker() as db:
rows = (await db.execute(select(CallRecord).where(
CallRecord.provider == "millionverifier"))).scalars().all()
assert len(rows) == 1
assert rows[0].cost_observed_micro == 0
async def test_millionverifier_own_key_precedes_platform_and_is_free(clients, enrichment_on, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_MILLIONVERIFIER", "PLATFORM-MV-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "millionverifier,leadmagic")
get_settings.cache_clear()
await clients.post("/secrets", json={"name": "millionverifier", "value": "OWN-MV-KEY"})
before = await _balance(clients)
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"millionverifier": [(200, {"result": "ok", "quality": "good", "error": ""})],
}, seen))
response = await clients.post("/call/treg.people.email.verify", json={"email": "support@millionverifier.com"})
assert response.status_code == 200, response.text
assert response.json()["_treg"]["served_by"] == "millionverifier.people.email.verify"
assert response.json()["_treg"]["tier"] == "credential"
assert await _balance(clients) == before
async def test_millionverifier_account_usage_requires_own_key(clients, enrichment_on, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_MILLIONVERIFIER", "PLATFORM-MV-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "millionverifier")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"millionverifier": [(200, {"credits": 123})],
}, seen))
before = await _balance(clients)
response = await clients.get("/call/millionverifier.account.usage")
assert response.status_code == 404, response.text
assert seen == []
assert await _balance(clients) == before
await clients.post("/secrets", json={"name": "millionverifier", "value": "OWN-MV-KEY"})
response = await clients.get("/call/millionverifier.account.usage")
assert response.status_code == 200, response.text
assert response.json() == {"credits": 123}
assert len(seen) == 1
assert await _balance(clients) == before
@pytest.mark.parametrize("result,free,charged", [
("ok", False, True), ("ok", True, True), ("invalid", False, True),
("disposable", False, True), ("catch_all", False, False), ("unknown", False, False),
])
async def test_millionverifier_platform_billing(clients, enrichment_on, monkeypatch, result, free, charged):
"""Definitive verdicts cost one credit; risky returns are free, unrelated to free-email flags."""
monkeypatch.setenv("TREG_PLATFORM_KEY_MILLIONVERIFIER", "PLATFORM-MV-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "millionverifier")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"millionverifier": [(200, {"result": result, "quality": "risky" if not charged else "good",
"error": "", "free": free, "credits": 497})],
}, seen))
before = await _balance(clients)
response = await clients.get("/call/millionverifier.people.email.verify", params={"email": "support@millionverifier.com"})
assert response.status_code == 200, response.text
delta = before - await _balance(clients)
assert delta == (1780 if charged else 0)
assert response.json()["result"] == result
@pytest.mark.parametrize('endpoint,given,query,body,field,data,credits', [
('people.email.find', {'full_name': 'Example Person', 'domain': 'example.com'},
{'first_name': 'Example', 'last_name': 'Person', 'company_url': 'example.com'}, None,
'email', {'email': 'person@example.com'}, 1),
('people.phone.find', {'linkedin_url': 'https://linkedin.com/in/example'},
{'linkedin_url': 'https://linkedin.com/in/example'}, None,
'phone', {'employee_phone': '+15550101000'}, 1),
('people.enrich', {'email': 'person@example.com'}, {'email': 'person@example.com'}, None,
'full_name', {'first_name': 'Example', 'last_name': 'Person'}, 1),
('people.search', {'company_domain': 'example.com', 'title': 'CEO', 'country': 'us', 'limit': 2}, {},
{'company_url': {'include': ['example.com'], 'exclude': []},
'title': {'include': ['CEO'], 'exclude': []}, 'country_code': {'include': ['US'], 'exclude': []}, 'per_page': 2},
'people', [{'first_name': 'Example', 'has_email': True}], 0),
('companies.search', {'domain': 'example.com', 'limit': 2}, {},
{'company_url': 'example.com', 'per_page': 2},
'companies', [{'company_name': 'Example'}], 1),
])
async def test_routed_enrichment_adapter_requests_and_usage(
clients, platform_on, monkeypatch, endpoint, given, query, body, field, data, credits):
monkeypatch.setenv('TREG_PLATFORM_KEY_QUICKENRICH', 'PLATFORM-QUICKENRICH-KEY')
monkeypatch.setenv('TREG_PLATFORM_PROVIDERS', 'quickenrich')
get_settings.cache_clear()
cat = catalog_store.load()
child = 'quickenrich.' + endpoint
parent = 'treg.' + cat.by_id[child]['capability']
assert child in cat.by_id[parent]['routed_children']
raw = {'success': True, 'data': data, 'meta': {'credits_used': credits, 'next_cursor': 'next'}}
seen = []
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({'quickenrich': [(200, raw)]}, seen))
before = await _balance(clients)
response = await clients.post('/call/' + parent, json=given)
assert response.status_code == 200, response.text
result = response.json()
assert result['_treg']['served_by'] == child
assert result['output'][field]
assert result['raw'] == raw
assert seen[0][2:] == (query, body)
assert before - await _balance(clients) == credits * 4834
if endpoint == 'people.search':
assert 'email' not in result['output']['people'][0]
assert result['output']['next_cursor'] == 'next'
@pytest.mark.parametrize('value', [None, '', 'N/A', ' n/a ', 'null', 'none'])
@pytest.mark.parametrize('endpoint,field', [('people.email.find', 'email'), ('people.phone.find', 'employee_phone')])
def test_contact_adapters_reject_empty_markers(endpoint, field, value):
ad = catalog_store.load().adapters['quickenrich.' + endpoint]
assert ad.is_miss({'success': True, 'data': {field: value}})
assert ad.is_miss({'success': True, 'data': []})
assert not ad.is_miss({'success': True, 'data': {field: 'contact-value'}})
def test_quickenrich_paid_adapter_fixtures_verify_success_outputs():
cat = catalog_store.load()
expected = {
'quickenrich.people.email.find': ('email', 'person@example.com'),
'quickenrich.people.phone.find': ('phone', '+15550101000'),
'quickenrich.people.enrich': ('full_name', 'Example Person'),
}
for endpoint_id, (field, value) in expected.items():
adapter = cat.adapters[endpoint_id]
assert adapter.verified is True
assert adapter.verify_note == ''
example = json.loads(
(Path(__file__).resolve().parents[1] / 'src/treg/catalog/examples'
/ cat.by_id[endpoint_id]['example_file']).read_text()
)
assert not adapter.is_miss(example)
assert adapter.from_upstream(example)[field] == value
def test_search_adapters_preserve_filters_and_fixed_page_quote():
from treg.domain.catalog.routing.contracts import adapter_accepts
cat = catalog_store.load()
ad = cat.adapters['quickenrich.people.search.domain']
for title, expected in [(None, 4834), ('CEO', 96680)]:
given = {'company_domain': 'example.com', 'limit': 1}
if title:
given['title'] = title
ident, _ = canonical_identity(cat.contracts['people.search'], given)
q, b = ad.to_upstream(ident, adapter_accepts(ad, ident))
assert q == {'company_url': 'example.com', **({'title': title} if title else {})}
assert b == {}
assert cost_at(cat.cost_view(cat.by_id[ad.endpoint_id]['cost'], 'quickenrich'), ident, ad) == expected
assert 'quickenrich.people.search.domain' in cat.by_id['treg.people.search']['routed_children']
reverse = cat.adapters['quickenrich.people.enrich']
assert adapter_accepts(reverse, {'linkedin_url': 'https://linkedin.com/in/example'}) is None
discovery = cat.adapters['quickenrich.people.search']
q, b = discovery.to_upstream({'title': 'CEO', 'limit': 3}, ('title',))
assert b == {'title': {'include': ['CEO'], 'exclude': []}, 'per_page': 3}
company = cat.adapters['quickenrich.companies.search']
q, b = company.to_upstream({'industry': 'Software', 'country': 'us', 'limit': 3}, ('industry',))
assert b == {'industry': {'include': ['Software'], 'exclude': []},
'country_code': {'include': ['US'], 'exclude': []}, 'per_page': 3}
assert adapter_accepts(company, {'technology': 'Python'}) is None
async def test_routed_fixed_page_price_respects_ceiling(clients, platform_on, monkeypatch):
monkeypatch.setenv('TREG_PLATFORM_KEY_QUICKENRICH', 'PLATFORM-QUICKENRICH-KEY')
monkeypatch.setenv('TREG_PLATFORM_PROVIDERS', 'quickenrich')
get_settings.cache_clear()
seen = []
# Free discovery misses. The paid title search needs a 20-credit ceiling, even with limit=1.
miss = {'success': True, 'data': [], 'meta': {'credits_used': 0}}
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({'quickenrich': [(200, miss)]}, seen))
response = await clients.post('/call/treg.people.search',
json={'company_domain': 'example.com', 'title': 'CEO', 'limit': 1},
headers={'X-Treg-Route-Max-Cost': '0.01'})
assert response.status_code == 200, response.text
assert len(seen) == 1 and seen[0][1] == 'POST'
async def test_routed_discovery_miss_tries_domain_search(clients, platform_on, monkeypatch):
monkeypatch.setenv('TREG_PLATFORM_KEY_QUICKENRICH', 'PLATFORM-QUICKENRICH-KEY')
monkeypatch.setenv('TREG_PLATFORM_PROVIDERS', 'quickenrich')
get_settings.cache_clear()
seen = []
miss = {'success': True, 'data': [], 'meta': {'credits_used': 0}}
hit = {'success': True, 'data': [{'first_name': 'Example', 'email': 'person@example.com'}],
'meta': {'credits_used': 1}}
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({'quickenrich': [(200, miss), (200, hit)]}, seen))
before = await _balance(clients)
response = await clients.post('/call/treg.people.search', json={'company_domain': 'example.com', 'limit': 1})
assert response.status_code == 200, response.text
assert response.json()['_treg']['served_by'] == 'quickenrich.people.search.domain'
assert [s[1] for s in seen] == ['POST', 'GET']
assert before - await _balance(clients) == 4834
async def test_routed_contact_miss_uses_next_provider(clients, enrichment_on, monkeypatch):
monkeypatch.setenv('TREG_PLATFORM_KEY_QUICKENRICH', 'PLATFORM-QUICKENRICH-KEY')
monkeypatch.setenv('TREG_PLATFORM_PROVIDERS', 'quickenrich,tomba')
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'quickenrich': [(200, {'success': True, 'data': {'email': 'N/A'}, 'meta': {'credits_used': 0}})],
'tomba': [(200, {'data': {'email': 'person@example.com', 'verification': {'status': 'valid'}}})],
}, seen))
before = await _balance(clients)
response = await clients.post('/call/treg.people.email.find',
json={'first_name': 'Example', 'last_name': 'Person', 'domain': 'example.com'})
assert response.status_code == 200, response.text
assert response.json()['_treg']['served_by'] == 'tomba.people.email.find'
assert [s[0] for s in seen] == ['quickenrich', 'tomba']
assert before - await _balance(clients) == 8900
@pytest.mark.parametrize('expression,expected', [('0', 0), ('2', 9668), ('-1', None), ('true', None), ("'2'", None)])
def test_adapter_unit_quote_requires_nonnegative_integer(expression, expected):
from dataclasses import replace
ad = replace(catalog_store.load().adapters['quickenrich.people.search.domain'], cost_units=expression)
assert cost_at({'usd': 0.004834}, {}, ad) == expected
@pytest.mark.parametrize('endpoint,given,expected', [
('people.email.find', {'first_name': 'Example', 'last_name': 'Person', 'domain': 'example.com'}, 4834),
('people.phone.find', {'linkedin_url': 'https://linkedin.com/in/example'}, 4834),
('people.enrich', {'email': 'person@example.com'}, 4834),
('people.search', {'company_domain': 'example.com', 'limit': 10}, 0),
('people.search.domain', {'company_domain': 'example.com', 'limit': 1}, 4834),
('people.search.domain', {'company_domain': 'example.com', 'title': 'CEO', 'limit': 1}, 96680),
('companies.search', {'domain': 'example.com'}, 48340),
('companies.search', {'domain': 'example.com', 'limit': 1}, 4834),
('companies.search', {'domain': 'example.com', 'limit': 100}, 483400),
])
def test_enrichment_route_quote_matches_direct_reservation(endpoint, given, expected):
from treg.application.call.resolve import _marketplace_pricing
from treg.domain.catalog.routing.contracts import adapter_accepts
cat = catalog_store.load()
ep = cat.by_id['quickenrich.' + endpoint]
ad = cat.adapters[ep['id']]
ident, _ = canonical_identity(cat.contracts[ep['capability']], given)
query, body = ad.to_upstream(ident, adapter_accepts(ad, ident))
cost = cat.cost_view(ep['cost'], ep['provider'])
direct, _ = _marketplace_pricing(ep['provider'], ep['id'], cost, query, json.dumps(body).encode())
assert direct == cost_at(cost, ident, ad) == expected
@pytest.mark.parametrize('cap,identity,doc,expected',[
('find',{'first_name':'Erol','last_name':'Toker','domain':'trykitt.ai'}, {'email':'erol@trykitt.ai','validity':'valid','credits':{'jobCredits':.005}},5000),
('verify',{'email':'erol@trykitt.ai'}, {'validity':'unknown','credits':{'jobCredits':.0015}},1500),
])
async def test_trykitt_routed_calls(clients,monkeypatch,kitt_on,cap,identity,doc,expected):
seen=[]
monkeypatch.setattr(call_service,'relay',_relay_by_provider({'trykitt':[(200,doc)]},seen))
before=await _balance(clients)
r=await clients.post('/call/treg.people.email.'+cap,json=identity)
assert r.status_code==200,r.text
assert seen[0][3]['realtime'] is True
if cap=='find':
assert seen[0][3]['fullName']=='Erol Toker'
assert r.json()['output']['verified'] is True
else: assert r.json()['output']['status']=='unknown'
assert await _balance(clients)==before-expected
async def test_trykitt_throttle_releases_and_routes_to_next_provider(clients,monkeypatch,kitt_on):
monkeypatch.setenv('TREG_PLATFORM_PROVIDERS','trykitt,leadmagic')
monkeypatch.setenv('TREG_PLATFORM_KEY_LEADMAGIC','TEST-LEADMAGIC')
get_settings.cache_clear()
seen=[]
monkeypatch.setattr(call_service,'relay',_relay_by_provider({'trykitt':[(418,{'message': 'temporarily throttled', 'response_code': 418})],'leadmagic':[(200,{'email':'a@example.com','status':'valid','credits_consumed':1})]},seen))
before=await _balance(clients)
r=await clients.post('/call/treg.people.email.find',json={'full_name':'A B','domain':'example.com'})
assert r.status_code==200,r.text
assert [row[0] for row in seen]==['trykitt','leadmagic']
assert await _balance(clients)==before-25000
@pytest.mark.parametrize("verdict", ["valid", "invalid", "accept_all", "disposable", "unknown"])
async def test_contactout_routed_verification_preserves_verdict_and_is_free(
clients, contactout_platform, monkeypatch, verdict,
):
cat = catalog_store.load()
assert cat.adapters["contactout.people.email.verify"].verified
assert "contactout.people.email.verify" in cat.by_id["treg.people.email.verify"]["routed_children"]
payload = {"status_code": 200, "data": {"status": verdict}}
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({"contactout": [(200, payload)]}, seen))
before = await _balance(clients)
response = await clients.post("/call/treg.people.email.verify", json={"email": "person+tag@example.test"})
assert response.status_code == 200, response.text
doc = response.json()
assert doc["output"] == {"valid": verdict == "valid", "status": verdict}
assert doc["raw"] == payload
assert doc["_treg"]["served_by"] == "contactout.people.email.verify"
assert doc["_treg"]["outcome"] == "hit"
assert seen == [("contactout", "GET", {"email": "person+tag@example.test"}, None)]
assert int(response.headers["X-Treg-Cost-Micro"]) == 0
assert before == await _balance(clients)
async with session_maker() as db:
assert not (await db.execute(select(Hold))).scalars().all()
entries = (await db.execute(select(LedgerEntry).where(LedgerEntry.kind.in_(["reserve", "settle"])))).scalars().all()
assert all(e.amount_micro == 0 for e in entries)
@pytest.mark.parametrize("payload", [
{}, {"status_code": 200, "data": {}}, {"status_code": 200, "data": {"status": ""}},
{"status_code": 403, "message": "No access", "data": {"status": "valid"}},
])
async def test_contactout_verifier_missing_verdict_and_embedded_errors_fall_back(
clients, enrichment_on, monkeypatch, payload,
):
monkeypatch.setenv("TREG_PLATFORM_KEY_CONTACTOUT", "PLATFORM-TEST")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "contactout,tomba")
get_settings.cache_clear()
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"contactout": [(200, payload)],
"tomba": [(200, {"data": {"email": {"status": "invalid", "score": 0}}})],
}, seen))
response = await clients.post("/call/treg.people.email.verify", json={"email": "person@example.test"})
assert response.status_code == 200, response.text
assert [r[0] for r in seen] == ["contactout", "tomba"]
assert response.json()["_treg"]["served_by"] == "tomba.people.email.verify"
assert response.json()["output"]["status"] == "invalid"
async def test_own_verifier_key_precedes_free_contactout_platform_candidate(
clients, contactout_platform, monkeypatch,
):
await clients.post('/secrets', json={'name': 'hunter', 'value': 'OWN-TEST'})
seen = []
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'hunter': [(200, {'data': {'status': 'valid'}})],
}, seen))
before = await _balance(clients)
response = await clients.post('/call/treg.people.email.verify', json={'email': 'person@example.test'})
assert response.status_code == 200, response.text
assert response.json()['_treg']['served_by'] == 'hunter.people.email.verify'
assert [row[0] for row in seen] == ['hunter']
assert before == await _balance(clients)
@pytest.mark.parametrize('value,expected', [
({'a.example': {'name': 'A'}, 'b.example': {'name': 'B'}}, [{'name': 'A'}, {'name': 'B'}]),
([{'name': 'A'}], [{'name': 'A'}]), ({}, []), ([], []), (None, None), ('bad', None),
])
def test_row_values_and_nested_lookup_expressions(value, expected):
assert P.evaluate('values(rows)', {'rows': value}) == expected
assert P.evaluate("get(values(rows), '[0].name')", {'rows': value}) == (
'A' if expected else None)
_CONTACTOUT_DISCOVERY = [
('people.email.find', 'people.contact.work',
{'linkedin_url': 'https://www.linkedin.com/in/example'},
'GET', {'profile': 'https://www.linkedin.com/in/example',
'email_type': 'work', 'include_phone': False}, None,
{'status_code': 200, 'profile': {'work_email': ['work@example.test']}},
'email', 'work@example.test', 150_000),
('people.phone.find', 'people.contact.phone',
{'linkedin_url': 'https://www.linkedin.com/in/example'},
'GET', {'profile': 'https://www.linkedin.com/in/example',
'email_type': 'none', 'include_phone': True}, None,
{'status_code': 200, 'profile': {'phone': ['+10000000000']}},
'phone', '+10000000000', 250_000),
('companies.search', 'companies.search', {'domain': 'example.test'},
'POST', {}, {'domain': ['example.test']},
{'status_code': 200, 'companies': [{'name': 'Example'}]}, 'companies', [{'name': 'Example'}], 20_000),
('companies.enrich', 'companies.enrich', {'domain': 'example.test'},
'POST', {}, {'domains': ['example.test']},
{'status_code': 200, 'companies': {'example.test': {'name': 'Example', 'domain': 'example.test'}}},
'name', 'Example', 20_000),
]
@pytest.mark.parametrize('cap,child,identity,method,query,body,payload,field,expected,charge', _CONTACTOUT_DISCOVERY)
async def test_contactout_discovery_routes_preserve_selectors_and_settle(
clients, contactout_platform, monkeypatch,
cap, child, identity, method, query, body, payload, field, expected, charge,
):
cat = catalog_store.load()
eid = 'contactout.' + child
assert cat.adapters[eid].verified and not cat.adapters[eid].verify_note
assert eid in cat.by_id['treg.' + cap]['routed_children']
seen = []
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({'contactout': [(200, payload)]}, seen))
before = await _balance(clients)
response = await clients.post('/call/treg.' + cap, json=identity)
assert response.status_code == 200, response.text
doc = response.json()
assert doc['raw'] == payload and doc['output'][field] == expected
assert doc['_treg']['served_by'] == eid and doc['_treg']['outcome'] == 'hit'
assert seen == [('contactout', method, query, body)]
assert int(response.headers['X-Treg-Cost-Micro']) == charge
assert before - await _balance(clients) == charge
async with session_maker() as db:
assert not (await db.execute(select(Hold))).scalars().all()
@pytest.mark.parametrize('cap,child,identity,method,query,body,payload,field,expected,charge', _CONTACTOUT_DISCOVERY)
@pytest.mark.parametrize('failed', [False, True])
async def test_contactout_discovery_empty_or_error_response_is_not_a_hit(
clients, contactout_platform, monkeypatch,
cap, child, identity, method, query, body, payload, field, expected, charge, failed,
):
payload = {'status_code': 403, **{k: v for k, v in payload.items() if k != 'status_code'}} if failed else {'status_code': 200}
seen = []
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({'contactout': [(200, payload)]}, seen))
before = await _balance(clients)
response = await clients.post('/call/treg.' + cap, json=identity)
assert response.status_code == 200, response.text
assert response.json()['_treg']['outcome'] == 'miss'
assert before == await _balance(clients)
async with session_maker() as db:
assert not (await db.execute(select(Hold))).scalars().all()
def test_contactout_unverified_pii_routes_stay_direct_only():
cat = catalog_store.load()
for cap, child in [('people.search', 'people.search'), ('people.enrich', 'people.enrich'),
('linkedin.user.profile', 'people.linkedin.enrich')]:
eid = 'contactout.' + child
assert eid not in cat.adapters
assert eid not in cat.by_id['treg.' + cap]['routed_children']
assert 'contactout.people.contact.personal' not in cat.adapters
assert cat.by_id['contactout.people.contact.personal']['platform'] == 'people'
# ---- regression: adapter exceptions after child success must not crash the parent (2026-09) ----
def _make_throwing_adapter(real, throw_on: str):
"""Create a wrapper adapter that throws on the specified method."""
class ThrowingAdapter:
def __init__(self, real):
self._real = real
# Copy ALL attributes from the real Adapter dataclass
self.endpoint_id = real.endpoint_id
self.accepts = real.accepts
self.in_map = real.in_map
self.out_map = real.out_map
self.miss = real.miss
self.const = getattr(real, 'const', {})
self.in_expr = getattr(real, 'in_expr', {})
self.body_array = getattr(real, 'body_array', False)
self.test_identity = getattr(real, 'test_identity', {})
self.cost_units = getattr(real, 'cost_units', '')
self.additional_capabilities = getattr(real, 'additional_capabilities', ())
self.verified_capabilities = getattr(real, 'verified_capabilities', ())
self.verified = real.verified
self.verify_note = getattr(real, 'verify_note', '')
self._filter_keys = getattr(real, '_filter_keys', ())
def to_upstream(self, identity, variant):
if throw_on == 'to_upstream':
raise KeyError("simulated to_upstream failure")
return self._real.to_upstream(identity, variant)
def from_upstream(self, provider_body):
if throw_on == 'from_upstream':
raise ValueError("simulated from_upstream failure")
return self._real.from_upstream(provider_body)
def is_miss(self, provider_body):
if throw_on == 'is_miss':
raise TypeError("simulated is_miss failure")
return self._real.is_miss(provider_body)
return ThrowingAdapter(real)
def _patched_catalog_with_throwing_adapter(original_cat, endpoint_id: str, throw_on: str):
"""Return a new Catalog with one adapter replaced by a throwing wrapper."""
from dataclasses import replace
new_adapters = dict(original_cat.adapters)
new_adapters[endpoint_id] = _make_throwing_adapter(original_cat.adapters[endpoint_id], throw_on)
return replace(original_cat, adapters=new_adapters)
def _patched_catalog_all_email_find_throw(original_cat):
"""Return a new Catalog where all email.find adapters throw on from_upstream."""
from dataclasses import replace
new_adapters = {}
for eid, adapter in original_cat.adapters.items():
if 'email.find' in eid:
new_adapters[eid] = _make_throwing_adapter(adapter, 'from_upstream')
else:
new_adapters[eid] = adapter
return replace(original_cat, adapters=new_adapters)
async def test_adapter_from_upstream_throws_after_child_200_waterfall_continues(
clients: AsyncClient, enrichment_on, monkeypatch,
):
"""Regression for 2026-09 bug: adapter.from_upstream throwing after a child returned 200 used
to crash the parent with a bare 502, leaving children audited OK but parent failed. Now the
adapter failure is recorded as an error and the waterfall continues to the next provider."""
original_cat = catalog_store.load()
# Patch tomba's adapter to throw (tomba is first in price order for this identity)
patched_cat = _patched_catalog_with_throwing_adapter(original_cat, "tomba.people.email.find", "from_upstream")
monkeypatch.setattr(catalog_store, 'load', lambda: patched_cat)
seen = []
# Tomba returns 200 but adapter throws; hunter returns 200 and works fine
# '*' catches other providers in waterfall (findymail, etc) returning miss
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': None})] * 10, # Other providers return miss-like response
'tomba': [(200, {'data': {'email': 'bad@format.test', 'unexpectedField': True}})],
'hunter': [(200, {'data': {'email': 'found@example.test', 'score': 80, 'verification': {'status': 'valid'}}})],
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'})
assert r.status_code == 200, r.text
doc = r.json()
# Waterfall continued to hunter after tomba's adapter failed
assert doc['_treg']['served_by'] == 'hunter.people.email.find'
assert doc['_treg']['outcome'] == 'hit'
# The tomba error should be recorded in `tried`
tried = {t['endpoint_id']: t for t in doc['_treg']['tried']}
assert 'tomba.people.email.find' in tried
assert tried['tomba.people.email.find']['outcome'] == 'error'
assert 'adapter.from_upstream failed' in tried['tomba.people.email.find']['detail']
# Hunter succeeded
assert tried['hunter.people.email.find']['outcome'] == 'hit'
async def test_adapter_throws_on_all_children_returns_structured_502_with_tried(
clients: AsyncClient, enrichment_on, monkeypatch,
):
"""When ALL adapters throw on 200 responses, the parent must return a structured 502
with proper error details and `tried` list - not a bare 500 or empty 502."""
original_cat = catalog_store.load()
patched_cat = _patched_catalog_all_email_find_throw(original_cat)
monkeypatch.setattr(catalog_store, 'load', lambda: patched_cat)
seen = []
# All providers return 200 but adapters throw
# '*' wildcard catches all providers - return data that adapters will parse
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': {'email': 'x@test.test'}})] * 15,
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'})
# Should be 502 route_failed, not 500 or empty body
assert r.status_code == 502, r.text
doc = r.json()
assert doc['detail']['error'] == 'route_failed'
# The tried list should have some error outcomes (adapters that threw)
tried = doc['detail']['tried']
assert len(tried) > 0
error_outcomes = [t for t in tried if t['outcome'] == 'error']
# At least one adapter should have thrown (those with email.find in name)
assert len(error_outcomes) > 0, f"Expected at least one error outcome, got: {tried}"
# Check that error details mention adapter failure
for t in error_outcomes:
if 'detail' in t and t['detail']:
assert 'adapter' in t['detail'] or 'failed' in t['detail'], f"Unexpected error detail: {t}"
async def test_adapter_to_upstream_throws_records_error_and_continues(
clients: AsyncClient, enrichment_on, monkeypatch,
):
"""If adapter.to_upstream throws (before the child call), the error is recorded
and the waterfall continues to the next candidate."""
original_cat = catalog_store.load()
# Patch tomba's adapter to throw on to_upstream
patched_cat = _patched_catalog_with_throwing_adapter(original_cat, "tomba.people.email.find", "to_upstream")
monkeypatch.setattr(catalog_store, 'load', lambda: patched_cat)
seen = []
# Tomba's to_upstream will throw before relay is called
# '*' catches other providers, returning miss-like response
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': None})] * 10, # Other providers return miss-like response
'hunter': [(200, {'data': {'email': 'found@example.test', 'score': 80, 'verification': {'status': 'valid'}}})],
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'})
assert r.status_code == 200, r.text
doc = r.json()
# Tomba's to_upstream failed, waterfall continued to hunter
assert doc['_treg']['served_by'] == 'hunter.people.email.find'
tried = {t['endpoint_id']: t for t in doc['_treg']['tried']}
assert 'tomba.people.email.find' in tried
assert tried['tomba.people.email.find']['outcome'] == 'error'
assert 'adapter.to_upstream failed' in tried['tomba.people.email.find']['detail']
async def test_adapter_is_miss_throws_records_error_and_continues(
clients: AsyncClient, enrichment_on, monkeypatch,
):
"""If adapter.is_miss throws after parsing the response, the error is recorded
and the waterfall continues."""
original_cat = catalog_store.load()
# Patch tomba's adapter to throw on is_miss
patched_cat = _patched_catalog_with_throwing_adapter(original_cat, "tomba.people.email.find", "is_miss")
monkeypatch.setattr(catalog_store, 'load', lambda: patched_cat)
seen = []
# '*' catches other providers, returning miss-like response
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': None})] * 10, # Other providers return miss-like response
'tomba': [(200, {'data': {'email': 'tomba@test.test', 'score': 99}})],
'hunter': [(200, {'data': {'email': 'found@example.test', 'score': 80, 'verification': {'status': 'valid'}}})],
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'})
assert r.status_code == 200, r.text
doc = r.json()
# Tomba's is_miss failed, waterfall continued to hunter
assert doc['_treg']['served_by'] == 'hunter.people.email.find'
tried = {t['endpoint_id']: t for t in doc['_treg']['tried']}
assert 'tomba.people.email.find' in tried
assert tried['tomba.people.email.find']['outcome'] == 'error'
assert 'adapter.is_miss failed' in tried['tomba.people.email.find']['detail']
async def test_prospeo_no_match_400_is_treated_as_miss_not_error(
clients: AsyncClient, enrichment_with_miss_declarers_on, monkeypatch,
):
"""Prospeo returns 400 with error_code=NO_MATCH for 'no result' — this is a semantic miss,
not a caller fault. The waterfall should continue and the parent should not 502."""
seen = []
# Prospeo returns 400 NO_MATCH (semantic miss), tomba returns 200 hit
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': None})] * 10, # Other providers miss
'prospeo': [(400, {'error': True, 'error_code': 'NO_MATCH'})],
'tomba': [(200, {'data': {'email': 'found@example.test', 'score': 99, 'verification': {'status': 'valid'}}})],
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'},
headers={'X-Treg-Route-Prefer': 'prospeo'}) # ask it first; otherwise tomba's hit ends the waterfall before it runs
assert r.status_code == 200, r.text
doc = r.json()
# Waterfall continued past Prospeo's NO_MATCH
assert doc['_treg']['outcome'] == 'hit'
tried = {t['endpoint_id']: t for t in doc['_treg']['tried']}
# Prospeo should be recorded as miss, not error
prospeo_attempts = [t for t in doc['_treg']['tried'] if t['provider'] == 'prospeo']
assert prospeo_attempts, doc['_treg']['tried']
for attempt in prospeo_attempts:
assert attempt['outcome'] == 'miss', f"Prospeo NO_MATCH should be miss, not {attempt['outcome']}"
async def test_limadata_404_is_treated_as_miss_not_error(
clients: AsyncClient, enrichment_with_miss_declarers_on, monkeypatch,
):
"""LimaData returns 404 for 'no email found' — with the miss status declared, this should
be treated as a miss and the waterfall should continue."""
seen = []
# LimaData returns 404 (declared miss), tomba returns 200 hit
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': None})] * 10, # Other providers miss
'limadata': [(404, {})],
'tomba': [(200, {'data': {'email': 'found@example.test', 'score': 99, 'verification': {'status': 'valid'}}})],
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'},
headers={'X-Treg-Route-Prefer': 'limadata'}) # ask it first; otherwise tomba's hit ends the waterfall before it runs
assert r.status_code == 200, r.text
doc = r.json()
# Waterfall continued past LimaData's 404
assert doc['_treg']['outcome'] == 'hit'
tried = {t['endpoint_id']: t for t in doc['_treg']['tried']}
# LimaData should be recorded as miss, not error
limadata_attempts = [t for t in doc['_treg']['tried'] if t['provider'] == 'limadata']
assert limadata_attempts, doc['_treg']['tried']
for attempt in limadata_attempts:
assert attempt['outcome'] == 'miss', f"LimaData 404 should be miss, not {attempt['outcome']}"
# ---- miss.when: one status, two meanings (prospeo 400 NO_MATCH vs INVALID_DATAPOINTS) ----
def test_declared_miss_honours_when_predicate_and_never_crashes():
from treg.application.call import route as call_route
ep = {"id": "x", "miss": {"status": 400, "when": "error_code == 'NO_MATCH'", "means": "no match"}}
assert call_route._declared_miss(ep, 400, b'{"error":true,"error_code":"NO_MATCH"}')
assert not call_route._declared_miss(ep, 400, b'{"error":true,"error_code":"INVALID_DATAPOINTS"}')
assert not call_route._declared_miss(ep, 404, b'{"error":true,"error_code":"NO_MATCH"}')
assert not call_route._declared_miss(ep, 400, b'["NO_MATCH"]') # array body: no crash, not a miss
assert not call_route._declared_miss(ep, 400, b'not json')
plain = {"id": "y", "miss": {"status": 404, "means": "gone"}}
assert call_route._declared_miss(plain, 404, b'Not Found')
assert not call_route._declared_miss(plain, 400, b'')
def test_prospeo_and_limadata_person_finders_declare_their_miss():
cat = catalog_store.load()
from treg.domain.catalog.routing.contracts import declared_miss
for eid in ("prospeo.people.email.find", "prospeo.people.phone.find", "prospeo.people.enrich"):
ep = cat.by_id[eid]
assert ep["miss"]["status"] == 400, eid
# evaluate the predicate, not just its spelling: a misspelt path would silently never match
assert declared_miss(ep, 400, {"error": True, "error_code": "NO_MATCH"}), eid
assert not declared_miss(ep, 400, {"error": True, "error_code": "INVALID_DATAPOINTS"}), eid
assert "when" not in catalog_store.endpoint_view(ep, "Prospeo", cat)["miss"], "internal predicate leaks to agents"
for eid in ("limadata.people.email.find.name", "limadata.people.email.find.linkedin", "limadata.people.phone.find"):
assert cat.by_id[eid]["miss"]["status"] == 404, eid
async def test_prospeo_invalid_datapoints_400_stays_a_vendor_fault(
clients: AsyncClient, enrichment_with_miss_declarers_on, monkeypatch,
):
"""The same 400 with a non-NO_MATCH body is a rejected request: recorded as an error, the
waterfall goes on to free-on-failure providers, and the outcome is never a clean miss."""
seen = []
monkeypatch.setattr(call_service, 'relay', _relay_by_provider({
'*': [(200, {'data': None})] * 10,
'prospeo': [(400, {'error': True, 'error_code': 'INVALID_DATAPOINTS'})],
}, seen))
r = await clients.post(f'/call/{ROUTED}', json={'full_name': 'Example Person', 'domain': 'example.com'},
headers={'X-Treg-Route-Prefer': 'prospeo'}) # ask it first; otherwise tomba's hit ends the waterfall before it runs
assert r.status_code == 502, r.text
prospeo = [t for t in r.json()['detail']['tried'] if t['provider'] == 'prospeo']
assert prospeo and all(t['outcome'] == 'error' for t in prospeo)
def test_linkedin_url_is_normalised_once_for_every_adapter():
from treg.domain.catalog.routing import paths as P
from treg.domain.catalog.routing.contracts import canonical_identity
assert P.linkedin_url("linkedin.com/in/patrickcollison") == "https://linkedin.com/in/patrickcollison"
assert P.linkedin_url("www.linkedin.com/in/patrickcollison/") == "https://www.linkedin.com/in/patrickcollison/"
assert P.linkedin_url("https://www.linkedin.com/in/patrickcollison") == "https://www.linkedin.com/in/patrickcollison"
assert P.linkedin_url("patrickcollison") == "https://www.linkedin.com/in/patrickcollison"
contract = catalog_store.load().contracts["people.email.find"]
ident, variant = canonical_identity(contract, {"linkedin_url": "linkedin.com/in/patrickcollison"})
assert variant == ("linkedin_url",)
assert ident["linkedin_url"] == "https://linkedin.com/in/patrickcollison"
assert ident["linkedin_handle"] == "patrickcollison"
def test_linkedin_url_only_trusts_a_linkedin_host():
from treg.domain.catalog.routing import paths as P
# a path that merely mentions linkedin.com is a handle-shaped string, never promoted to that host
assert P.linkedin_url("evil.example/?linkedin.com/in/x") == "https://www.linkedin.com/in/evil.example/?linkedin.com/in/x"
assert P.linkedin_url("uk.linkedin.com/in/x") == "https://uk.linkedin.com/in/x"
def test_arena_and_router_read_the_miss_block_the_same_way():
from treg.domain import arena
cat = catalog_store.load()
ep = cat.by_id["prospeo.people.email.find"]; ad = cat.adapters[ep["id"]]; contract = cat.contracts["people.email.find"]
assert arena.classify(contract, ad, ep, 400, {"error": True, "error_code": "NO_MATCH"})[0] == "miss"
assert arena.classify(contract, ad, ep, 400, {"error": True, "error_code": "INVALID_DATAPOINTS"})[0] == "error"
def test_linkedin_url_lowercases_the_host_so_the_handle_derives():
from treg.domain.catalog.routing import paths as P
assert P.linkedin_url("LinkedIn.com/in/Patrick") == "https://linkedin.com/in/Patrick"
assert P.linkedin_handle(P.linkedin_url("WWW.LinkedIn.com/in/Patrick")) == "Patrick"
async def test_company_blind_search_provider_is_dropped_not_billed(clients: AsyncClient, platform_on, monkeypatch):
"""`people.search` declares `scoping: [company_domain]`. A title-only provider asked for
`{company_domain, title}` answers with the same strangers for every company and bills them as
a hit, so it leaves the plan instead of ranking last — and still serves a title-only search."""
for p in ("LUSHA", "COMPANYENRICH"):
monkeypatch.setenv(f"TREG_PLATFORM_KEY_{p}", f"PLATFORM-{p}-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "lusha,companyenrich")
get_settings.cache_clear()
cat = catalog_store.load()
for eid in cat.by_id["treg.people.search"]["routed_children"]:
if eid not in ("lusha.people.search", "companyenrich.people.search"):
monkeypatch.delitem(cat.adapters, eid, raising=False)
seen = []
monkeypatch.setattr(call_service, "relay", _relay_by_provider({
"lusha": [(200, {"results": [{"name": "Some CEO"}], "pagination": {"total": 1}})],
"companyenrich": [(200, {"items": [], "totalItems": 0})]}, seen))
r = await clients.post("/call/treg.people.search", json={"company_domain": "example.com", "title": "CEO"})
assert [s[0] for s in seen] == ["companyenrich"], seen
assert r.status_code == 200 and r.json()["_treg"]["outcome"] == "miss", r.text
assert any(d["endpoint_id"] == "lusha.people.search" and "company_domain" in d["why"]
for d in r.json()["_treg"]["dropped"]), r.json()["_treg"]
seen.clear()
r = await clients.post("/call/treg.people.search", json={"title": "CEO"})
assert [s[0] for s in seen] == ["lusha"] and r.json()["_treg"]["served_by"] == "lusha.people.search", r.text
get_settings.cache_clear()