Skip to content

Commit 36effa4

Browse files
committed
test(router): keep optional method regressions separate from child validation
1 parent b6eba73 commit 36effa4

1 file changed

Lines changed: 104 additions & 104 deletions

File tree

‎tests/test_platform_router.py‎

Lines changed: 104 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,110 @@ def _make_ctx(account: Account[Any]) -> RequestContext[Any]:
157157
return RequestContext(account=account)
158158

159159

160+
class _CanonicalRouterChild(DecisioningPlatform):
161+
capabilities = _capabilities(["sales-non-guaranteed"])
162+
accounts = _SyncSalesPlatform.accounts
163+
__init__ = _SyncSalesPlatform.__init__
164+
get_products = _SyncSalesPlatform.get_products
165+
create_media_buy = _SyncSalesPlatform.create_media_buy
166+
update_media_buy = _SyncSalesPlatform.update_media_buy
167+
sync_creatives = _SyncSalesPlatform.sync_creatives
168+
get_media_buy_delivery = _SyncSalesPlatform.get_media_buy_delivery
169+
170+
171+
class _OptionalRouterChild(_CanonicalRouterChild):
172+
def list_creative_formats_legacy(self, req: Any, ctx: Any) -> Any:
173+
return {"formats": []}
174+
175+
def preview_creative_legacy(self, req: Any, ctx: Any) -> Any:
176+
return {"previews": []}
177+
178+
def list_products(self, req: Any, ctx: Any) -> Any:
179+
return {"products": []}
180+
181+
182+
@pytest.mark.asyncio
183+
@pytest.mark.parametrize("router_kind", ["eager", "lazy", "registry", "nested"])
184+
@pytest.mark.parametrize("implemented", [False, True])
185+
async def test_router_optional_advertisement_and_tenant_refusal(
186+
router_kind: str, implemented: bool
187+
) -> None:
188+
from concurrent.futures import ThreadPoolExecutor
189+
190+
from adcp.decisioning import LazyPlatformRouter
191+
from adcp.decisioning.capabilities import MediaBuy
192+
from adcp.decisioning.handler import PlatformHandler
193+
from adcp.decisioning.task_registry import InMemoryTaskRegistry
194+
from adcp.server.tenant_registry import TenantRegistry
195+
196+
accounts = _make_routing_account_store({"acct_a": "tenant-a", "acct_b": "tenant-b"})
197+
caps = _capabilities(["sales-non-guaranteed", "creative-template"])
198+
caps.media_buy = MediaBuy(lifecycle_tools=["list_products", "buy_products"])
199+
optional = {"list_creative_formats_legacy", "preview_creative_legacy", "list_products"}
200+
declaration = optional if implemented else set()
201+
child = _OptionalRouterChild("a") if implemented else _CanonicalRouterChild("a")
202+
canonical = _CanonicalRouterChild("b")
203+
constructed: list[str] = []
204+
205+
async def factory(tenant_id: str) -> DecisioningPlatform:
206+
constructed.append(tenant_id)
207+
return child if tenant_id == "tenant-a" else canonical
208+
209+
if router_kind == "eager":
210+
router = PlatformRouter(
211+
accounts=accounts,
212+
platforms={"tenant-a": child, "tenant-b": canonical},
213+
capabilities=caps,
214+
)
215+
elif router_kind in {"lazy", "nested"}:
216+
lazy = LazyPlatformRouter(
217+
accounts=accounts, factory=factory, capabilities=caps, optional_methods=declaration
218+
)
219+
router = (
220+
lazy
221+
if router_kind == "lazy"
222+
else PlatformRouter(
223+
accounts=accounts, platforms={"tenant-a": lazy, "tenant-b": lazy}, capabilities=caps
224+
)
225+
)
226+
else:
227+
registry = TenantRegistry(validator=None)
228+
for tenant_id in ("tenant-a", "tenant-b"):
229+
await registry.register_lazy(
230+
tenant_id, agent_url=f"https://{tenant_id}.example.com", factory=factory
231+
)
232+
router = registry.as_platform(
233+
accounts=accounts, capabilities=caps, optional_methods=declaration
234+
)
235+
with ThreadPoolExecutor() as executor:
236+
handler = PlatformHandler(router, executor=executor, registry=InMemoryTaskRegistry())
237+
tools = handler.advertised_tools_for_instance()
238+
assert constructed == []
239+
expected = {"list_creative_formats", "preview_creative", "list_products"}
240+
assert tools & (expected | {"buy_products"}) == (expected if implemented else set())
241+
assert isinstance(router.optional_methods, frozenset)
242+
243+
if implemented:
244+
ctx = _make_ctx(Account(id="acct_a", metadata={"tenant_id": "tenant-a"}))
245+
ctx.tenant_id = "tenant-a"
246+
assert await router.list_creative_formats_legacy({}, ctx) == {"formats": []}
247+
ctx = _make_ctx(Account(id="acct_b", metadata={"tenant_id": "tenant-b"}))
248+
ctx.tenant_id = "tenant-b"
249+
with pytest.raises(AdcpError) as caught:
250+
await router.list_creative_formats_legacy({}, ctx)
251+
assert caught.value.code == "UNSUPPORTED_FEATURE"
252+
assert "because at least one" not in str(caught.value)
253+
254+
255+
def test_eager_optional_declaration_excludes_inherited_protocol_stubs() -> None:
256+
router = PlatformRouter(
257+
accounts=_make_routing_account_store({"acct_a": "tenant-a"}),
258+
platforms={"tenant-a": _SyncSalesPlatform("a")},
259+
capabilities=_capabilities(["sales-non-guaranteed"]),
260+
)
261+
assert "list_creative_formats_legacy" not in router.optional_methods
262+
263+
160264
# ---------------------------------------------------------------------------
161265
# Tests
162266
# ---------------------------------------------------------------------------
@@ -515,107 +619,3 @@ def test_account_store_methods_denylist_matches_protocols() -> None:
515619
f"AccountStore Protocol method drift. Update _ACCOUNT_STORE_METHODS "
516620
f"in platform_router.py: {sorted(drift)}"
517621
)
518-
519-
520-
class _CanonicalRouterChild(DecisioningPlatform):
521-
capabilities = _capabilities(["sales-non-guaranteed"])
522-
accounts = _SyncSalesPlatform.accounts
523-
__init__ = _SyncSalesPlatform.__init__
524-
get_products = _SyncSalesPlatform.get_products
525-
create_media_buy = _SyncSalesPlatform.create_media_buy
526-
update_media_buy = _SyncSalesPlatform.update_media_buy
527-
sync_creatives = _SyncSalesPlatform.sync_creatives
528-
get_media_buy_delivery = _SyncSalesPlatform.get_media_buy_delivery
529-
530-
531-
class _OptionalRouterChild(_CanonicalRouterChild):
532-
def list_creative_formats_legacy(self, req: Any, ctx: Any) -> Any:
533-
return {"formats": []}
534-
535-
def preview_creative_legacy(self, req: Any, ctx: Any) -> Any:
536-
return {"previews": []}
537-
538-
def list_products(self, req: Any, ctx: Any) -> Any:
539-
return {"products": []}
540-
541-
542-
@pytest.mark.asyncio
543-
@pytest.mark.parametrize("router_kind", ["eager", "lazy", "registry", "nested"])
544-
@pytest.mark.parametrize("implemented", [False, True])
545-
async def test_router_optional_advertisement_and_tenant_refusal(
546-
router_kind: str, implemented: bool
547-
) -> None:
548-
from concurrent.futures import ThreadPoolExecutor
549-
550-
from adcp.decisioning import LazyPlatformRouter
551-
from adcp.decisioning.capabilities import MediaBuy
552-
from adcp.decisioning.handler import PlatformHandler
553-
from adcp.decisioning.task_registry import InMemoryTaskRegistry
554-
from adcp.server.tenant_registry import TenantRegistry
555-
556-
accounts = _make_routing_account_store({"acct_a": "tenant-a", "acct_b": "tenant-b"})
557-
caps = _capabilities(["sales-non-guaranteed", "creative-template"])
558-
caps.media_buy = MediaBuy(lifecycle_tools=["list_products", "buy_products"])
559-
optional = {"list_creative_formats_legacy", "preview_creative_legacy", "list_products"}
560-
declaration = optional if implemented else set()
561-
child = _OptionalRouterChild("a") if implemented else _CanonicalRouterChild("a")
562-
canonical = _CanonicalRouterChild("b")
563-
constructed: list[str] = []
564-
565-
async def factory(tenant_id: str) -> DecisioningPlatform:
566-
constructed.append(tenant_id)
567-
return child if tenant_id == "tenant-a" else canonical
568-
569-
if router_kind == "eager":
570-
router = PlatformRouter(
571-
accounts=accounts,
572-
platforms={"tenant-a": child, "tenant-b": canonical},
573-
capabilities=caps,
574-
)
575-
elif router_kind in {"lazy", "nested"}:
576-
lazy = LazyPlatformRouter(
577-
accounts=accounts, factory=factory, capabilities=caps, optional_methods=declaration
578-
)
579-
router = (
580-
lazy
581-
if router_kind == "lazy"
582-
else PlatformRouter(
583-
accounts=accounts, platforms={"tenant-a": lazy, "tenant-b": lazy}, capabilities=caps
584-
)
585-
)
586-
else:
587-
registry = TenantRegistry(validator=None)
588-
for tenant_id in ("tenant-a", "tenant-b"):
589-
await registry.register_lazy(
590-
tenant_id, agent_url=f"https://{tenant_id}.example.com", factory=factory
591-
)
592-
router = registry.as_platform(
593-
accounts=accounts, capabilities=caps, optional_methods=declaration
594-
)
595-
with ThreadPoolExecutor() as executor:
596-
handler = PlatformHandler(router, executor=executor, registry=InMemoryTaskRegistry())
597-
tools = handler.advertised_tools_for_instance()
598-
assert constructed == []
599-
expected = {"list_creative_formats", "preview_creative", "list_products"}
600-
assert tools & (expected | {"buy_products"}) == (expected if implemented else set())
601-
assert isinstance(router.optional_methods, frozenset)
602-
603-
if implemented:
604-
ctx = _make_ctx(Account(id="acct_a", metadata={"tenant_id": "tenant-a"}))
605-
ctx.tenant_id = "tenant-a"
606-
assert await router.list_creative_formats_legacy({}, ctx) == {"formats": []}
607-
ctx = _make_ctx(Account(id="acct_b", metadata={"tenant_id": "tenant-b"}))
608-
ctx.tenant_id = "tenant-b"
609-
with pytest.raises(AdcpError) as caught:
610-
await router.list_creative_formats_legacy({}, ctx)
611-
assert caught.value.code == "UNSUPPORTED_FEATURE"
612-
assert "because at least one" not in str(caught.value)
613-
614-
615-
def test_eager_optional_declaration_excludes_inherited_protocol_stubs() -> None:
616-
router = PlatformRouter(
617-
accounts=_make_routing_account_store({"acct_a": "tenant-a"}),
618-
platforms={"tenant-a": _SyncSalesPlatform("a")},
619-
capabilities=_capabilities(["sales-non-guaranteed"]),
620-
)
621-
assert "list_creative_formats_legacy" not in router.optional_methods

0 commit comments

Comments
 (0)