241 lines
8.8 KiB
Python
241 lines
8.8 KiB
Python
# #region Test.Api.GitMergeRoutes [C:3] [TYPE Module] [SEMANTICS test,git,merge,routes]
|
|
# @BRIEF Tests for Git merge API routes — status, conflicts, resolve, abort, continue.
|
|
# @RELATION BINDS_TO -> [GitMergeRoutes]
|
|
# @TEST_EDGE: success -> 200 with expected data
|
|
# @TEST_EDGE: unexpected_error -> 500
|
|
# @TEST_EDGE: dashboard_not_found -> 404 propagated from resolve_dashboard
|
|
|
|
import os
|
|
|
|
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
|
|
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
|
|
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
_src = str(Path(__file__).resolve().parent.parent.parent / "src")
|
|
if _src not in sys.path:
|
|
sys.path.insert(0, _src)
|
|
|
|
|
|
MERGE_STATUS = {
|
|
"has_unfinished_merge": True,
|
|
"repository_path": "/tmp/repo",
|
|
"git_dir": "/tmp/repo/.git",
|
|
"current_branch": "dev",
|
|
"merge_head": "abc123",
|
|
"merge_message_preview": "Merge branch 'feature' into dev",
|
|
"conflicts_count": 2,
|
|
}
|
|
|
|
MERGE_CONFLICTS = [
|
|
{"file_path": "dashboard.json", "mine": "my content", "theirs": "their content"},
|
|
{"file_path": "config.yaml", "mine": "my config", "theirs": "their config"},
|
|
]
|
|
|
|
|
|
def _make_client() -> TestClient:
|
|
from src.api.routes.git._router import router as parent_router
|
|
from src.core.database import get_db
|
|
from src.dependencies import get_current_user, has_permission
|
|
from src.schemas.auth import User, RoleSchema
|
|
|
|
app = FastAPI()
|
|
app.include_router(parent_router)
|
|
|
|
mock_user = User(
|
|
id="admin-1", username="admin", email="admin@x.com",
|
|
auth_source="LOCAL",
|
|
created_at=__import__("datetime").datetime.now(),
|
|
roles=[RoleSchema(id="r1", name="Admin", description="", permissions=[])],
|
|
)
|
|
|
|
app.dependency_overrides = {
|
|
get_db: lambda: MagicMock(),
|
|
get_current_user: lambda: mock_user,
|
|
}
|
|
return TestClient(app)
|
|
|
|
|
|
class TestGetMergeStatus:
|
|
"""GET /repositories/{dashboard_ref}/merge/status"""
|
|
|
|
def test_success(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.get_merge_status.return_value = MERGE_STATUS
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.get("/repositories/42/merge/status")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["has_unfinished_merge"] is True
|
|
assert data["current_branch"] == "dev"
|
|
|
|
def test_unexpected_error(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.get_merge_status.side_effect = RuntimeError("boom")
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.get("/repositories/42/merge/status")
|
|
assert resp.status_code == 500
|
|
assert "boom" in resp.text
|
|
|
|
def test_dashboard_not_found(self):
|
|
with (
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(side_effect=HTTPException(status_code=404, detail="Not found"))),
|
|
):
|
|
client = _make_client()
|
|
resp = client.get("/repositories/bad-ref/merge/status")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
class TestGetMergeConflicts:
|
|
"""GET /repositories/{dashboard_ref}/merge/conflicts"""
|
|
|
|
def test_success(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.get_merge_conflicts.return_value = MERGE_CONFLICTS
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.get("/repositories/42/merge/conflicts")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 2
|
|
assert data[0]["file_path"] == "dashboard.json"
|
|
|
|
def test_unexpected_error(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.get_merge_conflicts.side_effect = RuntimeError("boom")
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.get("/repositories/42/merge/conflicts")
|
|
assert resp.status_code == 500
|
|
|
|
|
|
class TestResolveMergeConflicts:
|
|
"""POST /repositories/{dashboard_ref}/merge/resolve"""
|
|
|
|
RESOLVE_PAYLOAD = {
|
|
"resolutions": [
|
|
{"file_path": "dashboard.json", "resolution": "mine"},
|
|
]
|
|
}
|
|
|
|
def test_success(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.resolve_merge_conflicts.return_value = ["dashboard.json"]
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/resolve", json=self.RESOLVE_PAYLOAD)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "success"
|
|
assert data["resolved_files"] == ["dashboard.json"]
|
|
|
|
def test_unexpected_error(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.resolve_merge_conflicts.side_effect = RuntimeError("boom")
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/resolve", json=self.RESOLVE_PAYLOAD)
|
|
assert resp.status_code == 500
|
|
|
|
|
|
class TestAbortMerge:
|
|
"""POST /repositories/{dashboard_ref}/merge/abort"""
|
|
|
|
def test_success(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.abort_merge.return_value = {"status": "aborted"}
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/abort")
|
|
assert resp.status_code == 200
|
|
|
|
def test_unexpected_error(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.abort_merge.side_effect = RuntimeError("boom")
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/abort")
|
|
assert resp.status_code == 500
|
|
|
|
|
|
class TestContinueMerge:
|
|
"""POST /repositories/{dashboard_ref}/merge/continue"""
|
|
|
|
def test_success(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.continue_merge.return_value = {"status": "merged"}
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/continue", json={"message": "Merge complete"})
|
|
assert resp.status_code == 200
|
|
|
|
def test_success_no_message(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.continue_merge.return_value = {"status": "merged"}
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/continue", json={})
|
|
assert resp.status_code == 200
|
|
|
|
def test_unexpected_error(self):
|
|
mock_gs = MagicMock()
|
|
mock_gs.continue_merge.side_effect = RuntimeError("boom")
|
|
|
|
with (
|
|
patch("src.api.routes.git._merge_routes.get_git_service", return_value=mock_gs),
|
|
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
|
|
):
|
|
client = _make_client()
|
|
resp = client.post("/repositories/42/merge/continue", json={"message": "x"})
|
|
assert resp.status_code == 500
|
|
# #endregion Test.Api.GitMergeRoutes
|