Files
treg/tests/test_marketplace_call.py

3206 lines
162 KiB
Python
Raw Permalink 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.
"""Direct marketplace calls: `treg call <catalog-endpoint-id>` with no registered tool.
The credential ladder (docs/context/architecture/catalog.md §platform-eligible, and the header
comment above `_resolve_marketplace_call`): an org tool for the provider wins (tier 1), else an org credential matching the provider is
injected via a virtual, never-persisted tool (tier 2), else — for an endpoint treg is willing to spend
its own money on — TREG'S OWN key, metered against the org's prepaid balance (tier 4), and only then
the actionable connect/secret error (tier 3).
Tier 4 is the only rung that spends OUR money, so most of what follows is about the fences around it:
it is shadowed by any credential the org already has, it is off unless the provider is allow-listed AND
keyed, it refuses demo orgs, it reserves before the request leaves and settles/releases after, and the
platform key must never appear in a response, an error, or an audit row.
"""
from __future__ import annotations
import json
import httpx
from datetime import datetime, timezone
import pytest
from httpx import AsyncClient
from treg import api as A, audit, oauth_providers
from treg.domain import money as ledger
from treg.domain.money import settlement as settlement_basis
from treg.domain.catalog import store as catalog_store
from treg.application.call import contactout, octen
from treg.application.call import resolve as call_resolution
from treg.application.call import settle as call_settle
from treg.application.call import service as call_service
from treg.application.call.types import ResolutionFailed, UpstreamResponse
from treg.config import get_settings
from treg.infra.db import session_maker
from treg.models import Org
EP = "tikhub.tiktok.video.comments" # GET /api/v1/tiktok/web/fetch_post_comment, aweme_id required
EP_PATH = "/api/v1/tiktok/web/fetch_post_comment"
EP_MICRO = 1_000 # $0.001/call, cost.type per_success
EP_CALL = "scrapecreators.x.v1-facebook-group" # GET, cost.type PER_CALL ($0.00188) — a 4xx is billable
EP_CALL_MICRO = 1_880
EP_DFS = "dataforseo.web.page.audit" # POST; priced per crawled PAGE, and dataforseo reports
EP_DFS_MICRO = 150 # $0.00015/page × the ONE task in the test body (array length drives the estimate)
PLATFORM_KEYS = { # never a real key: a test that leaked one into an assertion would print it
"TIKHUB": "PLATFORM-TIKHUB-KEY",
"SCRAPECREATORS": "PLATFORM-SC-KEY",
"DATAFORSEO": "PLATFORM-DFS-KEY",
"BRIGHTDATA": "PLATFORM-BD-KEY",
"APOLLO": "PLATFORM-APOLLO-KEY",
}
class _DropleadsJSONStream(httpx.AsyncByteStream):
def __init__(self, doc):
self.body = json.dumps(doc).encode()
async def __aiter__(self):
yield self.body
def _dropleads_response(status: int, doc: dict) -> httpx.Response:
return httpx.Response(
status,
headers={"content-type": "application/json"},
stream=_DropleadsJSONStream(doc),
)
@pytest.fixture
def platform_on(monkeypatch):
"""Turn tier 4 on the way a deploy does: keys in the environment AND the provider allow-listed."""
for name, value in PLATFORM_KEYS.items():
monkeypatch.setenv(f"TREG_PLATFORM_KEY_{name}", value)
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", ",".join(k.lower() for k in PLATFORM_KEYS))
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def diffbot_platform_on(monkeypatch):
"""Enable Diffbot tier 4 without exposing or calling a real provider credential."""
monkeypatch.setenv("TREG_PLATFORM_KEY_DIFFBOT", "PLATFORM-DIFFBOT-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "diffbot")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def openmart_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_OPENMART", "PLATFORM-OPENMART")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "openmart")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def trestleiq_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_TRESTLEIQ", "PLATFORM-TRESTLEIQ")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "trestleiq")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def adyntel_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_ADYNTEL", "PLATFORM-ADYNTEL")
monkeypatch.setenv("TREG_PLATFORM_EMAIL_ADYNTEL", "platform@example.com")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "adyntel")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def test_adyntel_byok_pair_wins_and_remains_unmetered(
clients, adyntel_platform_on,
):
key_id = (await clients.post(
"/secrets", json={"name": "adyntel-key", "value": "OWN-KEY"},
)).json()["id"]
email_id = (await clients.post(
"/secrets", json={"name": "adyntel-email", "value": "own@example.com"},
)).json()["id"]
tool = await clients.post("/tools", json={
"name": "adyntel",
"base_url": "https://api.adyntel.com",
"bindings": [
{"secret_id": key_id, "injector": "env", "location": "json",
"name": "api_key", "format": "{secret}"},
{"secret_id": email_id, "injector": "env", "location": "json",
"name": "email", "format": "{secret}"},
],
})
assert tool.status_code == 200, tool.text
captured = {}
def upstream(request: httpx.Request) -> httpx.Response:
captured.update(json.loads(request.content))
return httpx.Response(
200, stream=httpx.ByteStream(b'{"ads":[{"id":"sample"}]}'),
headers={"content-type": "application/json"},
)
await A.app.state.http.aclose()
A.app.state.http = AsyncClient(transport=httpx.MockTransport(upstream))
before = await _balance(clients)
response = await clients.post(
"/call/adyntel.google.ads.transparency", json={"company_domain": "example.com"},
)
assert response.status_code == 200, response.text
assert captured["api_key"] == "OWN-KEY"
assert captured["email"] == "own@example.com"
assert "X-Treg-Cost-Micro" not in response.headers
assert await _balance(clients) == before
@pytest.fixture
def tavily_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_TAVILY", "PLATFORM-TAVILY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "tavily")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture
def firecrawl_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_FIRECRAWL", "PLATFORM-FIRECRAWL")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "firecrawl")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def _balance(clients: AsyncClient) -> int:
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
return (await clients.get(f"/orgs/{org_id}/balance")).json()["balance_micro"]
async def test_litescrape_platform_and_own_key_ladder(clients, monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_LITESCRAPE", "PLATFORM-LITESCRAPE")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "litescrape")
get_settings.cache_clear()
seen = []
def upstream(request):
seen.append(request.headers["authorization"])
if request.url.path == "/api/google/search":
assert request.url.params["q"] == "example"
return _dropleads_response(200, {"organic_results": [{"title": "Example"}]})
if request.url.path == "/api/artifacts/web/screenshot":
assert request.url.params["id"] == "sample"
assert request.url.params["bucket"] == "20260929"
return httpx.Response(200, stream=httpx.ByteStream(b"\x89PNG\r\n\x1a\n"),
headers={"content-type": "image/png"})
assert request.url.path == "/api/web/screenshot"
assert request.url.params["url"] == "https://example.com"
return _dropleads_response(200, {"screenshot_url": "https://api.litescrape.com/api/artifacts/web/screenshot?id=sample&bucket=20260929"})
try:
async with AsyncClient(transport=httpx.MockTransport(upstream)) as vendor:
monkeypatch.setattr(A.app.state, "http", vendor)
before = await _balance(clients)
platform = await clients.get("/call/litescrape.google.serp.organic", params={"q": "example"})
assert platform.status_code == 200, platform.text
assert platform.headers["X-Treg-Cost-Micro"] == "150"
assert await _balance(clients) == before - 150
assert seen == ["Bearer PLATFORM-LITESCRAPE"]
screenshot = await clients.get("/call/litescrape.web.screenshot", params={"url": "https://example.com"})
assert screenshot.status_code == 404
assert seen == ["Bearer PLATFORM-LITESCRAPE"]
secret = await clients.post("/secrets", json={"name": "litescrape", "value": "OWN-LITESCRAPE"})
assert secret.status_code == 200, secret.text
own_before = await _balance(clients)
own = await clients.get("/call/litescrape.google.serp.organic", params={"q": "example"})
assert own.status_code == 200, own.text
assert "X-Treg-Cost-Micro" not in own.headers
assert await _balance(clients) == own_before
assert seen[-1] == "Bearer OWN-LITESCRAPE"
own_screenshot = await clients.get("/call/litescrape.web.screenshot", params={"url": "https://example.com"})
assert own_screenshot.status_code == 200, own_screenshot.text
assert own_screenshot.json()["screenshot_url"].endswith("id=sample&bucket=20260929")
assert "X-Treg-Cost-Micro" not in own_screenshot.headers
own_image = await clients.get("/call/litescrape.web.screenshot.download", params={
"id": "sample", "bucket": "20260929",
})
assert own_image.status_code == 200, own_image.text
assert own_image.content == b"\x89PNG\r\n\x1a\n"
assert await _balance(clients) == own_before
finally:
get_settings.cache_clear()
async def test_firecrawl_platform_scrape_bills_a_returned_404_and_byok_wins(
clients: AsyncClient, monkeypatch, firecrawl_platform_on,
):
seen = []
def upstream(request):
seen.append(request.headers["authorization"])
assert request.url.path == "/v2/scrape"
assert json.loads(request.content)["parsers"] == []
return _dropleads_response(200, {"success": True, "data": {
"markdown": "# Missing", "metadata": {"statusCode": 404},
}})
async with AsyncClient(transport=httpx.MockTransport(upstream)) as vendor:
monkeypatch.setattr(A.app.state, "http", vendor)
before = await _balance(clients)
response = await clients.post("/call/firecrawl.web.scrape", json={
"url": "https://example.com/missing", "formats": ["markdown"], "parsers": [],
})
assert response.status_code == 200, response.text
assert response.headers["X-Treg-Cost-Micro"] == "5000"
assert await _balance(clients) == before - 5000
assert seen == ["Bearer PLATFORM-FIRECRAWL"]
secret = await clients.post("/secrets", json={"name": "firecrawl", "value": "OWN-FIRECRAWL"})
assert secret.status_code == 200, secret.text
own_before = await _balance(clients)
own = await clients.post("/call/firecrawl.web.scrape", json={
"url": "https://example.com", "formats": ["markdown"], "parsers": [],
})
assert own.status_code == 200, own.text
assert "X-Treg-Cost-Micro" not in own.headers
assert await _balance(clients) == own_before
assert seen[-1] == "Bearer OWN-FIRECRAWL"
async def test_firecrawl_search_settles_reported_credits(
clients: AsyncClient, monkeypatch, firecrawl_platform_on,
):
def upstream(request):
assert request.url.path == "/v2/search"
return _dropleads_response(200, {
"success": True, "data": {"web": []}, "creditsUsed": 1,
})
async with AsyncClient(transport=httpx.MockTransport(upstream)) as vendor:
monkeypatch.setattr(A.app.state, "http", vendor)
before = await _balance(clients)
response = await clients.post("/call/firecrawl.web.search", json={
"query": "example", "limit": 3,
})
assert response.status_code == 200, response.text
assert response.headers["X-Treg-Cost-Micro"] == "5000"
assert await _balance(clients) == before - 5000
async def _entries(clients: AsyncClient) -> list[dict]:
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
return (await clients.get(f"/orgs/{org_id}/balance")).json()["entries"]["items"]
async def _telemetry(clients: AsyncClient) -> dict:
"""The newest audit row, with the marketplace/spend columns."""
await audit.drain()
rows = (await clients.get("/calls")).json()
return rows[0]
def _fake_relay(status_code: int, body: bytes = b"{}", *, raises: Exception | None = None):
"""Stand in for `relay` when the test needs a specific UPSTREAM outcome the echo app can't give
(a provider 5xx, a network error, a provider-reported cost). Everything else uses the real relay."""
async def _relay(request, upstream_url, tool, secrets, client, drop_params=None, force_identity=False):
if raises is not None:
raise raises
async def _stream():
yield body
async def _close():
return None
return UpstreamResponse(status_code, (), _stream(), _close)
return _relay
# ---- tiers 1-3 (unchanged behaviour) -----------------------------------------------------------
async def test_tier2_org_credential_no_tool(clients: AsyncClient):
"""A secret NAMED for the provider serves the call — and no tool row appears."""
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"})
r = await clients.get(f"/call/{EP}?aweme_id=7&count=5")
assert r.status_code == 200, r.text
d = r.json()
assert d["auth"] == "Bearer MKKEY" # injected the provider's way
assert d["raw_path"] == EP_PATH # endpoint id resolved to the real path
assert d["query"] == {"aweme_id": "7", "count": "5"}
tools = (await clients.get("/tools")).json()
assert tools == [], "tier 2 must not materialize a tool row"
assert (await _telemetry(clients))["tool_name"] == EP
async def test_tier1_registered_tool_wins(clients: AsyncClient):
"""An org tool for the provider's host serves the call with ITS binding — the registry
stays authoritative over the marketplace fallback."""
sid = (await clients.post("/secrets", json={"name": "own-key", "value": "OWN"})).json()["id"]
await clients.post("/tools", json={"name": "our-tikhub", "base_url": "https://api.tikhub.io", "secret_id": sid})
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"}) # tier-2 bait
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 200, r.text
assert r.json()["auth"] == "Bearer OWN"
assert (await _telemetry(clients))["tool_name"] == "our-tikhub"
async def test_catalog_only_route_cannot_be_shadowed_by_same_named_team_tool(clients: AsyncClient):
"""The directory route resolves the curated id directly; legacy `/call` still gives an exact
same-named team tool precedence, preserving both contracts at once."""
sid = (await clients.post("/secrets", json={"name": "own-key", "value": "OWN"})).json()["id"]
await clients.post("/tools", json={"name": EP, "base_url": "http://upstream", "secret_id": sid})
await clients.post("/secrets", json={"name": "tikhub", "value": "CATALOG"})
legacy = await clients.get(f"/call/{EP}?aweme_id=7")
directory = await clients.get(f"/catalog/call/{EP}?aweme_id=7")
assert legacy.status_code == 200 and legacy.json()["auth"] == "Bearer OWN"
assert directory.status_code == 200
assert directory.json()["auth"] == "Bearer CATALOG"
assert directory.json()["raw_path"] == EP_PATH
async def test_tier3_no_credential_is_an_actionable_404(clients: AsyncClient):
"""With tier 4 OFF (the default — no provider allow-listed), the ladder still dead-ends here."""
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 404
detail = r.json()["detail"]
assert "treg connections connect --provider tikhub" in detail
assert "treg secret add tikhub" in detail # tikhub is a pasted-key provider
async def test_missing_required_param_fails_before_any_credential(clients: AsyncClient):
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"})
r = await clients.get(f"/call/{EP}")
assert r.status_code == 400
assert "aweme_id" in r.json()["detail"]
async def test_method_mismatch_is_a_400_hint(clients: AsyncClient):
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"})
r = await clients.post(f"/call/{EP}?aweme_id=7")
assert r.status_code == 400
assert "GET" in r.json()["detail"]
async def test_provider_name_404_points_at_the_marketplace(clients: AsyncClient):
"""`treg call tikhub /path` (no such tool) keeps failing, but no longer dead-ends."""
r = await clients.get("/call/tikhub/api/v1/foo")
assert r.status_code == 404
assert "marketplace provider" in r.json()["detail"]
async def test_unknown_dotted_name_stays_a_plain_404(clients: AsyncClient):
r = await clients.get("/call/no.such.endpoint")
assert r.status_code == 404
def test_path_placeholders_fill_from_query_and_are_consumed():
"""Pure-function check: `{placeholder}` path params substitute (URL-encoded) from query
params and are reported as consumed so the relay drops them from the query string."""
provider = type("P", (), {"base_url": "https://api.example.com"})()
ep = {"id": "x.y.z", "path": "/v3/sites/{siteUrl}/query", "input": {}}
url, consumed = call_resolution._marketplace_upstream(
ep, provider, {"siteUrl": "sc-domain:ex.com", "row": "1"})
assert url == "https://api.example.com/v3/sites/sc-domain%3Aex.com/query"
assert consumed == {"siteUrl"}
encoded, _ = call_resolution._marketplace_upstream(
ep, provider, {"siteUrl": "sc-domain%3Aex.com"})
assert encoded == "https://api.example.com/v3/sites/sc-domain%3Aex.com/query"
# A literal `%` is not an encoded marker unless two following characters are hexadecimal.
literal, _ = call_resolution._marketplace_upstream(
ep, provider, {"siteUrl": "sc-domain:100%coverage.example"})
assert literal == "https://api.example.com/v3/sites/sc-domain%3A100%25coverage.example/query"
with pytest.raises(ResolutionFailed) as exc:
call_resolution._marketplace_upstream(ep, provider, {})
assert exc.value.status_code == 400 and "siteUrl" in exc.value.detail
async def test_deny_rules_cover_marketplace_calls(clients: AsyncClient):
"""Policy is evaluated on the RESOLVED upstream — an endpoint-id call can't dodge a host block."""
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"})
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
r = await clients.post(f"/orgs/{org_id}/deny", json={"host": "api.tikhub.io", "note": "no tikhub"})
assert r.status_code == 200, r.text
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 403
# ---- tier 4: treg's own key, billed to the org balance ------------------------------------------
@pytest.mark.parametrize(("endpoint", "params", "target", "charge_micro"), [
(
"diffbot.x.extract-article",
{"url": "https://news.example/article"},
("api.diffbot.com", "/v3/article"),
1_196,
),
(
"diffbot.x.extract-event",
{"url": "https://events.example/conference"},
("api.diffbot.com", "/v3/event"),
1_196,
),
(
"diffbot.companies.enrich",
{"type": "Organization", "url": "https://company.example"},
("kg.diffbot.com", "/kg/v3/enhance"),
29_900,
),
])
async def test_diffbot_shared_key_uses_each_catalog_endpoint_host(
clients: AsyncClient, diffbot_platform_on, endpoint, params, target, charge_micro,
):
"""Exercise extraction and KG through the full host-sensitive HTTP call path."""
outbound: list[tuple[str, str]] = []
def upstream(request: httpx.Request) -> httpx.Response:
assert request.url.params.get("token") == "PLATFORM-DIFFBOT-KEY"
assert request.headers.get("authorization") is None
outbound.append((request.url.host, request.url.path))
status = 200 if outbound[-1] == target else 404
return httpx.Response(
status,
stream=httpx.ByteStream(b'{"objects":[{"name":"Synthetic example"}]}'),
headers={"content-type": "application/json"},
)
await A.app.state.http.aclose()
A.app.state.http = AsyncClient(transport=httpx.MockTransport(upstream))
before = await _balance(clients)
response = await clients.get(f"/call/{endpoint}", params=params)
assert response.status_code == 200, response.text
assert outbound == [target]
assert await _balance(clients) == before - charge_micro
async def test_you_shared_key_reaches_both_api_hosts_and_settles_returned_pages(
clients: AsyncClient, monkeypatch,
):
monkeypatch.setenv("TREG_PLATFORM_KEY_YOU", "PLATFORM-YOU-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "you")
get_settings.cache_clear()
outbound = []
def upstream(request: httpx.Request) -> httpx.Response:
assert request.headers["x-api-key"] == "PLATFORM-YOU-KEY"
outbound.append((request.url.host, request.url.path))
if request.url.path == "/v1/contents":
# The second requested page was not returned, so only one page is metered.
body = b'[{"url":"https://example.com/a","markdown":"A"}]'
elif request.url.path == "/v1/research":
body = b'{"output":"Example","sources":[]}'
else:
body = b'{"answer":"Example","citations":[]}'
return httpx.Response(200, stream=httpx.ByteStream(body),
headers={"content-type": "application/json"})
await A.app.state.http.aclose()
A.app.state.http = AsyncClient(transport=httpx.MockTransport(upstream))
try:
before = await _balance(clients)
pages = await clients.post("/call/you.web.contents", json={
"urls": ["https://example.com/a", "https://example.com/b"], "formats": ["markdown"],
})
answer = await clients.post("/call/you.web.answer", json={"query": "What is example.com?"})
research = await clients.post("/call/you.web.research", json={
"input": "What is example.com?", "research_effort": "lite",
})
assert pages.status_code == 200, pages.text
assert answer.status_code == 200, answer.text
assert research.status_code == 200, research.text
assert outbound == [("ydc-index.io", "/v1/contents"),
("api.you.com", "/v1/answer"),
("api.you.com", "/v1/research")]
assert await _balance(clients) == before - 1_000 - 5_000 - 12_000
finally:
get_settings.cache_clear()
@pytest.mark.parametrize(("endpoint", "body"), [
("you.finance.research.exhaustive", {"input": "Explain Apple Inc.", "research_effort": "exhaustive"}),
("you.finance.research", {"input": "Explain Apple Inc.", "research_effort": "exhaustive"}),
])
async def test_you_finance_exhaustive_never_reaches_the_shared_key(clients: AsyncClient, monkeypatch, endpoint, body):
monkeypatch.setenv("TREG_PLATFORM_KEY_YOU", "PLATFORM-YOU-KEY")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "you")
get_settings.cache_clear()
outbound = []
def upstream(request: httpx.Request) -> httpx.Response:
outbound.append(request.url.path)
return httpx.Response(200)
await A.app.state.http.aclose()
A.app.state.http = AsyncClient(transport=httpx.MockTransport(upstream))
try:
before = await _balance(clients)
response = await clients.post(f"/call/{endpoint}", json=body)
assert response.status_code >= 400, response.text
assert outbound == []
assert await _balance(clients) == before
finally:
get_settings.cache_clear()
@pytest.mark.parametrize(("effort", "expected_micro"), [
("deep", 100_000), ("exhaustive", 450_000), ("frontier", 1_200_000),
])
def test_you_background_research_reserves_its_documented_tier(effort, expected_micro):
cat = catalog_store.load()
endpoint = cat.by_id["you.web.research.background"]
assert cat.platform_eligible(endpoint)
body = json.dumps({"input": "Explain example.com", "research_effort": effort, "background": True}).encode()
basis = settlement_basis.derive_basis(
endpoint["cost"],
request=settlement_basis.request_evidence([], body),
input_schema=endpoint["input"], unit_micro=1_000_000, terminal=True,
)
assert basis["when"] == "terminal"
assert basis["reserve_micro"] == expected_micro
async def test_diffbot_unapproved_catalog_host_fails_before_relay_or_reserve(
clients: AsyncClient, diffbot_platform_on, monkeypatch,
):
endpoint = catalog_store.load().by_id["diffbot.x.extract-article"]
monkeypatch.setitem(endpoint, "host", "credentials.example")
called = False
def upstream(request: httpx.Request) -> httpx.Response:
nonlocal called
called = True
return httpx.Response(500)
await A.app.state.http.aclose()
A.app.state.http = AsyncClient(transport=httpx.MockTransport(upstream))
before = await _balance(clients)
response = await clients.get(
"/call/diffbot.x.extract-article",
params={"url": "https://news.example/article"},
)
assert response.status_code == 502
assert response.json()["detail"].startswith("diffbot.x.extract-article declares")
assert called is False
assert await _balance(clients) == before
async def test_tier1_shadows_tier4(clients: AsyncClient, platform_on):
sid = (await clients.post("/secrets", json={"name": "own-key", "value": "OWN"})).json()["id"]
await clients.post("/tools", json={"name": "our-tikhub", "base_url": "https://api.tikhub.io", "secret_id": sid})
before = await _balance(clients)
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 200 and r.json()["auth"] == "Bearer OWN"
assert await _balance(clients) == before
assert (await _telemetry(clients))["credential_tier"] == "tool"
@pytest.mark.parametrize(("key", "providers"), [
(PLATFORM_KEYS["TIKHUB"], "dataforseo"), # the kill switch: keyed, but tikhub is not allow-listed
("", "tikhub"), # allow-listed without a key
])
async def test_tier4_off_unless_allow_listed_and_keyed_is_still_tier3(
clients: AsyncClient, monkeypatch, key, providers,
):
monkeypatch.setenv("TREG_PLATFORM_KEY_TIKHUB", key)
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", providers)
get_settings.cache_clear()
try:
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 404
assert "treg connections connect" in r.json()["detail"]
assert await _balance(clients) == 1_000_000
finally:
get_settings.cache_clear()
@pytest.mark.parametrize("why, patch", [
("own_account scope", {"scope": "own_account"}),
("unpriced", {"cost": {"type": "per_call", "value": None, "currency": "USD", "confidence": "unknown"}}),
("price merely inferred", {"cost": {"type": "per_call", "value": 0.001, "currency": "USD",
"per": 1, "unit": "call", "confidence": "inferred"}}),
("account kind", {"kind": "account"}),
])
async def test_ineligible_endpoints_fall_through_to_tier3(clients: AsyncClient, platform_on, monkeypatch, why, patch):
"""`platform_eligible` is the fence: treg spends its own money only where the price is
machine-computable, provenanced as verified, and the route has answered for real at least once."""
cat = A.catalog_store.load()
ep = dict(cat.by_id[EP])
ep.update(patch)
monkeypatch.setitem(cat.by_id, EP, ep)
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 404, f"{why} must not be served on treg's key"
assert await _balance(clients) == 1_000_000
async def test_caller_max_cost_header_refuses_a_direct_call_before_the_reserve(clients: AsyncClient, platform_on):
"""`X-Treg-Route-Max-Cost` on a plain /call/: a hard ceiling the caller sets, enforced before any
money moves. Below the price → 402 `route_max_cost` naming both figures, balance untouched; at or
above it → the call proceeds and is charged as usual; garbage → 400. No default: a direct call
without the header is uncapped (unlike the routed path's $1)."""
hdr = "X-Treg-Route-Max-Cost"
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={hdr: "0.0005"})
assert r.status_code == 402, r.text
d = r.json()["detail"]
assert d["error"] == "route_max_cost" and d["endpoint_id"] == EP
assert d["max_cost_micro"] == 500 and d["estimated_cost_micro"] == EP_MICRO
assert "nothing was charged" in d["message"]
assert await _balance(clients) == 1_000_000
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={hdr: "not-money"})
assert r.status_code == 400, r.text
assert await _balance(clients) == 1_000_000
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={hdr: "0.001"})
assert r.status_code == 200, r.text
assert r.headers["X-Treg-Cost-Micro"] == str(EP_MICRO)
assert await _balance(clients) == 1_000_000 - EP_MICRO
async def test_a_balance_refusal_is_a_treg_refused_event_not_a_vendor_402(
clients: AsyncClient, platform_on, posthog_events,
):
"""treg said no before any upstream trip, and the event says so as data, not as a status code."""
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
await ledger.reserve(db, org_id, "drain", 1_000_000)
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 402, r.text
# The team refilling by hand every hour is the one that should hear auto top-up exists.
d = r.json()["detail"]
assert d["autotopup_enabled"] is False
assert "treg topup --auto on" in d["message"]
(e,) = await posthog_events()
p = e["properties"]
assert p["status_code"] == 402
assert p["outcome"] == "treg_refused" and p["refused_by"] == "balance"
assert p["duration_ms"] is None and p["capacity_signal"] is None and p["smoothed"] is None
assert p["provider"] == "tikhub" and p["tier"] == "platform" and p["call_ref"]
async def test_402_with_autotopup_on_names_the_policy_not_a_missing_card(clients: AsyncClient, platform_on):
"""Auto top-up ON and still out of money means the cooldown or the cap is holding. Saying "add
funds" alone reads as "auto top-up is broken"; the message names the amount/threshold/cap and
the command that raises them (cobl.ai, 2026-08-25: 1,500 refusals between hourly $20 refills)."""
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
org = await db.get(Org, org_id)
org.autotopup_enabled = True
org.autotopup_consented_at = datetime.now(timezone.utc).replace(tzinfo=None) # naive UTC: TIMESTAMP WITHOUT TIME ZONE
org.autotopup_amount_micro = 20_000_000
org.autotopup_threshold_micro = 5_000_000
await db.commit()
await ledger.reserve(db, org_id, "drain", 1_000_000)
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 402, r.text
d = r.json()["detail"]
assert d["autotopup_enabled"] is True
assert "auto top-up: on" in d["message"] and "$20" in d["message"] and "$5" in d["message"]
assert "--auto on" in d["message"]
assert "treg connections connect --provider tikhub" in d["message"]
assert PLATFORM_KEYS["TIKHUB"] not in json.dumps(d), "an error must never carry the key"
row = await _telemetry(clients)
assert row["status_code"] == 402 and row["endpoint_id"] == EP, \
"a call refused for money is the event the org asks about first — it must be auditable"
assert row["cost_charged_micro"] == 0
async def test_malformed_marketplace_call_still_leaves_an_audit_row(clients: AsyncClient, platform_on):
"""Wrong method / missing param dies during resolution, before any tool exists — the attempt must
still land in the activity feed."""
r = await clients.post(f"/call/{EP}?aweme_id=7") # EP is GET
assert r.status_code == 400
row = await _telemetry(clients)
assert row["status_code"] == 400 and row["endpoint_id"] == EP
async def test_per_result_estimate_reads_a_body_limit(clients: AsyncClient, platform_on, monkeypatch):
"""dataforseo expresses row counts in the JSON body — `[{"limit": 3}]` must scale the reserve,
not fall back to the 20-row default (which would reserve $2.50/call on a lusha-priced endpoint)."""
monkeypatch.setattr(call_service, "relay", _fake_relay(200, json.dumps({"cost": 0.00015}).encode()))
await clients.post(f"/call/{EP_DFS}", json=[{"url": "https://x.co/", "limit": 3}])
row = await _telemetry(clients)
assert row["cost_estimated_micro"] == 150 * 3
@pytest.mark.parametrize(("body", "expected"), [
# companyenrich `pageSize`, exa `numResults`, icypeas/lusha `pagination.size`: a 2-row page must
# not reserve (and settle at) the 20-row default ($0.196 charged live for 2 rows at $0.0098 each)
({"pageSize": 2, "technologies": ["stripe"]}, 2),
({"query": "x", "numResults": 3}, 3),
({"query": {}, "pagination": {"size": 4}}, 4),
({"query": {}, "pagination": {"page": 0}}, None),
({"pagination": {"size": 7}}, 7),
# influencersclub discovery nests its page under `paging`
({"paging": {"limit": 10}, "filters": {}}, 10),
({"filters": {}}, None),
# one row per listed item: moz `targets` (a 1-target body settled 20 quota rows live)
({"targets": ["moz.com"], "distributions": True}, 1),
({"domains": ["a.com", "b.com"]}, 2),
# lusha buying-group: `contactsLimit` caps contacts PER COMPANY and is the whole bill
({"companies": [{"domain": "microsoft.com"}], "contactsLimit": 5}, 5),
# lusha people.enrich and companies.enrich: the `contacts` / `companies` arrays count, or a
# single lookup reserves the 20-row default (20x)
({"contacts": [{"firstName": "Jane", "lastName": "Doe", "companyDomain": "lusha.com"}],
"reveal": ["emails"]}, 1),
({"contacts": [{"firstName": "A"}, {"firstName": "B"}], "reveal": ["emails"]}, 2),
({"companies": [{"domain": "lusha.com"}]}, 1),
({"companies": [{"domain": "a.com"}, {"domain": "b.com"}, {"domain": "c.com"}]}, 3),
])
def test_body_limit_reads_the_row_count_the_request_names(body, expected):
assert call_resolution._body_limit(json.dumps(body).encode()) == expected
def test_hunter_domain_search_estimate_uses_credit_rounding():
"""Hunter bills 1 search credit per 10 emails RETURNED, rounded UP — the catalog's `per: 10` prices
at $0.00245/record, but Hunter actually charges whole credits. Feedback #116 (org 12770): catalog
showed ~$0.00245 but billed ~$0.0245 (10x) because the estimate used linear per-record math
while settle used rounded-up credits. The estimate must round up to whole credits too.
With limit=1: linear estimate was $0.00245, but settle = ceil(1/10) = 1 credit = $0.0245."""
credit_micro = 24_500 # $0.0245/credit (fx.yaml, Starter $49/mo / 2,000 credits)
# The estimate must round up to whole credits, same as settle does
cost = {"type": "per_result", "usd": 0.00245} # per-record price from cost_view
# Default limit (10 for Hunter) → 1 credit
est, unit = call_resolution._marketplace_pricing("hunter", "hunter.companies.emails", cost, {}, b"")
assert est == credit_micro, f"default limit (10) should reserve 1 whole credit: {est}"
assert unit == credit_micro, "unit should be 1 credit"
# limit=1 → still 1 credit (rounded up)
est_1, _ = call_resolution._marketplace_pricing("hunter", "hunter.companies.emails", cost, {"limit": "1"}, b"")
assert est_1 == credit_micro, f"limit=1 should still reserve 1 whole credit (ceil(1/10)=1): {est_1}"
# limit=10 → 1 credit
est_10, _ = call_resolution._marketplace_pricing("hunter", "hunter.companies.emails", cost, {"limit": "10"}, b"")
assert est_10 == credit_micro, f"limit=10 should reserve 1 credit: {est_10}"
# limit=11 → 2 credits (rounded up)
est_11, _ = call_resolution._marketplace_pricing("hunter", "hunter.companies.emails", cost, {"limit": "11"}, b"")
assert est_11 == 2 * credit_micro, f"limit=11 should reserve 2 credits (ceil(11/10)=2): {est_11}"
# limit=100 (max) → 10 credits
est_100, _ = call_resolution._marketplace_pricing("hunter", "hunter.companies.emails", cost, {"limit": "100"}, b"")
assert est_100 == 10 * credit_micro, f"limit=100 should reserve 10 credits: {est_100}"
async def test_unmetered_call_keeps_the_callers_encoding(clients: AsyncClient):
"""Tier 2 (org's own key) still streams and must keep the relay contract: the caller's own
compression choice travels upstream untouched."""
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"})
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"accept-encoding": "gzip, br"})
assert r.status_code == 200, r.text
assert r.json()["headers"]["accept-encoding"] == "gzip, br"
def _mk(provider: str, **kw) -> call_resolution.MarketplaceCall:
"""A minimal MarketplaceCall for observed-cost tests — only the fields the settle math reads."""
kw.setdefault("tier", "platform")
kw.setdefault("endpoint_id", "ep") # hunter's derived cost is keyed on the endpoint, not just the provider
return call_resolution.MarketplaceCall(tool=None, upstream="", consumed=set(), provider=provider, **kw)
def test_litescrape_ai_overview_unserved_is_free():
mk = _mk("litescrape", endpoint_id="litescrape.google.serp.ai_overview",
cost_type="per_success", estimate_micro=150)
assert call_settle._observed_cost_micro(
mk, b'{"ai_overview":null,"search_metadata":{"ai_overview_state":"not_served"}}') == 0
assert call_settle._observed_cost_micro(
mk, b'{"ai_overview":{"text":"answer"},"search_metadata":{"ai_overview_state":"served"}}') is None
assert call_settle._observed_cost_micro(mk, b'{"ai_overview":null}') is None
@pytest.mark.parametrize(("endpoint", "body", "credits"), [
("openmart.businesses.search", b'[]', 0),
("openmart.businesses.search", b'[{"id":"1"}]', 1),
("openmart.businesses.search", b'[{"id":"1"},{"id":"2"},{"id":"3"}]', 1),
("openmart.businesses.search", b'[{},{},{},{}]', 2),
("openmart.businesses.search", b'{"data":[{},{},{},{},{},{},{},{},{},{}]}', 3),
("openmart.companies.enrich", b'{"data":[]}', 0),
("openmart.companies.search", b'{"data":[{},{}]}', 1),
("openmart.businesses.lookup.openmart", b'{"a":{},"b":{}}', 1),
# undocumented response shapes settle at the estimate
("openmart.businesses.search", b'{"data":{}}', None),
("openmart.businesses.lookup.openmart", b'[]', None),
])
def test_openmart_settlement_rounds_three_credits_per_ten_records(endpoint, body, credits):
mk = _mk("openmart", endpoint_id=endpoint, cost_type="per_result", unit_micro=29_800)
expected = None if credits is None else credits * 29_800
assert call_settle._observed_cost_micro(mk, body) == expected
@pytest.mark.parametrize(("body", "expected"), [
(b'[]', 10_000),
(b'[{"mid":"1"}]', 15_000),
(b'[{},{},{}]', 25_000),
(b'[' + b','.join([b'{}'] * 5) + b']', 35_000),
(b'[' + b','.join([b'{}'] * 6) + b']', 50_000),
(b'[' + b','.join([b'{}'] * 50) + b']', 50_000),
(b'{"error":{"type":"run-failed"}}', None),
])
def test_apify_settlement_counts_rows_and_bills_the_cap_when_reached(monkeypatch, body, expected):
"""Rows x price + call_fee, but a run within two rows of its hold (maxTotalChargeUsd + fee)
reached the caller's cap: it may have billed an event it never pushed, so the cap is the bill."""
endpoint = {**catalog_store.load().by_id['apify.meta-ads.library.search']}
endpoint['cost'] = {**endpoint['cost'], 'call_fee': 0.01}
monkeypatch.setitem(catalog_store.load().by_id, endpoint['id'], endpoint)
mk = _mk('apify', endpoint_id=endpoint['id'], cost_type='per_result', unit_micro=5_000,
estimate_micro=50_000)
assert call_settle._observed_cost_micro(mk, body) == expected
@pytest.mark.parametrize(('items', 'hold'), [
((('maxTotalChargeUsd', '0.04'), ('timeout', '60')), 50_000),
((('maxTotalChargeUsd', '0.025'), ('timeout', '90')), 35_000),
((('maxTotalChargeUsd', '1'), ('maxItems', '3'), ('memory', '1024'), ('timeout', '90')),
1_010_000),
((('maxTotalChargeUsd', '0.024'), ('timeout', '60')), None),
((('maxTotalChargeUsd', '0.04'),), None),
((('maxTotalChargeUsd', '0.04'), ('timeout', '91')), None),
((('maxTotalChargeUsd', '0.04'), ('timeout', '0')), None),
((), None),
((('maxItems', '1'),), None),
((('maxTotalChargeUsd', '0'),), None),
((('maxTotalChargeUsd', '1.5'),), None),
((('maxTotalChargeUsd', '1e-1'),), None),
((('maxTotalChargeUsd', '0.5'),), None),
((('maxTotalChargeUsd', ' 0.5'),), None),
((('maxTotalChargeUsd', '0.5'), ('maxTotalChargeUsd', '0.1')), None),
((('maxTotalChargeUsd', '0.5'), ('maxItems', '2'), ('maxItems', '1')), None),
((('maxTotalChargeUsd', '0.5'), ('maxItems', '²')), None),
((('maxTotalChargeUsd', '0.5'), ('maxItems', '0')), None),
((('maxTotalChargeUsd', '0.5'), ('limit', '1')), None),
((('maxTotalChargeUsd', '0.5'), ('format', 'csv')), None),
((('maxTotalChargeUsd', '0.5'), ('unwind', 'x')), None),
])
def test_apify_platform_hold_is_the_charge_cap_plus_call_fee(items, hold):
ep = {**catalog_store.load().by_id['apify.meta-ads.library.search']}
ep.pop('platform_request', None)
cost = {**catalog_store.load().cost_view(ep['cost'], 'apify'), 'call_fee': 0.01}
ep['cost'] = cost
query = call_resolution.QueryValues(items)
if hold is None:
with pytest.raises(ResolutionFailed) as exc:
call_resolution._enforce_platform_request(ep, b'{}', query=query)
assert exc.value.detail['parameter'] == 'queryParams'
return
call_resolution._enforce_platform_request(ep, b'{}', query=query)
assert call_resolution._marketplace_pricing('apify', ep['id'], cost, query, b'{}') \
== (hold, 5_000)
@pytest.mark.parametrize(("endpoint", "doc", "expected"), [
*[("serper.web.extract", {"credits": reported, "text": "served"}, expected)
for reported, expected in
[(0, 0), (1, 1_000), ("2", 2_000), (3, 3_000), (6, 6_000), (10, 10_000)]],
*[("serper.web.search", {"credits": reported, "organic": []}, None)
for reported in [None, True, -1, "bad", "NaN", "Infinity"]],
])
def test_serper_settles_exact_reported_credits(endpoint, doc, expected):
mk = _mk("serper", endpoint_id=endpoint,
cost_type="per_call", reported_charge_unit_micro=1_000)
assert call_settle._observed_cost_micro(mk, json.dumps(doc).encode()) == expected
@pytest.mark.parametrize(("endpoint", "body", "expected"), [
("openmart.businesses.search", {"query": "coffee", "limit": 1}, 29_800),
("openmart.businesses.search", {"query": "coffee", "limit": 4}, 59_600),
("openmart.companies.search", {"pagination": {"limit": 25}}, 238_400),
("openmart.businesses.lookup.openmart", ["a", "b", "c"], 29_800),
])
def test_openmart_reservations_use_the_same_whole_credit_rounding(endpoint, body, expected):
cat = catalog_store.load()
cost = cat.cost_view(cat.by_id[endpoint]["cost"], "openmart")
estimate, unit = call_resolution._marketplace_pricing(
"openmart", endpoint, cost, {}, json.dumps(body).encode())
assert estimate == expected
assert unit == 29_800
@pytest.mark.parametrize(("endpoint", "body"), [
("openmart.businesses.search", {"query": "coffee"}),
("openmart.businesses.search", {"query": "coffee", "limit": 26}),
("openmart.companies.search", {"pagination": {"limit": 26}}),
("openmart.businesses.lookup.openmart", [str(i) for i in range(26)]),
])
async def test_openmart_platform_calls_require_an_explicit_one_to_25_cap(
clients, openmart_platform_on, endpoint, body,
):
before = await _balance(clients)
response = await clients.request(
catalog_store.load().by_id[endpoint]["method"], f"/call/{endpoint}",
content=json.dumps(body), headers={"content-type": "application/json"},
)
assert response.status_code == 400, response.text
assert await _balance(clients) == before
assert not [e for e in await _entries(clients) if e["kind"] in ("reserve", "settle", "release")]
async def test_openmart_byok_keeps_upstream_limits_and_is_unmetered(clients, openmart_platform_on):
await clients.post("/secrets", json={"name": "openmart", "value": "OWN-OPENMART"})
before = await _balance(clients)
body = {"query": "coffee", "limit": 100}
response = await clients.post("/call/openmart.businesses.search", json=body)
assert response.status_code == 200, response.text
echoed = response.json()
assert echoed["auth"] == "Bearer OWN-OPENMART"
assert json.loads(echoed["body"]) == body
assert await _balance(clients) == before
assert not [e for e in await _entries(clients) if e["kind"] in ("reserve", "settle", "release")]
async def test_openmart_byok_lookup_preserves_the_documented_get_array_body(
clients, openmart_platform_on,
):
await clients.post("/secrets", json={"name": "openmart", "value": "OWN-OPENMART"})
ids = ["00000000-0000-4000-8000-000000000001"]
response = await clients.request(
"GET", "/call/openmart.businesses.lookup.openmart",
content=json.dumps(ids), headers={"content-type": "application/json"},
)
assert response.status_code == 200, response.text
assert response.json()["auth"] == "Bearer OWN-OPENMART"
assert json.loads(response.json()["body"]) == ids
def test_observed_cost_only_trusts_a_real_number():
"""A missing, non-numeric or negative charge means "we never learned it" — settle at the estimate.
A reported ZERO is different: the provider is saying it did not charge, and is honoured."""
assert call_settle._observed_cost_micro(_mk("dataforseo"), b'{"cost": 0}') == 0
assert call_settle._observed_cost_micro(_mk("dataforseo"), b'{"cost": "0.5"}') is None
assert call_settle._observed_cost_micro(_mk("dataforseo"), b'{"cost": -1}') is None
assert call_settle._observed_cost_micro(_mk("dataforseo"), b"not json") is None
assert call_settle._observed_cost_micro(_mk("dataforseo"), b"[1,2,3]") is None
assert call_settle._observed_cost_micro(_mk("tikhub"), b'{"cost": 0.5}') is None, "tikhub doesn't report a charge"
# exa reports dollars one level down; a 20-result search is the base plus ten $0.001 riders
assert call_settle._observed_cost_micro(_mk("exa"), b'{"costDollars": {"total": 0.016, "search": {"neural": 0.016}}}') == 16_000
assert call_settle._observed_cost_micro(_mk("exa"), b'{"costDollars": {"total": 0}}') == 0
assert call_settle._observed_cost_micro(_mk("exa"), b'{"costDollars": {"total": "0.007"}}') is None
assert call_settle._observed_cost_micro(_mk("exa"), b'{"costDollars": 0.007}') is None
assert call_settle._observed_cost_micro(_mk("exa"), b'{"results": []}') is None
assert call_settle._observed_cost_micro(_mk("scrapecreators"), b'{"credits_charged": 2}') == 2 * EP_CALL_MICRO
assert call_settle._observed_cost_micro(_mk("scrapecreators"), b'{"success": true}') is None
# akta reports `credits_consumed` — the field that makes its per-section enrich billable at
# actuals rather than the catalog's upper-bound estimate. $0.05/credit (fx.yaml).
assert call_settle._observed_cost_micro(_mk("akta"), b'{"credits_consumed": 0.5}') == 25_000
assert call_settle._observed_cost_micro(_mk("akta"), b'{"credits_consumed": 0}') == 0, "a reported zero is honoured"
assert call_settle._observed_cost_micro(_mk("akta"), b'{"credits_charged": 2}') is None, "wrong field name means we never learned it"
def test_valyu_empty_search_releases_the_result_reserve():
mk = _mk("valyu", endpoint_id="valyu.markets.predictions.search", cost_type="per_call")
assert call_settle._observed_cost_micro(
mk, b'{"results": [], "total_deduction_dollars": null}'
) == 0
assert call_settle._observed_cost_micro(
mk, b'{"total_deduction_dollars": null}'
) == 0
assert call_settle._observed_cost_micro(
mk, b'{"results": null, "total_deduction_dollars": null}'
) is None # malformed results are not proof of an empty search
assert call_settle._observed_cost_micro(
mk, b'{"results": [{"id": "event"}], "total_deduction_dollars": 0.005}'
) is None # the declarative usage basis reads the reported amount
# Providers whose body carries no billing field report the call's charge in a response header.
_CRUSTDATA = ("crustdata", {"endpoint_id": "crustdata.companies.search"}, b'{"rows": []}')
# AI Ark reports a debit as a NEGATIVE X-Credit value; the sign rule is provider-specific.
_AIARK = ("aiark", {"endpoint_id": "aiark.people.phone.find", "cost_type": "per_success",
"unit_micro": 26_335}, b'{"data": {"data": [["+15550101000"]]}}')
# cloro's X-Credits-Charged is absent on free routes and failed extractions (neither is billed),
# which settles as unreported rather than as zero.
_CLORO = ("cloro", {"endpoint_id": "cloro.ai-search.chatgpt.scrape"}, b'{"success": true}')
_CREDIT_HEADERS = [
(_CRUSTDATA, {"X-Credits-Used": "0.03"}, 9_000),
(_CRUSTDATA, {}, None),
(_CRUSTDATA, {"X-Credits-Used": "not-a-number"}, None),
(_AIARK, {"X-Credit": "-5"}, 26_335),
(_AIARK, {"X-Credit": "-0.5"}, 2_634),
(_AIARK, {"X-Credit": "0"}, 0),
(_AIARK, {"X-Credit": "5"}, None),
(_AIARK, {"X-Credit": "not-a-number"}, None),
(_AIARK, {"X-Credit": "NaN"}, None),
(_AIARK, {"X-Credit": "-Infinity"}, None),
(_AIARK, {}, None),
(_CLORO, {"X-Credits-Charged": "7", "X-Credits-Remaining": "37493"}, 2_800),
(_CLORO, {"X-Credits-Charged": "9"}, 3_600),
(_CLORO, {}, None),
(_CLORO, {"X-Credits-Charged": "?"}, None),
]
@pytest.mark.parametrize(("call", "headers", "expected"), _CREDIT_HEADERS)
def test_settles_from_the_response_credit_header(call, headers, expected):
provider, fields, body = call
mk = _mk(provider, **fields)
assert call_settle._observed_cost_micro(mk, body, httpx.Headers(headers)) == expected
def test_aviato_conditional_prices_follow_live_balance_deltas():
cat = A.catalog_store.load()
def price(endpoint_id, query=None, body=None):
ep = cat.by_id[endpoint_id]
cv = cat.cost_view(ep["cost"], "aviato")
return call_resolution._marketplace_pricing(
"aviato", endpoint_id, cv, query or {}, json.dumps(body or {}).encode())
assert price("aviato.companies.enrich", {"preview": "true"}) == (0, 0)
assert price("aviato.companies.enrich", {"rescrape": "true"}) == (200_000, 150_000)
assert price("aviato.people.enrich", {"email": "a@example.com", "rescrape": "true"}) == (100_000, 80_000)
assert price("aviato.companies.enrich.bulk", body={
"lookups": [{"website": "a.com"}, {"website": "b.com"}], "rescrape": True,
}) == (400_000, 150_000)
assert price("aviato.people.enrich.bulk", body={
"lookups": [{"email": "a@example.com"}, {"email": "b@example.com"}], "rescrape": True,
}) == (200_000, 70_000)
assert price("aviato.people.search.simple", {"perPage": "3", "enrich": "false"}) == (2_500, 0)
assert price("aviato.people.search.simple", {"perPage": "3", "enrich": "true"}) == (32_500, 0)
assert price("aviato.people.search.simple", {"perPage": "5", "enrich": "true"}) == (52_500, 0)
@pytest.mark.parametrize(("endpoint", "unit_micro", "body", "expected"), [
# bulk enrich settles from the rows it returned
("aviato.companies.enrich.bulk", 150_000, b'{"companies": [{"id": "1"}, null]}', 150_000),
("aviato.people.enrich.bulk", 70_000, b'[{"id": "1"}, null]', 70_000),
# simple search releases its unbilled enrich rider
("aviato.people.search.simple", 0,
b'{"items": [{"id":"1"},{"id":"2"},{"id":"3"},{"id":"4"},{"id":"5"}]}', 2_500),
# single enrich releases documented but live-unbilled riders
("aviato.companies.enrich", 150_000, b'{"id":"company"}', 150_000),
("aviato.people.enrich", 80_000, b'{"id":"person"}', 80_000),
])
def test_aviato_settles_from_counts_and_releases_unbilled_riders(endpoint, unit_micro, body, expected):
mk = _mk("aviato", endpoint_id=endpoint, unit_micro=unit_micro)
assert call_settle._observed_cost_micro(mk, body) == expected
def test_observed_cost_counts_resources_for_billed_oauth_reads():
"""An oauth-billed per_result call settles against the RESPONSE — X bills per resource returned,
so `data`'s length is the bill: 7 posts back on a 100-post ask settles at 7, an empty page at
zero, and a single-object `data` (a profile read) at one. Anything unparseable falls back to
the estimate (None), and a non-per_result billed call never counts."""
x = _mk("x", tier="tool", billed_oauth=True, cost_type="per_result", unit_micro=5_000)
assert call_settle._observed_cost_micro(x, b'{"data": [{}, {}, {}]}') == 15_000
assert call_settle._observed_cost_micro(x, b'{"data": []}') == 0
assert call_settle._observed_cost_micro(x, b'{"data": {"id": "1"}}') == 5_000
assert call_settle._observed_cost_micro(x, b'{"errors": [{}]}') == 0, "no data key = nothing served"
assert call_settle._observed_cost_micro(x, b"not json") is None, "unreadable body settles at the estimate"
write = _mk("x", tier="tool", billed_oauth=True, cost_type="per_call", unit_micro=0)
assert call_settle._observed_cost_micro(write, b'{"data": {"id": "1"}}') is None, "per_call settles at the estimate"
# fiber-ai reports `chargeInfo.creditsCharged` on every envelope at $0.02/credit (fx.yaml):
# a 2-credit profile fetch, a free identity resolve, and — the case that matters — an error
# body with no `chargeInfo`, which settles as unreported so a per_call 400/404 releases.
# A poll's "charged-for-async-process" repeats its job's charge and is NOT honoured.
fiber = _mk("fiber-ai")
assert call_settle._observed_cost_micro(fiber, b'{"output": {}, "chargeInfo": {"method": "charged-now", "creditsCharged": 2}}') == 40_000
assert call_settle._observed_cost_micro(fiber, b'{"output": {}, "chargeInfo": {"method": "charged-now", "creditsCharged": 0}}') == 0
assert call_settle._observed_cost_micro(fiber, b'{"message": "body/identifier Required", "statusCode": 400}') is None
assert call_settle._observed_cost_micro(fiber, b'{"chargeInfo": {"method": "charged-for-async-process", "creditsCharged": 5}}') is None
# leadmagic reports `credits_consumed` too — including 0 on a 2xx miss (observed at verify
# time) and fractions (email verify = 0.25 credits). $0.025/credit (fx.yaml).
assert call_settle._observed_cost_micro(_mk("leadmagic"), b'{"credits_consumed": 1}') == 25_000
assert call_settle._observed_cost_micro(_mk("leadmagic"), b'{"credits_consumed": 0}') == 0, "a 2xx miss is free"
assert call_settle._observed_cost_micro(_mk("leadmagic"), b'{"credits_consumed": 0.25}') == 6_250
# lusha nests the same contract one level down: billing.creditsCharged — 0 on a 2xx miss
# (the captured people.enrich example is one), 2 credits on a company enrich. $0.1248/credit.
assert call_settle._observed_cost_micro(_mk("lusha"), b'{"billing": {"creditsCharged": 1, "resultsReturned": 10}}') == 124_800
assert call_settle._observed_cost_micro(_mk("lusha"), b'{"billing": {"creditsCharged": 0, "resultsReturned": 0}}') == 0, "a 2xx miss is free"
assert call_settle._observed_cost_micro(_mk("lusha"), b'{"billing": {"creditsCharged": 2}}') == 249_600
assert call_settle._observed_cost_micro(_mk("lusha"), b'{"requestId": "x"}') is None, "no billing block means we never learned it"
def test_apollo_settles_a_2xx_miss_at_zero():
"""Apollo answers a no-match with 2xx and charges nothing for it — `organization: null` on
enrich, an empty `organizations` page on search. Status-based billing would charge the
caller the full credit for a response Apollo gave away; the body is what decides. A body
carrying neither documented shape (people enrichment's 1-9 credit range) stays at the
estimate — deriving is only safe where the rule is flat."""
credit = 26_000 # $0.026/credit (fx.yaml, Basic $65/mo / 2,500 credits)
assert call_settle._observed_cost_micro(_mk("apollo"), b'{"organization": {"name": "Apple"}}') == credit
assert call_settle._observed_cost_micro(_mk("apollo"), b'{"organization": null}') == 0, "a 2xx miss is free"
assert call_settle._observed_cost_micro(_mk("apollo"), b'{"organizations": [{"name": "Apple"}], "pagination": {}}') == credit
assert call_settle._observed_cost_micro(_mk("apollo"), b'{"organizations": [], "pagination": {}}') == 0, "an empty page is free"
assert call_settle._observed_cost_micro(_mk("apollo"), b'{"person": {"id": "x"}}') is None, "1-9 credit range: estimate, not a guess"
assert call_settle._observed_cost_micro(_mk("apollo"), b"not json") is None
def test_hunter_domain_search_settles_on_the_emails_it_returned():
"""Hunter's domain search bills one whole SEARCH credit per 10 emails RETURNED, rounded up, and
a domain it knows nobody at is free — a rule the catalog's per-row price (1 credit ÷ 10 =
$0.00245/result) cannot express, so the estimate is wrong in both directions. Settling on
`data.emails` is what makes the published number and the ledger agree: zero emails costs zero,
and one email costs the same whole credit ten do."""
credit = 24_500 # $0.0245/credit (fx.yaml, Starter $49/mo / 2,000 credits)
h = _mk("hunter", endpoint_id="hunter.companies.emails", cost_type="per_result")
assert call_settle._observed_cost_micro(h, b'{"data": {"domain": "x.com", "emails": []}}') == 0, \
"a domain with no results is free — the catalog says so and Hunter bills so"
assert call_settle._observed_cost_micro(h, b'{"data": {"emails": [{"value": "a@x.com"}]}}') == credit, \
"one email costs a whole search credit, not a tenth of one"
def _emails(n: int) -> bytes:
return json.dumps({"data": {"emails": [{"value": f"p{i}@x.com"} for i in range(n)]}}).encode()
assert call_settle._observed_cost_micro(h, _emails(10)) == credit, "ten still fit in one credit"
assert call_settle._observed_cost_micro(h, _emails(11)) == 2 * credit, "the 11th rounds up to a second credit"
assert call_settle._observed_cost_micro(h, b'{"errors": [{"code": "wrong_params"}]}') is None, \
"no emails key at all: we never learned the count, settle at the estimate"
assert call_settle._observed_cost_micro(h, b"not json") is None
other = _mk("hunter", endpoint_id="hunter.people.email.verify", cost_type="per_call")
assert call_settle._observed_cost_micro(other, b'{"data": {"emails": []}}') is None, \
"only domain search bills per 10 returned; every other hunter route settles at its estimate"
def test_hunter_email_finder_miss_is_free():
"""The finder's rule is flat: one whole SEARCH credit when an email comes back, nothing on a
miss — Hunter's pricing says a miss is free, but a miss still answers HTTP 200 with
`email: null`, so settling at the estimate billed the full credit for a name Hunter had
nothing on. The body is the only place the found/missed distinction exists."""
credit = 24_500 # $0.0245/credit (fx.yaml, Starter $49/mo / 2,000 credits)
f = _mk("hunter", endpoint_id="hunter.people.email.find", cost_type="per_success")
assert call_settle._observed_cost_micro(f, b'{"data": {"email": "a@x.com", "score": 92}}') == credit
assert call_settle._observed_cost_micro(f, b'{"data": {"email": null, "score": null}}') == 0, \
"a miss is free — the catalog says so and Hunter bills so"
assert call_settle._observed_cost_micro(f, b'{"data": {"email": "", "score": null}}') == 0, \
"an empty string is a miss too"
assert call_settle._observed_cost_micro(f, b'{"errors": [{"code": "wrong_params"}]}') is None, \
"no email key at all: we never learned the outcome, settle at the estimate"
assert call_settle._observed_cost_micro(f, b"not json") is None
def test_tikhub_envelope_no_charge_settles_at_zero():
"""TikHub reports billing in prose, not a number: a 2xx whose payload is an embedded error
still says the request will incur a charge — and TikHub really does charge us for it
(verified live 2026-07-30), so those settle at the estimate, faithfully. Only the explicit
no-charge phrasing settles at zero."""
t = _mk("tikhub", cost_type="per_success")
assert call_settle._observed_cost_micro(t, b'{"code": 200, "message": "Request successful. This request will incur a charge.", "data": {}}') is None, \
"a billed answer settles at the estimate — that IS what TikHub takes"
assert call_settle._observed_cost_micro(t, b'{"code": 200, "message": "Request successful. This request will incur a charge.", "data": {"error": "dead_page"}}') is None, \
"a dead page TikHub bills us for is passed through, not eaten"
assert call_settle._observed_cost_micro(t, b'{"code": 400, "message": "Request failed. You won\'t be charged for this request.", "data": null}') == 0
assert call_settle._observed_cost_micro(t, b'{"code": 200, "message": "This request will not incur charges.", "data": {}}') == 0
assert call_settle._observed_cost_micro(t, b'{"code": 200, "data": {}}') is None, "no message: estimate"
assert call_settle._observed_cost_micro(t, b"not json") is None
async def test_daily_cap_fails_closed(clients: AsyncClient, platform_on, monkeypatch):
"""The per-org daily ceiling on treg's keys — the blast radius of a runaway agent. Unlike the soft
per-user call cap, it refuses rather than letting spend through."""
monkeypatch.setenv("TREG_PLATFORM_DAILY_CAP_USD", "0.0015") # 1500 micro = one call, not two
get_settings.cache_clear()
try:
assert (await clients.get(f"/call/{EP}?aweme_id=7")).status_code == 200
r = await clients.get(f"/call/{EP}?aweme_id=8")
assert r.status_code == 429, r.text
d = r.json()["detail"]
assert d["error"] == "platform_daily_cap_reached"
assert d["spent_today_micro"] == EP_MICRO and d["daily_cap_micro"] == 1_500
assert "connect your own key" in d["message"]
assert await _balance(clients) == 1_000_000 - EP_MICRO, "the refused call cost nothing"
finally:
get_settings.cache_clear()
async def test_daily_cap_refuses_when_it_cannot_be_verified(clients: AsyncClient, platform_on, monkeypatch):
"""FAIL CLOSED: if we can't count today's spend, we don't spend."""
async def _boom(db, org_id):
raise RuntimeError("ledger unavailable")
monkeypatch.setattr(ledger, "spent_today", _boom)
monkeypatch.setenv("TREG_PLATFORM_DAILY_CAP_USD", "1")
get_settings.cache_clear()
try:
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 429
assert "refusing to spend" in r.json()["detail"]
assert await _balance(clients) == 1_000_000
finally:
get_settings.cache_clear()
async def test_no_daily_cap_by_default_and_the_team_can_raise_past_any_deployment_default(
clients: AsyncClient, platform_on, monkeypatch):
"""With no limit set anywhere, the ledger is never consulted and the balance is the bound. A
team's own figure wins over the deployment default in BOTH directions."""
async def _boom(db, org_id):
raise RuntimeError("must not be consulted when no cap applies")
real_spent_today = ledger.spent_today
monkeypatch.setattr(ledger, "spent_today", _boom)
assert (await clients.get(f"/call/{EP}?aweme_id=7")).status_code == 200
monkeypatch.setattr(ledger, "spent_today", real_spent_today)
monkeypatch.setenv("TREG_PLATFORM_DAILY_CAP_USD", "0.0015") # default: one call, not two
get_settings.cache_clear()
try:
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 429 and r.json()["detail"]["error"] == "platform_daily_cap_reached"
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
raised = await clients.patch(f"/orgs/{org_id}/settings", json={"daily_cap_micro": 10 * EP_MICRO})
assert raised.status_code == 200, raised.text
assert (await clients.get(f"/call/{EP}?aweme_id=7")).status_code == 200
finally:
get_settings.cache_clear()
async def test_the_platform_key_never_appears_anywhere(clients: AsyncClient, platform_on):
"""The key may exist in exactly one place: the header the upstream receives. Not in the response we
return, not in an audit row, not in the ledger's metadata, not in a tool listing."""
r = await clients.get(f"/call/{EP}?aweme_id=7")
assert r.status_code == 200
key = PLATFORM_KEYS["TIKHUB"]
assert key in r.json()["headers"]["authorization"], "the upstream did receive it"
assert (await clients.get("/tools")).json() == [], "tier 4 must not materialize a tool row"
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
for path in ("/calls", "/tools", "/secrets", f"/orgs/{org_id}/balance", f"/catalog/endpoints/{EP}/access"):
assert key not in (await clients.get(path)).text, f"{path} leaked the platform key"
async def test_telemetry_row_records_the_endpoint_and_the_spend(clients: AsyncClient, platform_on):
r = await clients.get(f"/call/{EP}?aweme_id=7&count=3")
assert r.status_code == 200
row = await _telemetry(clients)
assert row["tool_name"] == EP
assert row["endpoint_id"] == EP and row["provider"] == "tikhub"
assert row["credential_tier"] == "platform"
assert row["cost_estimated_micro"] == EP_MICRO
assert row["duration_ms"] is not None and row["response_bytes"] > 0
assert len(row["params_hash"]) == 64
# The same call again hashes the same; a different param does not.
await clients.get(f"/call/{EP}?aweme_id=7&count=3")
again = await _telemetry(clients)
assert again["params_hash"] == row["params_hash"]
await clients.get(f"/call/{EP}?aweme_id=8&count=3")
assert (await _telemetry(clients))["params_hash"] != row["params_hash"]
async def test_access_probe_reports_the_tier(clients: AsyncClient):
r = await clients.get(f"/catalog/endpoints/{EP}/access")
assert r.status_code == 200 and r.json()["tier"] == "none"
await clients.post("/secrets", json={"name": "tikhub", "value": "MKKEY"})
assert (await clients.get(f"/catalog/endpoints/{EP}/access")).json()["tier"] == "credential"
sid = (await clients.post("/secrets", json={"name": "k2", "value": "OWN"})).json()["id"]
await clients.post("/tools", json={"name": "our-tikhub", "base_url": "https://api.tikhub.io", "secret_id": sid})
assert (await clients.get(f"/catalog/endpoints/{EP}/access")).json()["tier"] == "tool"
async def test_access_probe_reports_the_platform_tier(clients: AsyncClient, platform_on):
d = (await clients.get(f"/catalog/endpoints/{EP}/access")).json()
assert d["tier"] == "platform"
assert d["estimated_cost_micro"] == EP_MICRO
assert "no key needed" in d["detail"] and "0.001" in d["detail"]
async def test_a_user_may_not_forge_a_platform_binding(clients: AsyncClient, platform_on):
"""The other door onto treg's keys: a tool the caller registers themselves. `relay` resolves
`platform_setting` from settings without looking at ownership, so the validator has to refuse it."""
sid = (await clients.post("/secrets", json={"name": "mine", "value": "X"})).json()["id"]
r = await clients.post("/tools", json={
"name": "stealer", "base_url": "https://api.tikhub.io",
"bindings": [{"secret_id": sid, "platform_setting": "platform_key_tikhub", "injector": "env",
"location": "header", "name": "Authorization", "format": "Bearer {secret}"}],
})
assert r.status_code == 422
assert "platform_setting" in r.json()["detail"]
def test_local_run_cannot_export_a_platform_binding():
"""`treg run --local` hands credentials to the member's own machine, so it may only ever release
secrets the tool BINDS BY ID. A platform binding has no secret_id — there is nothing to resolve,
and the settings value is never in reach of the grant path."""
from treg import localrun
from treg.models import Tool
provider = oauth_providers.get("tikhub")
tool = Tool(org_id=1, name=EP, base_url=provider.base_url, host="api.tikhub.io",
bindings=oauth_providers.platform_bindings(provider),
cli={"enabled": True, "bin": "sh", "inject": [{"via": "env", "name": "TIKHUB_API_KEY"}]})
assert all(b.get("secret_id") is None for b in tool.bindings)
assert localrun._resolve_secret_id(tool.cli["inject"][0], tool) is None
def test_platform_estimate_normalizes_per_result_pricing():
"""A per-row price needs a row count: the caller's own limit param, else a page, and capped so one
call can't reserve an org's whole balance."""
per_call = {"type": "per_call", "usd": 0.002}
assert call_resolution._platform_estimate_micro(per_call, {}) == 2_000
per_row = {"type": "per_result", "usd": 0.0001}
assert call_resolution._platform_estimate_micro(per_row, {}) == 0.0001 * call_resolution._PLATFORM_PAGE_DEFAULT * 1_000_000
assert call_resolution._platform_estimate_micro(per_row, {"limit": "5"}) == 500
assert call_resolution._platform_estimate_micro(per_row, {"limit": "100000"}) == 0.0001 * call_resolution._PLATFORM_PAGE_MAX * 1_000_000
assert call_resolution._platform_estimate_micro({"type": "per_call", "usd": None}, {}) == 0
# rounds UP — a sub-micro fraction must never round to free
assert call_resolution._platform_estimate_micro({"type": "per_call", "usd": 0.0000005}, {}) == 1
def test_platform_estimate_prices_text_to_speech_by_input_characters():
"""MiniMax publishes TTS per character, so the request's text length—not a result-page
default or a flat call price—sets the reserve. Unicode code points count as characters."""
hd = {"type": "per_success", "unit": "character", "usd": 0.0001}
turbo = {"type": "per_success", "unit": "character", "usd": 0.00006}
assert call_resolution._platform_estimate_micro(hd, {}, b'{"text":"Hello."}') == 600
assert call_resolution._platform_estimate_micro(turbo, {}, ' {"text":"Hi 👋"}'.encode()) == 240
assert call_resolution._platform_estimate_micro(hd, {}, b'{"text":""}') == 100
assert call_resolution._platform_estimate_micro(hd, {}, b'not-json') == 100
def test_platform_estimate_counts_input_entities_not_a_page():
"""A price per TARGET / DOMAIN / KEYWORD is per thing asked about, never per returned row: with
no limit param the 20-row page default billed a one-target SE Ranking summary 20x ($0.358 for a
$0.0179 call) and a one-domain Serpstat overview likewise (behavehealth, 2026-09-04). The
request names the count — repeated or comma-separated query values, a body array (top level or
a JSON-RPC `params`), else exactly one — and `call` is always one."""
est = call_resolution._platform_estimate_micro
per_target = {"type": "per_result", "unit": "target", "usd": 0.0179}
assert est(per_target, {}) == 17_900 # catalog display: one call
assert est(per_target, {"target": "bestnotes.com", "mode": "domain"}) == 17_900
assert est(per_target, {"target": "a.com,b.com,c.com"}) == 3 * 17_900
# a real QueryValues-shaped object with repeated keys
class Q:
def __init__(self, items): self._i = items
def get(self, k, d=None): return next((v for kk, v in self._i if kk == k), d)
def multi_items(self): return list(self._i)
assert est(per_target, Q([("target", "a.com"), ("target", "b.com")])) == 2 * 17_900
assert est(per_target, Q([("targets[]", "a.com"), ("targets[]", "b.com")])) == 2 * 17_900
# serpstat JSON-RPC: the domains live under params
per_domain = {"type": "per_result", "unit": "domain", "usd": 0.0025}
body = b'{"id":"1","method":"SerpstatDomainProcedure.getDomainsInfo","params":{"domains":["a.com","b.com"],"se":"g_us"}}'
assert est(per_domain, {}, body) == 5_000
assert est(per_domain, {}, b'{"params":{"domains":["only.com"],"se":"g_us"}}') == 2_500
# seranking keywords export: a 5,000-keyword body is 5,000 keywords, not a 100-row cap
per_kw = {"type": "per_result", "unit": "keyword", "usd": 0.00179}
kw_body = ('{"keywords":' + str([f"k{i}" for i in range(5000)]).replace("'", '"') + '}').encode()
assert est(per_kw, {"source": "us"}, kw_body) == 5000 * 1_790
# a limit param on an entity-priced route is NOT a row count
assert est(per_target, {"target": "a.com", "limit": "50"}) == 17_900
# `call` is the flat case whatever the request carries
assert est({"type": "per_result", "unit": "call", "usd": 0.002}, {}, b'{"domain":"x.com","roles":["ceo","cto"]}') == 2_000
# row-priced routes keep the page semantics
assert est({"type": "per_result", "unit": "row", "usd": 0.0001}, {}) == 0.0001 * call_resolution._PLATFORM_PAGE_DEFAULT * 1_000_000
assert est({"type": "quota_rows", "unit": "quota_row", "usd": 0.006667}, {}, b'{"target":"x.com","limit":1}') == 6_667
def _tavily_cost(endpoint: str) -> dict:
cat = A.catalog_store.load()
return cat.cost_view(cat.by_id[endpoint]["cost"], "tavily")
@pytest.mark.parametrize("body,expected", [
({"query": "x", "search_depth": "basic"}, 8_000),
({"query": "x", "search_depth": "fast"}, 8_000),
({"query": "x", "search_depth": "ultra-fast"}, 8_000),
({"query": "x", "search_depth": "advanced"}, 16_000),
({"query": "x", "auto_parameters": True}, 16_000),
({"query": "x", "auto_parameters": True, "search_depth": "basic"}, 8_000),
])
def test_tavily_search_reserve_honors_depth_and_explicit_basic_override(body, expected):
reserve, unit = call_resolution._marketplace_pricing(
"tavily", "tavily.web.search", _tavily_cost("tavily.web.search"), {},
json.dumps(body).encode(),
)
assert (reserve, unit) == (expected, 8_000)
@pytest.mark.parametrize("rates", [
None,
{},
{"basic": 1, "fast": 1, "ultra_fast": 1},
{"basic": 1, "fast": 1, "ultra_fast": 1, "advanced": 2, "typo": 1},
{"basic": 0, "fast": 1, "ultra_fast": 1, "advanced": 2},
{"basic": -1, "fast": 1, "ultra_fast": 1, "advanced": 2},
{"basic": True, "fast": 1, "ultra_fast": 1, "advanced": 2},
{"basic": "1", "fast": 1, "ultra_fast": 1, "advanced": 2},
{"basic": float("nan"), "fast": 1, "ultra_fast": 1, "advanced": 2},
{"basic": float("inf"), "fast": 1, "ultra_fast": 1, "advanced": 2},
])
def test_tavily_pricing_fails_closed_on_incomplete_or_invalid_rates(rates):
cost = _tavily_cost("tavily.web.search") | {"tavily_rates": rates}
with pytest.raises(ResolutionFailed) as caught:
call_resolution._marketplace_pricing(
"tavily", "tavily.web.search", cost, {},
b'{"query":"x","search_depth":"basic"}',
)
assert caught.value.kind == "catalog_price_invalid"
assert caught.value.status_code == 503
@pytest.mark.parametrize("count", [1, 4, 5, 6, 20])
def test_tavily_extract_reserve_and_basic_settlement_are_fractional_per_success(count):
urls = [f"https://example.com/{i}" for i in range(count)]
body = json.dumps({"urls": urls, "extract_depth": "basic"}).encode()
reserve, unit = call_resolution._marketplace_pricing(
"tavily", "tavily.web.extract", _tavily_cost("tavily.web.extract"), {}, body,
)
assert (reserve, unit) == (count * 1_600, 1_600)
mk = _mk("tavily", endpoint_id="tavily.web.extract", cost_type="per_success",
unit_micro=unit, request_data={"body": {"urls": urls}})
results = [{"url": url, "raw_content": "ok"} for url in urls]
response = json.dumps({"results": results, "usage": {"credits": 999}}).encode()
assert call_settle._observed_cost_micro(mk, response) == count * 1_600
def test_tavily_extract_advanced_counts_only_successes_and_ignores_grouped_usage():
urls = [f"https://example.com/{i}" for i in range(6)]
request = {"urls": urls, "extract_depth": "advanced"}
reserve, unit = call_resolution._marketplace_pricing(
"tavily", "tavily.web.extract", _tavily_cost("tavily.web.extract"), {},
json.dumps(request).encode(),
)
assert (reserve, unit) == (19_200, 3_200)
mk = _mk("tavily", endpoint_id="tavily.web.extract", cost_type="per_success",
unit_micro=unit, request_data={"body": request})
doc = {"results": [{"url": urls[0]}, {"url": urls[1]}],
"failed_results": [{"url": url} for url in urls[2:]],
"usage": {"credits": 0}}
assert call_settle._observed_cost_micro(mk, json.dumps(doc).encode()) == 6_400
doc["usage"]["credits"] = 1
assert call_settle._observed_cost_micro(mk, json.dumps(doc).encode()) == 6_400
@pytest.mark.parametrize("endpoint,body,expected_unit", [
("tavily.web.map", {"url": "https://example.com", "limit": 10}, 800),
("tavily.web.map", {"url": "https://example.com", "limit": 10,
"instructions": "Find docs"}, 1_600),
("tavily.web.map", {"url": "https://example.com", "limit": 10,
"instructions": ""}, 800),
("tavily.web.crawl", {"url": "https://example.com", "limit": 10,
"extract_depth": "basic"}, 2_400),
("tavily.web.crawl", {"url": "https://example.com", "limit": 10,
"extract_depth": "basic", "instructions": "Find docs"}, 3_200),
("tavily.web.crawl", {"url": "https://example.com", "limit": 10,
"extract_depth": "advanced"}, 4_000),
("tavily.web.crawl", {"url": "https://example.com", "limit": 10,
"extract_depth": "advanced", "instructions": "Find docs"}, 4_800),
])
def test_tavily_map_and_crawl_reserves_use_caller_modes(endpoint, body, expected_unit):
reserve, unit = call_resolution._marketplace_pricing(
"tavily", endpoint, _tavily_cost(endpoint), {}, json.dumps(body).encode(),
)
assert (reserve, unit) == (10 * expected_unit, expected_unit)
@pytest.mark.parametrize("endpoint,unit,results,expected", [
("tavily.web.map", 800, [], 0),
("tavily.web.map", 800, [f"https://example.com/{i}" for i in range(9)], 7_200),
("tavily.web.map", 800, [f"https://example.com/{i}" for i in range(10)], 8_000),
("tavily.web.map", 1_600, [f"https://example.com/{i}" for i in range(11)], 17_600),
("tavily.web.crawl", 2_400, [], 0),
("tavily.web.crawl", 4_800, [{"url": f"https://example.com/{i}"} for i in range(3)], 14_400),
])
def test_tavily_map_and_conservative_crawl_settle_returned_results(
endpoint, unit, results, expected,
):
mk = _mk("tavily", endpoint_id=endpoint, cost_type="per_success", unit_micro=unit,
request_data={"body": {"limit": 20}})
body = json.dumps({"results": results, "usage": {"credits": -10}}).encode()
assert call_settle._observed_cost_micro(mk, body) == expected
@pytest.mark.parametrize("endpoint,bad_results", [
("tavily.web.extract", None),
("tavily.web.extract", [None]),
("tavily.web.map", {}),
("tavily.web.map", [123]),
("tavily.web.crawl", "not-a-list"),
("tavily.web.crawl", [{"raw_content": "missing URL"}]),
])
def test_tavily_malformed_result_evidence_falls_back_to_the_reserve(endpoint, bad_results):
mk = _mk("tavily", endpoint_id=endpoint, cost_type="per_success", unit_micro=800,
request_data={"body": {"limit": 20, "urls": ["https://example.com"]}})
assert call_settle._observed_cost_micro(
mk, json.dumps({"results": bad_results, "usage": {"credits": 0}}).encode()) is None
@pytest.mark.parametrize("endpoint,results", [
("tavily.web.extract", [{"url": f"https://example.com/{i}"} for i in range(8)]),
("tavily.web.map", [f"https://example.com/{i}" for i in range(8)]),
("tavily.web.crawl", [{"url": f"https://example.com/{i}"} for i in range(8)]),
])
def test_tavily_returned_results_cannot_charge_beyond_the_request(endpoint, results):
request = {"urls": ["a", "b", "c"]} if endpoint.endswith("extract") else {"limit": 3}
mk = _mk("tavily", endpoint_id=endpoint, cost_type="per_success", unit_micro=800,
request_data={"body": request})
assert call_settle._observed_cost_micro(
mk, json.dumps({"results": results, "usage": {"credits": 99}}).encode()) == 2_400
@pytest.mark.parametrize("body,credits,expected", [
({"query": "x", "search_depth": "basic", "include_usage": True}, 1, 8_000),
({"query": "x", "search_depth": "advanced", "include_usage": True}, 2, 16_000),
({"query": "x", "search_depth": "fast", "include_usage": True}, 1, 8_000),
({"query": "x", "search_depth": "ultra-fast", "include_usage": True}, 1, 8_000),
({"query": "x", "auto_parameters": True, "include_usage": True}, 2, 16_000),
({"query": "x", "auto_parameters": True, "search_depth": "basic",
"include_usage": True}, 1, 8_000),
])
async def test_tavily_search_settles_per_request_usage_even_for_an_empty_routing_miss(
clients, monkeypatch, tavily_platform_on, body, credits, expected,
):
def serve(request):
assert request.method == "POST" and request.url.path == "/search"
assert request.headers["authorization"] == "Bearer PLATFORM-TAVILY"
assert json.loads(request.content) == body
return _dropleads_response(200, {"results": [], "usage": {"credits": credits}})
before = await _balance(clients)
async with httpx.AsyncClient(transport=httpx.MockTransport(serve)) as upstream:
monkeypatch.setattr(A.app.state, "http", upstream)
response = await clients.post("/call/tavily.web.search", json=body)
assert response.status_code == 200, response.text
assert response.headers["x-treg-cost-micro"] == str(expected)
assert before - await _balance(clients) == expected
@pytest.mark.parametrize("usage", [None, {}, {"credits": None}, {"credits": True},
{"credits": -1}, {"credits": "1"},
{"credits": "nan"}, {"credits": "inf"}])
async def test_tavily_search_missing_or_malformed_usage_uses_the_safe_reserve(
clients, monkeypatch, tavily_platform_on, usage,
):
doc = {"results": [{"url": "https://example.com"}]}
if usage is not None:
doc["usage"] = usage
monkeypatch.setattr(call_service, "relay", _fake_relay(200, json.dumps(doc).encode()))
before = await _balance(clients)
response = await clients.post("/call/tavily.web.search", json={
"query": "x", "search_depth": "fast", "include_usage": True,
})
assert response.status_code == 200, response.text
assert before - await _balance(clients) == 8_000
@pytest.mark.parametrize("endpoint,body", [
("search", {"query": "x"}),
("map", {"url": "https://example.com"}),
("map", {"url": "https://example.com", "limit": 0}),
("map", {"url": "https://example.com", "limit": -1}),
("map", {"url": "https://example.com", "limit": 21}),
("map", {"url": "https://example.com", "limit": True}),
("crawl", {"url": "https://example.com"}),
])
async def test_tavily_platform_gates_search_usage_and_site_work_limits(
clients, tavily_platform_on, endpoint, body,
):
before = await _balance(clients)
response = await clients.post(f"/call/tavily.web.{endpoint}", json=body)
assert response.status_code == 400
assert response.json()["detail"]["error"] == "catalog_parameter_invalid"
assert await _balance(clients) == before
def test_cloro_state_targeting_rider_is_reserved():
"""`state` is a top-level body field priced by a `cost.modifiers` rule (+2 credits on the four
engines that support it). The reserve must carry it; before the modifiers path was opened to
providers other than Aviato it silently did not (found reviewing #349)."""
cat = A.catalog_store.load()
def price(endpoint_id, body):
ep = cat.by_id[endpoint_id]
cv = cat.cost_view(ep["cost"], "cloro")
return call_resolution._marketplace_pricing("cloro", endpoint_id, cv, {}, json.dumps(body).encode())
plain = {"prompt": "what is a stock split", "country": "US"}
assert price("cloro.ai-search.perplexity.answer", plain) == (2_400, 0) # 6 credits × $0.0004
assert price("cloro.ai-search.perplexity.answer", {**plain, "state": "CA"}) == (3_200, 0) # 8
assert price("cloro.ai-search.chatgpt.scrape", {**plain, "state": "CA"}) == (4_400, 0) # 11
# AI Mode has no state rider and no modifiers block: the plain estimate, untouched
assert price("cloro.google.serp.ai_mode", {"prompt": "x", "gl": "US"}) == (2_400, 0)
def test_brightdata_estimate_counts_the_body_array():
"""Bright Data bills per record delivered and takes its targets as a bare JSON array, so the
reserve has to scale with the array's LENGTH — there is no limit param in the query to read."""
cost = {"type": "per_result", "usd": 0.0015}
assert call_resolution._platform_estimate_micro(cost, {}, json.dumps([{"url": "a"}]).encode()) == 1_500
five = json.dumps([{"url": u} for u in "abcde"]).encode()
assert call_resolution._platform_estimate_micro(cost, {}, five) == 7_500
# ---- idempotent calls: step 1, the table and its tenant boundary ----------------------------
# ---- idempotency step 2: the lookup and replay (storage still off) ---------------------------
async def _seed_answer(clients: AsyncClient, key: str, *, body: bytes = b'{"seeded":true}',
fingerprint: str = "", status: str = "done", charged: int = 4200,
ttl_s: int = 3600) -> int:
"""Write a stored answer by hand. Step 2 only READS; storage arrives in step 3, so seeding is
how the read path gets exercised at all."""
from datetime import timedelta
from sqlmodel import select
from treg.infra.db import session_maker
from treg.models import IdempotentCall, Membership
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
m = (await db.execute(select(Membership).where(
Membership.org_id == org_id).order_by(Membership.id))).scalars().first()
row = IdempotentCall(
org_id=org_id, membership_id=m.id, key=key, request_fingerprint=fingerprint,
endpoint_id="seeded", status=status, charged_micro=charged,
response_status=200 if status == "done" else None,
response_body=body if status == "done" else None,
response_media_type="application/json",
expires_at=datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(seconds=ttl_s))
db.add(row)
await db.commit()
await db.refresh(row)
return row.id
async def test_one_callers_label_is_invisible_to_another(clients: AsyncClient, platform_on):
"""The tenant boundary, exercised through the HTTP path rather than asserted on the schema. A
second caller using the same label must reach the provider, not read the first one's answer."""
await _seed_answer(clients, "shared-label", body=b'{"owner":"first"}')
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
made = await clients.post(f"/orgs/{org_id}/agents", json={"name": "other-agent"})
assert made.status_code in (200, 201), made.text
other_token = made.json().get("token")
assert other_token, made.text
prev = clients.headers.get("X-Treg-Token")
clients.headers["X-Treg-Token"] = other_token
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "shared-label"})
if prev:
clients.headers["X-Treg-Token"] = prev
assert r.status_code == 200
assert "X-Treg-Idempotent-Replay" not in r.headers, "another caller must not read this answer"
assert r.json() != {"owner": "first"}
# ---- idempotency step 3: storing the answer --------------------------------------------------
async def test_an_unmetered_call_is_not_stored(clients: AsyncClient):
"""A team calling on its OWN key is billed by the provider, not by us. There is nothing to
protect, and treg has no business holding their response."""
from sqlmodel import select
from treg.infra.db import session_maker
from treg.models import IdempotentCall
await clients.post("/secrets", json={"name": "tikhub", "value": "OWNKEY"})
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "own-key-call"})
assert r.status_code == 200 and r.json()["auth"] == "Bearer OWNKEY"
async with session_maker() as db:
row = (await db.execute(select(IdempotentCall).where(
IdempotentCall.key == "own-key-call"))).scalar_one_or_none()
assert row is None, "an unmetered call must leave nothing behind, not even a claim"
async def test_a_stale_label_reused_later_starts_fresh(clients: AsyncClient, platform_on):
"""A caller with stable labels (`nightly-report`, say) must be able to call again tomorrow.
Note what this does NOT prove: the read path already drops an expired row when it looks one up,
so this passes with the sweep removed. The sweep is covered separately below — I wrote this one
believing it tested the sweep, and only found out by deleting the sweep and watching it pass."""
from datetime import timedelta
from sqlmodel import select
from treg.infra.db import session_maker
from treg.models import IdempotentCall, Membership
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
m = (await db.execute(select(Membership).where(
Membership.org_id == org_id).order_by(Membership.id))).scalars().first()
db.add(IdempotentCall(
org_id=org_id, membership_id=m.id, key="nightly-report", endpoint_id=EP,
status="done", response_status=200, response_body=b'{"yesterday":true}',
response_media_type="application/json", charged_micro=999,
expires_at=datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1)))
await db.commit()
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "nightly-report"})
assert r.status_code == 200, r.text
assert "X-Treg-Idempotent-Replay" not in r.headers, "yesterday's answer must not be served"
assert r.json() != {"yesterday": True}
async with session_maker() as db:
rows = (await db.execute(select(IdempotentCall).where(
IdempotentCall.key == "nightly-report"))).scalars().all()
assert len(rows) == 1, "exactly one row: the dead one swept, today's kept"
assert rows[0].response_body != b'{"yesterday":true}'
async def test_the_sweep_clears_labels_NOBODY_COMES_BACK_FOR(clients: AsyncClient, platform_on):
"""What the sweep is actually for, and the only thing that covers it.
A label used once and never again is never looked up, so the read path never sees it and never
drops it. Without a sweep those rows accumulate forever, and they hold response BODIES. Any later
call by the same caller clears them.
Lazy and caller-scoped, matching the hold reaper in domain/money: a background timer would need a
scheduler and a leader election on a multi-instance deploy, and would still only run on a timer.
"""
from datetime import timedelta
from sqlmodel import select
from treg.infra.db import session_maker
from treg.models import IdempotentCall, Membership
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
async with session_maker() as db:
m = (await db.execute(select(Membership).where(
Membership.org_id == org_id).order_by(Membership.id))).scalars().first()
db.add(IdempotentCall(
org_id=org_id, membership_id=m.id, key="abandoned-label", endpoint_id=EP,
status="done", response_status=200, response_body=b'{"big":"body"}',
response_media_type="application/json", charged_micro=500,
expires_at=datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=2)))
await db.commit()
# a call under a DIFFERENT label: the abandoned row is never looked up, only swept
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "unrelated"})
assert r.status_code == 200, r.text
async with session_maker() as db:
gone = (await db.execute(select(IdempotentCall).where(
IdempotentCall.key == "abandoned-label"))).scalar_one_or_none()
assert gone is None, "an expired row nobody returns for must still be reclaimed"
async def _claim_row(key: str):
from sqlmodel import select
from treg.infra.db import session_maker
from treg.models import IdempotentCall
async with session_maker() as db:
return (await db.execute(select(IdempotentCall).where(
IdempotentCall.key == key))).scalar_one_or_none()
async def _set_claim(key: str, **values) -> None:
from sqlalchemy import update
from treg.infra.db import session_maker
from treg.models import IdempotentCall
async with session_maker() as db:
await db.execute(update(IdempotentCall).where(IdempotentCall.key == key).values(**values))
await db.commit()
def _ago(**kw):
from datetime import timedelta
return datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(**kw)
async def _ledger(org_id: int, call_id: str, kind: str, reason: str = "", amount: int = 0) -> None:
import uuid
from treg.infra.db import session_maker
from treg.models import LedgerEntry
async with session_maker() as db:
db.add(LedgerEntry(id=uuid.uuid4().hex, org_id=org_id, kind=kind, amount_micro=amount,
call_id=call_id, endpoint_id=EP, meta={"reason": reason} if reason else {}))
await db.commit()
async def test_an_abandoned_claim_is_closed_with_a_stored_410_never_run_again(
clients: AsyncClient, platform_on, monkeypatch):
"""Even an owner whose visible money shows only a clean release is not proof the operation
finished: a later child (overflow polling, an async worker) can still settle. So the key is
never run again; a live lease answers 409, a stale one a stored 410, and no provider call or
charge happens under it."""
from treg.application.call import idempotency
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
await _seed_answer(clients, "stuck-label", status="pending")
await _set_claim("stuck-label", call_ref="gone-owner")
await _ledger(org_id, "gone-owner", "reserve", amount=-100)
await _ledger(org_id, "gone-owner", "release", reason="not_billable_502", amount=100)
live = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "stuck-label"})
assert live.status_code == 409, live.text
balance = (await clients.get(f"/orgs/{org_id}/balance")).json()["balance_micro"]
monkeypatch.setattr(idempotency, "IDEMPOTENCY_STALE_PENDING_S", 0)
for _ in range(2):
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "stuck-label"})
assert r.status_code == 410, r.text
assert r.json()["detail"]["error"] == "idempotency_outcome_unknown"
assert "X-Treg-Original-Cost-Micro" not in r.headers, "the original cost is not known"
assert r.headers["X-Treg-Call-Id"] == "gone-owner"
assert (await clients.get(f"/orgs/{org_id}/balance")).json()["balance_micro"] == balance
assert (await _claim_row("stuck-label")).call_ref == "gone-owner"
@pytest.mark.parametrize("trail", ["none", "reaped"])
async def test_an_abandoned_claim_with_no_proof_of_finishing_fails_closed(
clients: AsyncClient, platform_on, monkeypatch, trail):
"""No money trail (an unmetered or pre-reserve owner), or a hold the stale-hold reaper released
while the owner may still have been upstream: the outcome is unknown, so a stored 410 instead
of a second upstream call."""
from treg.application.call import idempotency
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
await _seed_answer(clients, "unknown-label", status="pending")
await _set_claim("unknown-label", call_ref="silent-owner")
if trail == "reaped":
await _ledger(org_id, "silent-owner", "reserve", amount=-100)
await _ledger(org_id, "silent-owner", "release", reason="stale_hold_reaped", amount=100)
monkeypatch.setattr(idempotency, "IDEMPOTENCY_STALE_PENDING_S", 0)
for _ in range(2):
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "unknown-label"})
assert r.status_code == 410, r.text
assert r.json()["detail"]["error"] == "idempotency_outcome_unknown"
assert "X-Treg-Original-Cost-Micro" not in r.headers, "the original cost is not known"
@pytest.mark.parametrize("task_status", ["settled", "pending"])
async def test_an_async_child_that_settles_after_the_owner_died_is_still_a_charge(
clients: AsyncClient, platform_on, monkeypatch, task_status):
"""A routed owner whose early child released cleanly and whose later async child is settled
by the worker AFTER the owner's lifetime: that late settle is a charge (410, never a takeover
that bills the key again), and a task still pending is money in flight (409)."""
import uuid
from treg.application.call import idempotency
from treg.infra.db import session_maker
from treg.models import AsyncTaskRecord, LedgerEntry
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
await _seed_answer(clients, "async-label", status="pending")
await _set_claim("async-label", call_ref="routed-owner", created_at=_ago(hours=2))
stamp = _ago(hours=2)
async with session_maker() as db:
db.add(LedgerEntry(id=uuid.uuid4().hex, org_id=org_id, kind="release", amount_micro=100,
call_id="routed-owner:r0", endpoint_id=EP,
meta={"reason": "not_billable_404"}, created_at=stamp))
db.add(AsyncTaskRecord(call_id="routed-owner:r1", org_id=org_id, provider="p", endpoint_id=EP,
reserved_micro=500, next_check_at=stamp, status=task_status,
created_at=stamp))
if task_status == "settled": # the worker settles long after the owner stopped
db.add(LedgerEntry(id=uuid.uuid4().hex, org_id=org_id, kind="settle", amount_micro=-500,
call_id="routed-owner:r1", endpoint_id=EP, meta={}))
await db.commit()
monkeypatch.setattr(idempotency, "IDEMPOTENCY_STALE_PENDING_S", 60)
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "async-label"})
if task_status == "pending":
assert r.status_code == 409, r.text
else:
assert r.status_code == 410, r.text
assert r.json()["detail"]["error"] == "idempotency_response_lost"
assert r.json()["detail"]["charged_micro"] == 500
async def test_a_renewal_between_read_and_close_keeps_the_lease(clients: AsyncClient, platform_on):
"""The compare-and-swap includes the lease timestamp: an owner that renews after the retry
read the stale row keeps its claim."""
from types import SimpleNamespace
from treg.application.call import idempotency
from treg.infra.db import session_maker
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
await _seed_answer(clients, "race-label", status="pending")
await _set_claim("race-label", call_ref="alive-owner", created_at=_ago(hours=1))
await _ledger(org_id, "alive-owner", "release", reason="not_billable_502", amount=100)
stale = await _claim_row("race-label")
await idempotency._renew_claim_lease((stale.membership_id, stale.key, "alive-owner"))
caller = SimpleNamespace(org_id=org_id, membership=SimpleNamespace(id=stale.membership_id))
async with session_maker() as db:
out = await idempotency._resolve_stale_claim(stale, caller, db)
assert out is None
row = await _claim_row("race-label")
assert row.call_ref == "alive-owner" and row.status == "pending"
async def test_an_abandoned_CHARGED_claim_answers_410_and_never_charges_twice(
clients: AsyncClient, platform_on, monkeypatch):
"""The incident: the owner was charged, then marking its claim done failed. Forgetting the claim
would bill the same key again, so the retry gets a stored 410 naming the charge instead."""
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
first = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "paid-label"})
assert first.status_code == 200, first.text
owner = first.headers["X-Treg-Call-Id"]
from treg.application.call import idempotency
await _set_claim("paid-label", status="pending", response_status=None, response_body=None,
charged_micro=0)
monkeypatch.setattr(idempotency, "IDEMPOTENCY_STALE_PENDING_S", 0)
balance = (await clients.get(f"/orgs/{org_id}/balance")).json()["balance_micro"]
for _ in range(2):
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "paid-label"})
assert r.status_code == 410, r.text
detail = r.json()["detail"]
assert detail["error"] == "idempotency_response_lost" and detail["call_id"] == owner
assert detail["charged_micro"] > 0
assert r.headers["X-Treg-Call-Id"] == owner
assert r.headers["X-Treg-Original-Cost-Micro"] == str(detail["charged_micro"])
assert (await clients.get(f"/orgs/{org_id}/balance")).json()["balance_micro"] == balance
async def test_an_expired_lease_with_money_in_flight_still_answers_409(
clients: AsyncClient, platform_on):
"""An open hold under the owner's call id (or a child's) means the call or its async worker is
not finished, however old the lease."""
from treg.infra.db import session_maker
from treg.models import Hold
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
await _seed_answer(clients, "busy-label", status="pending")
await _set_claim("busy-label", call_ref="busy-owner", created_at=_ago(hours=1))
async with session_maker() as db:
db.add(Hold(id="busy-owner:r2", org_id=org_id, endpoint_id=EP, amount_micro=100))
await db.commit()
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "busy-label"})
assert r.status_code == 409, r.text
async def test_a_legacy_claim_without_an_owner_is_never_taken_over(clients: AsyncClient, platform_on):
"""Rows written before claims carried their call id have no money to check: they wait out the
window rather than risk a second charge."""
await _seed_answer(clients, "legacy-label", status="pending")
await _set_claim("legacy-label", created_at=_ago(hours=1))
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "legacy-label"})
assert r.status_code == 409, r.text
async def test_a_renewal_during_closing_answers_409_not_a_phantom_410(
clients: AsyncClient, platform_on, monkeypatch):
"""Through the real replay path: the owner renews while the retry is deciding. The lost swap
must not leave the retry's loaded row looking closed."""
from treg.application.call import idempotency
await _seed_answer(clients, "phantom-label", status="pending")
await _set_claim("phantom-label", call_ref="alive-owner", created_at=_ago(hours=1))
real = idempotency._money_of
async def renew_meanwhile(db, org_id, call_ref, since, until):
row = await _claim_row("phantom-label")
await idempotency._renew_claim_lease((row.membership_id, row.key, "alive-owner"))
return await real(db, org_id, call_ref, since, until)
monkeypatch.setattr(idempotency, "_money_of", renew_meanwhile)
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "phantom-label"})
assert r.status_code == 409, r.text
row = await _claim_row("phantom-label")
assert row.status == "pending" and row.call_ref == "alive-owner"
async def test_the_old_owner_cannot_touch_a_claim_taken_over(clients: AsyncClient, platform_on):
"""After a takeover, the late original call's release and store are fenced out."""
from types import SimpleNamespace
from treg.application.call import idempotency
await _seed_answer(clients, "fenced-label", status="pending")
await _set_claim("fenced-label", call_ref="new-owner")
row = await _claim_row("fenced-label")
await idempotency._release_idempotent_claim((row.membership_id, row.key, "old-owner"))
caller = SimpleNamespace(membership=SimpleNamespace(id=row.membership_id))
await idempotency._store_idempotent(
row.key, caller, status_code=200, body=b"{}", media_type="application/json",
charged_micro=1, metered=True, call_ref="old-owner")
after = await _claim_row("fenced-label")
assert after is not None and after.status == "pending" and after.call_ref == "new-owner"
async def test_marking_a_claim_done_survives_one_pool_timeout(
clients: AsyncClient, platform_on, monkeypatch):
"""The saturated pool that stranded claims: one timeout is retried on a fresh session."""
from sqlalchemy.exc import TimeoutError as PoolTimeoutError
from treg.application.call import idempotency
real, failed = idempotency.session_maker, []
def flaky():
if not failed:
failed.append(1)
raise PoolTimeoutError("QueuePool limit reached")
return real()
monkeypatch.setattr(idempotency, "session_maker", flaky)
monkeypatch.setattr(idempotency, "_POOL_RETRY_PAUSE_S", 0)
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "flaky-label"})
assert r.status_code == 200, r.text
assert failed, "the store must have hit the simulated timeout"
assert (await _claim_row("flaky-label")).status == "done"
async def test_the_owner_renews_its_lease_and_nobody_else_does(clients: AsyncClient, platform_on):
from treg.application.call import idempotency
await _seed_answer(clients, "lease-label", status="pending")
await _set_claim("lease-label", call_ref="owner", created_at=_ago(hours=1))
row = await _claim_row("lease-label")
await idempotency._renew_claim_lease((row.membership_id, row.key, "stranger"))
assert (await _claim_row("lease-label")).created_at < _ago(minutes=30)
await idempotency._renew_claim_lease((row.membership_id, row.key, "owner"))
assert (await _claim_row("lease-label")).created_at > _ago(minutes=1)
async def test_the_sweep_leaves_OTHER_callers_rows_alone(clients: AsyncClient, platform_on):
"""Scoped to the caller doing the work. A sweep that reached across callers would be a caller
able to delete another's stored answers by making one call of their own."""
from datetime import timedelta
from sqlmodel import select
from treg.infra.db import session_maker
from treg.models import IdempotentCall, Membership
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
made = await clients.post(f"/orgs/{org_id}/agents", json={"name": "bystander"})
assert made.status_code in (200, 201), made.text
async with session_maker() as db:
members = (await db.execute(select(Membership).where(
Membership.org_id == org_id).order_by(Membership.id))).scalars().all()
other = members[-1]
db.add(IdempotentCall(
org_id=org_id, membership_id=other.id, key="someone-elses", endpoint_id=EP,
status="done", response_status=200, response_body=b"{}", charged_micro=1,
expires_at=datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1)))
await db.commit()
r = await clients.get(f"/call/{EP}?aweme_id=7", headers={"Idempotency-Key": "mine"})
assert r.status_code == 200
async with session_maker() as db:
still = (await db.execute(select(IdempotentCall).where(
IdempotentCall.key == "someone-elses"))).scalar_one_or_none()
assert still is not None, "one caller's sweep must not delete another's rows"
# ---- 429 is never billable (shared-plan pricing, step 2) ------------------------------------
def test_the_billability_truth_table():
"""The exact contract of `_platform_billable`, pinned row by row so a future edit changes it on
purpose or not at all.
The 429 row is the shared-plan fix: a rate-limit rejection is capacity refusing the request. On a
shared plan key it is treg's own saturation, and billing it would charge teams for our
congestion. It also corrects an existing wrong: under `per_call` the old rule billed upstream
429s, and no vendor bills a request it refused to accept."""
# The contract widened in PR #122 and the old table was WRONG about one row: it asserted an
# upstream 402 under per_call bills the caller ("the provider billing for acceptance"). A 402 is
# the provider REFUSING — usually because OUR platform key ran out of quota — and no vendor
# charges for a refusal. The caller pays only for rejections about their own input.
cases = [
(200, "per_success", True), (200, "per_call", True),
# not the caller's fault: credential, payment, quota, timeout, rate limit — never billed
(401, "per_call", False), (402, "per_call", False), (403, "per_call", False),
(405, "per_call", False), (407, "per_call", False), (408, "per_call", False),
(429, "per_call", False), (429, "per_success", False), (429, "per_result", False),
# the caller's own input: MAY bill under per_call only — and then only at the charge the
# provider reports (`test_a_4xx_bills_only_what_the_provider_reports`)
(400, "per_call", True), (404, "per_call", True), (422, "per_call", True),
(400, "per_success", False), (400, "per_result", False),
(503, "per_call", False), (503, "per_success", False),
(302, "per_call", False),
]
for status, cost_type, expected in cases:
got = call_settle._platform_billable(status, cost_type)
assert got is expected, f"({status}, {cost_type}) -> {got}, expected {expected}"
# ---- trial pools: $0 on treg's key, capped per team per day (fx.yaml kind: treg_trial) -------
@pytest.fixture()
def trial_on(monkeypatch):
"""Tier 4 for a TRIAL provider: treg's free-tier key in the env, provider allow-listed."""
monkeypatch.setenv("TREG_PLATFORM_KEY_FINNHUB", "trial-pool-test-key")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "tikhub,scrapecreators,dataforseo,finnhub")
get_settings.cache_clear()
yield
get_settings.cache_clear()
@pytest.fixture()
def getleadsio_trial_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_GETLEADSIO", "PLATFORM-GETLEADSIO")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "getleadsio")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def test_a_trial_call_is_served_keyless_and_charges_NOTHING(clients: AsyncClient, trial_on,
monkeypatch):
monkeypatch.setattr(call_service, "relay", _fake_relay(200, b'{"c": 231.5, "pc": 230.1}'))
before = await _balance(clients)
r = await clients.get("/call/finnhub.quote?symbol=AAPL")
assert r.status_code == 200, r.text
assert await _balance(clients) == before, "a $0 trial call must not move money"
async def test_the_trial_allowance_bites_at_the_fx_number(clients: AsyncClient, trial_on,
monkeypatch):
"""Seed today's audit at the allowance (50 for finnhub, from fx.yaml) — the next call must be
refused with the connect-your-own-key hint, unbilled. Failed calls are seeded too and must NOT
count: a 4xx produced nothing, the same line billability draws."""
from treg.models import CallRecord
async with session_maker() as db:
for i in range(50):
db.add(CallRecord(org_id=1, user_email="u@example.com", tool_name="finnhub.quote",
method="GET", path="/quote", status_code=200,
credential_tier="platform"))
for i in range(10): # failures do not consume the allowance
db.add(CallRecord(org_id=1, user_email="u@example.com", tool_name="finnhub.quote",
method="GET", path="/quote", status_code=502))
await db.commit()
monkeypatch.setattr(call_service, "relay", _fake_relay(200, b'{"c": 1}'))
before = await _balance(clients)
r = await clients.get("/call/finnhub.quote?symbol=AAPL")
assert r.status_code == 429, r.text
d = r.json()["detail"]
assert d["error"] == "trial_allowance_reached" and d["allowance_per_day"] == 50
assert "connect" in d["message"]
assert await _balance(clients) == before
async def test_failures_alone_never_exhaust_a_trial(clients: AsyncClient, trial_on, monkeypatch):
from treg.models import CallRecord
async with session_maker() as db:
for i in range(60):
db.add(CallRecord(org_id=1, user_email="u@example.com", tool_name="finnhub.quote",
method="GET", path="/quote", status_code=429))
await db.commit()
monkeypatch.setattr(call_service, "relay", _fake_relay(200, b'{"c": 1}'))
assert (await clients.get("/call/finnhub.quote?symbol=AAPL")).status_code == 200
async def test_own_key_history_never_consumes_a_later_platform_trial(
clients: AsyncClient, trial_on, monkeypatch):
from treg.models import CallRecord
async with session_maker() as db:
for _ in range(50):
db.add(CallRecord(
org_id=1, user_email="u@example.com", tool_name="finnhub.quote",
method="GET", path="/quote", status_code=200,
credential_tier="credential",
))
await db.commit()
monkeypatch.setattr(call_service, "relay", _fake_relay(200, b'{"c": 1}'))
assert (await clients.get("/call/finnhub.quote?symbol=AAPL")).status_code == 200
async def test_another_orgs_usage_never_burns_MY_trial(clients: AsyncClient, trial_on, monkeypatch):
"""The allowance is per TEAM. Another org's fifty calls must not touch this org's pool — the
multi-tenancy assertion, and the one failure here that would be unfair rather than merely
wrong."""
from treg.models import CallRecord
other = await clients.post("/orgs", json={"name": "another-trial-team"})
assert other.status_code == 200, other.text
async with session_maker() as db:
for i in range(50):
db.add(CallRecord(org_id=other.json()["org_id"], user_email="other@example.com",
tool_name="finnhub.quote", method="GET", path="/quote",
status_code=200, credential_tier="platform"))
await db.commit()
monkeypatch.setattr(call_service, "relay", _fake_relay(200, b'{"c": 1}'))
assert (await clients.get("/call/finnhub.quote?symbol=AAPL")).status_code == 200
@pytest.mark.parametrize(("seeded", "called", "body"), [
# free calls do not consume the paid trial allowance
(("getleadsio.people.search.count", "/api/v1/contacts/search/count"),
"getleadsio.people.search", {"filters": {"domains": ["example.com"]}, "limit": 1}),
# free calls still work after the paid trial allowance is spent
(("getleadsio.people.search", "/api/v1/contacts/search"),
"getleadsio.people.search.count", {"filters": {"domains": ["example.com"]}}),
])
async def test_getleadsio_free_and_paid_calls_keep_separate_trial_allowances(
clients: AsyncClient, getleadsio_trial_on, seeded, called, body):
from treg.models import CallRecord
tool_name, path = seeded
async with session_maker() as db:
for _ in range(5):
db.add(CallRecord(
org_id=1, user_email="u@example.com",
tool_name=tool_name, method="POST", path=path, status_code=200,
credential_tier="platform",
))
await db.commit()
result = await clients.post(f"/call/{called}", json=body)
assert result.status_code == 200, result.text
# ---- X: the catalog price and the metered price are the same number ----------------------------
# The bug this pins: `x.extended.yaml` shipped 168 routes priced `free` (a note about the Free/Basic/
# Pro plan caps X abolished in Feb 2026), while `_oauth_billed_estimate` skipped that block — its
# `usd` is 0, which is falsy — and charged the provider fallback instead. The catalog said $0 and
# the balance said $0.10, which is the one disagreement a published price must never have.
def _x_endpoints():
from treg.domain.catalog import store as catalog_store
return [e for e in catalog_store.load().by_id.values() if e.get("provider") == "x"]
def test_x_catalog_price_equals_what_the_meter_charges():
"""For every X route, the price the catalog publishes is the price the proxy reserves. Walked
over the whole provider rather than a sample, because the failure mode is one stale entry."""
from treg import oauth_providers
x = oauth_providers.get("x")
for ep in _x_endpoints():
method = (ep.get("method") or "GET").upper()
est, ctype, _ = call_resolution._oauth_billed_estimate(x, ep, method, {}, b"")
published = call_resolution._platform_estimate_micro(
A.catalog_store.load().cost_view(ep["cost"], "x"), {}, b"")
assert est == published and ctype == ep["cost"]["type"], (
f"{ep['id']}: catalog says {published} micro ({ep['cost']['type']}), "
f"meter reserves {est} micro ({ctype})")
def test_a_zero_price_on_a_billed_provider_falls_back_rather_than_billing_zero():
"""Belt and braces for the next stale ingest: if a `free` block ever reappears on X, the meter
must charge the provider rate rather than serve an upstream we get billed for at $0."""
from treg import oauth_providers
x = oauth_providers.get("x")
ep = {"id": "x.x.stale", "provider": "x", "method": "GET", "path": "/2/tweets",
"cost": {"type": "free", "value": 0, "currency": "USD", "unit": "call"}}
est, ctype, unit = call_resolution._oauth_billed_estimate(x, ep, "GET", {}, b"")
assert est > 0 and ctype == "per_result" and unit == call_resolution._usd_to_micro(x.billed_read_usd)
# ---- X end to end: the published price is the price the balance loses --------------------------
# Everything above tests the pricing FUNCTIONS. This walks the whole path a real X call takes —
# registry connection → `_billed_marketplace` → reserve → relay → settle — because the free-price
# bug was invisible to every unit test and only showed up as a number on a screen.
def test_observed_cost_counts_brightdata_records():
"""Bright Data bills per record DELIVERED and reports no charge field — the body is the bill.
Before this settled-by-count existed, every per_result call settled as one record: $13.61
consumed upstream vs $0.35 billed over three weeks (2026-08-24)."""
bd = _mk("brightdata", cost_type="per_result", unit_micro=1500)
# sync scrape / snapshot download, format=json: an array, one element per record
assert call_settle._observed_cost_micro(bd, b'[{"url": "a"}, {"url": "b"}, {"url": "c"}]') == 4500
assert call_settle._observed_cost_micro(bd, b"[]") == 0
# the >60s sync fallback and /trigger hand back a snapshot id: zero records HERE — they bill
# when the snapshot is downloaded
assert call_settle._observed_cost_micro(bd, b'{"snapshot_id": "sd_x"}') == 0
# an early snapshot download answers the job's state, not rows: nothing delivered, nothing billed
assert call_settle._observed_cost_micro(bd, b'{"status": "running", "message": "not ready"}') == 0
# ndjson: one record per line
assert call_settle._observed_cost_micro(bd, b'{"url": "a"}\n{"url": "b"}\n') == 3000
# csv: header + rows
assert call_settle._observed_cost_micro(bd, b"url,name\na,x\nb,y\n") == 3000
# a payload the 8MB metered buffer truncated must settle at the estimate, never a partial count
assert call_settle._observed_cost_micro(bd, b'[{"url": "a"}, {"url"') is None
# gzipped (compress=true): can't count, estimate wins
assert call_settle._observed_cost_micro(bd, b"\x1f\x8b\x08\x00junk") is None
# a free management route (progress polls) never reaches the counter
assert call_settle._observed_cost_micro(_mk("brightdata", cost_type="free"), b'{"status": "ready"}') is None
def test_influencersclub_settle_counts_accounts():
from types import SimpleNamespace
from treg.application.call.settle import _observed_cost_micro
mk = SimpleNamespace(cost_type="per_result", unit_micro=5980, billed_oauth=False,
endpoint_id="influencersclub.creators.search", provider="influencersclub")
body = json.dumps({"total": 634, "limit": 10,
"accounts": [{"user_id": i} for i in range(10)]}).encode()
# 10 creators returned → 10 × 5,980µ$ = $0.0598, NOT the 20-row estimate ($0.1196)
assert _observed_cost_micro(mk, body) == 59_800
# an envelope with no rows costs nothing
assert _observed_cost_micro(mk, json.dumps({"detail": "quota"}).encode()) == 0
@pytest.mark.parametrize(('query', 'count', 'page_size', 'credits'), [
('', 0, 10, 0), ('', 10, 10, 1), ('&limit=1', 1, 1, 1),
('&limit=10', 6, 10, 1), ('&limit=20', 6, 20, 2),
('&limit=50', 50, 50, 5), ('&limit=20&page=1000', 0, 20, 0),
])
async def test_tomba_domain_search_settles_returned_emails(
clients: AsyncClient, platform_on, monkeypatch, query, count, page_size, credits,
):
"""Bill non-empty pages by page size; empty pages are free regardless of total matches."""
monkeypatch.setenv('TREG_PLATFORM_KEY_TOMBA', 'SYNTHETIC-TOMBA-KEY')
monkeypatch.setenv('TREG_PLATFORM_PROVIDERS', 'tomba')
get_settings.cache_clear()
body = json.dumps({'data': {
'domain': 'company.example',
'emails': [{'email': f'person{i}@company.example'} for i in range(count)],
}, 'meta': {'total': 100, 'pageSize': page_size}}).encode()
monkeypatch.setattr(call_service, 'relay', _fake_relay(200, body))
before = await _balance(clients)
response = await clients.get(f'/call/tomba.companies.emails.list?domain=company.example{query}')
assert response.status_code == 200
assert response.content == body
assert await _balance(clients) == before - credits * 8_900
telemetry = await _telemetry(clients)
assert telemetry['cost_estimated_micro'] == max(1, (page_size + 9) // 10) * 8_900
assert telemetry['cost_observed_micro'] == credits * 8_900
assert telemetry['cost_charged_micro'] == credits * 8_900
_TOMBA_LIST = {'endpoint_id': 'tomba.companies.emails.list', 'cost_type': 'per_result'}
@pytest.mark.parametrize(('fields', 'body'), [
# unknown result shapes keep the estimate
*[(_TOMBA_LIST, body) for body in [
b'{}', b'{"data": {}}', b'{"data": {"emails": null}}',
b'{"data": {"emails": {}}}', b'{"data": null}', b'not json',
]],
# the domain-search count does not apply to other endpoints
({'endpoint_id': 'tomba.people.email.verify', 'cost_type': 'per_call'},
b'{"data": {"emails": []}}'),
# an unknown page size is never guessed from the email count
*[({'endpoint_id': 'tomba.companies.emails.list', 'unit_micro': 8_900},
json.dumps({'data': {'emails': [{'email': 'person@company.example'}]},
'meta': {'pageSize': page_size}}).encode())
for page_size in [None, 0, -1, True, "20", 1.5]],
])
def test_tomba_settles_at_the_estimate_without_countable_evidence(fields, body):
assert call_settle._observed_cost_micro(_mk('tomba', **fields), body) is None
@pytest.mark.parametrize('endpoint,doc,expected', [
*[('people.email.find', {'meta': {'credits_used': credits}}, expected) for credits, expected in
[(0, 0), (1, 4834), (6, 29004), (-1, None), (True, None), ('1', None), (float('inf'), None)]],
# billing-relevant shapes from live checks: reported usage wins over result count
*[(endpoint, {'success': True, 'data': data, 'meta': {'credits_used': credits}}, expected)
for endpoint, data, credits, expected in [
('people.email.find', {'email': 'person@example.com'}, 1, 4834),
# QuickEnrich bills a phone-only answer, but an email finder without an email is a free miss
('people.email.find', {'email': 'N/A', 'employee_phone': '+15550101000'}, 1, 0),
('people.phone.find', {'employee_phone': '+15550101000'}, 1, 4834),
('people.enrich', {'email': 'person@example.com'}, 1, 4834),
('people.email.find', [], 0, 0),
('people.phone.find', [], 0, 0),
('people.search.domain', [{'email': 'person@example.com'}] * 20, 1, 4834),
('people.search.domain', [{'email': 'person@example.com'}] * 6 + [{'email': 'N/A'}] * 2, 6, 29004),
('companies.search', [{'company_name': 'Example'}], 1, 4834),
('companies.search', [], 0, 0),
]],
])
def test_quickenrich_settles_reported_credits_at_frozen_rate(endpoint, doc, expected):
mk = _mk('quickenrich', endpoint_id='quickenrich.' + endpoint, cost_type='per_success', unit_micro=4834)
assert call_settle._observed_cost_micro(mk, json.dumps(doc).encode()) == expected
@pytest.mark.parametrize('endpoint,data,title,credits', [
('people.email.find', {'email': 'a@example.com'}, '', 1),
('people.email.find', {'email': None, 'employee_phone': 'N/A'}, '', 0),
('people.email.find', {'email': None, 'employee_phone': '+15550101000'}, '', 0),
('people.phone.find', {'employee_phone': '+15550101000'}, '', 1),
('people.phone.find', {'employee_phone': 'N/A'}, '', 0),
('people.enrich', {'first_name': 'Example'}, '', 1),
('people.enrich', [], '', 0),
('people.search.domain', [{'email': 'a@example.com'}, {'employee_phone': '+15550101000'}, {'email': 'N/A'}], '', 1),
('people.search.domain', [{'email': 'a@example.com', 'employee_phone': '+15550101000'}, {'email': 'N/A'}], 'CEO', 1),
('people.search.domain', [], 'CEO', 0),
('companies.search', [{}, {}], '', 2),
('companies.search', [], '', 0),
])
def test_quickenrich_fallback_counts_billable_results(endpoint, data, title, credits):
mk = _mk('quickenrich', endpoint_id='quickenrich.' + endpoint, cost_type='per_success', unit_micro=4834,
request_data={'queryParams': {'title': title}})
assert call_settle._observed_cost_micro(mk, json.dumps({'success': True, 'data': data}).encode()) == credits * 4834
@pytest.mark.parametrize('endpoint,query,body,expected', [
('people.search.domain', {}, {}, 4834),
('people.search.domain', {'title': 'CEO'}, {}, 96680),
('companies.search', {}, {}, 48340),
('companies.search', {}, {'per_page': 1}, 4834),
('companies.search', {}, {'per_page': 100}, 483400),
])
def test_quickenrich_reserves_real_page_size(endpoint, query, body, expected):
cat = catalog_store.load()
ep = cat.by_id['quickenrich.' + endpoint]
cost = cat.cost_view(ep['cost'], 'quickenrich')
estimate, unit = call_resolution._marketplace_pricing('quickenrich', ep['id'], cost, query, json.dumps(body).encode())
assert (estimate, unit) == (expected, 4834)
@pytest.mark.parametrize('amount,expected', [(0.005,5000),(.0015,1500),(.0025,2500),(0,0),('0.0015',1500), (None,None), (True,None),(-1,None),('NaN',None),('Infinity',None),({},None)])
def test_trykitt_usd_charge(amount, expected):
mk = _mk('trykitt', endpoint_id='trykitt.people.email.find', cost_type='per_success')
raw = {'email':'a@example.com','credits':{'jobCredits':amount,'remainingCredits':'900'}}
assert call_settle._observed_cost_micro(mk,json.dumps(raw).encode()) == expected
def test_trykitt_null_miss_is_free_and_verification_verdicts_are_answers():
cat = catalog_store.load()
for email in ['no-results-found',None,'']:
doc={'email':email,'credits':{'jobCredits':None}}
assert cat.adapters['trykitt.people.email.find'].is_miss(doc)
assert call_settle._observed_cost_micro(_mk('trykitt',endpoint_id='trykitt.people.email.find',cost_type='per_success'),json.dumps(doc).encode()) == 0
for status in ['valid','invalid','unknown','catchall']:
assert not cat.adapters['trykitt.people.email.verify'].is_miss({'validity':status})
assert call_settle._observed_cost_micro(_mk('trykitt',endpoint_id='trykitt.people.email.verify',cost_type='per_success'),json.dumps({'validity':status,'credits':{'jobCredits':None}}).encode()) is None
@pytest.mark.parametrize('amount,expected', [(0, 0), ('0.0000015', 2),
(0.025, 25000), (None, None)])
def test_reported_charge_uses_catalog_path_for_any_provider(monkeypatch, amount, expected):
endpoint = {'id': 'example.lookup', 'provider': 'example', 'cost': {
'type': 'per_call', 'value': 0.03, 'currency': 'USD',
'reported_charge': {'path': 'billing.actual', 'unit': 'usd'},
}}
monkeypatch.setitem(catalog_store.load().by_id, endpoint['id'], endpoint)
body = json.dumps({'billing': {'actual': amount}, 'credits': {'jobCredits': 999}}).encode()
assert call_settle._observed_cost_micro(
_mk('example', endpoint_id=endpoint['id']), body) == expected
@pytest.mark.parametrize('body,valid', [
(b'{"mode":"sync"}', True),
(b'{"mode":"async"}', False),
(b'{}', False),
(b'{"mode":"sync","mode":"sync"}', False),
(b'[]', False),
])
def test_platform_request_constraints_do_not_require_a_price_table(body, valid):
ep = {'id': 'example.lookup', 'platform_request': {'body.mode': 'sync'},
'cost': {'type': 'per_call', 'value': 0.01}}
if valid:
call_resolution._enforce_platform_request(ep, body)
else:
with pytest.raises(ResolutionFailed):
call_resolution._enforce_platform_request(ep, body)
@pytest.mark.parametrize('items,valid', [
((('memory', '1024'), ('maxTotalChargeUsd', '1')), True),
((('memory', '1024.0'), ('maxTotalChargeUsd', '1.0')), False),
((('memory', '1024'), ('maxTotalChargeUsd', '1')), False),
((('memory', '1_024'), ('maxTotalChargeUsd', '1')), False),
((('memory', '+1024'), ('maxTotalChargeUsd', '1')), False),
((('memory', ' 1024 '), ('maxTotalChargeUsd', '1')), False),
((('memory', '2048'), ('maxTotalChargeUsd', '1')), False),
((('memory', '1024'), ('memory', '1024'), ('maxTotalChargeUsd', '1')), False),
((('maxTotalChargeUsd', '1'),), False),
((('memory', 'lots'), ('maxTotalChargeUsd', '1')), False),
])
def test_platform_request_pins_query_values_by_type(items, valid):
ep = {'id': 'example.run', 'platform_request': {
'queryParams.memory': 1024, 'queryParams.maxTotalChargeUsd': 1.0}}
query = call_resolution.QueryValues(items)
if valid:
call_resolution._enforce_platform_request(ep, b'', query=query)
else:
with pytest.raises(ResolutionFailed):
call_resolution._enforce_platform_request(ep, b'', query=query)
# ---- ContactOut ----
def _contactout_cost(eid):
return catalog_store.load().cost_view(
catalog_store.load().by_id["contactout." + eid]["cost"], "contactout"
)
@pytest.mark.parametrize(
"work,personal,phone,expected",
[
(False, False, False, 0),
(True, False, False, 150000),
(False, True, False, 250000),
(True, False, True, 400000),
(False, True, True, 500000),
(True, True, True, 650000),
],
)
def test_contactout_contact_hits_are_per_type_per_profile(work, personal, phone, expected):
c = _contactout_cost("people.linkedin.enrich")
doc = {
"status_code": 200,
"profile": {
"work_email": ["a@example.test", "b@example.test"] if work else [],
"personal_email": ["c@example.test"] if personal else [],
"phone": ["123", "456"] if phone else [],
"email": ["duplicate-combined@example.test"],
"contact_availability": {"phone": True},
},
}
assert contactout.observed(c, {"queryParams": {}}, doc) == expected
assert contactout.estimate(c, {}) == 650000
def test_contactout_search_counts_returned_profiles_and_contacts_not_availability_or_total():
c = _contactout_cost("people.search.reveal")
doc = {
"status_code": 200,
"metadata": {"total_results": 10000},
"profiles": {
"one": {
"contact_info": {
"work_emails": ["a@example.test", "b@example.test"],
"phones": ["1"],
}
},
"two": {"contact_availability": {"personal_email": True}},
"three": {"contact_info": {"personal_emails": ["c@example.test"]}},
},
}
assert contactout.observed(c, {"body": {"reveal_info": True}}, doc) == 710000
assert contactout.observed(c, {"body": {"reveal_info": False}}, doc) == 60000
assert contactout.estimate(c, {"page_size": 3, "reveal_info": True}) == 2010000
assert contactout.estimate(c, {"reveal_info": False}) == 500000
assert (
contactout.observed(c, {"body": {}}, {"status_code": 200, "profiles": []}) == 0
)
def test_contactout_person_and_email_echo_and_search_surcharge():
doc = {
"status_code": 200,
"profile": {
"email": "input@example.test",
"workEmail": "work@example.test",
"phone": "123",
},
}
c = _contactout_cost("people.enrich")
request = {
"email": "input@example.test",
"include": ["work_email", "personal_email", "phone"],
}
assert contactout.observed(c, {"body": request}, doc) == 420000
assert contactout.observed(c, {"body": {}}, doc) == 20000
assert (
contactout.observed(
_contactout_cost("people.email.enrich"),
{"queryParams": {"email": "input@example.test", "include": "work_email"}},
doc,
)
== 400000
)
@pytest.mark.parametrize(
"rows",
[
[],
[{"name": "A"}, None, {}],
{"example.test": {"name": "A"}, "missing.test": None},
],
)
def test_contactout_company_counts(rows):
expected = 0 if rows == [] else 20000
assert (
contactout.observed(
_contactout_cost("companies.enrich"), {}, {"status_code": 200, "companies": rows}
)
== expected
)
@pytest.mark.parametrize(
"eid,params,amount",
[
("people.contact.work", {"email_type": "work"}, 150000),
(
"people.contact.work",
{"email_type": "work", "include_phone": "true"},
400000,
),
(
"people.contact.personal",
{"email_type": "personal", "include_phone": "true"},
500000,
),
("people.contact.phone", {"email_type": "none", "include_phone": True}, 250000),
("people.enrich", {"include": ["phone"]}, 270000),
("companies.enrich", {"domains": ["a.test", "b.test"]}, 40000),
("people.linkedin.from-email", {}, 60000),
("people.email.verify", {}, 0),
],
)
def test_contactout_holds(eid, params, amount):
assert contactout.estimate(_contactout_cost(eid), params) == amount
async def _contactout_balance(client):
org = (await client.get("/orgs")).json()[0]["org_id"]
return (await client.get(f"/orgs/{org}/balance")).json()
@pytest.mark.parametrize(
"own,status,doc,charge",
[
(
False,
200,
{
"status_code": 200,
"profile": {"work_email": ["a@example.test"], "phone": ["123"]},
},
400000,
),
(False, 200, {"status_code": 200, "profile": {"work_email": []}}, 0),
(False, 200, {"status_code": 403, "message": "No access"}, 0),
(False, 403, {"status_code": 403, "message": "Out of credits"}, 0),
(False, 429, {"status_code": 429}, 0),
(
True,
200,
{
"status_code": 200,
"profile": {"work_email": ["a@example.test"], "phone": ["123"]},
},
0,
),
],
)
async def test_contactout_platform_settles_once_and_own_key_wins(
clients, contactout_platform, monkeypatch, own, status, doc, charge
):
if own:
await clients.post("/secrets", json={"name": "contactout", "value": "OWN-TEST"})
async def relay(request, upstream_url, tool, secrets, client, **kwargs):
assert "/v1/people/linkedin" in upstream_url
binding = tool.bindings[0]
assert ("secret_id" in binding) == own
assert binding["name"] == "token"
async def stream():
yield json.dumps(doc).encode()
async def close():
pass
return UpstreamResponse(status, (), stream(), close)
monkeypatch.setattr(call_service, "relay", relay)
before = await _contactout_balance(clients)
response = await clients.get(
"/call/contactout.people.contact.work",
params={
"profile": "https://linkedin.com/in/test",
"email_type": "work",
"include_phone": "true",
},
)
assert response.status_code == status, response.text
assert response.json() == doc
after = await _contactout_balance(clients)
assert before["balance_micro"] - after["balance_micro"] == charge
entries = after["entries"]["items"]
closing = [e for e in entries if e["kind"] in ("settle", "release")]
assert len(closing) == (0 if own else 1)
def test_contactout_combined_email_array_is_not_billed_twice():
doc = {
"status_code": 200,
"profile": {
"work_email": ["work@example.test"],
"personal_email": [],
"email": ["work@example.test"],
},
}
c = _contactout_cost("people.enrich")
assert (
contactout.observed(
c, {"body": {"include": ["work_email", "personal_email"]}}, doc
)
== 170000
)
@pytest.mark.parametrize("body", [b"", b"not json", b"[]", b"{}", b'{"status_code":200}', b'{"status_code":200,"profile":"unexpected"}'])
def test_contactout_contact_reveals_require_recognizable_hit_evidence(body):
from types import SimpleNamespace
from treg.application.call.settle import _observed_cost_micro
mk = SimpleNamespace(provider="contactout", endpoint_id="contactout.people.contact.work",
cost_type="per_success", unit_micro=0, billed_oauth=False, request_data={})
assert _observed_cost_micro(mk, body) == 0
@pytest.mark.parametrize("profile_only", [True, "true", "1"])
@pytest.mark.parametrize("doc,charge", [
({"status_code": 200, "profile": {"full_name": "Synthetic Example"}}, 20000),
({"status_code": 200, "profile": {}}, 0),
({"status_code": 200, "profile": []}, 0),
({"status_code": 403, "profile": {"full_name": "Synthetic Example"}}, 0),
])
def test_contactout_profile_only_found_and_miss_billing(profile_only, doc, charge):
cost = _contactout_cost("people.linkedin.enrich")
request = {"profile_only": profile_only}
assert contactout.estimate(cost, request) == 20000
assert contactout.observed(cost, {"queryParams": request}, doc) == charge
def test_email_path_keeps_at_sign_but_cannot_inject_path_or_query():
# Synthetic path-parameter endpoint: the current Tomba verifier uses a query.
ep = {**catalog_store.load().by_id['tomba.people.email.verify'], 'path':'/v1/email-verifier/{email}'}
url, consumed = call_resolution._marketplace_upstream(
ep, oauth_providers.TOMBA, {'email': 'person@example.com'})
assert url == 'https://api.tomba.io/v1/email-verifier/person@example.com'
assert consumed == {'email'}
url, _ = call_resolution._marketplace_upstream(
ep, oauth_providers.TOMBA, {'email': 'person@example.com/extra?x=1#fragment'})
assert url.endswith('person@example.com%2Fextra%3Fx%3D1%23fragment')
@pytest.mark.parametrize(
"endpoint,body,expected",
[
("dropleads.companies.enrich", {"domains": [f"{i}.test" for i in range(25)],
"companyNames": [str(i) for i in range(25)]}, 90_000),
("dropleads.companies.search", {"filters": {},
"pagination": {"page": 1, "limit": 50}}, 90_000),
("dropleads.companies.search", {"filters": {},
"pagination": {"page": 1, "limit": "50"}}, 90_000),
],
)
def test_dropleads_request_shapes_reserve_exact_valid_maxima(endpoint, body, expected):
catalog = catalog_store.load()
ep = catalog.by_id[endpoint]
cost = catalog.cost_view(ep["cost"], "dropleads")
estimate, _ = call_resolution._marketplace_pricing(
"dropleads", endpoint, cost, {}, json.dumps(body).encode()
)
assert estimate == expected
@pytest.mark.parametrize(
"endpoint,unit_micro,doc,expected",
[
*[(endpoint, 24_500, doc, expected) for endpoint, doc, expected in [
("prospeo.people.email.find",
{"error": False, "free_enrichment": False, "person": {"email": {"email": "jane@example.com"}}},
24_500),
("prospeo.people.email.find",
{"error": False, "free_enrichment": False, "person": {"email": {"email": None}}}, 0),
("prospeo.people.email.find",
{"error": False, "free_enrichment": False, "person": {}}, 0),
("prospeo.people.enrich",
{"error": False, "free_enrichment": True, "person": {"person_id": "p1"}}, 0),
("prospeo.people.enrich",
{"error": False, "free_enrichment": False,
"person": {"person_id": "p1", "email": {"email": None}}}, 24_500),
("prospeo.people.enrich",
{"error": False, "free_enrichment": False, "person": {}}, 0),
("prospeo.companies.enrich",
{"error": False, "free_enrichment": False, "company": {"company_id": "c1"}}, 24_500),
("prospeo.companies.enrich",
{"error": False, "free_enrichment": False, "company": None}, 0),
("prospeo.companies.enrich",
{"error": False, "free_enrichment": False}, 0),
("prospeo.people.search",
{"error": False, "free": False, "results": [{"person": {"person_id": "p1"}}]}, 24_500),
("prospeo.companies.search",
{"error": False, "free": True, "results": [{"company": {"company_id": "c1"}}]}, 0),
("prospeo.search.suggestions",
{"error": False, "location_results": []}, 0),
("prospeo.people.email.find", {"error": True, "error_code": "NO_MATCH"}, 0),
]],
# a phone reveal settles only on an actual mobile number
*[("prospeo.people.phone.find", 245_000, doc, expected) for doc, expected in [
({"error": False, "free_enrichment": False,
"person": {"mobile": {"mobile_international": "+15550101000"}}}, 245_000),
({"error": False, "free_enrichment": False,
"person": {"mobile": {"mobile_international": None}}}, 0),
({"error": False, "free_enrichment": False, "person": {"mobile": {}}}, 0),
({"error": False, "free_enrichment": False, "person": {}}, 0),
({"error": False, "free_enrichment": False}, 0),
({"error": False, "free_enrichment": False, "person": "malformed"}, None),
]],
],
)
def test_prospeo_settles_only_from_response_evidence(endpoint, unit_micro, doc, expected):
mk = _mk("prospeo", endpoint_id=endpoint, cost_type="per_success", unit_micro=unit_micro)
assert call_settle._observed_cost_micro(mk, json.dumps(doc).encode()) == expected
@pytest.mark.parametrize(("endpoint", "params"), [
("trestleiq.people.phone.verify", {"phone": "+13005550103"}),
("trestleiq.people.contact.verify", {"name": "Jon Snow", "phone": "300-555-0103"}),
("trestleiq.people.address.verify", {"street_line_1": "800 Bellevue Way NE"}),
])
async def test_trestleiq_strict_query_blocks_paid_add_ons_for_every_tier(
clients, trestleiq_platform_on, endpoint, params,
):
response = await clients.get(f"/call/{endpoint}", params={**params, "add_ons": "paid"})
assert response.status_code == 400
await clients.post("/secrets", json={"name": "trestleiq", "value": "OWN-TRESTLEIQ"})
response = await clients.get(f"/call/{endpoint}", params={**params, "add_ons": "paid"})
assert response.status_code == 400
def _priced(endpoint_id: str, query: dict | None = None, body: dict | None = None, **kw):
"""A MarketplaceCall priced the way resolve prices it: the reserve and the per-count unit come
from `_marketplace_pricing` over the real catalog row, never from a constant."""
catalog = catalog_store.load()
ep = catalog.by_id[endpoint_id]
cv = catalog.cost_view(ep["cost"], ep["provider"])
raw = json.dumps(body).encode() if body is not None else b""
estimate, unit = call_resolution._marketplace_pricing(
ep["provider"], endpoint_id, cv, call_resolution.QueryValues(tuple((query or {}).items())), raw)
mk = _mk(ep["provider"], endpoint_id=endpoint_id, cost_type=ep["cost"]["type"],
unit_micro=unit, estimate_micro=estimate, **kw)
return mk, estimate, _usd_to_micro_for_test(cv["usd"])
def _usd_to_micro_for_test(usd) -> int:
return int(round(float(usd) * 1_000_000))
@pytest.mark.parametrize(("endpoint_id", "query", "req", "body", "rows"), [
# CompanyEnrich: 2 credits per person returned, the 2-credit minimum on an empty page.
("companyenrich.people.search", None, {"pageSize": 10}, b'{"items":[]}', 1),
("companyenrich.people.search", None, {"pageSize": 10}, b'{"items":[{},{},{}]}', 3),
("companyenrich.people.search", None, {"pageSize": 10}, b'{"totalItems":0}', None),
# Icypeas bulk: only FOUND rows bill, at the row's credits (10 per reverse-email hit).
("icypeas.people.identity.resolve.bulk", None, {"data": [["a@x.io"], ["b@x.io"], ["c@x.io"]]},
b'{"data":[{"status":"FOUND"},{"status":"NOT_FOUND"},{"status":"FOUND"}]}', 2),
("icypeas.profile.url.bulk", None, {"data": [["a"], ["b"]]}, b'{"data":[{"status":"NOT_FOUND"}]}', 0),
# Icypeas lead-database search: 0.02 credit per lead returned, never the requested page.
("icypeas.people.search", None, {"query": {}, "pagination": {"size": 5}},
b'{"success":true,"leads":[],"total":0}', 0),
("icypeas.people.search", None, {"query": {}, "pagination": {"size": 5}},
b'{"success":true,"leads":[{},{}],"total":2}', 2),
("icypeas.people.search", None, {"query": {}, "pagination": {"size": 5}},
b'{"validationErrors":[{"field":"query"}],"success":false}', 0),
("icypeas.companies.search", None, {"query": {}, "pagination": {"size": 5}},
b'{"success":true,"leads":[{}],"total":1}', 1),
# Serpstat: an error envelope is free, rows bill with a 1-credit minimum, unknown shapes estimate.
("serpstat.web.backlinks.list", None, {"params": {"size": 50}},
b'{"id":"1","error":{"code":-32600,"message":"Data not found"}}', 0),
("serpstat.web.backlinks.list", None, {"params": {"size": 50}}, b'{"id":"1","result":{"data":[{},{}]}}', 2),
("serpstat.web.backlinks.list", None, {"params": {"size": 50}}, b'{"id":"1","result":{"data":[]}}', 1),
("serpstat.google.domain.overview", None, {"params": {"domains": ["a.com", "b.com"]}},
b'{"id":"1","result":{"a.com":{},"b.com":{}}}', None),
# TheCompaniesAPI search: one credit per company returned.
("thecompaniesapi.companies.search", {"size": "10"}, None, b'{"companies":[]}', 0),
("thecompaniesapi.companies.search", {"size": "10"}, None, b'{"companies":[{},{}]}', 2),
# Findymail employee search: one credit per contact, never above the hold.
("findymail.search.employees", None, {"website": "x.io", "job_titles": ["CEO"], "count": 5}, b'[]', 0),
])
def test_per_result_search_settles_on_rows_returned_not_rows_requested(endpoint_id, query, req, body, rows):
"""Each reserves the requested page; the body says how many rows the vendor billed. The unit
comes from the real pricing path, where a credit-priced row's unit is ONE credit."""
mk, estimate, per_row = _priced(endpoint_id, query, req)
observed = call_settle._observed_cost_micro(mk, body)
if rows is None:
assert observed is None
else:
assert observed == min(rows * per_row, estimate), (observed, per_row, estimate)
def test_row_counts_never_bill_above_the_hold():
"""A row whose catalog unit names an input entity reserves per thing asked about; counting
returned rows may lower that bill, never raise it."""
mk, estimate, _ = _priced("findymail.search.employees", None,
{"website": "x.io", "job_titles": ["CEO"], "count": 5})
assert call_settle._observed_cost_micro(mk, json.dumps([{"name": str(i)} for i in range(5)]).encode()) == estimate
mk, estimate, _ = _priced("serpstat.google.domain.ranked_keywords", None,
{"params": {"domain": "a.com", "se": "g_us", "size": 1000}})
assert call_settle._observed_cost_micro(
mk, json.dumps({"id": "1", "result": {"data": [{}] * 1000}}).encode()) == estimate
def test_thecompaniesapi_simplified_is_free_only_where_declared():
mk, _, _ = _priced("thecompaniesapi.companies.search", {"size": "10", "simplified": "true"}, None,
request_data={"queryParams": {"size": "10", "simplified": "true"}})
assert call_settle._observed_cost_micro(mk, b'{"companies":[{},{}]}') == 0
ep = catalog_store.load().by_id["thecompaniesapi.companies.email_pattern"]
assert "simplified" not in ((ep.get("input") or {}).get("queryParams") or {})
other = _mk("thecompaniesapi", endpoint_id=ep["id"], cost_type=ep["cost"]["type"], unit_micro=9_500,
request_data={"queryParams": {"simplified": "true"}})
assert call_settle._observed_cost_micro(other, b'{"pattern":"{first}"}') != 0
def test_icypeas_profile_url_miss_settles_at_zero():
"""No adapter reads these bodies, so the endpoint's `expect` rule is what makes a miss free."""
for endpoint in ("icypeas.people.profile.url", "icypeas.companies.profile.url"):
mk = _mk("icypeas", endpoint_id=endpoint, cost_type="per_success", unit_micro=3_800)
assert call_settle._observed_cost_micro(mk, b'{"success":true,"result":null,"status":"NOT_FOUND"}') == 0
assert call_settle._observed_cost_micro(
mk, b'{"success":true,"result":"https://www.linkedin.com/in/x","status":"FOUND"}') is None
@pytest.mark.parametrize("task,rows,credits", [
("email-verification", 3, 0.3), ("email-search", 3, 3), ("domain-search", 1, 1)])
def test_icypeas_bulk_search_bills_its_rows_at_the_task_rate(task, rows, credits):
"""The start answer has no rows, so the reserve is the bill: never the 20-row default."""
body = {"name": "x", "task": task, "data": [["a@example.com"]] * rows}
mk, estimate, credit = _priced("icypeas.bulk.search", None, body, request_data={"body": body})
assert estimate == round(credits * credit)
assert call_settle._observed_cost_micro(
mk, b'{"success":true,"status":"in_progress","file":"f"}') in (None, estimate)
def test_icypeas_single_email_search_bills_one_credit_not_a_page():
body = {"firstname": "A", "lastname": "B", "domainOrCompany": "example.com"}
mk, estimate, credit = _priced("icypeas.people.email.find", None, body, request_data={"body": body})
assert estimate == credit
assert call_settle._observed_cost_micro(
mk, b'{"success":true,"item":{"_id":"x","status":"NONE"}}') in (None, estimate)
def test_icypeas_company_scrape_bills_the_company_rate():
body = {"type": "company", "data": ["https://www.linkedin.com/company/a", "https://www.linkedin.com/company/b"]}
mk, estimate, per_row = _priced("icypeas.scrape.bulk", None, body, request_data={"body": body})
found = b'{"data":[{"status":"FOUND"},{"status":"FOUND"}]}'
assert call_settle._observed_cost_micro(mk, found) == 2 * mk.unit_micro // 2 # 0.5 credit each
mk, _, _ = _priced("icypeas.scrape.bulk", None, {**body, "type": "profile"},
request_data={"body": {**body, "type": "profile"}})
assert call_settle._observed_cost_micro(mk, found) == 3 * mk.unit_micro # 1.5 credits each
# ---------------------------------------------------------------------------------------------
# A synchronous `settle: usage` endpoint (Jev through OpenRouter, added for the AI visibility tool)
@pytest.fixture
def openrouter_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_OPENROUTER", "PLATFORM-OPENROUTER")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "openrouter")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def test_a_sync_usage_settled_call_charges_the_providers_reported_cost(
clients, monkeypatch, openrouter_platform_on,
):
"""Live 2026-09-23: Jev settled at its $0.0005 reserve because the `usage` basis only read the
async worker's terminal document. A synchronous call now hands its own body: the reply's
`usage.cost` (here $0.00002 = 20 µ$) is what the caller pays, and the rest of the hold is
given back. The `..` path lands on /api/alpha/decisions, outside the provider's /api/v1 base."""
reply = {"model": "typesafe/jev-1.13-20260917",
"answers": {"q": {"type": "noul", "noul": 0.95}},
"usage": {"cost": 0.00002, "prompt_tokens": 476}}
def serve(request):
assert request.url.path == "/api/alpha/decisions"
assert request.headers["authorization"] == "Bearer PLATFORM-OPENROUTER"
return _dropleads_response(200, reply) # a fresh stream per call, like every mock here
before = await _balance(clients)
async with httpx.AsyncClient(transport=httpx.MockTransport(serve)) as upstream:
monkeypatch.setattr(A.app.state, "http", upstream)
r = await clients.post("/call/openrouter.ai-judge.decide", json={
"model": "typesafe/jev-1.13", "state": "# Decision context\n\n<text>x</text>",
"questions": {"q": {"type": "noul", "instructions": "is it x?",
"criteria": {"true": "x", "false": "not x"}}}})
assert r.status_code == 200, r.text
assert r.json() == reply
assert r.headers["x-treg-cost-micro"] == "20"
assert before - await _balance(clients) == 20
@pytest.mark.parametrize(('body', 'fee'), [
({'jobTitles': ['a'], 'locations': ['x']}, 1_000),
({'jobTitles': ['a', 'b', 'c'], 'locations': ['x']}, 3_000),
({'jobTitles': ['a'], 'locations': ['x', 'y']}, 2_000),
({'jobTitles': ['a', 'b'], 'locations': []}, 2_000),
({'jobTitles': ['a', 'b']}, 2_000),
])
def test_apify_call_fee_multiplies_by_each_query_the_actor_starts(body, fee):
"""LinkedIn jobs bills one actor-start per job title x location searched."""
cost = catalog_store.load().by_id['apify.linkedin.search.jobs']['cost']
mk = _mk('apify', endpoint_id='apify.linkedin.search.jobs', cost_type='per_result',
unit_micro=1_000, estimate_micro=1_001_000, request_data={'body': body})
assert call_settle._apify_call_fee_micro(mk, cost) == fee
assert call_settle._observed_cost_micro(mk, b'[{}]') == 1_000 + fee
# Enrichlayer settles from this call's returned result count.
@pytest.fixture
def enrichlayer_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_ENRICHLAYER", "PLATFORM-ENRICHLAYER")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "enrichlayer")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def test_enrichlayer_result_count_and_own_key_ladder(
clients, monkeypatch, enrichlayer_platform_on,
):
seen = []
def serve(request):
assert request.url.path == "/api/v2/search/person"
seen.append(request.headers["authorization"])
return _dropleads_response(200, {"results": [{"profile_url": "https://example.com/p"}]})
params = {"current_company_name": "Microsoft", "page_size": "1",
"enrich_profiles": "enrich", "use_cache": "if-recent"}
async with httpx.AsyncClient(transport=httpx.MockTransport(serve)) as upstream:
monkeypatch.setattr(A.app.state, "http", upstream)
before = await _balance(clients)
response = await clients.get("/call/enrichlayer.people.search", params=params)
assert response.status_code == 200, response.text
assert response.headers["x-treg-cost-micro"] == "600000"
assert before - await _balance(clients) == 600000
await clients.post("/secrets", json={"name": "enrichlayer", "value": "OWN-ENRICHLAYER"})
before = await _balance(clients)
own = await clients.get("/call/enrichlayer.people.search", params=params)
assert own.status_code == 200, own.text
assert "x-treg-cost-micro" not in own.headers
assert before == await _balance(clients)
assert seen == ["Bearer PLATFORM-ENRICHLAYER", "Bearer OWN-ENRICHLAYER"]
# Octen reserves a maximum before relay and settles from this response's usage.
@pytest.fixture
def octen_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_OCTEN", "PLATFORM-OCTEN")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "octen")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def test_octen_platform_key_serves_then_own_key_wins_unmetered(
clients, monkeypatch, octen_platform_on,
):
assert get_settings().platform_key_for("octen") == "PLATFORM-OCTEN"
seen = []
document = {"code": 0, "data": {"results": []}, "meta": {"usage": {
"num_search_queries": 1, "full_content_extra_count": 0,
}}}
def serve(request):
assert request.method == "POST" and request.url.path == "/search"
seen.append(request.headers["x-api-key"])
return _dropleads_response(200, document)
async with httpx.AsyncClient(transport=httpx.MockTransport(serve)) as upstream:
monkeypatch.setattr(A.app.state, "http", upstream)
before = await _balance(clients)
platform = await clients.post("/call/octen.web.search", json={"query": "x", "count": 1})
assert platform.status_code == 200, platform.text
assert platform.json() == document
assert platform.headers["x-treg-cost-micro"] == "5000"
assert before - await _balance(clients) == 5000
await clients.post("/secrets", json={"name": "octen", "value": "OWN-OCTEN"})
before = await _balance(clients)
own = await clients.post("/call/octen.web.search", json={"query": "x", "count": 1})
assert own.status_code == 200, own.text
assert own.json() == document
assert "x-treg-cost-micro" not in own.headers
assert await _balance(clients) == before
assert seen == ["PLATFORM-OCTEN", "OWN-OCTEN"]
_OCTEN_RATES = {
"octen.web.search": {"call": 5000, "full_content_extra": 500},
"octen.web.search.broad": {"subquery": 5000, "full_content_extra": 500},
"octen.web.search.news": {"call": 3000, "full_content_extra": 500},
"octen.web.extract": {"standard": 1000, "advanced": 2500},
}
@pytest.mark.parametrize("endpoint,payload,hold,usage,settled", [
("octen.web.search", {"query": "x", "count": 11}, 5000,
{"num_search_queries": 1, "full_content_extra_count": 0}, 5000),
("octen.web.search", {"query": "x", "count": 11, "full_content": {"enable": True}}, 5500,
{"num_search_queries": 1, "full_content_extra_count": 1}, 5500),
("octen.web.search.broad", {"query": "x", "max_queries": 4,
"search_options": {"count": 12, "full_content": {"enable": True}}},
24000, {"num_search_queries": 2, "full_content_extra_count": 1}, 10500),
("octen.web.search.news", {"query": "x", "count": 11,
"subjects": {"enable": False}, "full_content": {"enable": True}},
3500, {"num_search_queries": 1, "num_subject_search_queries": 0,
"full_content_extra_count": 1}, 3500),
("octen.web.extract", {"urls": ["https://example.com", "https://example.invalid"],
"mode": "auto"}, 5000,
{"total_urls": 2, "successful_urls": 1,
"successful_by_mode": {"standard_urls": 1, "advanced_urls": 0}}, 1000),
])
def test_octen_hold_and_actual_usage(endpoint, payload, hold, usage, settled):
body = json.dumps(payload).encode()
assert octen.invalid_platform_parameter(endpoint, body) is None
assert octen.estimate_micro(endpoint, _OCTEN_RATES[endpoint], body) == hold
doc = {"code": 0, "meta": {"usage": usage}}
assert octen.observed_micro(endpoint, _OCTEN_RATES[endpoint], {"body": payload}, doc, hold) == settled
def test_octen_news_hold_covers_subject_full_content():
request = {"query": "x", "count": 100, "subjects": {"count": 5, "max_sub_news": 20},
"full_content": {"enable": True}}
assert octen.estimate_micro("octen.web.search.news", _OCTEN_RATES["octen.web.search.news"],
json.dumps(request).encode()) == 98000
@pytest.mark.parametrize("endpoint,payload,field", [
("octen.web.search", {"query": "x", "count": 101}, "body.count"),
("octen.web.search", {"query": "x", "full_content": {"enable": "yes"}},
"body.full_content"),
("octen.web.search.broad", {"query": "x", "max_queries": 31}, "body.max_queries"),
("octen.web.search.broad", {"query": "x", "search_options": {"count": 101}},
"body.search_options.count"),
("octen.web.search.news", {"query": "x", "subjects": {"max_sub_news": 21}},
"body.subjects.max_sub_news"),
("octen.web.extract", {"urls": ["https://example.com"] * 21}, "body.urls"),
])
def test_octen_platform_rejects_unbounded_request(endpoint, payload, field):
assert octen.invalid_platform_parameter(endpoint, json.dumps(payload).encode()) == field
def test_octen_missing_or_impossible_usage_falls_back_to_hold():
endpoint = "octen.web.search.broad"
request = {"body": {"query": "x", "max_queries": 1}}
rates = _OCTEN_RATES[endpoint]
assert octen.observed_micro(endpoint, rates, request, {"code": 0}, 5000) is None
assert octen.observed_micro(endpoint, rates, request, {"code": 400}, 5000) == 0
assert octen.observed_micro(endpoint, rates, request, {"code": 0, "meta": {"usage": {
"num_search_queries": 2, "full_content_extra_count": 0}}}, 5000) is None
assert octen.observed_micro(endpoint, rates, request, {"code": 0, "meta": {"usage": {
"num_search_queries": True, "full_content_extra_count": 0}}}, 5000) is None
def test_octen_rate_table_is_complete_and_micro_precise():
endpoint = "octen.web.extract"
assert octen.rates_micro(endpoint, {"octen_rates": {
"standard": 0.001, "advanced": 0.0025}}) == _OCTEN_RATES[endpoint]
for rates in ({"standard": 0.001}, {"standard": 0, "advanced": 0.0025},
{"standard": 0.0010001, "advanced": 0.0025}):
with pytest.raises(ValueError):
octen.rates_micro(endpoint, {"octen_rates": rates})
with pytest.raises(ResolutionFailed) as missing:
call_resolution._marketplace_pricing("octen", endpoint, None, {}, b'{"urls":["https://example.com"]}')
assert missing.value.kind == "catalog_price_invalid"
assert missing.value.status_code == 503
def test_octen_runtime_uses_frozen_rates_and_checks_platform_shape():
endpoint = "octen.web.extract"
payload = {"urls": ["https://example.com"], "mode": "auto"}
body = json.dumps(payload).encode()
cost = {"octen_rates": {"standard": 0.001, "advanced": 0.0025}}
assert call_resolution._marketplace_pricing("octen", endpoint, cost, {}, body) == (2500, 0)
mk = _mk("octen", endpoint_id=endpoint, cost_type="per_success",
estimate_micro=2500, request_data={"body": payload},
settlement_basis={"octen_rates_micro": _OCTEN_RATES[endpoint]},
)
result = {"code": 0, "meta": {"usage": {"successful_urls": 1,
"successful_by_mode": {"standard_urls": 1, "advanced_urls": 0}}}}
assert call_settle._observed_cost_micro(mk, json.dumps(result).encode()) == 1000
with pytest.raises(ResolutionFailed) as caught:
call_resolution._enforce_platform_request({"provider": "octen", "id": endpoint},
json.dumps({"urls": ["https://example.com"] * 21}).encode())
assert caught.value.kind == "catalog_parameter_invalid"