Files
ss-tools/backend/tests/services/git/test_git_remote_providers.py
busya 8c10632494 feat(semantic): curator-driven protocol hardening — decision memory + relation repair
- Add @RATIONALE/@REJECTED to 103+ C4/C5 contracts across backend core, services, API routes, and frontend models
- Fix 109 unresolved @RELATION edges (Auth.*, SupersetClient.*, AgentChat.*, ADR cross-refs)
- Add 13 @ingroup tags for DSA/HCA attention grouping
- Repair 29 stale graph edges via index rebuild
- Update .kilo agent prompts and skills for GRACE-Poly v2.6 compliance
- Git integration: merge routes, branch lifecycle, remote providers, UX components
- 0 broken anchor pairs, index rebuilt with 0 parse warnings
2026-07-02 08:53:19 +03:00

292 lines
12 KiB
Python

# #region Test.Git.RemoteProviders [C:3] [TYPE Module] [SEMANTICS test,git,github,gitlab,remote,provider]
# @BRIEF Tests for GitServiceGithubMixin and GitServiceGitlabMixin — repository creation and merge request creation via HTTP.
# @RELATION BINDS_TO -> [GitServiceRemoteMixin]
# @TEST_EDGE: create_github_repo_success -> repo created
# @TEST_EDGE: create_github_repo_enterprise -> uses enterprise API URL
# @TEST_EDGE: create_github_repo_api_error -> HTTPException
# @TEST_EDGE: create_github_repo_network_error -> HTTPException 503
# @TEST_EDGE: create_gitlab_repo_success -> repo created with normalized URLs
# @TEST_EDGE: create_gitlab_repo_api_error -> HTTPException
# @TEST_EDGE: create_gitlab_merge_request_success -> MR created
# @TEST_EDGE: create_gitlab_merge_request_error -> HTTPException
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from fastapi import HTTPException
from src.services.git._remote_providers import GitServiceGithubMixin, GitServiceGitlabMixin
class TestableGitGithub(GitServiceGithubMixin):
"""Concrete test class for GitHub mixin."""
def __init__(self):
self._http_client = AsyncMock()
def _normalize_git_server_url(self, raw_url):
return (raw_url or "").strip().rstrip("/")
class TestableGitGitlab(GitServiceGitlabMixin):
"""Concrete test class for GitLab mixin."""
def __init__(self):
self._http_client = AsyncMock()
def _normalize_git_server_url(self, raw_url):
return (raw_url or "").strip().rstrip("/")
def _parse_remote_repo_identity(self, remote_url):
from urllib.parse import urlparse
normalized = str(remote_url or "").strip()
path = urlparse(normalized).path.strip("/")
if path.endswith(".git"):
path = path[:-4]
parts = [s for s in path.split("/") if s]
owner, repo = parts[0], parts[-1]
namespace = "/".join(parts[:-1])
return {"owner": owner, "repo": repo, "namespace": namespace, "full_name": f"{namespace}/{repo}"}
# ── GitHub ──
class TestCreateGitHubRepository:
"""create_github_repository — GitHub repo creation."""
# #region test_github_repo_success [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_github_repo_success(self):
"""GitHub repo created successfully."""
svc = TestableGitGithub()
resp = MagicMock()
resp.status_code = 201
resp.json.return_value = {"name": "new-repo", "clone_url": "https://github.com/user/new-repo.git"}
svc._http_client.post.return_value = resp
result = await svc.create_github_repository(
"https://github.com", "pat", "new-repo",
private=True, description="test repo",
)
assert result["name"] == "new-repo"
svc._http_client.post.assert_called_once()
call_url = svc._http_client.post.call_args[0][0]
assert "api.github.com" in call_url
# #endregion test_github_repo_success
# #region test_github_repo_enterprise [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_github_repo_enterprise(self):
"""GitHub Enterprise URL → uses enterprise API path."""
svc = TestableGitGithub()
resp = MagicMock()
resp.status_code = 201
resp.json.return_value = {"name": "repo"}
svc._http_client.post.return_value = resp
await svc.create_github_repository(
"https://github.internal.com", "pat", "repo",
)
call_url = svc._http_client.post.call_args[0][0]
assert "/api/v3" in call_url
# #endregion test_github_repo_enterprise
# #region test_github_repo_api_error [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_github_repo_api_error(self):
"""API error → HTTPException with detail."""
svc = TestableGitGithub()
resp = MagicMock()
resp.status_code = 422
resp.text = "Validation Failed"
resp.json.return_value = {"message": "Name already exists"}
svc._http_client.post.return_value = resp
with pytest.raises(HTTPException, match="already exists"):
await svc.create_github_repository("https://github.com", "pat", "existing-repo")
# #endregion test_github_repo_api_error
# #region test_github_repo_api_error_no_json [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_github_repo_api_error_no_json(self):
"""API error without JSON → uses text."""
svc = TestableGitGithub()
resp = MagicMock()
resp.status_code = 403
resp.text = "rate limit exceeded"
resp.json.side_effect = ValueError("no json")
svc._http_client.post.return_value = resp
with pytest.raises(HTTPException, match="rate limit"):
await svc.create_github_repository("https://github.com", "pat", "repo")
# #endregion test_github_repo_api_error_no_json
# #region test_github_repo_network_error [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_github_repo_network_error(self):
"""Network error → HTTPException 503."""
svc = TestableGitGithub()
svc._http_client.post.side_effect = Exception("connection refused")
with pytest.raises(HTTPException) as exc_info:
await svc.create_github_repository("https://github.com", "pat", "repo")
assert exc_info.value.status_code == 503
# #endregion test_github_repo_network_error
# #region test_github_repo_default_params [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_github_repo_default_params(self):
"""Default parameters passed correctly."""
svc = TestableGitGithub()
resp = MagicMock()
resp.status_code = 201
resp.json.return_value = {"name": "repo"}
svc._http_client.post.return_value = resp
await svc.create_github_repository("https://github.com", "pat", "my-repo")
call_json = svc._http_client.post.call_args[1]["json"]
assert call_json["name"] == "my-repo"
assert call_json["private"] is True
assert call_json["auto_init"] is True
assert call_json["default_branch"] == "prod"
# #endregion test_github_repo_default_params
# ── GitLab ──
class TestCreateGitLabRepository:
"""create_gitlab_repository — GitLab project creation."""
# #region test_gitlab_repo_success [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_repo_success(self):
"""GitLab project created with normalized fields."""
svc = TestableGitGitlab()
resp = MagicMock()
resp.status_code = 201
resp.json.return_value = {
"id": 42, "name": "new-project",
"http_url_to_repo": "https://gitlab.com/user/new-project.git",
"web_url": "https://gitlab.com/user/new-project",
"ssh_url_to_repo": "git@gitlab.com:user/new-project.git",
"path_with_namespace": "user/new-project",
}
svc._http_client.post.return_value = resp
result = await svc.create_gitlab_repository(
"https://gitlab.com", "pat", "new-project",
private=True, description="test",
)
assert result["name"] == "new-project"
assert result["clone_url"] is not None
assert result["html_url"] is not None
assert result["ssh_url"] is not None
assert result["full_name"] is not None
# #endregion test_gitlab_repo_success
# #region test_gitlab_repo_api_error [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_repo_api_error(self):
"""API error → HTTPException."""
svc = TestableGitGitlab()
resp = MagicMock()
resp.status_code = 400
resp.text = "Bad Request"
resp.json.return_value = {"message": "Name has already been taken"}
svc._http_client.post.return_value = resp
with pytest.raises(HTTPException, match="already been taken"):
await svc.create_gitlab_repository("https://gitlab.com", "pat", "existing")
# #endregion test_gitlab_repo_api_error
# #region test_gitlab_repo_network_error [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_repo_network_error(self):
"""Network error → HTTPException 503."""
svc = TestableGitGitlab()
svc._http_client.post.side_effect = Exception("timeout")
with pytest.raises(HTTPException) as exc_info:
await svc.create_gitlab_repository("https://gitlab.com", "pat", "repo")
assert exc_info.value.status_code == 503
# #endregion test_gitlab_repo_network_error
# #region test_gitlab_repo_normalized_fields_fallback [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_repo_normalized_fields_fallback(self):
"""Missing clone_url/html_url → uses _to_repo fields."""
svc = TestableGitGitlab()
resp = MagicMock()
resp.status_code = 201
resp.json.return_value = {"name": "project"} # No clone_url etc.
svc._http_client.post.return_value = resp
result = await svc.create_gitlab_repository("https://gitlab.com", "pat", "project")
assert result["clone_url"] is None # No fallback data
assert result["full_name"] == "project"
# #endregion test_gitlab_repo_normalized_fields_fallback
class TestCreateGitLabMergeRequest:
"""create_gitlab_merge_request — GitLab MR creation."""
# #region test_gitlab_mr_success [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_mr_success(self):
"""MR created successfully."""
svc = TestableGitGitlab()
resp = MagicMock()
resp.status_code = 201
resp.json.return_value = {
"iid": 7, "web_url": "https://gitlab.com/org/repo/-/merge_requests/7",
"state": "opened",
}
svc._http_client.post.return_value = resp
result = await svc.create_gitlab_merge_request(
"https://gitlab.com", "pat",
"https://gitlab.com/org/repo.git",
"feature", "prod", "Add feature", description="desc",
)
assert result["id"] == 7
assert result["status"] == "opened"
assert "merge_requests" in result["url"]
# #endregion test_gitlab_mr_success
# #region test_gitlab_mr_api_error [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_mr_api_error(self):
"""API error → HTTPException."""
svc = TestableGitGitlab()
resp = MagicMock()
resp.status_code = 400
resp.text = "Bad Request"
resp.json.return_value = {"message": "Source branch not found"}
svc._http_client.post.return_value = resp
with pytest.raises(HTTPException, match="not found"):
await svc.create_gitlab_merge_request(
"https://gitlab.com", "pat",
"https://gitlab.com/org/repo.git",
"bad-branch", "prod", "Title",
)
# #endregion test_gitlab_mr_api_error
# #region test_gitlab_mr_network_error [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_gitlab_mr_network_error(self):
"""Network error → HTTPException 503."""
svc = TestableGitGitlab()
svc._http_client.post.side_effect = Exception("timeout")
with pytest.raises(HTTPException) as exc_info:
await svc.create_gitlab_merge_request(
"https://gitlab.com", "pat",
"https://gitlab.com/org/repo.git",
"feature", "prod", "Title",
)
assert exc_info.value.status_code == 503
# #endregion test_gitlab_mr_network_error