194 lines
6.3 KiB
Python
194 lines
6.3 KiB
Python
"""Integration tests for configuration API endpoints."""
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from src.server.fastapi_app import app
|
|
from src.server.models.config import AppConfig
|
|
from src.server.services.auth_service import auth_service
|
|
from src.server.services.config_service import ConfigService
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_config_dir():
|
|
"""Create temporary directory for test config files."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
yield Path(tmpdir)
|
|
|
|
|
|
@pytest.fixture
|
|
def config_service(temp_config_dir):
|
|
"""Create ConfigService instance with temporary paths."""
|
|
config_path = temp_config_dir / "config.json"
|
|
backup_dir = temp_config_dir / "backups"
|
|
return ConfigService(
|
|
config_path=config_path, backup_dir=backup_dir, max_backups=3
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config_service(config_service):
|
|
"""Mock get_config_service to return test instance."""
|
|
with patch(
|
|
"src.server.api.config.get_config_service",
|
|
return_value=config_service
|
|
):
|
|
yield config_service
|
|
|
|
|
|
@pytest.fixture
|
|
async def client():
|
|
"""Create async test client."""
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
yield ac
|
|
|
|
|
|
@pytest.fixture
|
|
async def authenticated_client():
|
|
"""Create authenticated async test client."""
|
|
# Setup auth if not configured
|
|
if not auth_service.is_configured():
|
|
auth_service.setup_master_password("TestPass123!")
|
|
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
# Login to get token
|
|
r = await ac.post("/api/auth/login", json={"password": "TestPass123!"})
|
|
if r.status_code == 200:
|
|
token = r.json()["access_token"]
|
|
ac.headers["Authorization"] = f"Bearer {token}"
|
|
yield ac
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_get_config_public(client, mock_config_service):
|
|
"""Test getting configuration."""
|
|
resp = await client.get("/api/config")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "name" in data
|
|
assert "data_dir" in data
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_validate_config(authenticated_client, mock_config_service):
|
|
"""Test configuration validation."""
|
|
cfg = {
|
|
"name": "Aniworld",
|
|
"data_dir": "data",
|
|
"scheduler": {"enabled": True, "interval_minutes": 30},
|
|
"logging": {"level": "INFO"},
|
|
"backup": {"enabled": False},
|
|
"other": {},
|
|
}
|
|
resp = await authenticated_client.post("/api/config/validate", json=cfg)
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body.get("valid") is True
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_validate_invalid_config(authenticated_client, mock_config_service):
|
|
"""Test validation of invalid configuration."""
|
|
cfg = {
|
|
"name": "Aniworld",
|
|
"backup": {"enabled": True, "path": None}, # Invalid
|
|
}
|
|
resp = await authenticated_client.post("/api/config/validate", json=cfg)
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body.get("valid") is False
|
|
assert len(body.get("errors", [])) > 0
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_update_config_unauthorized(client):
|
|
"""Test that update requires authentication."""
|
|
update = {"scheduler": {"enabled": False}}
|
|
resp = await client.put("/api/config", json=update)
|
|
assert resp.status_code in (401, 422)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_list_backups(authenticated_client, mock_config_service):
|
|
"""Test listing configuration backups."""
|
|
# Create a sample config first
|
|
sample_config = AppConfig(name="TestApp", data_dir="test_data")
|
|
mock_config_service.save_config(sample_config, create_backup=False)
|
|
mock_config_service.create_backup(name="test_backup")
|
|
|
|
resp = await authenticated_client.get("/api/config/backups")
|
|
assert resp.status_code == 200
|
|
backups = resp.json()
|
|
assert isinstance(backups, list)
|
|
if len(backups) > 0:
|
|
assert "name" in backups[0]
|
|
assert "size_bytes" in backups[0]
|
|
assert "created_at" in backups[0]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_create_backup(authenticated_client, mock_config_service):
|
|
"""Test creating a configuration backup."""
|
|
# Create a sample config first
|
|
sample_config = AppConfig(name="TestApp", data_dir="test_data")
|
|
mock_config_service.save_config(sample_config, create_backup=False)
|
|
|
|
resp = await authenticated_client.post("/api/config/backups")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "name" in data
|
|
assert "message" in data
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_restore_backup(authenticated_client, mock_config_service):
|
|
"""Test restoring configuration from backup."""
|
|
# Create initial config and backup
|
|
sample_config = AppConfig(name="TestApp", data_dir="test_data")
|
|
mock_config_service.save_config(sample_config, create_backup=False)
|
|
mock_config_service.create_backup(name="restore_test")
|
|
|
|
# Modify config
|
|
sample_config.name = "Modified"
|
|
mock_config_service.save_config(sample_config, create_backup=False)
|
|
|
|
# Restore from backup
|
|
resp = await authenticated_client.post("/api/config/backups/restore_test.json/restore")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["name"] == "TestApp" # Original name restored
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_delete_backup(authenticated_client, mock_config_service):
|
|
"""Test deleting a configuration backup."""
|
|
# Create a sample config and backup
|
|
sample_config = AppConfig(name="TestApp", data_dir="test_data")
|
|
mock_config_service.save_config(sample_config, create_backup=False)
|
|
mock_config_service.create_backup(name="delete_test")
|
|
|
|
resp = await authenticated_client.delete("/api/config/backups/delete_test.json")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "deleted successfully" in data["message"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_config_persistence(client, mock_config_service):
|
|
"""Test end-to-end configuration persistence."""
|
|
# Get initial config
|
|
resp = await client.get("/api/config")
|
|
assert resp.status_code == 200
|
|
initial = resp.json()
|
|
|
|
# Validate it can be loaded again
|
|
resp2 = await client.get("/api/config")
|
|
assert resp2.status_code == 200
|
|
assert resp2.json() == initial
|