Skip to content

Commit bdfded0

Browse files
committed
test: align requirement IDs, add transport applicability, and enforce two-way test coverage
1 parent 7709b98 commit bdfded0

20 files changed

Lines changed: 412 additions & 138 deletions

tests/interaction/_requirements.py

Lines changed: 294 additions & 74 deletions
Large diffs are not rendered by default.

tests/interaction/lowlevel/test_cancellation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
pytestmark = pytest.mark.anyio
2020

2121

22-
@requirement("cancellation:in-flight")
22+
@requirement("protocol:cancel:in-flight")
2323
async def test_cancellation_stops_in_flight_handler() -> None:
2424
"""Cancelling an in-flight request interrupts its handler and fails the pending call.
2525
@@ -68,7 +68,7 @@ async def call_and_capture_error() -> None:
6868
assert errors == snapshot([ErrorData(code=0, message="Request cancelled")])
6969

7070

71-
@requirement("cancellation:server-survives")
71+
@requirement("protocol:cancel:server-survives")
7272
async def test_session_serves_requests_after_cancellation() -> None:
7373
"""A request cancelled mid-flight does not poison the session: the next request succeeds."""
7474
started = anyio.Event()
@@ -114,7 +114,7 @@ async def call_and_swallow_cancellation_error() -> None:
114114
assert result == snapshot(CallToolResult(content=[TextContent(text="still alive")]))
115115

116116

117-
@requirement("cancellation:unknown-request")
117+
@requirement("protocol:cancel:unknown-id-ignored")
118118
async def test_cancellation_for_unknown_request_is_ignored() -> None:
119119
"""A cancellation referencing a request id that is not in flight is ignored without error."""
120120

tests/interaction/lowlevel/test_completion.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
pytestmark = pytest.mark.anyio
2020

2121

22-
@requirement("completion:complete:prompt-ref")
22+
@requirement("completion:prompt-arg")
2323
async def test_complete_prompt_argument() -> None:
2424
"""Completing a prompt argument delivers the ref, argument name, and current value to the handler.
2525
@@ -46,7 +46,7 @@ async def completion(ctx: ServerRequestContext, params: types.CompleteRequestPar
4646
)
4747

4848

49-
@requirement("completion:complete:resource-ref")
49+
@requirement("completion:resource-template-arg")
5050
async def test_complete_resource_template_variable() -> None:
5151
"""Completing a URI template variable delivers the template URI and variable name to the handler."""
5252

@@ -67,7 +67,7 @@ async def completion(ctx: ServerRequestContext, params: types.CompleteRequestPar
6767
assert result == snapshot(CompleteResult(completion=Completion(values=["modelcontextprotocol"])))
6868

6969

70-
@requirement("completion:complete:context")
70+
@requirement("completion:context-arguments")
7171
async def test_complete_receives_context_arguments() -> None:
7272
"""Previously-resolved arguments passed as completion context reach the handler.
7373
@@ -93,6 +93,7 @@ async def completion(ctx: ServerRequestContext, params: types.CompleteRequestPar
9393

9494

9595
@requirement("completion:complete:not-supported")
96+
@requirement("protocol:error:method-not-found")
9697
async def test_complete_without_handler_is_method_not_found() -> None:
9798
"""A server with no completion handler advertises no completions capability and rejects the request."""
9899
server = Server("incomplete")

tests/interaction/lowlevel/test_elicitation.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
}
3333

3434

35-
@requirement("elicitation:form:accept")
35+
@requirement("elicitation:form:action:accept")
36+
@requirement("elicitation:form:basic")
37+
@requirement("tools:call:elicitation-roundtrip")
3638
async def test_elicit_form_accepted_content_returns_to_handler() -> None:
3739
"""An accepted form elicitation returns the user's content to the requesting handler.
3840
@@ -86,7 +88,7 @@ async def answer_form(context: ClientRequestContext, params: types.ElicitRequest
8688
)
8789

8890

89-
@requirement("elicitation:form:decline")
91+
@requirement("elicitation:form:action:decline")
9092
async def test_elicit_form_decline_returns_no_content() -> None:
9193
"""A declined form elicitation returns the decline action to the handler with no content."""
9294

@@ -113,7 +115,7 @@ async def answer_form(context: ClientRequestContext, params: types.ElicitRequest
113115
assert result == snapshot(CallToolResult(content=[TextContent(text="decline content=None")]))
114116

115117

116-
@requirement("elicitation:form:cancel")
118+
@requirement("elicitation:form:action:cancel")
117119
async def test_elicit_form_cancel_returns_no_content() -> None:
118120
"""A cancelled form elicitation returns the cancel action to the handler with no content."""
119121

@@ -172,7 +174,8 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
172174
assert result == snapshot(CallToolResult(content=[TextContent(text="-32600: Elicitation not supported")]))
173175

174176

175-
@requirement("elicitation:url:accept")
177+
@requirement("elicitation:url:action:accept-no-content")
178+
@requirement("elicitation:url:basic")
176179
async def test_elicit_url_delivers_url_and_returns_accept_without_content() -> None:
177180
"""A URL elicitation delivers the message, URL, and elicitation id to the client; accepting it
178181
returns the action with no content.
@@ -276,7 +279,7 @@ async def answer_url(context: ClientRequestContext, params: types.ElicitRequestP
276279
assert result == snapshot(CallToolResult(content=[TextContent(text="cancel content=None")]))
277280

278281

279-
@requirement("elicitation:complete-notification")
282+
@requirement("elicitation:url:complete-notification")
280283
async def test_elicitation_complete_notification_carries_the_elicited_id_back_to_the_client() -> None:
281284
"""After a URL elicitation finishes, the server announces it with a notification carrying the same id.
282285

tests/interaction/lowlevel/test_initialize.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ async def test_initialize_returns_instructions() -> None:
8484

8585

8686
@requirement("lifecycle:initialize:capabilities:from-handlers")
87+
@requirement("tools:capability:declared")
88+
@requirement("resources:capability:declared")
89+
@requirement("prompts:capability:declared")
90+
@requirement("completion:capability:declared")
8791
async def test_initialize_capabilities_reflect_registered_handlers() -> None:
8892
"""Each feature area with a registered handler is advertised as a capability.
8993
@@ -253,7 +257,8 @@ async def list_tools(ctx: ServerRequestContext, params: types.PaginatedRequestPa
253257
assert pong == snapshot(EmptyResult())
254258

255259

256-
@requirement("lifecycle:initialize:protocol-version")
260+
@requirement("lifecycle:version:match")
261+
@requirement("lifecycle:version:server-fallback-latest")
257262
async def test_initialize_negotiates_protocol_version() -> None:
258263
"""The server echoes a supported requested version and answers an unsupported one with its latest.
259264
@@ -284,7 +289,7 @@ def initialize_request(protocol_version: str) -> InitializeRequest:
284289
assert result.protocol_version == snapshot("2025-11-25")
285290

286291

287-
@requirement("lifecycle:initialize:protocol-version:client-rejects")
292+
@requirement("lifecycle:version:reject-unsupported")
288293
async def test_unsupported_server_protocol_version_fails_initialization() -> None:
289294
"""An initialize response carrying a protocol version the client does not support fails initialization.
290295

tests/interaction/lowlevel/test_list_changed.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
pytestmark = pytest.mark.anyio
2626

2727

28-
@requirement("notifications:tools:list-changed")
28+
@requirement("tools:list-changed")
2929
async def test_tool_list_changed_notification() -> None:
3030
"""A tools/list_changed notification sent during a tool call reaches the client's message handler."""
3131
received: list[IncomingMessage] = []
@@ -51,7 +51,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
5151
assert received == snapshot([ToolListChangedNotification()])
5252

5353

54-
@requirement("notifications:resources:list-changed")
54+
@requirement("resources:list-changed")
5555
async def test_resource_list_changed_notification() -> None:
5656
"""A resources/list_changed notification sent during a tool call reaches the client's message handler."""
5757
received: list[IncomingMessage] = []
@@ -77,7 +77,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
7777
assert received == snapshot([ResourceListChangedNotification()])
7878

7979

80-
@requirement("notifications:prompts:list-changed")
80+
@requirement("prompts:list-changed")
8181
async def test_prompt_list_changed_notification() -> None:
8282
"""A prompts/list_changed notification sent during a tool call reaches the client's message handler."""
8383
received: list[IncomingMessage] = []

tests/interaction/lowlevel/test_logging.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq
4848
assert result == snapshot(EmptyResult())
4949

5050

51-
@requirement("logging:message:notification")
51+
@requirement("logging:message:fields")
52+
@requirement("tools:call:logging-mid-execution")
5253
async def test_log_messages_reach_logging_callback_in_order() -> None:
5354
"""Log messages sent during a tool call arrive at the logging callback, in order, before the call returns.
5455

tests/interaction/lowlevel/test_pagination.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
pytestmark = pytest.mark.anyio
2727

2828

29-
@requirement("pagination:cursor-round-trip")
29+
@requirement("tools:list:pagination")
3030
async def test_next_cursor_round_trips_through_the_client() -> None:
3131
"""The next_cursor a list handler returns reaches the client, and the cursor the client sends
3232
back on the following call reaches the handler verbatim.
@@ -57,6 +57,7 @@ async def list_tools(ctx: ServerRequestContext, params: types.PaginatedRequestPa
5757

5858

5959
@requirement("pagination:exhaustion")
60+
@requirement("tools:list:pagination")
6061
async def test_paginating_until_next_cursor_is_absent_yields_every_page() -> None:
6162
"""Following next_cursor until it is absent visits every page exactly once, in order."""
6263
pages: dict[str | None, tuple[str, str | None]] = {
@@ -89,7 +90,7 @@ async def list_tools(ctx: ServerRequestContext, params: types.PaginatedRequestPa
8990
assert requests_made == len(pages)
9091

9192

92-
@requirement("pagination:resources")
93+
@requirement("resources:list:pagination")
9394
async def test_resources_list_supports_cursor_pagination() -> None:
9495
"""resources/list round-trips the cursor like every other list operation."""
9596
seen_cursors: list[str | None] = []
@@ -116,7 +117,7 @@ async def list_resources(
116117
assert second_page.next_cursor is None
117118

118119

119-
@requirement("pagination:resource-templates")
120+
@requirement("resources:templates:pagination")
120121
async def test_resource_templates_list_supports_cursor_pagination() -> None:
121122
"""resources/templates/list round-trips the cursor like every other list operation."""
122123
seen_cursors: list[str | None] = []
@@ -148,7 +149,7 @@ async def list_resource_templates(
148149
assert second_page.next_cursor is None
149150

150151

151-
@requirement("pagination:prompts")
152+
@requirement("prompts:list:pagination")
152153
async def test_prompts_list_supports_cursor_pagination() -> None:
153154
"""prompts/list round-trips the cursor like every other list operation."""
154155
seen_cursors: list[str | None] = []

tests/interaction/lowlevel/test_progress.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
pytestmark = pytest.mark.anyio
2121

2222

23-
@requirement("progress:server-to-client")
23+
@requirement("protocol:progress:callback")
24+
@requirement("tools:call:progress")
2425
async def test_progress_during_tool_call_reaches_callback_in_order() -> None:
2526
"""Progress notifications emitted by a tool handler reach the caller's progress callback in order."""
2627
received: list[tuple[float, float | None, str | None]] = []
@@ -52,7 +53,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
5253
assert received == snapshot([(1.0, 3.0, "first chunk"), (2.0, 3.0, "second chunk"), (3.0, 3.0, "done")])
5354

5455

55-
@requirement("progress:token-propagation")
56+
@requirement("protocol:progress:token-injected")
5657
async def test_progress_token_visible_to_handler() -> None:
5758
"""Supplying a progress callback attaches a progress token that the handler can read from the request meta."""
5859

@@ -79,7 +80,7 @@ async def ignore(progress: float, total: float | None, message: str | None) -> N
7980
assert result == snapshot(CallToolResult(content=[TextContent(text="1")]))
8081

8182

82-
@requirement("progress:no-token")
83+
@requirement("protocol:progress:no-token")
8384
async def test_no_progress_callback_means_no_token() -> None:
8485
"""Without a progress callback the request carries no progress token.
8586
@@ -105,7 +106,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
105106
assert result == snapshot(CallToolResult(content=[TextContent(text="None")]))
106107

107108

108-
@requirement("progress:client-to-server")
109+
@requirement("protocol:progress:client-to-server")
109110
async def test_client_progress_notification_reaches_server_handler() -> None:
110111
"""A progress notification sent by the client is delivered to the server's progress handler."""
111112
received: list[ProgressNotificationParams] = []

tests/interaction/lowlevel/test_prompts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async def list_prompts(ctx: ServerRequestContext, params: types.PaginatedRequest
6565
)
6666

6767

68-
@requirement("prompts:get:arguments")
68+
@requirement("prompts:get:with-args")
6969
async def test_get_prompt_substitutes_arguments() -> None:
7070
"""Arguments supplied by the client reach the prompt handler; the templated message comes back."""
7171

0 commit comments

Comments
 (0)