docs: update README and add test infrastructure

- Enhance .gitignore with comprehensive Python patterns
- Improve README with better setup and testing instructions
- Add test infrastructure:
  * Test requirements file
  * Environment setup script
  * Test runner script
  * Comprehensive test suite
  * Coverage configuration
This commit is contained in:
2025-08-08 10:54:56 +02:00
parent 9e1f2128a2
commit 0e3311df8e
12 changed files with 840 additions and 14 deletions
+209
View File
@@ -0,0 +1,209 @@
import pytest
import requests
from unittest.mock import patch, MagicMock
import jwt
from main import (
verify_token,
refresh_token,
get_well_known_config,
get_jwks,
get_user_info_from_endpoint,
get_userinfo_endpoint,
get_token_endpoint
)
def test_verify_token_invalid_input():
"""Test verify_token with invalid inputs."""
# Test with None
with pytest.raises(ValueError, match="Invalid token provided"):
verify_token(None)
# Test with empty string
with pytest.raises(ValueError, match="Invalid token provided"):
verify_token("")
# Test with non-string
with pytest.raises(ValueError, match="Invalid token provided"):
verify_token(123)
@patch('main.get_jwks')
@patch('main.jwt.get_unverified_header')
def test_verify_token_missing_kid(mock_get_header, mock_get_jwks):
"""Test verify_token when token header is missing 'kid'."""
# Mock the token header to not include 'kid'
mock_get_header.return_value = {}
with pytest.raises(ValueError, match="Token header missing 'kid' field"):
verify_token("some.token.string")
@patch('main.get_jwks')
@patch('main.jwt.get_unverified_header')
def test_verify_token_no_matching_key(mock_get_header, mock_get_jwks):
"""Test verify_token when no matching key is found in JWKS."""
# Mock the token header to include 'kid'
mock_get_header.return_value = {"kid": "nonexistent-key"}
# Mock JWKS with a different key ID
mock_get_jwks.return_value = {
"keys": [
{
"kid": "different-key",
"kty": "RSA",
"n": "modulus",
"e": "AQAB"
}
]
}
with pytest.raises(ValueError, match="Unable to find matching key in JWKS"):
verify_token("some.token.string")
@patch('main.requests.get')
def test_get_well_known_config_success(mock_get):
"""Test successful retrieval of well-known configuration."""
# Mock successful response
mock_response = MagicMock()
mock_response.json.return_value = {
"issuer": "https://login.infomaniak.com",
"jwks_uri": "https://login.infomaniak.com/.well-known/jwks.json"
}
mock_response.text = '{"issuer": "https://login.infomaniak.com"}'
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
config = get_well_known_config()
assert "issuer" in config
assert config["issuer"] == "https://login.infomaniak.com"
@patch('main.requests.get')
def test_get_well_known_config_failure(mock_get):
"""Test failed retrieval of well-known configuration."""
# Clear cache first
get_well_known_config.cache_clear()
# Mock failed response with requests exception
mock_get.side_effect = requests.exceptions.RequestException("Network error")
with pytest.raises(ValueError, match="Failed to fetch well-known configuration"):
get_well_known_config()
@patch('main.requests.get')
def test_get_jwks_success(mock_get):
"""Test successful retrieval of JWKS."""
# Mock successful response for well-known config
with patch('main.get_well_known_config') as mock_config:
mock_config.return_value = {
"jwks_uri": "https://login.infomaniak.com/.well-known/jwks.json"
}
# Mock successful JWKS response
mock_response = MagicMock()
mock_response.json.return_value = {
"keys": [{"kid": "test-key"}]
}
mock_response.text = '{"keys": [{"kid": "test-key"}]}'
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
jwks = get_jwks()
assert "keys" in jwks
assert len(jwks["keys"]) > 0
@patch('main.requests.get')
def test_get_jwks_failure(mock_get):
"""Test failed retrieval of JWKS."""
# Clear cache first
get_jwks.cache_clear()
# Mock successful response for well-known config
with patch('main.get_well_known_config') as mock_config:
mock_config.return_value = {
"jwks_uri": "https://login.infomaniak.com/.well-known/jwks.json"
}
# Mock failed response with requests exception
mock_get.side_effect = requests.exceptions.RequestException("Network error")
with pytest.raises(ValueError, match="Failed to fetch JWKS"):
get_jwks()
@patch('main.requests.post')
def test_refresh_token_success(mock_post):
"""Test successful token refresh."""
# Mock environment variables
with patch.dict('main.os.environ', {'CLIENT_ID': 'test-client', 'CLIENT_SECRET': 'test-secret'}):
# Mock successful response
mock_response = MagicMock()
mock_response.ok = True
mock_response.json.return_value = {
"access_token": "new-access-token",
"id_token": "new-id-token",
"expires_in": 3600,
"token_type": "Bearer"
}
mock_post.return_value = mock_response
with patch('main.get_token_endpoint') as mock_endpoint:
mock_endpoint.return_value = "https://login.infomaniak.com/token"
result = refresh_token("valid-refresh-token")
assert "access_token" in result
assert result["access_token"] == "new-access-token"
@patch('main.requests.post')
def test_refresh_token_failure(mock_post):
"""Test failed token refresh."""
# Mock environment variables
with patch.dict('main.os.environ', {'CLIENT_ID': 'test-client', 'CLIENT_SECRET': 'test-secret'}):
# Mock failed response
mock_response = MagicMock()
mock_response.ok = False
mock_response.status_code = 400
mock_response.text = "Bad Request"
mock_post.return_value = mock_response
with patch('main.get_token_endpoint') as mock_endpoint:
mock_endpoint.return_value = "https://login.infomaniak.com/token"
with pytest.raises(ValueError, match="Token refresh failed"):
refresh_token("invalid-refresh-token")
def test_refresh_token_invalid_input():
"""Test refresh_token with invalid inputs."""
# Test with None
with pytest.raises(ValueError, match="Invalid refresh token provided"):
refresh_token(None)
# Test with empty string
with pytest.raises(ValueError, match="Invalid refresh token provided"):
refresh_token("")
# Test with non-string
with pytest.raises(ValueError, match="Invalid refresh token provided"):
refresh_token(123)
@patch('main.get_token_endpoint')
@patch('main.requests.post')
def test_refresh_token_missing_secret(mock_post, mock_get_token_endpoint):
"""Test refresh_token when client secret is not configured."""
# Mock the token endpoint to avoid that error
mock_get_token_endpoint.return_value = "https://login.infomaniak.com/token"
# Mock the response to avoid going further in the function
mock_response = MagicMock()
mock_response.ok = True
mock_response.json.return_value = {}
mock_post.return_value = mock_response
import main
original_client_secret = main.CLIENT_SECRET
# Temporarily set CLIENT_SECRET to empty string
main.CLIENT_SECRET = ""
try:
with pytest.raises(ValueError, match="Client secret not configured"):
refresh_token("valid-refresh-token")
finally:
# Restore original value
main.CLIENT_SECRET = original_client_secret