"""
Tests for GitLab tracker CRUD methods.
"""
from unittest.mock import MagicMock, patch, AsyncMock
from unittest import IsolatedAsyncioTestCase
from preloop.sync.trackers.gitlab import GitLabTracker
from preloop.sync.exceptions import TrackerResponseError
class TestGitLabTrackerCRUD(IsolatedAsyncioTestCase):
"""Test GitLab tracker's CRUD methods."""
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_create_issue_success(self, mock_gitlab_constructor):
"""Test successful issue creation."""
# Arrange
from preloop.schemas.tracker_models import IssueCreate
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
mock_project = MagicMock()
mock_project.path_with_namespace = "testgroup/testproject"
mock_issue = MagicMock()
mock_issue.id = 12345
mock_issue.iid = 1
mock_issue.title = "New Issue"
mock_issue.description = "Issue description"
mock_issue.state = "opened"
mock_issue.created_at = "2023-01-01T10:00:00.000Z"
mock_issue.updated_at = "2023-01-01T10:00:00.000Z"
mock_issue.web_url = "https://gitlab.com/testgroup/testproject/-/issues/1"
mock_issue.labels = ["bug"]
mock_issue.attributes = {
"id": 12345,
"iid": 1,
"title": "New Issue",
"description": "Issue description",
"state": "opened",
"created_at": "2023-01-01T10:00:00.000Z",
"updated_at": "2023-01-01T10:00:00.000Z",
"web_url": "https://gitlab.com/testgroup/testproject/-/issues/1",
"labels": ["bug"],
"assignee": None,
"assignees": [],
"author": {
"id": 123,
"name": "Author",
"avatar_url": "https://example.com/avatar.png",
},
"_links": {"self": "https://gitlab.com/api/v4/projects/proj-1/issues/1"},
}
mock_project.issues.create.return_value = mock_issue
connection_details = {"project_id": "proj-1", "url": "https://gitlab.com"}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
tracker._make_request = AsyncMock()
tracker._make_request.side_effect = [mock_project, mock_issue]
issue_create = IssueCreate(
title="New Issue",
description="Issue description",
labels=["bug"],
)
# Act
result = await tracker.create_issue("proj-1", issue_create)
# Assert
self.assertEqual(result.id, "12345")
self.assertEqual(result.title, "New Issue")
self.assertEqual(result.labels, ["bug"])
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_update_issue_success(self, mock_gitlab_constructor):
"""Test successful issue update."""
# Arrange
from preloop.schemas.tracker_models import IssueUpdate
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
mock_project = MagicMock()
mock_project.path_with_namespace = "testgroup/testproject"
mock_issue = MagicMock()
mock_issue.id = 12345
mock_issue.iid = 1
mock_issue.title = "Updated Issue"
mock_issue.description = "Updated description"
mock_issue.state = "closed"
mock_issue.created_at = "2023-01-01T10:00:00.000Z"
mock_issue.updated_at = "2023-01-02T10:00:00.000Z"
mock_issue.web_url = "https://gitlab.com/testgroup/testproject/-/issues/1"
mock_issue.attributes = {
"id": 12345,
"iid": 1,
"title": "Updated Issue",
"description": "Updated description",
"state": "closed",
"created_at": "2023-01-01T10:00:00.000Z",
"updated_at": "2023-01-02T10:00:00.000Z",
"web_url": "https://gitlab.com/testgroup/testproject/-/issues/1",
"labels": [],
"assignee": None,
"assignees": [],
"author": {
"id": 123,
"name": "Author",
"avatar_url": "https://example.com/avatar.png",
},
"_links": {"self": "https://gitlab.com/api/v4/projects/proj-1/issues/1"},
}
mock_issue.save = MagicMock()
connection_details = {"project_id": "proj-1", "url": "https://gitlab.com"}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
tracker._make_request = AsyncMock()
tracker._make_request.side_effect = [mock_project, mock_issue, mock_issue]
issue_update = IssueUpdate(
title="Updated Issue",
description="Updated description",
status="closed",
)
# Act
result = await tracker.update_issue("1", issue_update)
# Assert
self.assertEqual(result.title, "Updated Issue")
self.assertEqual(result.status.id, "closed")
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_add_comment_success(self, mock_gitlab_constructor):
"""Test successful comment addition."""
# Arrange
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
mock_project = MagicMock()
mock_project.path_with_namespace = "testgroup/testproject"
mock_issue = MagicMock()
mock_issue.id = 1
mock_issue.web_url = "https://gitlab.com/testgroup/testproject/-/issues/1"
mock_note = MagicMock()
mock_note.id = 1001
mock_note.body = "Test comment"
mock_note.created_at = "2023-01-01T12:00:00.000Z"
mock_note.updated_at = "2023-01-01T12:00:00.000Z"
mock_note.author = {
"id": 123,
"name": "testuser",
"avatar_url": "https://avatar.png",
}
mock_issue.notes.create.return_value = mock_note
connection_details = {"project_id": "proj-1", "url": "https://gitlab.com"}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
tracker._make_request = AsyncMock()
# Return project, issue, then note
tracker._make_request.side_effect = [mock_project, mock_issue, mock_note]
# Act
result = await tracker.add_comment("1", "Test comment")
# Assert
self.assertEqual(result.id, "1001")
self.assertEqual(result.body, "Test comment")
self.assertEqual(result.author.name, "testuser")
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_add_relation_success(self, mock_gitlab_constructor):
"""Test successful issue relation creation."""
# Arrange
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
mock_project = MagicMock()
mock_project.path_with_namespace = "testgroup/testproject"
mock_issue = MagicMock()
mock_link = MagicMock()
mock_issue.links.create.return_value = mock_link
connection_details = {"project_id": "proj-1", "url": "https://gitlab.com"}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
tracker._make_request = AsyncMock()
tracker._make_request.side_effect = [mock_project, mock_issue, mock_link]
# Act
result = await tracker.add_relation("1", "2", "relates_to")
# Assert
self.assertTrue(result)
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_search_issues_success(self, mock_gitlab_constructor):
"""Test successful issue search."""
# Arrange
from preloop.schemas.tracker_models import IssueFilter
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
mock_project = MagicMock()
mock_project.path_with_namespace = "testgroup/testproject"
mock_issue = MagicMock()
mock_issue.id = 12345
mock_issue.iid = 1
mock_issue.title = "Bug Issue"
mock_issue.description = "Bug description"
mock_issue.state = "opened"
mock_issue.created_at = "2023-01-01T10:00:00.000Z"
mock_issue.updated_at = "2023-01-02T11:00:00.000Z"
mock_issue.web_url = "https://gitlab.com/testgroup/testproject/-/issues/1"
mock_issue.labels = ["critical"]
mock_issue.attributes = {
"id": 12345,
"iid": 1,
"title": "Bug Issue",
"description": "Bug description",
"state": "opened",
"created_at": "2023-01-01T10:00:00.000Z",
"updated_at": "2023-01-02T11:00:00.000Z",
"web_url": "https://gitlab.com/testgroup/testproject/-/issues/1",
"labels": ["critical"],
"assignee": None,
"assignees": [],
"author": {
"id": 123,
"name": "Author",
"avatar_url": "https://example.com/avatar.png",
},
"_links": {"self": "https://gitlab.com/api/v4/projects/proj-1/issues/1"},
}
mock_project.issues.list.return_value = [mock_issue]
connection_details = {"project_id": "proj-1", "url": "https://gitlab.com"}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
tracker._make_request = AsyncMock()
tracker._make_request.side_effect = [mock_project, [mock_issue]]
filter_params = IssueFilter(query="bug", status=["opened"], labels=["critical"])
# Act
issues, total = await tracker.search_issues("proj-1", filter_params, limit=10)
# Assert
self.assertEqual(total, 1)
self.assertGreater(len(issues), 0)
# search_issues doesn't pass project to parser, so it uses project_id from connection_details
self.assertEqual(issues[0].key, "proj-1#1")
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_get_project_metadata_success(self, mock_gitlab_constructor):
"""Test successful project metadata retrieval."""
# Arrange
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
mock_project = MagicMock()
mock_project.id = 12345
mock_project.name = "testproject"
mock_project.path_with_namespace = "testgroup/testproject"
mock_project.description = "Test project description"
mock_project.web_url = "https://gitlab.com/testgroup/testproject"
mock_project.attributes = {
"id": 12345,
"name": "testproject",
"path_with_namespace": "testgroup/testproject",
"description": "Test project description",
"web_url": "https://gitlab.com/testgroup/testproject",
}
connection_details = {"project_id": "proj-1", "url": "https://gitlab.com"}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
tracker._make_request = AsyncMock(return_value=mock_project)
# Act
result = await tracker.get_project_metadata("proj-1")
# Assert
self.assertEqual(result.key, "testgroup/testproject")
self.assertEqual(result.name, "testproject")
self.assertEqual(result.description, "Test project description")
self.assertGreater(len(result.statuses), 0)
@patch("preloop.sync.trackers.gitlab.gitlab.Gitlab")
async def test_create_issue_project_not_found(self, mock_gitlab_constructor):
"""Test error when project doesn't exist."""
# Arrange
from preloop.schemas.tracker_models import IssueCreate
mock_gl_instance = MagicMock()
mock_gitlab_constructor.return_value = mock_gl_instance
connection_details = {
"project_id": "nonexistent-project",
"url": "https://gitlab.com",
}
tracker = GitLabTracker("tracker-1", "api-key", connection_details)
# Mock the _make_request to raise an error when trying to get the project
tracker._make_request = AsyncMock(
side_effect=TrackerResponseError("Project not found")
)
issue_create = IssueCreate(title="New Issue")
# Act & Assert
with self.assertRaises(TrackerResponseError) as context:
await tracker.create_issue("nonexistent-project", issue_create)
self.assertIn("Project not found", str(context.exception))