DocHub/backend/tests/test_main.py
Tianyang 360e9e5a14 refactor: rename all minio references to garage, drop minio SDK for boto3
The backend no longer mentions MinIO anywhere: MinioClient -> GarageClient
(minio_client.py -> garage_client.py), MINIO_* env vars -> GARAGE_*, and
the `minio` Python package is replaced with `boto3` (AWS's generic S3
client) since Garage is the only storage backend this project targets
now. S3 API semantics are unchanged, so document_service.py's behavior
is identical — this is a naming and dependency swap, not a logic change.

Also updates test fixtures/names (FakeMinioClient -> FakeGarageClient,
FakeMinioSDK -> FakeS3SDK) and storage/README.md, which no longer frames
Garage as a MinIO replacement (that migration note has served its purpose
now that no MinIO reference remains in the codebase).

Backend: 54/54 tests passing. Frontend: 38/38 tests passing (unaffected,
verified for regressions since it talks to the backend only through the
unchanged REST API).
2026-08-01 10:51:49 +08:00

134 lines
4.4 KiB
Python

from unittest.mock import MagicMock
import pytest
from fastapi.testclient import TestClient
import app.main as main_module
from app.main import app
from app.document_service import DocumentNotFoundError
from app.dependencies import get_document_service, get_search_client
client = TestClient(app)
def test_health_returns_ok():
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_startup_creates_bucket_when_missing(monkeypatch):
fake_garage = MagicMock()
fake_garage.list_buckets.return_value = {"Buckets": []}
monkeypatch.setattr(main_module, "_get_garage_sdk", lambda: fake_garage)
with TestClient(app):
pass
fake_garage.list_buckets.assert_called()
fake_garage.create_bucket.assert_called()
def test_startup_does_not_create_bucket_when_it_already_exists(monkeypatch):
fake_garage = MagicMock()
fake_garage.list_buckets.return_value = {"Buckets": [{"Name": "dochub"}]}
monkeypatch.setattr(main_module, "_get_garage_sdk", lambda: fake_garage)
with TestClient(app):
pass
fake_garage.create_bucket.assert_not_called()
def test_startup_does_not_crash_app_when_garage_is_unreachable(monkeypatch):
def raise_connection_error():
raise ConnectionError("garage unreachable")
monkeypatch.setattr(main_module, "_get_garage_sdk", raise_connection_error)
with TestClient(app) as test_client:
response = test_client.get("/health")
assert response.status_code == 200
class FakeDocumentService:
def __init__(self):
self.storage = {}
def list_documents(self, path):
prefix = path if not path or path.endswith("/") else f"{path}/"
files = [
{"key": key, "name": key[len(prefix):], "size": len(data), "modifiedAt": "2026-08-01T10:00:00Z"}
for key, data in self.storage.items()
if key.startswith(prefix)
]
return {"folders": [], "files": files}
def get_document(self, key):
if key not in self.storage:
raise DocumentNotFoundError(key)
return {"key": key, "content": self.storage[key].decode("utf-8"), "contentType": "text/markdown"}
def save_document(self, key, content, content_type):
self.storage[key] = content
return {"key": key, "updatedAt": "2026-08-01T10:05:00Z"}
def delete_document(self, key):
self.storage.pop(key, None)
def upload_document(self, key, data, content_type):
self.storage[key] = data
return {"key": key, "name": key.rsplit("/", 1)[-1], "size": len(data)}
class FakeSearchClient:
def search(self, query):
return [
{
"key": "产品文档/架构设计.md",
"title": "架构设计.md",
"path": "产品文档",
"snippet": "全文<em>检索</em>示例",
}
]
@pytest.fixture
def e2e_client():
fake_service = FakeDocumentService()
fake_search = FakeSearchClient()
app.dependency_overrides[get_document_service] = lambda: fake_service
app.dependency_overrides[get_search_client] = lambda: fake_search
with TestClient(app) as test_client:
yield test_client
app.dependency_overrides.clear()
def test_end_to_end_upload_get_list_search_delete_flow(e2e_client):
upload_response = e2e_client.post(
"/api/upload",
data={"path": "产品文档"},
files={"file": ("架构设计.md", "# 架构设计\n\nDocHub 支持全文检索。".encode("utf-8"), "text/markdown")},
)
assert upload_response.status_code == 201
key = upload_response.json()["key"]
assert key == "产品文档/架构设计.md"
get_response = e2e_client.get(f"/api/documents/{key}")
assert get_response.status_code == 200
assert "架构设计" in get_response.json()["content"]
list_response = e2e_client.get("/api/documents", params={"path": "产品文档"})
assert list_response.status_code == 200
assert any(f["key"] == key for f in list_response.json()["files"])
search_response = e2e_client.get("/api/search", params={"q": "检索"})
assert search_response.status_code == 200
assert search_response.json()["results"][0]["key"] == "产品文档/架构设计.md"
delete_response = e2e_client.delete(f"/api/documents/{key}")
assert delete_response.status_code == 204
missing_response = e2e_client.get(f"/api/documents/{key}")
assert missing_response.status_code == 404