Skip to content

Commit 217ee0c

Browse files
bokelleyclaude
andcommitted
fixup(hello_seller): list_creatives spec-valid envelope + model_validate tests
Per review on #531: - list_creatives stub returned {"creatives": []} which is missing the spec-required query_summary and pagination fields. A buyer hitting the running example would get a non-conformant ListCreativesResponse. Now returns a minimal valid envelope with an explanatory docstring pointing adopters at the post-filter count and cursor state. - Smoke tests now run ``Response.model_validate(resp)`` against the canonical Pydantic model so spec drift fails the test, not just dict-key presence. - Class docstring lede: "all nine required methods" → "all nine methods" (four are soft-required, distinction is two lines below). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6cc834c commit 217ee0c

2 files changed

Lines changed: 30 additions & 14 deletions

File tree

examples/hello_seller.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
class HelloSeller(DecisioningPlatform):
8686
"""The canonical v6.0 sales-non-guaranteed adopter.
8787
88-
Implements all nine required methods of ``sales-non-guaranteed``: the
88+
Implements all nine methods of the ``sales-non-guaranteed`` surface: the
8989
five hard-required (``get_products``, ``create_media_buy``,
9090
``update_media_buy``, ``sync_creatives``, ``get_media_buy_delivery``)
9191
and the four soft-required by the SalesPlatform Protocol in v6.0 rc.1+
@@ -288,8 +288,17 @@ def list_creatives(
288288
Return all creatives the buyer has synced. Wire to your creative
289289
asset store in production; buyers use this to check approval
290290
statuses and discover available creatives.
291+
292+
``query_summary`` and ``pagination`` are required by the spec
293+
envelope — fill ``total_matching``/``returned`` from the
294+
post-filter result count and ``has_more`` from the cursor state
295+
once you have a real store.
291296
"""
292-
return {"creatives": []}
297+
return {
298+
"query_summary": {"total_matching": 0, "returned": 0},
299+
"pagination": {"has_more": False},
300+
"creatives": [],
301+
}
293302

294303
def provide_performance_feedback(
295304
self,

tests/test_hello_seller_integration.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -259,35 +259,42 @@ async def test_advertised_tools_class_attribute_set(
259259

260260

261261
@pytest.mark.asyncio
262-
async def test_get_media_buys_returns_empty_list(handler: PlatformHandler) -> None:
263-
"""Smoke: stub returns valid wire shape for get_media_buys."""
264-
from adcp.types import GetMediaBuysRequest
262+
async def test_get_media_buys_returns_spec_valid_envelope(handler: PlatformHandler) -> None:
263+
"""Stub returns a wire shape that satisfies ``GetMediaBuysResponse``."""
264+
from adcp.types import GetMediaBuysRequest, GetMediaBuysResponse
265265

266266
req = GetMediaBuysRequest(account={"account_id": "buyer-1"})
267267
resp = await handler.get_media_buys(req, ToolContext())
268-
assert isinstance(resp, dict)
268+
# Validate against the canonical Pydantic model — catches drift
269+
# between stub and spec, not just dict-key presence.
270+
GetMediaBuysResponse.model_validate(resp)
269271
assert resp["media_buys"] == []
270272

271273

272274
@pytest.mark.asyncio
273-
async def test_list_creative_formats_returns_empty_list(handler: PlatformHandler) -> None:
274-
"""Smoke: stub returns valid wire shape for list_creative_formats."""
275-
from adcp.types import ListCreativeFormatsRequest
275+
async def test_list_creative_formats_returns_spec_valid_envelope(
276+
handler: PlatformHandler,
277+
) -> None:
278+
"""Stub returns a wire shape that satisfies ``ListCreativeFormatsResponse``."""
279+
from adcp.types import ListCreativeFormatsRequest, ListCreativeFormatsResponse
276280

277281
req = ListCreativeFormatsRequest()
278282
resp = await handler.list_creative_formats(req, ToolContext())
279-
assert isinstance(resp, dict)
283+
ListCreativeFormatsResponse.model_validate(resp)
280284
assert resp["formats"] == []
281285

282286

283287
@pytest.mark.asyncio
284-
async def test_list_creatives_returns_empty_list(handler: PlatformHandler) -> None:
285-
"""Smoke: stub returns valid wire shape for list_creatives."""
286-
from adcp.types import ListCreativesRequest
288+
async def test_list_creatives_returns_spec_valid_envelope(handler: PlatformHandler) -> None:
289+
"""Stub returns a wire shape that satisfies ``ListCreativesResponse``,
290+
including the spec-required ``query_summary`` and ``pagination``
291+
envelopes (a buyer hitting the example otherwise gets a non-conformant
292+
response)."""
293+
from adcp.types import ListCreativesRequest, ListCreativesResponse
287294

288295
req = ListCreativesRequest(account={"account_id": "buyer-1"})
289296
resp = await handler.list_creatives(req, ToolContext())
290-
assert isinstance(resp, dict)
297+
ListCreativesResponse.model_validate(resp)
291298
assert resp["creatives"] == []
292299

293300

0 commit comments

Comments
 (0)