feat(testing): add SellerTestClient for in-process handler testing#666
Merged
Conversation
Eliminates the per-test-file boilerplate of constructing JSON-RPC envelopes, calling mcp.call_tool(), and extracting adcp_error from structuredContent. SellerTestClient.invoke() wraps all three steps into a single typed call that returns ToolInvokeResult with a .passed property and a typed AdcpErrorPayload. Refs #662 https://claude.ai/code/session_01VZRaadSyoHDm7CU945e3Ba
- _ensure_mcp now holds asyncio.Lock to prevent double-initialization when concurrent coroutines call invoke() before _mcp is set - FastMCP return-shape handling now guards against unknown shapes with an explicit RuntimeError instead of a silent type: ignore unpack - Removes unused # type: ignore[misc] comment flagged by mypy https://claude.ai/code/session_01VZRaadSyoHDm7CU945e3Ba
…ntent ToolInvokeResult is the captured outcome of a single tool call — making it mutable invited surprising patterns (the deleted test asserted you could mutate adcp_error to flip passed). Match AdcpErrorPayload's frozen=True. The raw field is structuredContent from FastMCP's call_tool result, not the underlying CallToolResult or 2-tuple — rename so adopters reaching for an escape hatch get an accurate name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Independent dx-expert + code-reviewer pass surfaced four issues: - Rename .passed -> .ok. `assert result.passed` inside a pytest function reads as a tautology with the test runner's own pass/fail vocabulary; .ok is unambiguous before the API is publicly importable. - Raise on missing required adcp_error fields. The harness exists to catch server non-conformance — silently substituting "UNKNOWN" for a missing code hid exactly the spec-violation cases adopters need surfaced. - Document the FastMCP-coupling accurately. The 2-tuple success shape is produced by this SDK's _AdcpFuncMetadata.convert_result override, not the public FastMCP.call_tool contract. Add a defensive plain-dict branch so a FastMCP minor-version bump doesn't silently break the harness. - Drop two tests that asserted on the private _mcp attribute; testing implementation, not behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Fold candidate from #678. Issue #678 proposes Before this PR merges, consider whether to stub out Suggestion only — skip if scope feels right as-is. Triaged by Claude Code. Session: https://claude.ai/code/session_015nGuiswmggSyTdEinF6SgX Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #662
Adds
SellerTestClient— an in-process MCP test harness that eliminates the per-file boilerplate of constructing JSON-RPC envelopes, callingmcp.call_tool(), and extractingadcp_errorfromstructuredContent. Adopters now get a single typed call that handles all three.The harness calls
mcp.call_tool()in-process — it exercises the full handler dispatch and error-translation stack without HTTP or SSE framing. For HTTP-level tests (auth, CORS, middleware),build_test_clientremains the right tool.What this PR does not include (see #662 for status):
run_scenario()— requires bundled compliance scenario playbooks that are not yet in the SDKWhat was tested
pytest tests/test_seller_test_client.py— 18 new tests, all passingpytest tests/ -q --ignore=tests/integration— 4447 passed, 1 pre-existing network flake (test_real_tls_handshake_still_validates_hostname)ruff check src/adcp/testing/— no issuesmypy src/adcp/testing/harness.py --ignore-missing-imports— no new errors from this fileNew public API
SellerTestClientadcp.testingDecisioningPlatformToolInvokeResultadcp.testing.passed,.data,.adcp_error,.rawAdcpErrorPayloadadcp.testingadcp_errorfieldsvalidation=None(default) disables schema validation so tests target handler behavior. PassSERVER_DEFAULT_VALIDATIONto match production behavior (build_test_clientdoes this by default — see migration note in__init__docstring).Nits surfaced by review (not fixed in this PR)
_mcpattribute accessed in two tests — low-value coupling to implementation detail; safe to remove in a follow-up cleanupdocs/testing-guide.mdhas no mention ofSellerTestClient— should be added once the API stabilisesresult.datamay includecontextecho keys alongside business-payload keys when context-echo is active — documented inrawas the escape hatchPre-PR review
_ensure_mcp; RuntimeError fallthrough guard for unknown FastMCP return shape)# type: ignore[misc]); nits noted aboveSession: https://claude.ai/code/session_01VZRaadSyoHDm7CU945e3Ba
Generated by Claude Code