Files
wehub-resource-sync 60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

178 lines
6.2 KiB
Python

"""Tests for authentication utility helpers."""
import base64
import json
import pytest
from fastmcp.utilities.auth import (
decode_jwt_header,
decode_jwt_payload,
parse_scopes,
)
def create_jwt(header: dict, payload: dict, signature: bytes = b"fake-sig") -> str:
"""Create a test JWT token."""
header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b"=")
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b"=")
sig_b64 = base64.urlsafe_b64encode(signature).rstrip(b"=")
return f"{header_b64.decode()}.{payload_b64.decode()}.{sig_b64.decode()}"
class TestDecodeJwtHeader:
"""Tests for decode_jwt_header utility."""
def test_decode_basic_header(self):
"""Test decoding a basic JWT header."""
header = {"alg": "RS256", "typ": "JWT"}
payload = {"sub": "user-123"}
token = create_jwt(header, payload)
result = decode_jwt_header(token)
assert result == header
assert result["alg"] == "RS256"
assert result["typ"] == "JWT"
def test_decode_header_with_kid(self):
"""Test decoding header with key ID for JWKS lookup."""
header = {"alg": "RS256", "typ": "JWT", "kid": "key-id-123"}
payload = {"sub": "user-123"}
token = create_jwt(header, payload)
result = decode_jwt_header(token)
assert result["kid"] == "key-id-123"
def test_invalid_jwt_format_two_parts(self):
"""Test that two-part token raises ValueError."""
with pytest.raises(ValueError, match="Invalid JWT format"):
decode_jwt_header("header.payload")
def test_invalid_jwt_format_one_part(self):
"""Test that single-part token raises ValueError."""
with pytest.raises(ValueError, match="Invalid JWT format"):
decode_jwt_header("not-a-jwt")
def test_invalid_jwt_format_four_parts(self):
"""Test that four-part token raises ValueError."""
with pytest.raises(ValueError, match="Invalid JWT format"):
decode_jwt_header("a.b.c.d")
def test_decode_header_length_divisible_by_4(self):
"""Test decoding when base64 length is divisible by 4 (no padding needed).
This tests the edge case where len(part) % 4 == 0.
The padding calculation (-len % 4) correctly yields 0 in this case.
"""
# Create a header that encodes to exactly 12 chars (divisible by 4)
header = {"x": ""} # eyJ4IjogIiJ9 = 12 chars
payload = {"sub": "user"}
token = create_jwt(header, payload)
result = decode_jwt_header(token)
assert result == header
class TestDecodeJwtPayload:
"""Tests for decode_jwt_payload utility."""
def test_decode_basic_payload(self):
"""Test decoding a basic JWT payload."""
header = {"alg": "RS256", "typ": "JWT"}
payload = {"sub": "user-123", "name": "Test User"}
token = create_jwt(header, payload)
result = decode_jwt_payload(token)
assert result == payload
assert result["sub"] == "user-123"
assert result["name"] == "Test User"
def test_decode_payload_with_claims(self):
"""Test decoding payload with various claims."""
header = {"alg": "RS256"}
payload = {
"sub": "user-123",
"oid": "object-456",
"name": "Test User",
"email": "test@example.com",
"roles": ["admin", "user"],
"exp": 1234567890,
}
token = create_jwt(header, payload)
result = decode_jwt_payload(token)
assert result["sub"] == "user-123"
assert result["oid"] == "object-456"
assert result["name"] == "Test User"
assert result["email"] == "test@example.com"
assert result["roles"] == ["admin", "user"]
assert result["exp"] == 1234567890
def test_invalid_jwt_format(self):
"""Test that invalid format raises ValueError."""
with pytest.raises(ValueError, match="Invalid JWT format"):
decode_jwt_payload("not-a-jwt")
def test_decode_payload_with_padding_edge_cases(self):
"""Test that base64 padding is handled correctly."""
# Create payloads of different sizes to test padding
for payload_size in [1, 2, 3, 4, 5, 10, 100]:
payload = {"data": "x" * payload_size}
token = create_jwt({"alg": "RS256"}, payload)
result = decode_jwt_payload(token)
assert result == payload
def test_decode_payload_length_divisible_by_4(self):
"""Test decoding when base64 length is divisible by 4 (no padding needed).
This tests the edge case where len(part) % 4 == 0.
The padding calculation (-len % 4) correctly yields 0 in this case.
"""
# Create a payload that encodes to exactly 12 chars (divisible by 4)
header = {"alg": "RS256"}
payload = {"x": ""} # eyJ4IjogIiJ9 = 12 chars
token = create_jwt(header, payload)
result = decode_jwt_payload(token)
assert result == payload
class TestParseScopes:
"""Tests for parse_scopes utility."""
def test_parse_none(self):
"""Test that None returns None."""
assert parse_scopes(None) is None
def test_parse_empty_string(self):
"""Test that empty string returns empty list."""
assert parse_scopes("") == []
def test_parse_space_separated(self):
"""Test parsing space-separated scopes."""
assert parse_scopes("read write delete") == ["read", "write", "delete"]
def test_parse_comma_separated(self):
"""Test parsing comma-separated scopes."""
assert parse_scopes("read,write,delete") == ["read", "write", "delete"]
def test_parse_json_array(self):
"""Test parsing JSON array string."""
assert parse_scopes('["read", "write", "delete"]') == [
"read",
"write",
"delete",
]
def test_parse_list(self):
"""Test that list is returned as-is."""
assert parse_scopes(["read", "write"]) == ["read", "write"]
def test_parse_strips_whitespace(self):
"""Test that whitespace is stripped."""
assert parse_scopes(" read , write ") == ["read", "write"]