Files
treg/tests/test_capacity_overflow_routes.py
T

644 lines
41 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Step B′ of docs/PROVIDER-CAPACITY-PLAN.md — overflow routes, the signature table and the
aggregator envelopes. Fixtures are recorded aggregator bodies (PII hashed) from the 2026-08-26
mapping run. No call-path involvement."""
from __future__ import annotations
import json
from datetime import timedelta
from pathlib import Path
import pytest
from sqlalchemy import select
from treg.infra.db import reset_db, session_maker
from treg.domain.capacity import routes as R
from treg.domain.capacity import signatures as S
from treg.domain.capacity import verify as V
from treg.domain.capacity.policy import ensure_policies
from treg.domain.catalog import store as catalog_store
from treg.infra.upstream.aggregators import (VENDOR_DRY, VENDOR_REFUSAL, by_name, monid,
orthogonal, with_vendor_verdict)
from treg.models import CapacityPolicy, OverflowRoute
from treg.timeutil import utcnow_naive
FIX = Path(__file__).parent / "fixtures" / "aggregators"
def _fixture(name: str) -> dict:
return json.loads((FIX / f"{name}.json").read_text())
def _route(**kw) -> OverflowRoute:
base = dict(endpoint_id="findymail.search.business-profile", aggregator="orthogonal", provider="findymail",
method="POST", path="/search/business-profile", agg_slug="findymail",
agg_path="/api/search/business-profile", agg_price_micro=70_000, agg_unit="call", ratio=3.54,
last_verified_at=utcnow_naive())
base.update(kw)
return OverflowRoute(**base)
# ---- rules ------------------------------------------------------------------------------------
def test_unit_kind_and_event_price_treat_hunters_per_ten_as_a_call():
per10 = {"type": "per_result", "value": 1, "currency": "credit", "per": 10, "unit": "record"}
assert R.our_unit_kind(per10) == "call"
assert R.our_event_usd({**per10, "usd": 0.00245}) == pytest.approx(0.0245)
assert R.our_unit_kind({"type": "per_result", "per": 1}) == "result"
assert R.our_unit_kind({"type": "per_success"}) == "call"
assert R.price_ratio(0.01, 0.0245) == pytest.approx(0.4082, abs=1e-4)
assert R.price_ratio(0.01, None) is None
def test_eligibility_rule_in_order():
cost = {"type": "per_success", "value": 0.0198, "currency": "USD"}
ok = R.eligible(_route(), our_cost=cost, platform_eligible=True, policy=None)
assert ok.enabled
assert not R.eligible(_route(ratio=6.5), our_cost=cost, platform_eligible=True, policy=None).enabled
assert "ratio" in R.eligible(_route(ratio=6.5), our_cost=cost, platform_eligible=True, policy=None).reason
assert "unit mismatch" in R.eligible(_route(agg_unit="result"), our_cost=cost, platform_eligible=True, policy=None).reason
assert R.eligible(_route(agg_unit="result", single_result=True), our_cost=cost, platform_eligible=True, policy=None).enabled
assert "never verified" in R.eligible(_route(last_verified_at=None), our_cost=cost, platform_eligible=True, policy=None).reason
old = utcnow_naive() - timedelta(days=8)
assert "older than 7 days" in R.eligible(_route(last_verified_at=old), our_cost=cost, platform_eligible=True, policy=None).reason
assert "not platform-eligible" in R.eligible(_route(), our_cost=cost, platform_eligible=False, policy=None).reason
pol = CapacityPolicy(provider="findymail", overflow_allowed=False)
assert "disallows" in R.eligible(_route(), our_cost=cost, platform_eligible=True, policy=pol).reason
assert "no price" in R.eligible(_route(ratio=None), our_cost=cost, platform_eligible=True, policy=None).reason
free = {"type": "free", "value": 0, "currency": "USD"}
assert R.eligible(_route(ratio=None, agg_price_micro=10_000), our_cost=free, platform_eligible=True, policy=None, our_usd=0).enabled
assert "free for us" in R.eligible(_route(ratio=None, agg_price_micro=20_000), our_cost=free, platform_eligible=True, policy=None, our_usd=0).reason
def test_match_catalogs_by_exact_host_method_path_with_prefix_folding():
ours = [{"endpoint_id": "hunter.people.email.find", "provider": "hunter", "method": "GET",
"path": "/email-finder", "base_url": "https://api.hunter.io/v2"},
{"endpoint_id": "pdl.people.enrich", "provider": "pdl", "method": "GET",
"path": "/person/enrich", "base_url": "https://api.peopledatalabs.com/v5"}]
orth = [{"slug": "hunter", "baseUrl": "https://api.hunter.io",
"endpoints": [{"path": "/v2/email-finder", "method": "get", "price": "$0.01"},
{"path": "/v2/other", "method": "GET", "price": "$0.01"}]}]
monid_eps = [{"provider": "peopledatalabs", "endpoint": "/v5/person/enrich",
"price": {"type": "PER_RESULT", "amount": {"value": 0.3, "currency": "USD"}}},
{"provider": "hunterio", "endpoint": "/email-finder",
"price": {"type": "PER_RESULT", "amount": {"value": 0.02392, "currency": "USD"}}}]
rows = R.match_catalogs(ours, orthogonal_apis=orth, monid_endpoints=monid_eps,
monid_alias={"peopledatalabs": "pdl", "hunterio": "hunter"})
got = {(r["endpoint_id"], r["aggregator"]): r for r in rows}
assert got[("hunter.people.email.find", "orthogonal")]["agg_price_usd"] == 0.01
assert got[("hunter.people.email.find", "monid")]["agg_unit"] == "result"
assert got[("pdl.people.enrich", "monid")]["agg_slug"] == "peopledatalabs"
assert ("pdl.people.enrich", "orthogonal") not in got
assert R._usd("dynamic") is None and R._usd({"amount": {"value": 1, "currency": "EUR"}}) is None
# ---- sync from the seed ------------------------------------------------------------------------
async def test_sync_reproduces_the_verified_set_and_never_enables_a_bad_ratio(monkeypatch):
await reset_db()
# Preserve the August baseline; September provider verifications are tested separately.
seed = [{**x, "verified_at": None} if x["provider"] in ("influencersclub", "contactout", "akta") else x
for x in R.load_seed()]
verified = {(x["endpoint_id"], x["aggregator"]) for x in seed if x["verified_at"]}
# Freeze "now" at the mapping date so the seed's stamps are within the 7-day window.
now = R._dt("2026-08-27T00:00:00")
cat = catalog_store.load()
async with session_maker() as db:
await ensure_policies(db, has_key=lambda p: True)
result = await R.apply_sync(db, seed, catalog=cat, now=now)
await db.commit()
assert result.rows == len(seed)
async with session_maker() as db:
rows = (await db.execute(select(OverflowRoute))).scalars().all()
on = {(r.endpoint_id, r.aggregator) for r in rows if r.enabled}
by = {(r.endpoint_id, r.aggregator): r for r in rows}
# Every enabled row is verified; nothing unverified, over-ratio, unit-mismatched or policy-barred is on.
assert on <= verified
assert all((by[k].ratio is not None and by[k].ratio <= R.MAX_RATIO)
or (by[k].ratio is None and by[k].agg_price_micro <= R.FREE_ROUTE_MAX_USD * 1_000_000) for k in on)
assert not any(k[0].startswith(("scrapecreators.", "tikhub.")) for k in on)
assert ("findymail.search.business-profile", "orthogonal") in on # the removal check below needs it on
# What was verified but is NOT on, and why — every reason is one the rule names.
off = {k: by[k].disabled_reason for k in verified - on}
allowed = ("ratio ", "unit mismatch", "endpoint not platform-eligible",
"policy for scrapecreators disallows overflow", "no price on one side", "free for us")
assert all(r.startswith(allowed) for r in off.values()), off
# a route with ratio 6.5 in the seed never enables, and a re-sync without it disables it
bad = {**seed[0], "endpoint_id": "findymail.search.business-profile", "aggregator": "monid",
"agg_price_usd": 0.0198 * 6.5, "agg_unit": "call", "verified_at": "2026-08-26"}
async with session_maker() as db:
await R.apply_sync(db, seed + [bad], catalog=cat, now=now)
await db.commit()
row = await db.get(OverflowRoute, ("findymail.search.business-profile", "monid"))
assert not row.enabled and row.disabled_reason.startswith("ratio 6.5")
async with session_maker() as db:
r2 = await R.apply_sync(db, [x for x in seed if x["endpoint_id"] != "findymail.search.business-profile"],
catalog=cat, now=now)
await db.commit()
row = await db.get(OverflowRoute, ("findymail.search.business-profile", "orthogonal"))
assert not row.enabled and row.disabled_reason == "not in the current sync"
# seven days later, without a re-verify, everything switches off
async with session_maker() as db:
r3 = await R.apply_sync(db, seed, catalog=cat, now=now + timedelta(days=8))
assert r3.enabled == 0
async def test_verified_akta_news_monid_fallback_is_enabled_at_the_observed_default_price():
await reset_db()
candidate = next(x for x in R.load_seed()
if x["endpoint_id"] == "akta.companies.news" and x["aggregator"] == "monid")
async with session_maker() as db:
await ensure_policies(db, has_key=lambda p: True)
await R.apply_sync(db, [candidate], catalog=catalog_store.load(),
now=R._dt("2026-09-25T12:00:00"))
await db.commit()
row = await db.get(OverflowRoute, ("akta.companies.news", "monid"))
assert row.enabled
assert row.agg_price_micro == 10_000
assert row.agg_unit == "call"
assert row.ratio == 1
async def test_akta_enrichment_monid_route_is_eligible_only_after_live_verification():
await reset_db()
candidate = next(x for x in R.load_seed()
if x["endpoint_id"] == "akta.companies.enrich" and x["aggregator"] == "monid")
assert candidate["verified_at"] is None and candidate["single_result"] is True
candidate = {**candidate, "verified_at": "2026-09-25"}
async with session_maker() as db:
await ensure_policies(db, has_key=lambda p: True)
await R.apply_sync(db, [candidate], catalog=catalog_store.load(),
now=R._dt("2026-09-25T12:00:00"))
await db.commit()
row = await db.get(OverflowRoute, ("akta.companies.enrich", "monid"))
assert row.enabled and row.agg_unit == "result" and row.single_result is True
def test_route_for_orders_orthogonal_first():
rs = [_route(aggregator="monid", enabled=True), _route(aggregator="orthogonal", enabled=True),
_route(aggregator="orthogonal", endpoint_id="x", enabled=True), _route(aggregator="monid", enabled=False, endpoint_id="y")]
assert [r.aggregator for r in R.route_for(rs, "findymail.search.business-profile")] == ["orthogonal", "monid"]
assert R.route_for(rs, "y") == []
# ---- signatures / 429 classifier --------------------------------------------------------------
def test_signature_table_classifies_balance_quota_and_burst():
assert S.classify("findymail", 402, {}, b'{"message":"Not enough credits"}').kind == "balance"
assert S.classify("thecompaniesapi", 403, {}, b'{"code":"noCreditsRemaining"}').kind == "balance"
assert S.classify("thecompaniesapi", 403, {}, b'{"code":"nocreditsremaining"}').kind == "balance", "rows match any case"
assert S.classify("lusha", 400, {}, b"You have reached your credit limit").kind == "balance"
assert S.classify("anyone", 402, {}, b"").kind == "balance"
now = utcnow_naive().replace(hour=10)
lusha = S.classify("lusha", 429, {}, b'{"message":"Daily search limit reached"}', now=now)
assert lusha.kind == "quota" and lusha.resets_at == (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
hunter = S.classify("hunter", 429, {}, b"You reached the limit for the number of searches per billing period")
assert hunter.kind == "quota" and hunter.resets_at is None
burst = S.classify("leadsforge", 429, {"retry-after": "9", "x-ratelimit-limit": "120"}, b"")
assert burst.kind == "burst" and burst.retry_after_s == 9 and not S.is_exhausting(burst)
long = S.classify("someone", 429, {"retry-after": "3600"}, b"")
assert long.kind == "quota" and long.resets_at is not None
assert S.classify("scrapecreators", 429, {}, b"slow down").kind == "unknown"
assert S.classify("hunter", 400, {}, b"wrong params") is None
assert S.classify("hunter", 500, {}, b"") is None
assert S.classify("hunter", 200, {}, b"") is None
raw = ((b"Retry-After", b"2"),)
assert S.classify("crustdata", 429, raw, b"").retry_after_s == 2
assert S.is_exhausting(S.classify("findymail", 402, {}, b"x")) and not S.is_exhausting(None)
# Apollo API reference, "422 Unprocessable Entity": the body an empty credit pool gets (live 2026-09-01).
APOLLO_OUT_OF_CREDITS = b'{"error": "Insufficient credits. Please upgrade your plan."}'
# The SAME status for a caller's mistake - must never read as our account running dry.
APOLLO_VALIDATION = b'{"error": "Please provide at least one of: first_name, last_name, email"}'
def test_apollo_says_out_of_credits_with_a_422():
sig = S.classify("apollo", 422, None, APOLLO_OUT_OF_CREDITS)
assert sig.kind == "balance" and S.is_exhausting(sig) and sig.resets_at is None
assert S.classify("apollo", 422, None, APOLLO_VALIDATION) is None
# Rows are per provider: a 422 is a validation status almost everywhere else.
assert S.classify("hunter", 422, None, APOLLO_OUT_OF_CREDITS).kind == "unrecorded"
# Icypeas, every paid route, an empty pool: HTTP 200 (read from archived answers, 2026-09-27).
ICYPEAS_OUT_OF_CREDITS = (b'{"validationErrors":[{"field":"user","message":"insufficient_credits",'
b'"humanReadableMessage":"Insufficient credits to run the search",'
b'"type":"InsufficientCredits","expected":null,"actual":null}],"success":false}')
def test_icypeas_says_out_of_credits_with_a_200():
sig = S.classify("icypeas", 200, None, ICYPEAS_OUT_OF_CREDITS)
assert sig.kind == "balance" and S.is_exhausting(sig)
assert S.classify("icypeas", 200, None, b'{"total":3,"success":true,"leads":[]}') is None
assert S.classify("hunter", 200, None, ICYPEAS_OUT_OF_CREDITS) is None, "a 2xx is only ever read per provider"
def test_every_recorded_phrase_arms_the_tripwire():
"""Recording one vendor's wording must arm the tripwire for every other: each literal body
phrase in `_TABLE` (the 429 rows carry period words, not capacity phrases) is in CAPACITY_PHRASES."""
import re as _re
for provider, status, pattern, kind in S._TABLE:
if kind not in ("balance", "quota") or not pattern or status == 429 or _re.escape(pattern) != pattern:
continue # empty (the bare 402 row), a period word, or a regex we cannot use as a body
sig = S.classify("someone-else", 400, None, pattern.encode())
assert sig is not None and sig.kind == "unrecorded", f"{provider}'s phrase {pattern!r} does not arm the tripwire"
def test_an_unrecorded_vendor_phrase_is_a_tripwire_never_a_mark():
"""The next Apollo: a 4xx no row matched whose body still names credits/quota/balance. It is
logged and counted (`capacity_signal=unrecorded`) and does nothing else."""
sig = S.classify("someone", 403, None, b'{"message":"Your quota has been exceeded"}')
assert sig.kind == "unrecorded" and sig.detail == "quota has been exceeded" and not S.is_exhausting(sig)
# a phrase recorded for ONE vendor arms the tripwire for every other
assert S.classify("someone", 400, None, b"Not enough credits").kind == "unrecorded"
assert S.classify("someone", 422, None, b'{"error":"insufficient parameters: first_name"}') is None
# echoes of a caller's own request: period words, path names, half-words
assert S.classify("coingecko", 400, None, b'{"error":"invalid interval: daily"}') is None
assert S.classify("exa", 400, None, b"unbalanced quotes in query") is None
assert S.classify("tikhub", 400, None, b"unterminated quotation mark") is None
assert S.classify("twelvedata", 400, None, b"/balance_sheet: symbol parameter is missing") is None
assert S.classify("tikhub", 400, None, b'{"error":"field balance is required"}') is None
assert S.classify("tikhub", 400, None, b"quota must be an integer") is None
assert S.classify("someone", 403, None, b"Your API quota limit was reached").kind == "unrecorded"
# a rowless vendor saying it with a 429 and no retry-after trips the same wire, not `unknown`
sig = S.classify("serpstat", 429, None, b'{"error":"Your monthly quota has been exhausted"}')
assert sig.kind == "unrecorded" and sig.detail == "quota has been exhausted"
assert S.classify("scrapecreators", 429, None, b"slow down").kind == "unknown"
assert S.classify("someone", 401, None, b'{"error":"insufficient credits"}') is None, "401 is the key"
assert S.classify("someone", 404, None, b'{"error":"no balance found for id"}') is None
assert S.classify("someone", 500, None, b'{"error":"balance service down"}') is None
# Platform providers whose out-of-credit answer nobody has recorded in `_TABLE` yet. An acknowledged
# gap, not a claim the vendor never runs dry: their 4xx trips `unrecorded` instead.
_UNRECORDED_SIGNATURE = {
"adyntel", # no balance endpoint; documented 402 does not uniquely prove wallet exhaustion
"apify", "aviato", "branddev", "brightdata", "coingecko", "coresignal", "crustdata", "dataforseo",
"diffbot", "exa", "fiber-ai", "finnhub", "justoneapi", "marketstack",
"sumble", # exhaustion not forced; no overflow route claimed
"harvestapi", # wallet exhaustion unobserved; no overflow route
"quickenrich", # subscription exhaustion not observed; do not spend the trial to force it
"dropleads", # free trial was not exhausted; no provider-specific 402 body recorded
"prospeo", # Starter allowance was not exhausted; no provider-specific body was forced
"aiark", # funded allowance was not exhausted; no provider-specific body was forced
"wiza", # The funded grant was not exhausted; no provider-specific body was forced
"limadata", # Starter credits remain; no provider-specific empty-balance body was forced
"getleadsio", # promotional allocation was not exhausted; bare 402 remains the generic signal
"keenable", # funded request balance remains; documented bare 402 was not forced
"olostep", # funded credit balance remains; documented 402 was not forced
"firecrawl", # credit balance remains; documented 402 was not deliberately forced
"search1api", # funded credits remain; documented generic 402 was not deliberately forced
"linkup", # 429 means either exhausted credit or excess concurrency; balance was not exhausted
"scrapegraphai", # trial credits remain; no provider-specific empty-balance body was forced
"scrubby", # funded account not exhausted; no provider-specific empty-balance body recorded
"millionverifier", # funded-account exhaustion not observed; trial still has credits
"bounceban", # verification credits remain; exhaustion was not forced and no overflow is claimed
"zerobounce", # credits remain; exhaustion was not forced and no overflow is claimed
"datagma", # prepaid credits remain; exhaustion was not forced and no overflow is claimed
"moltsets", # rolling allowance exhaustion was not forced; no overflow route claimed
"openmart", # funded subscription was not exhausted; shared-key exhaustion was not forced
# Bare 402 is already the generic balance signal; the real empty-Credits body was not forced.
"financialdatasets",
"fetchinio", # funded credits remain; documented generic 402 was not deliberately forced
"fishaudio", # shared-key serving stays disabled until the funded-account signatures are verified
"minimax", "oceanio", "openrouter", "replicate", "scrapecreators", "seranking",
"google-ai", # postpaid Cloud project: 429 RESOURCE_EXHAUSTED is a tier rate/spend cap, not a wallet
"piapi", # prepaid wallet exhaustion not observed ($50 funded 2026-09-14); no overflow route
"serper", # funded credits remain; no provider-specific empty-balance response was forced
"litescrape", # funded call balance remains; no empty-balance response was forced
"tinyfish", # funded wallet remains; no provider-specific empty-wallet response was forced
"spidercloud", # funded dollar balance remains; no empty-balance response was forced
"perplexity", # auto top-up is enabled; no empty-credit response was forced
"trestleiq", # funded wallet remains; documented 403/429 shapes do not identify empty balance
"you", # funded wallet remains; no provider-specific empty-balance response was forced
"valyu", # subscription credits remain; no provider-specific empty-balance response was forced
"octen", # PAYG balance remains; no provider-specific empty-balance response was forced
"serpapi", "serpstat", "spyfu", "tiingo", "tikhub", "tomba", "twelvedata",
}
def test_every_platform_provider_has_a_recorded_or_acknowledged_signature():
"""The guard. A provider gains a `platform_key_*` slot → record how it says "out of credits"
(a row in `_TABLE`) or add it above knowingly. Silence is how Apollo's 422 went unseen."""
from treg.config import platform_setting_name
from treg.domain.capacity.collectors import all_platform_providers
from treg.domain.catalog import store as catalog_store
# Slot spelling is not provider spelling (`fiber_ai` vs `fiber-ai`); rows match `mk.provider`,
# the catalog id, so the guard must speak that or a row written to satisfy it never fires.
settings = {"platform_key_" + s for s in all_platform_providers()}
catalog_ids = {e["provider"] for e in catalog_store.load().endpoints}
slots = {p for p in catalog_ids if platform_setting_name(p) in settings}
assert len(slots) == len(settings), f"a key slot with no catalog provider: {sorted(settings - {platform_setting_name(p) for p in slots})}"
recorded = {p for p, *_ in S._TABLE if p != "*"}
missing = slots - recorded - _UNRECORDED_SIGNATURE
assert not missing, f"record how these say 'out of credits' or acknowledge them: {sorted(missing)}"
stale = (_UNRECORDED_SIGNATURE & recorded) | (_UNRECORDED_SIGNATURE - slots)
assert not stale, f"acknowledged providers that are now recorded or gone: {sorted(stale)}"
_CF_PAGE = (b"<!DOCTYPE html><html><head><title>Access denied | api.example.com used Cloudflare "
b"to restrict access</title></head><body>Error 1010</body></html>")
def test_a_cdn_block_page_is_an_edge_block_not_an_account_signal():
"""A CDN refusing the request's shape says nothing about treg's account: never exhausting."""
assert S.classify("anyone", 403, {"cf-mitigated": "challenge"}, b"").kind == "edge_block"
assert S.classify("anyone", 503, {"cf-mitigated": "challenge"}, b"").kind == "edge_block"
hdrs = ((b"Server", b"cloudflare"), (b"Content-Type", b"text/html; charset=UTF-8"), (b"CF-RAY", b"9x"))
sig = S.classify("anyone", 403, hdrs, _CF_PAGE)
assert sig.kind == "edge_block" and not S.is_exhausting(sig)
# A JSON 403 from behind the same CDN is the vendor's own answer.
assert S.classify("anyone", 403, {"server": "cloudflare", "content-type": "application/json"},
b'{"error":"too many requests"}') is None
assert S.classify("thecompaniesapi", 403, {"server": "cloudflare", "content-type": "application/json"},
b'{"code":"noCreditsRemaining"}').kind == "balance"
# An HTML 503 without the marker is the vendor down, not a block.
assert S.classify("anyone", 503, {"server": "cloudflare", "content-type": "text/html"}, b"<html>") is None
# The CDN's problem-JSON block (error 1010, "browser's signature"): no marker header, not HTML.
cf_json = (b'{"type":"https://developers.cloudflare.com/support/troubleshooting/http-status-codes/'
b'cloudflare-1xxx-errors/error-1010/","title":"Error 1010: Access denied","status":403}')
sig = S.classify("anyone", 403, {"content-type": "application/problem+json"}, cf_json)
assert sig.kind == "edge_block" and not S.is_exhausting(sig)
# ---- aggregator envelopes round-trip their fixtures --------------------------------------------
def test_orthogonal_build_wraps_the_vendor_request_unchanged():
req = orthogonal.build(_route(), "K", {"limit": 1, "domain": "stripe.com"}, b'{"name":"Patrick"}',
{"id": "42"})
assert req.url.endswith("/run") and req.headers["Authorization"] == "Bearer K"
assert req.json == {"api": "findymail", "path": "/api/search/business-profile",
"query": {"limit": "1", "domain": "stripe.com"}, "body": {"name": "Patrick"}}
req2 = orthogonal.build(_route(agg_path="/api/v1/{id}/x"), "K", [], None, {"id": "42"})
assert req2.json == {"api": "findymail", "path": "/api/v1/42/x"}
def test_orthogonal_parse_success_price_upstream_error_and_contract():
ok = _fixture("orthogonal_ok")
res = orthogonal.parse(ok["status"], json.dumps(ok["body"]).encode())
assert res.ok and res.upstream_status == 200 and res.cost_micro == ok["expect"]["price"] * 10_000
assert json.loads(res.upstream_body) == ok["body"]["data"] # the vendor body, verbatim
rel = _fixture("orthogonal_upstream_402")
res = orthogonal.parse(rel["status"], json.dumps(rel["body"]).encode())
assert res.failure is None and res.upstream_status == 402 and not res.ok
assert json.loads(res.upstream_body) == rel["body"]["data"]
con = _fixture("orthogonal_contract_404")
res = orthogonal.parse(con["status"], json.dumps(con["body"]).encode())
assert res.upstream_status == 404 and res.failure is None # the vendor DID answer here (data present)
only_contract = {"success": False, "error": "x", "_orthogonal": {"error": "orthogonal_endpoint_contract", "message": "company required"}}
res = orthogonal.parse(400, json.dumps(only_contract).encode())
assert res.failure == "contract" and res.cost_micro == 0
assert orthogonal.parse(401, b'{"error":"invalid key"}').failure == "aggregator_auth"
assert orthogonal.parse(402, b'{"success":false,"error":"insufficient balance"}').failure == "aggregator_balance"
assert orthogonal.parse(200, b"<html>").failure == "malformed"
def test_monid_build_and_parse_fixtures():
r = _route(aggregator="monid", agg_slug="hunterio", agg_path="/domain-search", agg_unit="result")
req = monid.build(r, "K", {"domain": "stripe.com", "limit": "1", "score": "0.5", "raw": "true", "id": "007a"}, None)
assert req.json == {"provider": "hunterio", "endpoint": "/domain-search",
"input": {"queryParams": {"domain": "stripe.com", "limit": 1, "score": 0.5, "raw": True, "id": "007a"},
"body": {}, "pathParams": {}}}, "Monid validates JSON types: numeric strings become numbers (live 2026-08-28)"
akta = _route(aggregator="monid", agg_slug="akta", agg_path="/v1/company/enrichment")
req = monid.build(akta, "K", {"company": "canva.com", "sections": "location,technology"}, None)
assert req.json["input"]["queryParams"] == {
"company": "canva.com", "sections": ["location", "technology"],
}, "Monid's Akta schema models the vendor's comma-separated sections parameter as an array"
alt = monid.build(r, "K", {"domain": "stripe.com"}, None, params_as_body=True)
assert alt.json["input"] == {"queryParams": {}, "body": {"domain": "stripe.com"}, "pathParams": {}}
ok = _fixture("monid_ok_sync")
res = monid.parse(ok["status"], json.dumps(ok["body"]).encode())
assert res.ok and res.upstream_status == 200 and res.cost_micro == 23_920
assert json.loads(res.upstream_body) == ok["body"]["output"]
miss = _fixture("monid_miss_zero_cost")
res = monid.parse(miss["status"], json.dumps(miss["body"]).encode())
assert res.ok and res.cost_micro == 0 and res.extra["result_count"] == 0
bad = _fixture("monid_validator_400")
res = monid.parse(bad["status"], json.dumps(bad["body"]).encode())
assert res.failure == "contract" and "Invalid input" in res.detail and res.cost_micro == 0
pend = _fixture("monid_async_202")
first = {"runId": pend["body"]["runId"], "status": "RUNNING"}
res = monid.parse(202, json.dumps(first).encode())
assert res.failure == "pending" and res.poll_url.endswith(pend["body"]["runId"])
done = monid.parse(200, json.dumps(pend["body"]).encode())
assert done.ok and done.cost_micro == 12_000 and json.loads(done.upstream_body) == pend["body"]["output"]
assert monid.parse(401, b"{}").failure == "aggregator_auth"
assert monid.parse(403, b'{"message":"invalid api key"}').failure == "aggregator_auth"
assert monid.parse(402, b'{"message":"hit your account maximum"}').failure == "aggregator_balance"
failed = {"runId": "r", "status": "FAILED", "message": "provider down", "providerResponse": {"httpStatus": 503}}
res = monid.parse(200, json.dumps(failed).encode())
assert res.failure is None and res.upstream_status == 503 and not res.ok
assert by_name("monid") is monid and by_name("orthogonal") is orthogonal
def test_monid_completed_vendor_403_is_not_an_aggregator_auth_failure():
fixture = _fixture("monid_contactout_quota_403")
relayed = monid.parse(fixture["status"], json.dumps(fixture["body"]).encode())
assert relayed.failure is None
assert relayed.upstream_status == fixture["expect"]["upstream_status"]
assert relayed.cost_micro == fixture["expect"]["cost_micro"]
assert json.loads(relayed.upstream_body)["message"].startswith("You're out of credits")
classified = with_vendor_verdict(relayed, "contactout")
assert classified.failure == fixture["expect"]["failure"] == VENDOR_DRY
assert classified.detail.startswith("quota:")
refused_doc = {
"runId": "run-refused", "status": "COMPLETED",
"providerResponse": {"httpStatus": 403, "error": {"message": "No access to endpoint"}},
}
refused = with_vendor_verdict(monid.parse(403, refused_doc), "contactout")
assert refused.failure == VENDOR_REFUSAL
assert "No access to endpoint" in refused.detail
def test_monid_failed_run_keeps_zero_cost_and_error_detail():
failed = {
"runId": "run-failed", "status": "FAILED",
"providerResponse": {"httpStatus": 503, "error": {"message": "provider unavailable"}},
}
res = monid.parse(503, failed)
assert res.failure is None and res.upstream_status == 503
assert res.cost_micro == 0
assert res.detail == "provider unavailable"
assert json.loads(res.upstream_body) == failed["providerResponse"]["error"]
def test_every_fixture_round_trips_through_its_adapter():
for path in FIX.glob("*.json"):
fx = json.loads(path.read_text())
adapter = orthogonal if path.name.startswith("orthogonal") else monid
res = adapter.parse(fx["status"], json.dumps(fx["body"]).encode())
assert res.failure in (None, "contract", "pending"), (path.name, res.failure, res.detail)
exp = fx["expect"]
if exp.get("ok") is True:
assert res.ok and res.upstream_body, path.name
if exp.get("upstream_status") and res.failure is None:
assert res.upstream_status == exp["upstream_status"], path.name
# ---- verification -----------------------------------------------------------------------------
def test_shape_fingerprint_ignores_values_but_not_structure():
a = b'{"data":{"email":"a@x.io","score":90,"sources":[{"uri":"u"}]}}'
b = b'{"data":{"email":"b@y.io","score":1,"sources":[{"uri":"v"},{"uri":"w"}]}}'
c = b'{"data":{"email":"b@y.io"}}'
assert V.shapes_match(a, b) is True and V.shapes_match(a, c) is False and V.shapes_match(a, b"nope") is None
diff = V.shape_difference(a, c)
assert "$.data.score:leaf" in diff and "$.data.sources:list" in diff
assert "a@x.io" not in diff
keyed = V.shape_difference(
b'{"results":{"ada@acme.com":{"score":1,"title":"x"}}}',
b'{"results":{"ada@acme.com":{"score":1}}}',
)
assert "ada@acme.com" not in keyed
assert "$.results.<identifier>.title:leaf" in keyed
assert V._safe_shape_key("acme.com") == "<identifier>"
assert V._safe_shape_key("550e8400-e29b-41d4-a716-446655440000") == "<identifier>"
assert V._safe_shape_key("title") == "title"
identifiers_only = V.shape_difference(
b'{"r":{"ada@acme.com":{"s":1}}}',
b'{"r":{"bob@other.com":{"s":1}}}',
)
assert identifiers_only == "difference is confined to redacted map keys"
def test_shape_empty_vs_nonempty_list_differs_but_both_empty_match():
"""Regression for leadsforge.people.enrich.job.list shape verification failure.
The endpoint lists account-specific jobs. Direct (treg's Leadsforge account) vs relay
(Orthogonal's Leadsforge account) returned different job histories: one had jobs, the
other empty. `shape()` distinguishes `[]` from `[{...}]`, so verify failed.
Fix: set `test_request.queryParams.from` to a far-future date, guaranteeing both
accounts return empty lists → same shape → verify passes.
"""
with_jobs = b'{"jobs": [{"jobID": "abc", "status": "completed"}], "offset": 0, "limit": 5, "total": 1}'
no_jobs = b'{"jobs": [], "offset": 0, "limit": 5, "total": 0}'
both_empty = b'{"jobs": [], "offset": 0, "limit": 5, "total": 0}'
assert V.shapes_match(with_jobs, no_jobs) is False, "non-empty vs empty list shapes must differ"
assert V.shapes_match(no_jobs, both_empty) is True, "both-empty lists must have same shape"
assert V.shape({"jobs": []}) == {"jobs": []}
assert V.shape({"jobs": [{"id": "x"}]}) == {"jobs": [{"id": "leaf"}]}
def _verification(**kw) -> V.Verification:
base = dict(endpoint_id="e", aggregator="orthogonal", direct_status=200, relay_status=200, same_shape=True,
cost_micro=1, verified_at=utcnow_naive(), note="")
return V.Verification(**{**base, **kw})
def test_verdict_disables_only_a_route_that_is_actually_wrong():
"""The verifier must never switch off the routes that exist to cover OUR key running dry, nor
every route behind an aggregator that is having a bad morning."""
assert V.verdict(_verification()) == "passed"
# nothing to compare with: our own account dry (the 2026-09-01 state), a 401, no vendor key,
# the vendor host down, a stale test_request that fails both legs, an async run still pending
assert V.verdict(_verification(direct_status=422, same_shape=None, verified_at=None, direct_dry=True)) == "inconclusive"
assert V.verdict(_verification(direct_status=401, same_shape=None, verified_at=None)) == "inconclusive"
assert V.verdict(_verification(direct_status=None, same_shape=None, verified_at=None)) == "inconclusive"
assert V.verdict(_verification(direct_status=None, relay_status=400, same_shape=None, verified_at=None)) == "inconclusive"
assert V.verdict(_verification(direct_status=401, relay_status=400, same_shape=None, verified_at=None)) == "inconclusive"
assert V.verdict(_verification(direct_status=422, relay_status=404, same_shape=None, verified_at=None, direct_dry=True)) == "inconclusive"
assert V.verdict(_verification(relay_status=None, same_shape=None, verified_at=None, failure="pending")) == "inconclusive"
# the aggregator's side: key, account, host, envelope, its vendor pool
for failure in ("aggregator_auth", "aggregator_balance", "malformed", "unreachable",
"vendor_dry", "vendor_refusal"):
assert V.verdict(_verification(direct_status=None, relay_status=None, same_shape=None, verified_at=None,
failure=failure)) == "aggregator", failure
# this route is shown wrong: the direct leg proves the request, the relay does not match it
assert V.verdict(_verification(same_shape=False, verified_at=None)) == "failed"
assert V.verdict(_verification(relay_status=None, same_shape=None, verified_at=None, failure="contract")) == "failed"
assert V.verdict(_verification(relay_status=500, same_shape=None, verified_at=None)) == "failed"
assert V.verdict(_verification(relay_status=404, same_shape=None, verified_at=None)) == "failed"
async def test_verify_route_with_our_own_key_dry_is_inconclusive_not_a_failure():
import httpx
r = _route(aggregator="orthogonal", agg_slug="apollo", agg_path="/people/match", method="POST", path="/people/match",
provider="apollo")
def handler(request: httpx.Request) -> httpx.Response:
if request.url.host == "api.orthogonal.dev" or "/run" in request.url.path:
return httpx.Response(200, json={"success": True, "data": {"person": {"id": "p"}}, "priceCents": 1.0})
return httpx.Response(422, json={"error": "Insufficient credits. Please upgrade your plan."})
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c:
v = await V.verify_route(c, r, key="K", direct=("https://api.apollo.io/api/v1/people/match", {}, b"{}"),
test_request={"body": {"email": "x@y.z"}}, direct_headers={})
assert not v.passed and v.direct_status == 422 and v.relay_status == 200
assert v.direct_dry and v.relay_ok, "our account, in Apollo's dialect - the worker still stamps this"
assert V.verdict(v) == "inconclusive", "the route still works; it is our account that is dry"
async def test_verify_route_reads_a_relayed_out_of_credits_answer_as_the_aggregators_dry_account():
import httpx
r = _route(aggregator="orthogonal", agg_slug="apollo", agg_path="/people/match", method="POST", path="/people/match",
provider="apollo")
def handler(request: httpx.Request) -> httpx.Response:
if "/run" in request.url.path: # Orthogonal relays Apollo's 422 with its body
return httpx.Response(422, json={"success": False, "error": "Upstream returned status 422",
"data": {"error": "Insufficient credits. Please upgrade your plan."}, "priceCents": 1.0})
return httpx.Response(200, json={"person": {"id": "p"}})
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c:
v = await V.verify_route(c, r, key="K", direct=("https://api.apollo.io/api/v1/people/match", {}, b"{}"),
test_request={"body": {"email": "x@y.z"}}, direct_headers={})
assert v.relay_status == 422 and v.failure == "vendor_dry" and v.note.startswith("vendor_dry: balance")
assert V.verdict(v) == "aggregator", "Orthogonal's Apollo account is empty; the route is not wrong"
async def test_verify_route_marks_same_shape_and_polls_async_runs():
import httpx
r = _route(aggregator="monid", agg_slug="hunterio", agg_path="/email-finder", agg_unit="result",
method="GET", path="/email-finder")
calls = []
def handler(request: httpx.Request) -> httpx.Response:
calls.append((request.method, request.url.path))
if request.url.path == "/v1/run":
return httpx.Response(202, json={"runId": "RUN1", "status": "RUNNING"})
if request.url.path == "/v1/runs/RUN1":
return httpx.Response(200, json={"runId": "RUN1", "status": "COMPLETED", "output": {"data": {"email": "x"}},
"providerResponse": {"httpStatus": 200},
"billing": {"reportedCost": {"value": 23920, "unit": "MICRO_DOLLAR"}}})
return httpx.Response(200, json={"data": {"email": "y"}}) # the direct call
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c:
v = await V.verify_route(c, r, key="K", direct=("https://api.hunter.io/v2/email-finder", {"domain": "x"}, None),
test_request={"queryParams": {"domain": "x"}}, direct_headers={})
assert v.passed and v.cost_micro == 23_920 and v.verified_at is not None
assert ("GET", "/v1/runs/RUN1") in calls
# verify never polls forever on a stuck run
async def stuck(request):
return httpx.Response(202, json={"runId": "R", "status": "RUNNING"})
async with httpx.AsyncClient(transport=httpx.MockTransport(stuck)) as c:
res = await V.relay_once(c, r, "K", {}, None, max_polls=2, poll_wait_s=0)
assert res.failure == "pending"
def test_pdl_operation_allowance_does_not_lock_other_pdl_products():
from treg.domain.capacity.marks import lock_key
body = b'{"status":402,"error":{"message":"You have hit your account maximum for person_identify (all matches used)"}}'
signal = S.classify('pdl', 402, None, body)
assert signal.kind == 'quota'
assert lock_key('pdl', 'pdl.x.person-identify', signal.kind) == 'pdl.x.person-identify'
assert S.classify('pdl', 402, None, b'{"error":"Insufficient credits"}').kind == 'balance'
assert S.classify('pdl', 400, None, b'{"error":"email is required"}') is None
def test_orthogonal_relaying_icypeas_empty_pool_is_the_aggregators_dry_account():
"""Orthogonal's own Icypeas pool can run dry too, and relays the same 200: never served, never billed."""
import json as _json
from treg.infra.upstream.aggregators import VENDOR_DRY, orthogonal, with_vendor_verdict
envelope = {"success": True, "data": _json.loads(ICYPEAS_OUT_OF_CREDITS), "priceCents": 1,
"requestId": "run_x", "billing": {"chargedPriceCents": 1}}
res = with_vendor_verdict(orthogonal.parse(200, _json.dumps(envelope).encode()), "icypeas")
assert res.failure == VENDOR_DRY