b8217dce8a
docker / test (pull_request) Successful in 24s
lint / lint (pull_request) Successful in 37s
lint / lint (push) Successful in 1m26s
test / test (push) Successful in 1m40s
test / test (pull_request) Successful in 34s
docker / lint (pull_request) Successful in 1m59s
docker / docker-test (pull_request) Successful in 14s
docker / docker-publish (pull_request) Has been skipped
- Enforce 32-char minimum on OAUTH_STATE_SECRET at startup (config.py) - Write DCR client registry with owner-only (0o600) permissions before atomic replace - Flip policy.yaml default write action from allow → deny - Add CLAUDE.md with architecture, commands, and AGENTS.md contract summary - Add .pre-commit-config.yaml mirroring `make lint` checks - Update .gitignore: add .venv, .claude, .mypy_cache, .ruff_cache, .coverage.* - Extend docs: audit log rotation guidance, OAUTH_STATE_SECRET and DCR_STORAGE_PATH notes - Tests: short-secret rejection, 32-char acceptance, POSIX permission check for DCR store Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
139 lines
4.9 KiB
Python
139 lines
4.9 KiB
Python
"""Tests for configuration management."""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from aegis_gitea_mcp.config import get_settings, reset_settings
|
|
|
|
|
|
def test_settings_from_env(mock_env: None) -> None:
|
|
"""Test loading settings from environment variables."""
|
|
settings = get_settings()
|
|
|
|
assert settings.gitea_base_url == "https://gitea.example.com"
|
|
assert settings.gitea_token == "test-token-12345"
|
|
assert settings.mcp_host == "127.0.0.1"
|
|
assert settings.mcp_port == 8080
|
|
assert settings.log_level == "DEBUG"
|
|
|
|
|
|
def test_settings_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test default values when not specified."""
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("GITEA_TOKEN", "test-token")
|
|
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
|
|
|
settings = get_settings()
|
|
|
|
assert settings.mcp_host == "127.0.0.1"
|
|
assert settings.mcp_port == 8080
|
|
assert settings.log_level == "INFO"
|
|
assert settings.max_file_size_bytes == 1_048_576
|
|
assert settings.request_timeout_seconds == 30
|
|
|
|
|
|
def test_settings_validation_missing_required(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None:
|
|
"""Test that missing required fields raise validation errors."""
|
|
|
|
monkeypatch.delenv("GITEA_URL", raising=False)
|
|
monkeypatch.delenv("GITEA_TOKEN", raising=False)
|
|
|
|
# Change to tmp directory so .env file won't be found
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
reset_settings()
|
|
|
|
with pytest.raises(ValidationError):
|
|
get_settings()
|
|
|
|
|
|
def test_settings_invalid_log_level(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that invalid log levels are rejected."""
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("GITEA_TOKEN", "test-token")
|
|
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
|
monkeypatch.setenv("LOG_LEVEL", "INVALID")
|
|
|
|
reset_settings()
|
|
|
|
with pytest.raises(ValidationError):
|
|
get_settings()
|
|
|
|
|
|
def test_settings_empty_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that empty tokens are rejected."""
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("GITEA_TOKEN", " ")
|
|
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
|
|
|
reset_settings()
|
|
|
|
with pytest.raises(ValidationError):
|
|
get_settings()
|
|
|
|
|
|
def test_settings_singleton(mock_env: None) -> None:
|
|
"""Test that get_settings returns same instance."""
|
|
settings1 = get_settings()
|
|
settings2 = get_settings()
|
|
|
|
assert settings1 is settings2
|
|
|
|
|
|
def test_write_mode_requires_whitelist_or_allow_all(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Write mode without whitelist must be rejected unless allow-all is enabled."""
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("GITEA_TOKEN", "test-token")
|
|
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
|
monkeypatch.setenv("WRITE_MODE", "true")
|
|
monkeypatch.delenv("WRITE_REPOSITORY_WHITELIST", raising=False)
|
|
monkeypatch.setenv("WRITE_ALLOW_ALL_TOKEN_REPOS", "false")
|
|
|
|
reset_settings()
|
|
with pytest.raises(ValidationError):
|
|
get_settings()
|
|
|
|
|
|
def test_write_mode_allows_all_token_repos(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Allow-all mode should pass validation without explicit repository whitelist."""
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("GITEA_TOKEN", "test-token")
|
|
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
|
monkeypatch.setenv("WRITE_MODE", "true")
|
|
monkeypatch.delenv("WRITE_REPOSITORY_WHITELIST", raising=False)
|
|
monkeypatch.setenv("WRITE_ALLOW_ALL_TOKEN_REPOS", "true")
|
|
|
|
reset_settings()
|
|
settings = get_settings()
|
|
assert settings.write_allow_all_token_repos is True
|
|
|
|
|
|
def _oauth_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Apply a minimal, valid OAuth-mode environment."""
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("OAUTH_MODE", "true")
|
|
monkeypatch.setenv("GITEA_OAUTH_CLIENT_ID", "test-client-id")
|
|
monkeypatch.setenv("GITEA_OAUTH_CLIENT_SECRET", "test-client-secret")
|
|
monkeypatch.setenv("STARTUP_VALIDATE_GITEA", "false")
|
|
|
|
|
|
def test_oauth_state_secret_too_short_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""OAUTH_STATE_SECRET shorter than 32 characters must fail validation."""
|
|
_oauth_env(monkeypatch)
|
|
monkeypatch.setenv("OAUTH_STATE_SECRET", "short-secret")
|
|
|
|
reset_settings()
|
|
with pytest.raises(ValidationError, match="at least 32 characters"):
|
|
get_settings()
|
|
|
|
|
|
def test_oauth_state_secret_minimum_length_accepted(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""A 32+ character OAUTH_STATE_SECRET passes validation."""
|
|
_oauth_env(monkeypatch)
|
|
monkeypatch.setenv("OAUTH_STATE_SECRET", "x" * 32)
|
|
|
|
reset_settings()
|
|
settings = get_settings()
|
|
assert settings.oauth_mode is True
|
|
assert len(settings.oauth_state_secret) == 32
|