Skip to content

Commit 79a8b84

Browse files
vvillait88claude
andcommitted
test: raise python coverage to 93% (pi_cache + UCP signed responses + openapi rails)
- pi_cache.py Redis-backed paths via sys.modules['redis.asyncio'] stub + eviction loop via asyncio.sleep monkeypatch. pi_cache.py 71 to 100 percent. - build_signed_ucp_response happy path + multi-rail (solana/stripe/tempo_session) + 503 misconfigured branch. - build_signed_jwks_response with X-Request-ID echo. - bootstrap_ucp_signing_key malformed-env throw + valid-env success. - default_a2a_services + well_known_cors_preflight_headers. - x_payment_info_from_checkout across all four rail types (stripe, base, solana with currency, tempo). openapi.py 86 to 100 percent. Drops an unused _FacilitatorStub ellipsis-body class from the bazaar test (code-quality bot flagged the no-op statement); facilitator is now opaque object(). Tests: 1146 passed, 4 skipped. Total coverage: 91.19 to 93.02 percent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 720b990 commit 79a8b84

3 files changed

Lines changed: 334 additions & 4 deletions

File tree

tests/test_lifted_helpers.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import asyncio
66
import base64
7+
import contextlib
78
import json
89
import time
910
from collections.abc import Awaitable
@@ -83,6 +84,104 @@ async def test_pi_cache_no_redis_url_falls_back_to_memory_only():
8384
cache.stop()
8485

8586

87+
# Fake redis.asyncio module so the Redis-backed branches of pi_cache run
88+
# without requiring the optional `redis` peer dep in the test env.
89+
class _FakeRedisAsync:
90+
def __init__(self) -> None:
91+
self.store: dict[str, str] = {}
92+
self.get_raises: bool = False
93+
94+
async def set(self, key: str, value: str, *, ex: int) -> None:
95+
self.store[key] = value
96+
97+
async def get(self, key: str) -> str | None:
98+
if self.get_raises:
99+
raise RuntimeError("simulated redis transient")
100+
return self.store.get(key)
101+
102+
103+
class _FakeRedisAsyncioModule:
104+
def __init__(self) -> None:
105+
self.instance = _FakeRedisAsync()
106+
107+
def from_url(self, _url: str) -> _FakeRedisAsync:
108+
return self.instance
109+
110+
111+
@pytest.fixture
112+
def _fake_redis(monkeypatch: pytest.MonkeyPatch) -> _FakeRedisAsync:
113+
import sys
114+
115+
module = _FakeRedisAsyncioModule()
116+
# Inject under `redis.asyncio` so `import_module("redis.asyncio")` returns our stub.
117+
monkeypatch.setitem(sys.modules, "redis.asyncio", module)
118+
return module.instance
119+
120+
121+
@pytest.mark.asyncio
122+
async def test_pi_cache_redis_backed_cache_address_round_trip(_fake_redis: _FakeRedisAsync) -> None:
123+
cache = create_pi_cache(redis_url="redis://localhost:6379", ttl_seconds=10)
124+
await cache.cache_address("0xredis-test")
125+
# set hits the Redis stub
126+
assert any("0xredis-test" in k for k in _fake_redis.store)
127+
# has_address hits the Redis stub and returns True
128+
assert await cache.has_address("0xredis-test") is True
129+
cache.stop()
130+
131+
132+
@pytest.mark.asyncio
133+
async def test_pi_cache_redis_get_error_falls_back_to_memory(_fake_redis: _FakeRedisAsync) -> None:
134+
cache = create_pi_cache(redis_url="redis://localhost:6379", ttl_seconds=10)
135+
await cache.cache_address("0xfallback") # writes to memory mirror + redis stub
136+
_fake_redis.get_raises = True
137+
# The redis-get raises, so the function falls back to the in-memory mirror.
138+
assert await cache.has_address("0xfallback") is True
139+
cache.stop()
140+
141+
142+
@pytest.mark.asyncio
143+
async def test_pi_cache_redis_url_set_but_module_missing_falls_back(monkeypatch: pytest.MonkeyPatch) -> None:
144+
import sys
145+
146+
# Force `import redis.asyncio` to raise even if a real redis lib is installed.
147+
monkeypatch.setitem(sys.modules, "redis.asyncio", None)
148+
cache = create_pi_cache(redis_url="redis://localhost:6379", ttl_seconds=10)
149+
await cache.cache_address("0xnoredis-pkg")
150+
assert await cache.has_address("0xnoredis-pkg") is True
151+
cache.stop()
152+
153+
154+
@pytest.mark.asyncio
155+
async def test_pi_cache_eviction_loop_clears_expired_entries() -> None:
156+
"""Drive _evict_loop manually by zeroing the sleep delay so the eviction body runs."""
157+
import agentscore_commerce.stripe_multichain.pi_cache as pi_cache_mod
158+
159+
real_sleep = asyncio.sleep
160+
sleep_calls: list[float] = []
161+
162+
async def _no_sleep(delay: float) -> None:
163+
sleep_calls.append(delay)
164+
if len(sleep_calls) >= 2:
165+
raise asyncio.CancelledError
166+
# Let the loop yield once so other tasks (e.g. test polling) can interleave.
167+
await real_sleep(0)
168+
169+
pi_cache_mod.asyncio.sleep = _no_sleep # type: ignore[assignment]
170+
try:
171+
cache = create_pi_cache(ttl_seconds=0)
172+
cache.cache_payment_intent("0xexp", "pi_exp")
173+
cache.cache_network_addresses("pi_exp", {"base": "0xb"})
174+
await cache.cache_address("0xexp-addr")
175+
# Wait for the eviction loop to fire (raises CancelledError after 2 iterations).
176+
with contextlib.suppress(asyncio.CancelledError):
177+
await real_sleep(0.05)
178+
# After one tick, expired entries should be evicted.
179+
assert cache.get_payment_intent_id("0xexp") is None
180+
cache.stop()
181+
finally:
182+
pi_cache_mod.asyncio.sleep = real_sleep # type: ignore[assignment]
183+
184+
86185
# ─────────────────────────────────────────────────────────────────────────────
87186
# simulate_deposit_if_test_mode
88187
# ─────────────────────────────────────────────────────────────────────────────

tests/test_payment_servers.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,6 @@ async def test_create_x402_server_bazaar_registers_extension(monkeypatch: pytest
267267

268268
registered: list[Any] = []
269269

270-
class _FacilitatorStub:
271-
async def get_supported(self) -> Any: ...
272-
273270
# Patch register_extension on the class BEFORE creating the server so the
274271
# bazaar branch records its extension call.
275272
import x402
@@ -282,7 +279,7 @@ def patched_init(self: Any, **kw: Any) -> None:
282279

283280
monkeypatch.setattr(x402.x402ResourceServer, "__init__", patched_init)
284281
await create_x402_server(
285-
facilitator=_FacilitatorStub(),
282+
facilitator=object(), # opaque facilitator; never called with initialize=False
286283
bazaar=True,
287284
initialize=False,
288285
)

tests/test_seamless_helpers.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
import asyncio
1717
import base64
18+
import contextlib as _contextlib
1819
import json
20+
import os
1921
from typing import Any
2022
from unittest.mock import patch
2123

@@ -595,6 +597,202 @@ def test_handle_django_returns_402_on_discovery_leg(monkeypatch: pytest.MonkeyPa
595597
# ─────────────────────────────────────────────────────────────────────────────
596598

597599

600+
# ─────────────────────────────────────────────────────────────────────────────
601+
# buildSignedUcpResponse / buildSignedJwksResponse / bootstrapUcpSigningKey
602+
# ─────────────────────────────────────────────────────────────────────────────
603+
604+
605+
@_contextlib.contextmanager
606+
def _env_key(jwk_dict: dict[str, Any]) -> Any:
607+
"""Yield with UCP_SIGNING_KEY_JWK_PRIVATE set to ``jwk_dict``, restoring on exit."""
608+
from agentscore_commerce.identity.ucp_jwks import _reset_ucp_signing_key_cache
609+
610+
_reset_ucp_signing_key_cache()
611+
prev = os.environ.get("UCP_SIGNING_KEY_JWK_PRIVATE")
612+
os.environ["UCP_SIGNING_KEY_JWK_PRIVATE"] = json.dumps(jwk_dict)
613+
try:
614+
yield
615+
finally:
616+
if prev is None:
617+
os.environ.pop("UCP_SIGNING_KEY_JWK_PRIVATE", None)
618+
else:
619+
os.environ["UCP_SIGNING_KEY_JWK_PRIVATE"] = prev
620+
_reset_ucp_signing_key_cache()
621+
622+
623+
def test_bootstrap_ucp_signing_key_throws_on_malformed_env() -> None:
624+
from agentscore_commerce.discovery import bootstrap_ucp_signing_key
625+
from agentscore_commerce.identity.ucp_jwks import _reset_ucp_signing_key_cache
626+
627+
_reset_ucp_signing_key_cache()
628+
prev = os.environ.get("UCP_SIGNING_KEY_JWK_PRIVATE")
629+
os.environ["UCP_SIGNING_KEY_JWK_PRIVATE"] = "not-json"
630+
try:
631+
with pytest.raises((ValueError, Exception)):
632+
bootstrap_ucp_signing_key()
633+
finally:
634+
if prev is None:
635+
os.environ.pop("UCP_SIGNING_KEY_JWK_PRIVATE", None)
636+
else:
637+
os.environ["UCP_SIGNING_KEY_JWK_PRIVATE"] = prev
638+
_reset_ucp_signing_key_cache()
639+
640+
641+
def test_bootstrap_ucp_signing_key_succeeds_with_valid_env() -> None:
642+
from agentscore_commerce.discovery import bootstrap_ucp_signing_key
643+
from agentscore_commerce.identity.ucp_jwks import generate_ucp_signing_key
644+
645+
key = generate_ucp_signing_key(kid="bootstrap-test")
646+
private_jwk = key.private_key.as_dict(private=True)
647+
with _env_key(private_jwk):
648+
bootstrap_ucp_signing_key() # should not raise
649+
650+
651+
def test_build_signed_jwks_response_emits_jwk_set_json() -> None:
652+
from agentscore_commerce.discovery import build_signed_jwks_response
653+
from agentscore_commerce.identity.ucp_jwks import generate_ucp_signing_key
654+
655+
key = generate_ucp_signing_key(kid="jwks-test")
656+
private_jwk = key.private_key.as_dict(private=True)
657+
with _env_key(private_jwk):
658+
resp = build_signed_jwks_response(request_headers={"X-Request-Id": "req-jwks"})
659+
assert resp.status == 200
660+
assert resp.media_type == "application/jwk-set+json"
661+
assert "max-age=300" in resp.headers["Cache-Control"]
662+
assert resp.headers["X-Request-ID"] == "req-jwks"
663+
body = json.loads(resp.content)
664+
assert len(body["keys"]) == 1
665+
666+
667+
def test_build_signed_ucp_response_misconfigured_when_no_rails() -> None:
668+
from agentscore_commerce.checkout import Checkout, PricingResult
669+
from agentscore_commerce.discovery import build_signed_ucp_response
670+
671+
async def _pricing(ctx: Any) -> PricingResult:
672+
return PricingResult(amount_usd=1.0)
673+
674+
checkout = Checkout(rails={}, url="https://x/purchase", compute_pricing=_pricing)
675+
resp = build_signed_ucp_response(
676+
checkout=checkout,
677+
name="X",
678+
well_known_ucp_url="https://x/.well-known/ucp",
679+
services={},
680+
request_headers={"X-Request-Id": "req-misc"},
681+
)
682+
assert resp.status == 503
683+
assert "max-age=60" in resp.headers["Cache-Control"]
684+
assert resp.headers["X-Request-ID"] == "req-misc"
685+
body = json.loads(resp.content)
686+
assert body["error"]["code"] == "ucp_misconfigured"
687+
688+
689+
def test_build_signed_ucp_response_happy_path_signs_profile() -> None:
690+
from agentscore_commerce.checkout import Checkout, PricingResult
691+
from agentscore_commerce.discovery import build_signed_ucp_response
692+
from agentscore_commerce.identity.ucp_jwks import generate_ucp_signing_key
693+
694+
async def _pricing(ctx: Any) -> PricingResult:
695+
return PricingResult(amount_usd=1.0)
696+
697+
key = generate_ucp_signing_key(kid="ucp-test")
698+
private_jwk = key.private_key.as_dict(private=True)
699+
checkout = Checkout(
700+
rails={
701+
"tempo": TempoRailSpec(recipient="0x" + "00" * 19 + "dE" + "aD"),
702+
"base": X402BaseRailSpec(recipient="0x" + "00" * 19 + "dE" + "aD"),
703+
},
704+
url="https://x/purchase",
705+
compute_pricing=_pricing,
706+
)
707+
with _env_key(private_jwk):
708+
resp = build_signed_ucp_response(
709+
checkout=checkout,
710+
name="AgentScore Store",
711+
well_known_ucp_url="https://x/.well-known/ucp",
712+
services={"dev.ucp.shopping": []},
713+
signing_kid="ucp-test",
714+
request_headers={"X-Request-Id": "req-ucp"},
715+
)
716+
assert resp.status == 200
717+
assert resp.headers["X-Request-ID"] == "req-ucp"
718+
assert "max-age=60" in resp.headers["Cache-Control"]
719+
body = json.loads(resp.content)
720+
assert body["ucp"]["name"] == "AgentScore Store"
721+
assert "signature" in body
722+
assert body["ucp"]["payment_handlers"]
723+
724+
725+
def test_build_signed_ucp_response_includes_solana_stripe_tempo_session() -> None:
726+
from agentscore_commerce.checkout import Checkout, PricingResult
727+
from agentscore_commerce.discovery import build_signed_ucp_response
728+
from agentscore_commerce.identity.ucp_jwks import generate_ucp_signing_key
729+
from agentscore_commerce.payment import SolanaMppRailSpec, StripeRailSpec, TempoSessionRailSpec
730+
731+
async def _pricing(ctx: Any) -> PricingResult:
732+
return PricingResult(amount_usd=1.0)
733+
734+
key = generate_ucp_signing_key(kid="ucp-multi")
735+
private_jwk = key.private_key.as_dict(private=True)
736+
checkout = Checkout(
737+
rails={
738+
"solana": SolanaMppRailSpec(recipient="SoLaNaReCiPiEnT"),
739+
"stripe": StripeRailSpec(profile_id="profile_abc"),
740+
"tempo_session": TempoSessionRailSpec(
741+
recipient="0x" + "00" * 20,
742+
escrow_contract="0x" + "11" * 20,
743+
store=object(),
744+
),
745+
},
746+
url="https://x/purchase",
747+
compute_pricing=_pricing,
748+
)
749+
with _env_key(private_jwk):
750+
resp = build_signed_ucp_response(
751+
checkout=checkout,
752+
name="Multi-Rail",
753+
well_known_ucp_url="https://x/.well-known/ucp",
754+
services={},
755+
signing_kid="ucp-multi",
756+
)
757+
assert resp.status == 200
758+
body = json.loads(resp.content)
759+
keys = list(body["ucp"]["payment_handlers"].keys())
760+
assert any("mpp" in k or "stripe" in k for k in keys)
761+
762+
763+
def test_default_a2a_services_returns_canonical_a2a_binding() -> None:
764+
from agentscore_commerce.discovery.well_known import default_a2a_services
765+
766+
services = default_a2a_services(agent_card_url="https://x/.well-known/agent-card.json")
767+
assert "dev.ucp.shopping" in services
768+
binding = services["dev.ucp.shopping"][0]
769+
assert binding.transport == "a2a"
770+
assert binding.endpoint == "https://x/.well-known/agent-card.json"
771+
772+
773+
def test_well_known_cors_preflight_headers_without_request() -> None:
774+
from agentscore_commerce.discovery import well_known_cors_preflight_headers
775+
776+
headers = well_known_cors_preflight_headers()
777+
assert headers["Access-Control-Allow-Origin"] == "*"
778+
assert "GET" in headers["Access-Control-Allow-Methods"]
779+
assert "Access-Control-Allow-Headers" not in headers
780+
781+
782+
def test_well_known_cors_preflight_headers_echoes_acrh() -> None:
783+
from agentscore_commerce.discovery import well_known_cors_preflight_headers
784+
785+
headers = well_known_cors_preflight_headers(
786+
{"Access-Control-Request-Headers": "x-foo, x-bar"},
787+
)
788+
assert headers["Access-Control-Allow-Headers"] == "x-foo, x-bar"
789+
790+
791+
# ─────────────────────────────────────────────────────────────────────────────
792+
# load_solana_fee_payer
793+
# ─────────────────────────────────────────────────────────────────────────────
794+
795+
598796
def test_load_solana_fee_payer_returns_none_on_empty() -> None:
599797
from agentscore_commerce.payment.solana import load_solana_fee_payer
600798

@@ -801,6 +999,42 @@ def test_x_payment_info_from_checkout_lists_protocols_per_rail() -> None:
801999
assert any(p.get("mpp", {}).get("method") == "tempo" for p in block["protocols"])
8021000

8031001

1002+
def test_x_payment_info_from_checkout_covers_all_rail_types() -> None:
1003+
from agentscore_commerce.checkout import Checkout, PricingResult
1004+
from agentscore_commerce.discovery import (
1005+
XPaymentInfoFixedPrice,
1006+
x_payment_info_from_checkout,
1007+
)
1008+
from agentscore_commerce.payment import SolanaMppRailSpec, StripeRailSpec
1009+
1010+
async def _pricing(ctx: Any) -> PricingResult:
1011+
return PricingResult(amount_usd=1.0)
1012+
1013+
checkout = Checkout(
1014+
rails={
1015+
"tempo": TempoRailSpec(recipient="0x" + "00" * 20),
1016+
"base": X402BaseRailSpec(recipient="0x" + "00" * 20),
1017+
"stripe": StripeRailSpec(profile_id="profile_abc"),
1018+
"solana": SolanaMppRailSpec(recipient="SoLaNaReCiPiEnT", token="EPjFWdd5..."),
1019+
},
1020+
url="https://x/purchase",
1021+
compute_pricing=_pricing,
1022+
)
1023+
ext = x_payment_info_from_checkout(
1024+
checkout=checkout,
1025+
price=XPaymentInfoFixedPrice(currency="USD", amount="1.00"),
1026+
)
1027+
protos = ext["x-payment-info"]["protocols"]
1028+
methods = [p.get("mpp", {}).get("method") or "x402" for p in protos]
1029+
assert "stripe" in methods
1030+
assert "tempo" in methods
1031+
assert "solana" in methods
1032+
assert "x402" in methods
1033+
# Solana entry should include the `currency` from token
1034+
solana_entry = next(p["mpp"] for p in protos if p.get("mpp", {}).get("method") == "solana")
1035+
assert solana_entry["currency"] == "EPjFWdd5..."
1036+
1037+
8041038
def test_x_payment_info_from_checkout_merges_protocol_extras() -> None:
8051039
from agentscore_commerce.discovery import (
8061040
XPaymentInfoFixedPrice,

0 commit comments

Comments
 (0)