feat: comprehensive test infrastructure improvements
- Fix unit test imports: add env setup in conftest.py before module imports - Add 24 new auth router tests (RBAC, preferences, password validation) - Add 16 new tests for litellm and chat_summary routers - Apply black formatting and ruff linting across codebase - Add pre-commit hooks configuration (black, ruff, file checks) - Increase CI coverage threshold from 40% to 50% Test Results: - 206 tests passing (91 unit + 115 integration) - Coverage: 58.79% on core modules - auth.py: 57% → 85%, litellm.py: 23% → 87%, chat_summary.py: 41% → 100% - auth_service: 96.51%, config: 100%, rbac: 93.48%
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Integration tests for file operations.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
import os
|
||||
|
||||
|
||||
@@ -10,11 +10,7 @@ class TestFileBrowse:
|
||||
|
||||
def test_browse_root(self, test_app, admin_headers, temp_volume_root):
|
||||
"""Test browsing root directory."""
|
||||
response = test_app.get(
|
||||
"/api/files/browse",
|
||||
headers=admin_headers,
|
||||
params={"path": "/"}
|
||||
)
|
||||
response = test_app.get("/api/files/browse", headers=admin_headers, params={"path": "/"})
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -28,22 +24,14 @@ class TestFileBrowse:
|
||||
|
||||
def test_browse_blocked_directory(self, test_app, admin_headers):
|
||||
"""Test browsing blocked directory."""
|
||||
response = test_app.get(
|
||||
"/api/files/browse",
|
||||
headers=admin_headers,
|
||||
params={"path": "/@appstore"}
|
||||
)
|
||||
response = test_app.get("/api/files/browse", headers=admin_headers, params={"path": "/@appstore"})
|
||||
|
||||
# Should be forbidden
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_browse_path_traversal_attempt(self, test_app, admin_headers):
|
||||
"""Test path traversal protection."""
|
||||
response = test_app.get(
|
||||
"/api/files/browse",
|
||||
headers=admin_headers,
|
||||
params={"path": "/../../../etc"}
|
||||
)
|
||||
response = test_app.get("/api/files/browse", headers=admin_headers, params={"path": "/../../../etc"})
|
||||
|
||||
# Should be forbidden
|
||||
assert response.status_code == 403
|
||||
@@ -57,12 +45,7 @@ class TestFileUpload:
|
||||
test_content = b"test file content"
|
||||
files = {"file": ("test.txt", test_content, "text/plain")}
|
||||
|
||||
response = test_app.post(
|
||||
"/api/files/upload",
|
||||
headers=admin_headers,
|
||||
files=files,
|
||||
params={"path": "/"}
|
||||
)
|
||||
response = test_app.post("/api/files/upload", headers=admin_headers, files=files, params={"path": "/"})
|
||||
|
||||
assert response.status_code in [200, 201]
|
||||
|
||||
@@ -70,32 +53,23 @@ class TestFileUpload:
|
||||
"""Test upload without authentication."""
|
||||
files = {"file": ("test.txt", b"content", "text/plain")}
|
||||
|
||||
response = test_app.post(
|
||||
"/api/files/upload",
|
||||
files=files,
|
||||
params={"path": "/"}
|
||||
)
|
||||
response = test_app.post("/api/files/upload", files=files, params={"path": "/"})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_upload_as_member_forbidden(self, test_app, mock_config, temp_auth_file, temp_rbac_file):
|
||||
"""Test that non-admin cannot upload."""
|
||||
from auth_service import create_access_token
|
||||
from datetime import timedelta
|
||||
|
||||
from auth_service import create_access_token
|
||||
|
||||
member_token = create_access_token(
|
||||
data={"sub": "memberuser", "role": "member"},
|
||||
expires_delta=timedelta(minutes=30)
|
||||
data={"sub": "memberuser", "role": "member"}, expires_delta=timedelta(minutes=30)
|
||||
)
|
||||
headers = {"Authorization": f"Bearer {member_token}"}
|
||||
files = {"file": ("test.txt", b"content", "text/plain")}
|
||||
|
||||
response = test_app.post(
|
||||
"/api/files/upload",
|
||||
headers=headers,
|
||||
files=files,
|
||||
params={"path": "/"}
|
||||
)
|
||||
response = test_app.post("/api/files/upload", headers=headers, files=files, params={"path": "/"})
|
||||
|
||||
# Upload should be admin-only
|
||||
assert response.status_code == 403
|
||||
@@ -111,39 +85,28 @@ class TestFileDelete:
|
||||
with open(test_file, "w") as f:
|
||||
f.write("test content")
|
||||
|
||||
response = test_app.delete(
|
||||
"/api/files/delete",
|
||||
headers=admin_headers,
|
||||
params={"path": "/test_delete.txt"}
|
||||
)
|
||||
response = test_app.delete("/api/files/delete", headers=admin_headers, params={"path": "/test_delete.txt"})
|
||||
|
||||
assert response.status_code in [200, 204]
|
||||
|
||||
def test_delete_without_auth(self, test_app):
|
||||
"""Test delete without authentication."""
|
||||
response = test_app.delete(
|
||||
"/api/files/delete",
|
||||
params={"path": "/test.txt"}
|
||||
)
|
||||
response = test_app.delete("/api/files/delete", params={"path": "/test.txt"})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_delete_as_member_forbidden(self, test_app, mock_config, temp_auth_file, temp_rbac_file):
|
||||
"""Test that non-admin cannot delete."""
|
||||
from auth_service import create_access_token
|
||||
from datetime import timedelta
|
||||
|
||||
from auth_service import create_access_token
|
||||
|
||||
member_token = create_access_token(
|
||||
data={"sub": "memberuser", "role": "member"},
|
||||
expires_delta=timedelta(minutes=30)
|
||||
data={"sub": "memberuser", "role": "member"}, expires_delta=timedelta(minutes=30)
|
||||
)
|
||||
headers = {"Authorization": f"Bearer {member_token}"}
|
||||
|
||||
response = test_app.delete(
|
||||
"/api/files/delete",
|
||||
headers=headers,
|
||||
params={"path": "/test.txt"}
|
||||
)
|
||||
response = test_app.delete("/api/files/delete", headers=headers, params={"path": "/test.txt"})
|
||||
|
||||
# Delete should be admin-only
|
||||
assert response.status_code == 403
|
||||
@@ -160,30 +123,19 @@ class TestFileDownload:
|
||||
with open(test_file, "w") as f:
|
||||
f.write(test_content)
|
||||
|
||||
response = test_app.get(
|
||||
"/api/files/download",
|
||||
headers=admin_headers,
|
||||
params={"path": "/test_download.txt"}
|
||||
)
|
||||
response = test_app.get("/api/files/download", headers=admin_headers, params={"path": "/test_download.txt"})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert test_content in response.text or response.content == test_content.encode()
|
||||
|
||||
def test_download_without_auth(self, test_app):
|
||||
"""Test download without authentication."""
|
||||
response = test_app.get(
|
||||
"/api/files/download",
|
||||
params={"path": "/test.txt"}
|
||||
)
|
||||
response = test_app.get("/api/files/download", params={"path": "/test.txt"})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_download_nonexistent_file(self, test_app, admin_headers):
|
||||
"""Test downloading nonexistent file."""
|
||||
response = test_app.get(
|
||||
"/api/files/download",
|
||||
headers=admin_headers,
|
||||
params={"path": "/nonexistent.txt"}
|
||||
)
|
||||
response = test_app.get("/api/files/download", headers=admin_headers, params={"path": "/nonexistent.txt"})
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user