fix: commit verified semantic repair changes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# [DEF:backend.src.api.routes.__tests__.test_git_api:Module]
|
||||
# @RELATION: VERIFIES -> src.api.routes.git
|
||||
# [DEF:TestGitApi:Module]
|
||||
# @COMPLEXITY: 3
|
||||
# @RELATION: VERIFIES ->[src.api.routes.git]
|
||||
# @PURPOSE: API tests for Git configurations and repository operations.
|
||||
|
||||
import pytest
|
||||
@@ -9,32 +10,52 @@ from fastapi import HTTPException
|
||||
from src.api.routes import git as git_routes
|
||||
from src.models.git import GitServerConfig, GitProvider, GitStatus, GitRepository
|
||||
|
||||
|
||||
class DbMock:
|
||||
def __init__(self, data=None):
|
||||
self._data = data or []
|
||||
self._deleted = []
|
||||
self._added = []
|
||||
self._filtered = None
|
||||
|
||||
def query(self, model):
|
||||
self._model = model
|
||||
self._filtered = None
|
||||
return self
|
||||
|
||||
def filter(self, condition):
|
||||
# Simplistic mocking for tests, assuming equality checks
|
||||
for item in self._data:
|
||||
# We assume condition is an equality expression like GitServerConfig.id == "123"
|
||||
# It's hard to eval the condition exactly in a mock without complex parsing,
|
||||
# so we'll just return items where type matches.
|
||||
pass
|
||||
# Honor simple SQLAlchemy equality expressions used by these route tests.
|
||||
candidates = [
|
||||
item
|
||||
for item in self._data
|
||||
if not hasattr(self, "_model") or isinstance(item, self._model)
|
||||
]
|
||||
try:
|
||||
left_key = getattr(getattr(condition, "left", None), "key", None)
|
||||
right_value = getattr(getattr(condition, "right", None), "value", None)
|
||||
if left_key is not None and right_value is not None:
|
||||
self._filtered = [
|
||||
item
|
||||
for item in candidates
|
||||
if getattr(item, left_key, None) == right_value
|
||||
]
|
||||
else:
|
||||
self._filtered = candidates
|
||||
except Exception:
|
||||
self._filtered = candidates
|
||||
return self
|
||||
|
||||
def first(self):
|
||||
if self._filtered is not None:
|
||||
return self._filtered[0] if self._filtered else None
|
||||
for item in self._data:
|
||||
if hasattr(self, "_model") and isinstance(item, self._model):
|
||||
return item
|
||||
return None
|
||||
|
||||
def all(self):
|
||||
if self._filtered is not None:
|
||||
return list(self._filtered)
|
||||
return self._data
|
||||
|
||||
def add(self, item):
|
||||
@@ -57,254 +78,410 @@ class DbMock:
|
||||
if not hasattr(item, "last_validated"):
|
||||
item.last_validated = "2026-03-08T00:00:00Z"
|
||||
|
||||
|
||||
# [DEF:test_get_git_configs_masks_pat:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_get_git_configs_masks_pat():
|
||||
"""
|
||||
@PRE: Database session `db` is available.
|
||||
@POST: Returns a list of all GitServerConfig objects from the database with PAT masked.
|
||||
"""
|
||||
db = DbMock([GitServerConfig(
|
||||
id="config-1", name="Test Server", provider=GitProvider.GITHUB,
|
||||
url="https://github.com", pat="secret-token",
|
||||
status=GitStatus.CONNECTED, last_validated="2026-03-08T00:00:00Z"
|
||||
)])
|
||||
|
||||
db = DbMock(
|
||||
[
|
||||
GitServerConfig(
|
||||
id="config-1",
|
||||
name="Test Server",
|
||||
provider=GitProvider.GITHUB,
|
||||
url="https://github.com",
|
||||
pat="secret-token",
|
||||
status=GitStatus.CONNECTED,
|
||||
last_validated="2026-03-08T00:00:00Z",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
result = asyncio.run(git_routes.get_git_configs(db=db))
|
||||
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].pat == "********"
|
||||
assert result[0].name == "Test Server"
|
||||
|
||||
|
||||
# [/DEF:test_get_git_configs_masks_pat:Function]
|
||||
|
||||
|
||||
# [DEF:test_create_git_config_persists_config:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_create_git_config_persists_config():
|
||||
"""
|
||||
@PRE: `config` contains valid GitServerConfigCreate data.
|
||||
@POST: A new GitServerConfig record is created in the database.
|
||||
"""
|
||||
from src.api.routes.git_schemas import GitServerConfigCreate
|
||||
|
||||
db = DbMock()
|
||||
config = GitServerConfigCreate(
|
||||
name="New Server", provider=GitProvider.GITLAB,
|
||||
url="https://gitlab.com", pat="new-token",
|
||||
default_branch="master"
|
||||
name="New Server",
|
||||
provider=GitProvider.GITLAB,
|
||||
url="https://gitlab.com",
|
||||
pat="new-token",
|
||||
default_branch="master",
|
||||
)
|
||||
|
||||
|
||||
result = asyncio.run(git_routes.create_git_config(config=config, db=db))
|
||||
|
||||
|
||||
assert len(db._added) == 1
|
||||
assert db._added[0].name == "New Server"
|
||||
assert db._added[0].pat == "new-token"
|
||||
assert result.name == "New Server"
|
||||
assert result.pat == "new-token" # Note: route returns unmasked until serialized by FastAPI usually, but in tests schema might catch it or not.
|
||||
assert (
|
||||
result.pat == "new-token"
|
||||
) # Note: route returns unmasked until serialized by FastAPI usually, but in tests schema might catch it or not.
|
||||
|
||||
|
||||
# [/DEF:test_create_git_config_persists_config:Function]
|
||||
|
||||
from src.api.routes.git_schemas import GitServerConfigUpdate
|
||||
|
||||
|
||||
# [DEF:test_update_git_config_modifies_record:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_update_git_config_modifies_record():
|
||||
"""
|
||||
@PRE: `config_id` corresponds to an existing configuration.
|
||||
@POST: The configuration record is updated in the database, preserving PAT if masked is sent.
|
||||
"""
|
||||
existing_config = GitServerConfig(
|
||||
id="config-1", name="Old Server", provider=GitProvider.GITHUB,
|
||||
url="https://github.com", pat="old-token",
|
||||
status=GitStatus.CONNECTED, last_validated="2026-03-08T00:00:00Z"
|
||||
id="config-1",
|
||||
name="Old Server",
|
||||
provider=GitProvider.GITHUB,
|
||||
url="https://github.com",
|
||||
pat="old-token",
|
||||
status=GitStatus.CONNECTED,
|
||||
last_validated="2026-03-08T00:00:00Z",
|
||||
)
|
||||
|
||||
# The monkeypatched query will return existing_config as it's the only one in the list
|
||||
class SingleConfigDbMock:
|
||||
def query(self, *args): return self
|
||||
def filter(self, *args): return self
|
||||
def first(self): return existing_config
|
||||
def commit(self): pass
|
||||
def refresh(self, config): pass
|
||||
def query(self, *args):
|
||||
return self
|
||||
|
||||
def filter(self, *args):
|
||||
return self
|
||||
|
||||
def first(self):
|
||||
return existing_config
|
||||
|
||||
def commit(self):
|
||||
pass
|
||||
|
||||
def refresh(self, config):
|
||||
pass
|
||||
|
||||
db = SingleConfigDbMock()
|
||||
update_data = GitServerConfigUpdate(name="Updated Server", pat="********")
|
||||
|
||||
result = asyncio.run(git_routes.update_git_config(config_id="config-1", config_update=update_data, db=db))
|
||||
|
||||
|
||||
result = asyncio.run(
|
||||
git_routes.update_git_config(
|
||||
config_id="config-1", config_update=update_data, db=db
|
||||
)
|
||||
)
|
||||
|
||||
assert existing_config.name == "Updated Server"
|
||||
assert existing_config.pat == "old-token" # Ensure PAT is not overwritten with asterisks
|
||||
assert (
|
||||
existing_config.pat == "old-token"
|
||||
) # Ensure PAT is not overwritten with asterisks
|
||||
assert result.pat == "********"
|
||||
|
||||
|
||||
# [/DEF:test_update_git_config_modifies_record:Function]
|
||||
|
||||
|
||||
# [DEF:test_update_git_config_raises_404_if_not_found:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_update_git_config_raises_404_if_not_found():
|
||||
"""
|
||||
@PRE: `config_id` corresponds to a missing configuration.
|
||||
@THROW: HTTPException 404
|
||||
"""
|
||||
db = DbMock([]) # Empty db
|
||||
db = DbMock([]) # Empty db
|
||||
update_data = GitServerConfigUpdate(name="Updated Server", pat="new-token")
|
||||
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
asyncio.run(git_routes.update_git_config(config_id="config-1", config_update=update_data, db=db))
|
||||
|
||||
asyncio.run(
|
||||
git_routes.update_git_config(
|
||||
config_id="config-1", config_update=update_data, db=db
|
||||
)
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 404
|
||||
assert exc_info.value.detail == "Configuration not found"
|
||||
|
||||
|
||||
# [/DEF:test_update_git_config_raises_404_if_not_found:Function]
|
||||
|
||||
|
||||
# [DEF:test_delete_git_config_removes_record:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_delete_git_config_removes_record():
|
||||
"""
|
||||
@PRE: `config_id` corresponds to an existing configuration.
|
||||
@POST: The configuration record is removed from the database.
|
||||
"""
|
||||
existing_config = GitServerConfig(id="config-1")
|
||||
|
||||
class SingleConfigDbMock:
|
||||
def query(self, *args): return self
|
||||
def filter(self, *args): return self
|
||||
def first(self): return existing_config
|
||||
def delete(self, config): self.deleted = config
|
||||
def commit(self): pass
|
||||
def query(self, *args):
|
||||
return self
|
||||
|
||||
def filter(self, *args):
|
||||
return self
|
||||
|
||||
def first(self):
|
||||
return existing_config
|
||||
|
||||
def delete(self, config):
|
||||
self.deleted = config
|
||||
|
||||
def commit(self):
|
||||
pass
|
||||
|
||||
db = SingleConfigDbMock()
|
||||
|
||||
|
||||
result = asyncio.run(git_routes.delete_git_config(config_id="config-1", db=db))
|
||||
|
||||
|
||||
assert db.deleted == existing_config
|
||||
assert result["status"] == "success"
|
||||
|
||||
|
||||
# [/DEF:test_delete_git_config_removes_record:Function]
|
||||
|
||||
|
||||
# [DEF:test_test_git_config_validates_connection_successfully:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_test_git_config_validates_connection_successfully(monkeypatch):
|
||||
"""
|
||||
@PRE: `config` contains provider, url, and pat.
|
||||
@POST: Returns success if the connection is validated via GitService.
|
||||
"""
|
||||
|
||||
class MockGitService:
|
||||
async def test_connection(self, provider, url, pat):
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(git_routes, "git_service", MockGitService())
|
||||
from src.api.routes.git_schemas import GitServerConfigCreate
|
||||
|
||||
|
||||
config = GitServerConfigCreate(
|
||||
name="Test Server", provider=GitProvider.GITHUB,
|
||||
url="https://github.com", pat="test-pat"
|
||||
name="Test Server",
|
||||
provider=GitProvider.GITHUB,
|
||||
url="https://github.com",
|
||||
pat="test-pat",
|
||||
)
|
||||
db = DbMock([])
|
||||
|
||||
|
||||
result = asyncio.run(git_routes.test_git_config(config=config, db=db))
|
||||
|
||||
|
||||
assert result["status"] == "success"
|
||||
|
||||
|
||||
# [/DEF:test_test_git_config_validates_connection_successfully:Function]
|
||||
|
||||
|
||||
# [DEF:test_test_git_config_fails_validation:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_test_git_config_fails_validation(monkeypatch):
|
||||
"""
|
||||
@PRE: `config` contains provider, url, and pat BUT connection fails.
|
||||
@THROW: HTTPException 400
|
||||
"""
|
||||
|
||||
class MockGitService:
|
||||
async def test_connection(self, provider, url, pat):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(git_routes, "git_service", MockGitService())
|
||||
from src.api.routes.git_schemas import GitServerConfigCreate
|
||||
|
||||
|
||||
config = GitServerConfigCreate(
|
||||
name="Test Server", provider=GitProvider.GITHUB,
|
||||
url="https://github.com", pat="bad-pat"
|
||||
name="Test Server",
|
||||
provider=GitProvider.GITHUB,
|
||||
url="https://github.com",
|
||||
pat="bad-pat",
|
||||
)
|
||||
db = DbMock([])
|
||||
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
asyncio.run(git_routes.test_git_config(config=config, db=db))
|
||||
|
||||
|
||||
assert exc_info.value.status_code == 400
|
||||
assert exc_info.value.detail == "Connection failed"
|
||||
|
||||
|
||||
# [/DEF:test_test_git_config_fails_validation:Function]
|
||||
|
||||
|
||||
# [DEF:test_list_gitea_repositories_returns_payload:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_list_gitea_repositories_returns_payload(monkeypatch):
|
||||
"""
|
||||
@PRE: config_id exists and provider is GITEA.
|
||||
@POST: Returns repositories visible to PAT user.
|
||||
"""
|
||||
|
||||
class MockGitService:
|
||||
async def list_gitea_repositories(self, url, pat):
|
||||
return [{"name": "test-repo", "full_name": "owner/test-repo", "private": True}]
|
||||
return [
|
||||
{"name": "test-repo", "full_name": "owner/test-repo", "private": True}
|
||||
]
|
||||
|
||||
monkeypatch.setattr(git_routes, "git_service", MockGitService())
|
||||
existing_config = GitServerConfig(
|
||||
id="config-1", name="Gitea Server", provider=GitProvider.GITEA,
|
||||
url="https://gitea.local", pat="gitea-token"
|
||||
id="config-1",
|
||||
name="Gitea Server",
|
||||
provider=GitProvider.GITEA,
|
||||
url="https://gitea.local",
|
||||
pat="gitea-token",
|
||||
)
|
||||
db = DbMock([existing_config])
|
||||
|
||||
result = asyncio.run(git_routes.list_gitea_repositories(config_id="config-1", db=db))
|
||||
|
||||
|
||||
result = asyncio.run(
|
||||
git_routes.list_gitea_repositories(config_id="config-1", db=db)
|
||||
)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].name == "test-repo"
|
||||
assert result[0].private is True
|
||||
|
||||
|
||||
# [/DEF:test_list_gitea_repositories_returns_payload:Function]
|
||||
|
||||
|
||||
# [DEF:test_list_gitea_repositories_rejects_non_gitea:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_list_gitea_repositories_rejects_non_gitea(monkeypatch):
|
||||
"""
|
||||
@PRE: config_id exists and provider is NOT GITEA.
|
||||
@THROW: HTTPException 400
|
||||
"""
|
||||
existing_config = GitServerConfig(
|
||||
id="config-1", name="GitHub Server", provider=GitProvider.GITHUB,
|
||||
url="https://github.com", pat="token"
|
||||
id="config-1",
|
||||
name="GitHub Server",
|
||||
provider=GitProvider.GITHUB,
|
||||
url="https://github.com",
|
||||
pat="token",
|
||||
)
|
||||
db = DbMock([existing_config])
|
||||
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
asyncio.run(git_routes.list_gitea_repositories(config_id="config-1", db=db))
|
||||
|
||||
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "GITEA provider only" in exc_info.value.detail
|
||||
|
||||
|
||||
# [/DEF:test_list_gitea_repositories_rejects_non_gitea:Function]
|
||||
|
||||
|
||||
# [DEF:test_create_remote_repository_creates_provider_repo:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_create_remote_repository_creates_provider_repo(monkeypatch):
|
||||
"""
|
||||
@PRE: config_id exists and PAT has creation permissions.
|
||||
@POST: Returns normalized remote repository payload.
|
||||
"""
|
||||
|
||||
class MockGitService:
|
||||
async def create_gitlab_repository(self, server_url, pat, name, private, description, auto_init, default_branch):
|
||||
async def create_gitlab_repository(
|
||||
self, server_url, pat, name, private, description, auto_init, default_branch
|
||||
):
|
||||
return {
|
||||
"name": name,
|
||||
"full_name": f"user/{name}",
|
||||
"private": private,
|
||||
"clone_url": f"{server_url}/user/{name}.git"
|
||||
"clone_url": f"{server_url}/user/{name}.git",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(git_routes, "git_service", MockGitService())
|
||||
from src.api.routes.git_schemas import RemoteRepoCreateRequest
|
||||
|
||||
|
||||
existing_config = GitServerConfig(
|
||||
id="config-1", name="GitLab Server", provider=GitProvider.GITLAB,
|
||||
url="https://gitlab.com", pat="token"
|
||||
id="config-1",
|
||||
name="GitLab Server",
|
||||
provider=GitProvider.GITLAB,
|
||||
url="https://gitlab.com",
|
||||
pat="token",
|
||||
)
|
||||
db = DbMock([existing_config])
|
||||
|
||||
|
||||
request = RemoteRepoCreateRequest(name="new-repo", private=True, description="desc")
|
||||
result = asyncio.run(git_routes.create_remote_repository(config_id="config-1", request=request, db=db))
|
||||
|
||||
result = asyncio.run(
|
||||
git_routes.create_remote_repository(
|
||||
config_id="config-1", request=request, db=db
|
||||
)
|
||||
)
|
||||
|
||||
assert result.provider == GitProvider.GITLAB
|
||||
assert result.name == "new-repo"
|
||||
assert result.full_name == "user/new-repo"
|
||||
|
||||
|
||||
# [/DEF:test_create_remote_repository_creates_provider_repo:Function]
|
||||
|
||||
|
||||
# [DEF:test_init_repository_initializes_and_saves_binding:Function]
|
||||
# @RELATION: BINDS_TO ->[TestGitApi]
|
||||
def test_init_repository_initializes_and_saves_binding(monkeypatch):
|
||||
"""
|
||||
@PRE: `dashboard_ref` exists and `init_data` contains valid config_id and remote_url.
|
||||
@POST: Repository is initialized on disk and a GitRepository record is saved in DB.
|
||||
"""
|
||||
from src.api.routes.git_schemas import RepoInitRequest
|
||||
|
||||
|
||||
class MockGitService:
|
||||
def init_repo(self, dashboard_id, remote_url, pat, repo_key, default_branch):
|
||||
self.init_called = True
|
||||
|
||||
def _get_repo_path(self, dashboard_id, repo_key):
|
||||
return f"/tmp/repos/{repo_key}"
|
||||
|
||||
git_service_mock = MockGitService()
|
||||
monkeypatch.setattr(git_routes, "git_service", git_service_mock)
|
||||
monkeypatch.setattr(git_routes, "_resolve_dashboard_id_from_ref", lambda *args, **kwargs: 123)
|
||||
monkeypatch.setattr(git_routes, "_resolve_repo_key_from_ref", lambda *args, **kwargs: "dashboard-123")
|
||||
|
||||
monkeypatch.setattr(
|
||||
git_routes, "_resolve_dashboard_id_from_ref", lambda *args, **kwargs: 123
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
git_routes,
|
||||
"_resolve_repo_key_from_ref",
|
||||
lambda *args, **kwargs: "dashboard-123",
|
||||
)
|
||||
|
||||
existing_config = GitServerConfig(
|
||||
id="config-1", name="GitLab Server", provider=GitProvider.GITLAB,
|
||||
url="https://gitlab.com", pat="token", default_branch="main"
|
||||
id="config-1",
|
||||
name="GitLab Server",
|
||||
provider=GitProvider.GITLAB,
|
||||
url="https://gitlab.com",
|
||||
pat="token",
|
||||
default_branch="main",
|
||||
)
|
||||
db = DbMock([existing_config])
|
||||
|
||||
init_data = RepoInitRequest(config_id="config-1", remote_url="https://git.local/repo.git")
|
||||
|
||||
result = asyncio.run(git_routes.init_repository(dashboard_ref="123", init_data=init_data, config_manager=MagicMock(), db=db))
|
||||
|
||||
|
||||
init_data = RepoInitRequest(
|
||||
config_id="config-1", remote_url="https://git.local/repo.git"
|
||||
)
|
||||
|
||||
result = asyncio.run(
|
||||
git_routes.init_repository(
|
||||
dashboard_ref="123", init_data=init_data, config_manager=MagicMock(), db=db
|
||||
)
|
||||
)
|
||||
|
||||
assert result["status"] == "success"
|
||||
assert git_service_mock.init_called is True
|
||||
assert len(db._added) == 1
|
||||
assert isinstance(db._added[0], GitRepository)
|
||||
assert db._added[0].dashboard_id == 123
|
||||
|
||||
# [/DEF:backend.src.api.routes.__tests__.test_git_api:Module]
|
||||
|
||||
# [/DEF:test_init_repository_initializes_and_saves_binding:Function]
|
||||
# [/DEF:TestGitApi:Module]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user