"""
Tests for GitHub tracker CRUD methods.
"""
import unittest
from unittest.mock import AsyncMock
from unittest import IsolatedAsyncioTestCase
from preloop.sync.trackers.github import GitHubTracker
from preloop.sync.exceptions import TrackerResponseError
class TestGitHubTrackerCRUD(IsolatedAsyncioTestCase):
"""Test GitHub tracker's CRUD methods."""
def setUp(self):
"""Set up test fixtures."""
self.connection_details = {
"owner": "testowner",
"repo": "testrepo",
}
self.tracker = GitHubTracker("tracker-1", "api-token", self.connection_details)
async def test_create_issue_success(self):
"""Test successful issue creation."""
# Arrange
from preloop.schemas.tracker_models import IssueCreate
issue_create = IssueCreate(
title="New Issue",
description="Issue description",
labels=["bug"],
)
created_issue_data = {
"id": 12345,
"number": 1,
"title": "New Issue",
"body": "Issue description",
"state": "open",
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T10:00:00Z",
"html_url": "https://github.com/testowner/testrepo/issues/1",
"url": "https://api.github.com/repos/testowner/testrepo/issues/1",
"labels": [{"name": "bug"}],
"assignees": [],
"user": {
"id": 123,
"login": "testuser",
"avatar_url": "https://avatar.png",
},
}
self.tracker._request = AsyncMock(return_value=created_issue_data)
# Act
result = await self.tracker.create_issue("testowner/testrepo", issue_create)
# Assert
self.assertEqual(result.id, "12345")
self.assertEqual(result.title, "New Issue")
self.assertEqual(result.labels, ["bug"])
# Verify API call
self.tracker._request.assert_called_once_with(
"POST", "/repos/testowner/testrepo/issues", data=unittest.mock.ANY
)
async def test_update_issue_success(self):
"""Test successful issue update."""
# Arrange
from preloop.schemas.tracker_models import IssueUpdate
issue_update = IssueUpdate(
title="Updated Issue",
description="Updated description",
status="closed",
)
updated_issue_data = {
"id": 12345,
"number": 1,
"title": "Updated Issue",
"body": "Updated description",
"state": "closed",
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-02T10:00:00Z",
"html_url": "https://github.com/testowner/testrepo/issues/1",
"url": "https://api.github.com/repos/testowner/testrepo/issues/1",
"labels": [],
"assignees": [],
"user": {
"id": 123,
"login": "testuser",
"avatar_url": "https://avatar.png",
},
}
self.tracker._request = AsyncMock(return_value=updated_issue_data)
# Act
result = await self.tracker.update_issue("1", issue_update)
# Assert
self.assertEqual(result.title, "Updated Issue")
self.assertEqual(result.status.id, "closed")
# Verify API call
self.tracker._request.assert_called_once_with(
"PATCH", "/repos/testowner/testrepo/issues/1", data=unittest.mock.ANY
)
async def test_add_comment_success(self):
"""Test successful comment addition."""
# Arrange
comment_data = {
"id": 1001,
"body": "Test comment",
"created_at": "2023-01-01T12:00:00Z",
"updated_at": "2023-01-01T12:00:00Z",
"html_url": "https://github.com/testowner/testrepo/issues/1#issuecomment-1001",
"user": {
"id": 123,
"login": "testuser",
"avatar_url": "https://avatar.png",
},
}
self.tracker._request = AsyncMock(return_value=comment_data)
# Act
result = await self.tracker.add_comment("1", "Test comment")
# Assert
self.assertEqual(result.id, "1001")
self.assertEqual(result.body, "Test comment")
self.assertEqual(result.author.name, "testuser")
# Verify API call
self.tracker._request.assert_called_once_with(
"POST",
"/repos/testowner/testrepo/issues/1/comments",
data={"body": "Test comment"},
)
async def test_add_relation_success(self):
"""Test successful issue relation creation (via comment)."""
# Arrange
comment_data = {
"id": 1002,
"body": "Relates to #2",
"created_at": "2023-01-01T12:00:00Z",
"updated_at": "2023-01-01T12:00:00Z",
"html_url": "https://github.com/testowner/testrepo/issues/1#issuecomment-1002",
"user": {
"id": 123,
"login": "testuser",
"avatar_url": "https://avatar.png",
},
}
self.tracker._request = AsyncMock(return_value=comment_data)
# Act
result = await self.tracker.add_relation("1", "2", "relates_to")
# Assert
self.assertTrue(result)
# Verify API call
self.tracker._request.assert_called_once()
async def test_search_issues_success(self):
"""Test successful issue search."""
# Arrange
from preloop.schemas.tracker_models import IssueFilter
filter_params = IssueFilter(query="bug", status=["open"], labels=["critical"])
search_response = {
"total_count": 1,
"items": [
{
"id": 12345,
"number": 1,
"title": "Bug Issue",
"body": "Bug description",
"state": "open",
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-02T11:00:00Z",
"html_url": "https://github.com/testowner/testrepo/issues/1",
"url": "https://api.github.com/repos/testowner/testrepo/issues/1",
"labels": [{"name": "critical"}],
"assignees": [],
"user": {
"id": 123,
"login": "testuser",
"avatar_url": "https://avatar.png",
},
}
],
}
self.tracker._request = AsyncMock(return_value=search_response)
# Act
issues, total = await self.tracker.search_issues(
"testowner/testrepo", filter_params, limit=10
)
# Assert
self.assertEqual(total, 1)
self.assertEqual(len(issues), 1)
self.assertEqual(issues[0].key, "testowner/testrepo#1")
# Verify search query was built
self.tracker._request.assert_called_once()
call_args = self.tracker._request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("search/issues", call_args[0][1])
async def test_get_project_metadata_success(self):
"""Test successful project metadata retrieval."""
# Arrange
repo_data = {
"id": 12345,
"name": "testrepo",
"full_name": "testowner/testrepo",
"description": "Test repository",
"html_url": "https://github.com/testowner/testrepo",
"default_branch": "main",
}
self.tracker._make_request = AsyncMock(return_value=repo_data)
# Act
result = await self.tracker.get_project_metadata("testowner/testrepo")
# Assert
self.assertEqual(result.key, "testowner/testrepo")
self.assertEqual(result.name, "testrepo")
self.assertEqual(result.description, "Test repository")
self.assertGreater(len(result.statuses), 0)
# Verify API call
self.tracker._make_request.assert_called_once_with("repos/testowner/testrepo")
async def test_create_issue_missing_connection_details(self):
"""Test error when connection details are missing."""
# Arrange
tracker = GitHubTracker("tracker-1", "api-key", {})
from preloop.schemas.tracker_models import IssueCreate
issue_create = IssueCreate(title="New Issue")
# Act & Assert
with self.assertRaises(TrackerResponseError) as context:
await tracker.create_issue("testowner/testrepo", issue_create)
self.assertIn("Owner/repo not found", str(context.exception))
async def test_add_comment_with_issue_number_format(self):
"""Test comment addition with issue number in various formats."""
# Arrange
comment_data = {
"id": 1001,
"body": "Test comment",
"created_at": "2023-01-01T12:00:00Z",
"updated_at": "2023-01-01T12:00:00Z",
"html_url": "https://github.com/testowner/testrepo/issues/1#issuecomment-1001",
"user": {
"id": 123,
"login": "testuser",
"avatar_url": "https://avatar.png",
},
}
self.tracker._request = AsyncMock(return_value=comment_data)
# Act - test with format "owner/repo#123"
result = await self.tracker.add_comment("testowner/testrepo#1", "Test comment")
# Assert
self.assertEqual(result.id, "1001")
# Verify the issue number was extracted correctly
call_args = self.tracker._request.call_args
self.assertIn("/issues/1/comments", call_args[0][1])