Files
Gondulf/tests/unit/test_config.py
Phil Skentelbery 115e733604 feat(phase-4a): complete Phase 3 implementation and gap analysis
Merges Phase 4a work including:

Implementation:
- Metadata discovery endpoint (/api/.well-known/oauth-authorization-server)
- h-app microformat parser service
- Enhanced authorization endpoint with client info display
- Configuration management system
- Dependency injection framework

Documentation:
- Comprehensive gap analysis for v1.0.0 compliance
- Phase 4a clarifications on development approach
- Phase 4-5 critical components breakdown

Testing:
- Unit tests for h-app parser (308 lines, comprehensive coverage)
- Unit tests for metadata endpoint (134 lines)
- Unit tests for configuration system (18 lines)
- Integration test updates

All tests passing with high coverage. Ready for Phase 4b security hardening.
2025-11-20 17:16:11 -07:00

201 lines
8.7 KiB
Python

"""
Unit tests for configuration module.
Tests environment variable loading, validation, and error handling.
"""
import os
import pytest
from gondulf.config import Config, ConfigurationError
class TestConfigLoad:
"""Tests for Config.load() method."""
def test_load_with_valid_secret_key(self, monkeypatch):
"""Test configuration loads successfully with valid SECRET_KEY."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
Config.load()
assert Config.SECRET_KEY == "a" * 32
def test_load_missing_secret_key_raises_error(self, monkeypatch):
"""Test that missing SECRET_KEY raises ConfigurationError."""
monkeypatch.delenv("GONDULF_SECRET_KEY", raising=False)
with pytest.raises(ConfigurationError, match="GONDULF_SECRET_KEY is required"):
Config.load()
def test_load_short_secret_key_raises_error(self, monkeypatch):
"""Test that SECRET_KEY shorter than 32 chars raises error."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "short")
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
with pytest.raises(ConfigurationError, match="at least 32 characters"):
Config.load()
def test_load_database_url_default(self, monkeypatch):
"""Test DATABASE_URL defaults to sqlite:///./data/gondulf.db."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.delenv("GONDULF_DATABASE_URL", raising=False)
Config.load()
assert Config.DATABASE_URL == "sqlite:///./data/gondulf.db"
def test_load_database_url_custom(self, monkeypatch):
"""Test DATABASE_URL can be customized."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_DATABASE_URL", "sqlite:////tmp/test.db")
Config.load()
assert Config.DATABASE_URL == "sqlite:////tmp/test.db"
def test_load_smtp_configuration_defaults(self, monkeypatch):
"""Test SMTP configuration uses sensible defaults."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
for key in [
"GONDULF_SMTP_HOST",
"GONDULF_SMTP_PORT",
"GONDULF_SMTP_USERNAME",
"GONDULF_SMTP_PASSWORD",
"GONDULF_SMTP_FROM",
"GONDULF_SMTP_USE_TLS",
]:
monkeypatch.delenv(key, raising=False)
Config.load()
assert Config.SMTP_HOST == "localhost"
assert Config.SMTP_PORT == 587
assert Config.SMTP_USERNAME is None
assert Config.SMTP_PASSWORD is None
assert Config.SMTP_FROM == "noreply@example.com"
assert Config.SMTP_USE_TLS is True
def test_load_smtp_configuration_custom(self, monkeypatch):
"""Test SMTP configuration can be customized."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_SMTP_HOST", "smtp.gmail.com")
monkeypatch.setenv("GONDULF_SMTP_PORT", "465")
monkeypatch.setenv("GONDULF_SMTP_USERNAME", "user@gmail.com")
monkeypatch.setenv("GONDULF_SMTP_PASSWORD", "password123")
monkeypatch.setenv("GONDULF_SMTP_FROM", "sender@example.com")
monkeypatch.setenv("GONDULF_SMTP_USE_TLS", "false")
Config.load()
assert Config.SMTP_HOST == "smtp.gmail.com"
assert Config.SMTP_PORT == 465
assert Config.SMTP_USERNAME == "user@gmail.com"
assert Config.SMTP_PASSWORD == "password123"
assert Config.SMTP_FROM == "sender@example.com"
assert Config.SMTP_USE_TLS is False
def test_load_token_expiry_default(self, monkeypatch):
"""Test TOKEN_EXPIRY defaults to 3600 seconds."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.delenv("GONDULF_TOKEN_EXPIRY", raising=False)
Config.load()
assert Config.TOKEN_EXPIRY == 3600
def test_load_code_expiry_default(self, monkeypatch):
"""Test CODE_EXPIRY defaults to 600 seconds."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.delenv("GONDULF_CODE_EXPIRY", raising=False)
Config.load()
assert Config.CODE_EXPIRY == 600
def test_load_token_expiry_custom(self, monkeypatch):
"""Test TOKEN_EXPIRY can be customized."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_TOKEN_EXPIRY", "7200")
Config.load()
assert Config.TOKEN_EXPIRY == 7200
def test_load_log_level_default_production(self, monkeypatch):
"""Test LOG_LEVEL defaults to INFO in production mode."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.delenv("GONDULF_LOG_LEVEL", raising=False)
monkeypatch.delenv("GONDULF_DEBUG", raising=False)
Config.load()
assert Config.LOG_LEVEL == "INFO"
assert Config.DEBUG is False
def test_load_log_level_default_debug(self, monkeypatch):
"""Test LOG_LEVEL defaults to DEBUG when DEBUG=true."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.delenv("GONDULF_LOG_LEVEL", raising=False)
monkeypatch.setenv("GONDULF_DEBUG", "true")
Config.load()
assert Config.LOG_LEVEL == "DEBUG"
assert Config.DEBUG is True
def test_load_log_level_custom(self, monkeypatch):
"""Test LOG_LEVEL can be customized."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_LOG_LEVEL", "WARNING")
Config.load()
assert Config.LOG_LEVEL == "WARNING"
def test_load_invalid_log_level_raises_error(self, monkeypatch):
"""Test invalid LOG_LEVEL raises ConfigurationError."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_LOG_LEVEL", "INVALID")
with pytest.raises(ConfigurationError, match="must be one of"):
Config.load()
class TestConfigValidate:
"""Tests for Config.validate() method."""
def test_validate_valid_configuration(self, monkeypatch):
"""Test validation passes with valid configuration."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
Config.load()
Config.validate() # Should not raise
def test_validate_smtp_port_too_low(self, monkeypatch):
"""Test validation fails when SMTP_PORT < 1."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
Config.load()
Config.SMTP_PORT = 0
with pytest.raises(ConfigurationError, match="must be between 1 and 65535"):
Config.validate()
def test_validate_smtp_port_too_high(self, monkeypatch):
"""Test validation fails when SMTP_PORT > 65535."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
Config.load()
Config.SMTP_PORT = 70000
with pytest.raises(ConfigurationError, match="must be between 1 and 65535"):
Config.validate()
def test_validate_token_expiry_negative(self, monkeypatch):
"""Test validation fails when TOKEN_EXPIRY < 300."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
Config.load()
Config.TOKEN_EXPIRY = -1
with pytest.raises(ConfigurationError, match="must be at least 300 seconds"):
Config.validate()
def test_validate_code_expiry_zero(self, monkeypatch):
"""Test validation fails when CODE_EXPIRY <= 0."""
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
Config.load()
Config.CODE_EXPIRY = 0
with pytest.raises(ConfigurationError, match="must be positive"):
Config.validate()