feat: add comprehensive unit and integration testing infrastructure
- Backend: pytest with unit tests (auth, rbac, config) and integration tests (auth flow, docker, files) - Frontend: vitest with unit tests (api client) and component tests (login) - CI: Gitea Actions workflow for automated testing with coverage reports - Documentation: TESTING.md guide with setup, usage, and best practices - Coverage goals: 80%+ backend, 70%+ frontend
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Integration tests for Docker operations.
|
||||
"""
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
|
||||
class TestDockerContainerList:
|
||||
"""Test listing Docker containers."""
|
||||
|
||||
def test_list_containers_success(self, test_app, admin_headers):
|
||||
"""Test listing containers successfully."""
|
||||
response = test_app.get("/api/docker/containers", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
if len(data) > 0:
|
||||
container = data[0]
|
||||
assert "id" in container
|
||||
assert "name" in container
|
||||
assert "status" in container
|
||||
|
||||
def test_list_containers_without_auth(self, test_app):
|
||||
"""Test listing containers without authentication."""
|
||||
response = test_app.get("/api/docker/containers")
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
class TestDockerContainerActions:
|
||||
"""Test Docker container start/stop/restart actions."""
|
||||
|
||||
def test_start_container_success(self, test_app, admin_headers, mock_docker_client):
|
||||
"""Test starting a container."""
|
||||
container_id = "abc123"
|
||||
|
||||
response = test_app.post(
|
||||
f"/api/docker/containers/{container_id}/start",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "message" in data or "status" in data
|
||||
|
||||
def test_stop_container_success(self, test_app, admin_headers, mock_docker_client):
|
||||
"""Test stopping a container."""
|
||||
container_id = "abc123"
|
||||
|
||||
response = test_app.post(
|
||||
f"/api/docker/containers/{container_id}/stop",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_restart_container_success(self, test_app, admin_headers, mock_docker_client):
|
||||
"""Test restarting a container."""
|
||||
container_id = "abc123"
|
||||
|
||||
response = test_app.post(
|
||||
f"/api/docker/containers/{container_id}/restart",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_container_action_without_auth(self, test_app):
|
||||
"""Test container action without authentication."""
|
||||
response = test_app.post("/api/docker/containers/abc123/start")
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_container_action_invalid_action(self, test_app, admin_headers):
|
||||
"""Test container action with invalid action."""
|
||||
response = test_app.post(
|
||||
"/api/docker/containers/abc123/invalid_action",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
class TestDockerContainerLogs:
|
||||
"""Test Docker container logs retrieval."""
|
||||
|
||||
def test_get_container_logs_success(self, test_app, admin_headers, mock_docker_client):
|
||||
"""Test getting container logs."""
|
||||
container_id = "abc123"
|
||||
|
||||
response = test_app.get(
|
||||
f"/api/docker/containers/{container_id}/logs",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "logs" in data or isinstance(data, str)
|
||||
|
||||
def test_get_container_logs_with_tail(self, test_app, admin_headers, mock_docker_client):
|
||||
"""Test getting container logs with tail limit."""
|
||||
container_id = "abc123"
|
||||
|
||||
response = test_app.get(
|
||||
f"/api/docker/containers/{container_id}/logs?tail=100",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_get_container_logs_without_auth(self, test_app):
|
||||
"""Test getting logs without authentication."""
|
||||
response = test_app.get("/api/docker/containers/abc123/logs")
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_get_container_logs_nonexistent(self, test_app, admin_headers, mock_docker_client):
|
||||
"""Test getting logs for nonexistent container."""
|
||||
# Mock container not found
|
||||
mock_docker_client.containers.get.side_effect = Exception("Container not found")
|
||||
|
||||
response = test_app.get(
|
||||
"/api/docker/containers/nonexistent/logs",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
# Should handle error gracefully
|
||||
assert response.status_code in [404, 500]
|
||||
|
||||
|
||||
class TestDockerPermissions:
|
||||
"""Test Docker operation permissions."""
|
||||
|
||||
@pytest.fixture
|
||||
def member_token(self, mock_config, temp_auth_file, temp_rbac_file):
|
||||
"""Create token for member role."""
|
||||
from auth import create_access_token
|
||||
from datetime import timedelta
|
||||
return create_access_token(
|
||||
data={"sub": "memberuser", "role": "member"},
|
||||
expires_delta=timedelta(minutes=30)
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def member_headers(self, member_token):
|
||||
"""Headers with member access token."""
|
||||
return {"Authorization": f"Bearer {member_token}"}
|
||||
|
||||
def test_member_can_list_containers(self, test_app, member_headers):
|
||||
"""Test that member can list containers."""
|
||||
response = test_app.get("/api/docker/containers", headers=member_headers)
|
||||
|
||||
# Members should be able to view containers
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_member_cannot_start_container(self, test_app, member_headers):
|
||||
"""Test that member cannot start containers (admin only)."""
|
||||
response = test_app.post(
|
||||
"/api/docker/containers/abc123/start",
|
||||
headers=member_headers
|
||||
)
|
||||
|
||||
# Should be forbidden for non-admin
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_viewer_can_list_containers(self, test_app, mock_config, temp_auth_file, temp_rbac_file):
|
||||
"""Test that viewer can list containers if they have docker page access."""
|
||||
from auth import create_access_token
|
||||
from datetime import timedelta
|
||||
|
||||
viewer_token = create_access_token(
|
||||
data={"sub": "vieweruser", "role": "viewer"},
|
||||
expires_delta=timedelta(minutes=30)
|
||||
)
|
||||
headers = {"Authorization": f"Bearer {viewer_token}"}
|
||||
|
||||
response = test_app.get("/api/docker/containers", headers=headers)
|
||||
|
||||
# Viewer may not have docker page access by default
|
||||
assert response.status_code in [200, 403]
|
||||
Reference in New Issue
Block a user