Skip to content

Commit 928b7bf

Browse files
committed
test(client/auth): cover the 403 step-up's exception relay; drop its stale no-cover pragma
The mid-discovery close test traverses the 403 branch's except clause (GeneratorExit evaluates the match), so strict-no-cover now flags the 'pragma: no cover' on that line as wrongly marked. Cover the handler properly instead: a new test drives an OAuthFlowError (RFC 8707 resource mismatch during step-up discovery) through the branch, asserting it propagates to the caller like the 401 branch's error contract, and the pragma is removed.
1 parent aedc88c commit 928b7bf

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

src/mcp/client/auth/oauth2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ async def async_auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx
933933
# Step 2b: Perform (re-)authorization and token exchange
934934
token_response = yield await self._perform_authorization()
935935
await self._handle_token_response(token_response)
936-
except Exception: # pragma: no cover
936+
except Exception:
937937
logger.exception("OAuth flow error")
938938
raise
939939

tests/client/test_auth.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4114,3 +4114,37 @@ def capturing(response: httpx2.Response) -> AsyncGenerator[httpx2.Request, httpx
41144114

41154115
with pytest.raises(StopAsyncIteration):
41164116
await captured[0].__anext__()
4117+
4118+
4119+
@pytest.mark.anyio
4120+
async def test_403_step_up_surfaces_oauth_flow_errors_to_the_caller(
4121+
oauth_provider: OAuthClientProvider,
4122+
):
4123+
"""An `OAuthFlowError` raised inside the 403 step-up — here the SEP-985/RFC 8707
4124+
resource-identity check failing during the step-up's discovery — propagates to the
4125+
caller instead of being swallowed, matching the 401 branch's error contract.
4126+
"""
4127+
# Restart shape: a live token and no stored registration, so the step-up discovers.
4128+
oauth_provider.context.current_tokens = OAuthToken(access_token="live-token")
4129+
oauth_provider._initialized = True
4130+
4131+
auth_flow = oauth_provider.async_auth_flow(httpx2.Request("GET", "https://api.example.com/v1/mcp"))
4132+
request = await auth_flow.__anext__()
4133+
4134+
response_403 = httpx2.Response(
4135+
403,
4136+
headers={"WWW-Authenticate": 'Bearer error="insufficient_scope", scope="write"'},
4137+
request=request,
4138+
)
4139+
prm_req = await auth_flow.asend(response_403)
4140+
4141+
# The PRM advertises a different resource, so the RFC 8707 identity check must fail.
4142+
mismatched_prm = httpx2.Response(
4143+
200,
4144+
content=(
4145+
b'{"resource": "https://other.example.com/mcp", "authorization_servers": ["https://auth.example.com"]}'
4146+
),
4147+
request=prm_req,
4148+
)
4149+
with pytest.raises(OAuthFlowError):
4150+
await auth_flow.asend(mismatched_prm)

0 commit comments

Comments
 (0)