"""Unit tests for cloud mode configuration."""
import pytest
from sso_mcp_server.config import AuthMode, ConfigurationError, Settings
class TestAuthModeEnum:
"""Tests for AuthMode enum."""
def test_local_mode_value(self):
"""Test LOCAL mode has correct value."""
assert AuthMode.LOCAL.value == "local"
def test_cloud_mode_value(self):
"""Test CLOUD mode has correct value."""
assert AuthMode.CLOUD.value == "cloud"
def test_auto_mode_value(self):
"""Test AUTO mode has correct value."""
assert AuthMode.AUTO.value == "auto"
def test_enum_from_string(self):
"""Test creating enum from string value."""
assert AuthMode("local") == AuthMode.LOCAL
assert AuthMode("cloud") == AuthMode.CLOUD
assert AuthMode("auto") == AuthMode.AUTO
class TestCloudModeSettingsValidation:
"""Tests for CLOUD mode settings validation."""
def test_cloud_mode_requires_resource_identifier(self, tmp_path):
"""Test CLOUD mode raises error when RESOURCE_IDENTIFIER missing."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="",
allowed_issuers=["https://issuer.example.com"],
checklist_dir=checklist_dir,
)
assert "RESOURCE_IDENTIFIER is required" in str(exc_info.value)
def test_cloud_mode_requires_allowed_issuers(self, tmp_path):
"""Test CLOUD mode raises error when ALLOWED_ISSUERS missing."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="https://resource.example.com",
allowed_issuers=[],
checklist_dir=checklist_dir,
)
assert "ALLOWED_ISSUERS is required" in str(exc_info.value)
def test_cloud_mode_validates_resource_identifier_url(self, tmp_path):
"""Test CLOUD mode validates RESOURCE_IDENTIFIER is a valid URL."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="not-a-url",
allowed_issuers=["https://issuer.example.com"],
checklist_dir=checklist_dir,
)
assert "must be a valid URL" in str(exc_info.value)
def test_cloud_mode_validates_allowed_issuers_urls(self, tmp_path):
"""Test CLOUD mode validates ALLOWED_ISSUERS are valid URLs."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="https://resource.example.com",
allowed_issuers=["https://valid.example.com", "not-a-url"],
checklist_dir=checklist_dir,
)
assert "must contain valid URLs" in str(exc_info.value)
def test_cloud_mode_accepts_valid_config(self, tmp_path):
"""Test CLOUD mode accepts valid configuration."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
settings = Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="https://resource.example.com",
allowed_issuers=["https://issuer.example.com"],
checklist_dir=checklist_dir,
)
assert settings.auth_mode == AuthMode.CLOUD
assert settings.resource_identifier == "https://resource.example.com"
assert settings.allowed_issuers == ["https://issuer.example.com"]
def test_cloud_mode_accepts_http_for_localhost(self, tmp_path):
"""Test CLOUD mode accepts http:// URLs (for local development)."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
settings = Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="http://localhost:8080",
allowed_issuers=["http://localhost:9000"],
checklist_dir=checklist_dir,
)
assert settings.resource_identifier == "http://localhost:8080"
class TestLocalModeSettingsValidation:
"""Tests for LOCAL mode settings validation."""
def test_local_mode_requires_azure_client_id(self, tmp_path):
"""Test LOCAL mode raises error when AZURE_CLIENT_ID missing."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.LOCAL,
azure_client_id="",
azure_tenant_id="tenant-123",
checklist_dir=checklist_dir,
)
assert "AZURE_CLIENT_ID is required" in str(exc_info.value)
def test_local_mode_requires_azure_tenant_id(self, tmp_path):
"""Test LOCAL mode raises error when AZURE_TENANT_ID missing."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.LOCAL,
azure_client_id="client-123",
azure_tenant_id="",
checklist_dir=checklist_dir,
)
assert "AZURE_TENANT_ID is required" in str(exc_info.value)
class TestAutoModeSettingsValidation:
"""Tests for AUTO mode settings validation."""
def test_auto_mode_does_not_require_cloud_settings(self, tmp_path):
"""Test AUTO mode doesn't require CLOUD mode settings."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
# AUTO mode with only LOCAL settings should work
settings = Settings(
auth_mode=AuthMode.AUTO,
azure_client_id="client-123",
azure_tenant_id="tenant-123",
checklist_dir=checklist_dir,
)
assert settings.auth_mode == AuthMode.AUTO
def test_auto_mode_accepts_both_local_and_cloud_settings(self, tmp_path):
"""Test AUTO mode accepts both LOCAL and CLOUD settings."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
settings = Settings(
auth_mode=AuthMode.AUTO,
azure_client_id="client-123",
azure_tenant_id="tenant-123",
resource_identifier="https://resource.example.com",
allowed_issuers=["https://issuer.example.com"],
checklist_dir=checklist_dir,
)
assert settings.auth_mode == AuthMode.AUTO
assert settings.azure_client_id == "client-123"
assert settings.resource_identifier == "https://resource.example.com"
class TestJwksCacheTtlValidation:
"""Tests for JWKS cache TTL validation."""
def test_negative_jwks_cache_ttl_raises_error(self, tmp_path):
"""Test negative JWKS_CACHE_TTL raises error."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
with pytest.raises(ConfigurationError) as exc_info:
Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="https://resource.example.com",
allowed_issuers=["https://issuer.example.com"],
jwks_cache_ttl=-1,
checklist_dir=checklist_dir,
)
assert "JWKS_CACHE_TTL must be non-negative" in str(exc_info.value)
def test_zero_jwks_cache_ttl_is_valid(self, tmp_path):
"""Test zero JWKS_CACHE_TTL is valid (no caching)."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
settings = Settings(
auth_mode=AuthMode.CLOUD,
resource_identifier="https://resource.example.com",
allowed_issuers=["https://issuer.example.com"],
jwks_cache_ttl=0,
checklist_dir=checklist_dir,
)
assert settings.jwks_cache_ttl == 0
class TestSettingsFromEnvCloudMode:
"""Tests for Settings.from_env with cloud mode settings."""
def test_from_env_parses_auth_mode(self, tmp_path, monkeypatch):
"""Test from_env parses AUTH_MODE correctly."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
monkeypatch.setenv("AUTH_MODE", "cloud")
monkeypatch.setenv("RESOURCE_IDENTIFIER", "https://resource.example.com")
monkeypatch.setenv("ALLOWED_ISSUERS", "https://issuer.example.com")
monkeypatch.setenv("CHECKLIST_DIR", str(checklist_dir))
settings = Settings.from_env()
assert settings.auth_mode == AuthMode.CLOUD
def test_from_env_parses_allowed_issuers_comma_separated(self, tmp_path, monkeypatch):
"""Test from_env parses comma-separated ALLOWED_ISSUERS."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
monkeypatch.setenv("AUTH_MODE", "cloud")
monkeypatch.setenv("RESOURCE_IDENTIFIER", "https://resource.example.com")
monkeypatch.setenv(
"ALLOWED_ISSUERS",
"https://issuer1.example.com, https://issuer2.example.com",
)
monkeypatch.setenv("CHECKLIST_DIR", str(checklist_dir))
settings = Settings.from_env()
assert settings.allowed_issuers == [
"https://issuer1.example.com",
"https://issuer2.example.com",
]
def test_from_env_parses_scopes_supported(self, tmp_path, monkeypatch):
"""Test from_env parses SCOPES_SUPPORTED."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
monkeypatch.setenv("AUTH_MODE", "cloud")
monkeypatch.setenv("RESOURCE_IDENTIFIER", "https://resource.example.com")
monkeypatch.setenv("ALLOWED_ISSUERS", "https://issuer.example.com")
monkeypatch.setenv("SCOPES_SUPPORTED", "read, write, admin")
monkeypatch.setenv("CHECKLIST_DIR", str(checklist_dir))
settings = Settings.from_env()
assert settings.scopes_supported == ["read", "write", "admin"]
def test_from_env_parses_jwks_cache_ttl(self, tmp_path, monkeypatch):
"""Test from_env parses JWKS_CACHE_TTL."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
monkeypatch.setenv("AUTH_MODE", "cloud")
monkeypatch.setenv("RESOURCE_IDENTIFIER", "https://resource.example.com")
monkeypatch.setenv("ALLOWED_ISSUERS", "https://issuer.example.com")
monkeypatch.setenv("JWKS_CACHE_TTL", "7200")
monkeypatch.setenv("CHECKLIST_DIR", str(checklist_dir))
settings = Settings.from_env()
assert settings.jwks_cache_ttl == 7200
def test_from_env_invalid_auth_mode_raises_error(self, tmp_path, monkeypatch):
"""Test from_env raises error for invalid AUTH_MODE."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
monkeypatch.setenv("AUTH_MODE", "invalid")
monkeypatch.setenv("CHECKLIST_DIR", str(checklist_dir))
with pytest.raises(ConfigurationError) as exc_info:
Settings.from_env()
assert "AUTH_MODE must be one of" in str(exc_info.value)
def test_from_env_defaults_to_local_mode(self, tmp_path, monkeypatch):
"""Test from_env defaults to LOCAL mode when AUTH_MODE not set."""
checklist_dir = tmp_path / "checklists"
checklist_dir.mkdir()
# Clear AUTH_MODE if set
monkeypatch.delenv("AUTH_MODE", raising=False)
monkeypatch.setenv("AZURE_CLIENT_ID", "client-123")
monkeypatch.setenv("AZURE_TENANT_ID", "tenant-123")
monkeypatch.setenv("CHECKLIST_DIR", str(checklist_dir))
settings = Settings.from_env()
assert settings.auth_mode == AuthMode.LOCAL