e98cbb0abb
Phase 1: Fix immediate test failures - Fix path handling in files router (strip leading slashes) - Fix test assertions to match actual API behavior - Add proper error handling for missing files in download endpoint - Reload files router in test fixtures to pick up config changes - Fix upload endpoint test to use query params instead of form data Phase 2: Improve test reliability - Standardize error responses: docker_router now uses HTTPException - Add global exception handlers for validation, Docker errors, and unhandled exceptions - Fix flaky TOTP replay protection test - Fix flaky password hash loading test with proper module reload - Enhanced Docker mock fixtures with error scenarios and factory pattern Phase 3: Add coverage reporting - Add pytest-cov with 40% minimum coverage threshold - Add pyproject.toml with pytest and coverage configuration - Update test workflow to generate coverage and JUnit XML reports - Remove -x flag to see all test failures - Configure asyncio_default_fixture_loop_scope to fix deprecation warning Results: - All 144 tests passing (was 137 passed, 5 failed, 2 skipped) - Test execution time: ~3s locally - Coverage: 43% (above 40% threshold) - No skipped tests - Standardized error handling across all endpoints
49 lines
1.5 KiB
Python
49 lines
1.5 KiB
Python
import docker
|
|
from fastapi import APIRouter, Depends, Query, HTTPException
|
|
from config import DOCKER_HOST
|
|
from rbac import require_admin
|
|
|
|
router = APIRouter()
|
|
|
|
_client = None
|
|
|
|
def get_docker_client():
|
|
"""Get or create Docker client (lazy initialization)."""
|
|
global _client
|
|
if _client is None:
|
|
_client = docker.DockerClient(base_url=DOCKER_HOST)
|
|
return _client
|
|
|
|
|
|
@router.get("/containers")
|
|
def list_containers():
|
|
client = get_docker_client()
|
|
return [
|
|
{
|
|
"id": c.short_id,
|
|
"name": c.name,
|
|
"status": c.status,
|
|
"health": (c.attrs.get("State", {}).get("Health", {}) or {}).get("Status", ""),
|
|
"image": c.image.tags[0] if c.image.tags else str(c.image.id)[:20],
|
|
"ports": c.ports,
|
|
}
|
|
for c in client.containers.list(all=True)
|
|
]
|
|
|
|
|
|
@router.post("/containers/{container_id}/{action}", dependencies=[Depends(require_admin())])
|
|
def container_action(container_id: str, action: str):
|
|
if action not in ("start", "stop", "restart"):
|
|
raise HTTPException(status_code=400, detail="Invalid action. Must be one of: start, stop, restart")
|
|
client = get_docker_client()
|
|
c = client.containers.get(container_id)
|
|
getattr(c, action)()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/containers/{container_id}/logs")
|
|
def container_logs(container_id: str, tail: int = Query(200, le=10000)):
|
|
client = get_docker_client()
|
|
c = client.containers.get(container_id)
|
|
return {"logs": c.logs(tail=tail, timestamps=True).decode(errors="replace")}
|