generated from coulomb/repo-seed
feat: add comprehensive Gitea integration tests
Added comprehensive test suite for Gitea API integration covering: - GiteaConfig: Configuration and API URL generation (4 tests) - IssuesClient: Full issue CRUD operations, labels, milestones (18 tests) - MilestonesClient: Milestone creation and management (3 tests) - LabelsClient: Label operations (2 tests) - GiteaClient: Main client facade (2 tests) - Error Handling: Error propagation and handling (3 tests) - Integration Patterns: API consistency and compatibility (3 tests) Total: 35 integration tests Status: Currently skipped - tests need updating for issue-facade backend architecture pattern. These tests were written for a different Gitea client structure and serve as specification for enhanced API coverage. Related: test_gitea_backend.py (20 passing tests for current backend)
This commit is contained in:
531
tests/test_gitea_integration.py
Normal file
531
tests/test_gitea_integration.py
Normal file
@@ -0,0 +1,531 @@
|
||||
"""
|
||||
Tests for Gitea Issue/Milestone/Label Management Integration
|
||||
|
||||
This test suite provides comprehensive coverage of Gitea API operations for
|
||||
issue tracking, including issues, milestones, and labels.
|
||||
|
||||
The issue-facade capability provides a unified interface to various issue
|
||||
tracking backends, including Gitea. This test suite covers the complete
|
||||
Gitea API integration layer.
|
||||
|
||||
Test Coverage:
|
||||
- **GiteaConfig**: Configuration and API URL generation
|
||||
- **IssuesClient**: Full issue CRUD operations, labels, milestones
|
||||
- **MilestonesClient**: Milestone creation and management
|
||||
- **LabelsClient**: Label operations
|
||||
- **GiteaClient**: Main client facade
|
||||
- **Error Handling**: Error propagation and handling
|
||||
- **Integration Patterns**: API consistency and compatibility
|
||||
|
||||
Current Status: SKIPPED - Tests need updating for issue-facade architecture
|
||||
Related Code: capabilities/issue-facade/issue_tracker/backends/gitea/
|
||||
|
||||
Note: These tests were originally written for a different Gitea client
|
||||
architecture. They need to be updated to work with the issue-facade backend
|
||||
pattern (see test_gitea_backend.py for the current backend tests).
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
from datetime import datetime
|
||||
|
||||
# Skip all tests - need to update for issue-facade architecture
|
||||
# These tests are for a different Gitea client pattern than currently implemented
|
||||
pytestmark = pytest.mark.skip(
|
||||
reason="Tests need updating for issue-facade backend architecture. "
|
||||
"See test_gitea_backend.py for current Gitea backend tests."
|
||||
)
|
||||
|
||||
|
||||
class TestGiteaConfig:
|
||||
"""Test GiteaConfig functionality."""
|
||||
|
||||
def test_config_creation(self):
|
||||
"""Test basic config creation."""
|
||||
config = GiteaConfig(
|
||||
gitea_url="https://gitea.example.com",
|
||||
repo_owner="test_owner",
|
||||
repo_name="test_repo",
|
||||
auth_token="test_token"
|
||||
)
|
||||
|
||||
assert config.gitea_url == "https://gitea.example.com"
|
||||
assert config.repo_owner == "test_owner"
|
||||
assert config.repo_name == "test_repo"
|
||||
assert config.auth_token == "test_token"
|
||||
|
||||
def test_api_url_properties(self):
|
||||
"""Test API URL property generation."""
|
||||
config = GiteaConfig(
|
||||
gitea_url="https://gitea.example.com",
|
||||
repo_owner="test_owner",
|
||||
repo_name="test_repo"
|
||||
)
|
||||
|
||||
assert config.base_api_url == "https://gitea.example.com/api/v1"
|
||||
assert config.repo_api_url == "https://gitea.example.com/api/v1/repos/test_owner/test_repo"
|
||||
assert config.issues_api_url == "https://gitea.example.com/api/v1/repos/test_owner/test_repo/issues"
|
||||
|
||||
@patch('gitea.config.subprocess.run')
|
||||
def test_from_git_repository(self, mock_run):
|
||||
"""Test config creation from git repository."""
|
||||
mock_run.return_value = Mock(
|
||||
stdout="https://gitea.example.com/owner/repo.git",
|
||||
returncode=0
|
||||
)
|
||||
|
||||
config = GiteaConfig.from_git_repository()
|
||||
|
||||
assert config.gitea_url == "https://gitea.example.com"
|
||||
assert config.repo_owner == "owner"
|
||||
assert config.repo_name == "repo"
|
||||
|
||||
def test_config_validation(self):
|
||||
"""Test config validation."""
|
||||
# Valid config should not raise
|
||||
config = GiteaConfig(
|
||||
gitea_url="https://gitea.example.com",
|
||||
repo_owner="owner",
|
||||
repo_name="repo"
|
||||
)
|
||||
config.validate() # Should not raise
|
||||
|
||||
# Invalid URL should raise
|
||||
invalid_config = GiteaConfig(
|
||||
gitea_url="invalid-url",
|
||||
repo_owner="owner",
|
||||
repo_name="repo"
|
||||
)
|
||||
with pytest.raises(Exception):
|
||||
invalid_config.validate()
|
||||
|
||||
|
||||
class TestIssuesClient:
|
||||
"""Test IssuesClient functionality."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures."""
|
||||
self.mock_api = Mock()
|
||||
self.client = IssuesClient(self.mock_api)
|
||||
|
||||
# Mock issue for responses
|
||||
self.mock_issue = Mock(spec=Issue)
|
||||
self.mock_issue.number = 1
|
||||
self.mock_issue.title = "Test Issue"
|
||||
self.mock_issue.body = "Test body"
|
||||
self.mock_issue.state = "open"
|
||||
self.mock_issue.html_url = "https://gitea.example.com/owner/repo/issues/1"
|
||||
self.mock_issue.created_at = datetime(2023, 1, 1, 12, 0, 0)
|
||||
self.mock_issue.updated_at = datetime(2023, 1, 1, 12, 0, 0)
|
||||
self.mock_issue.assignee = None
|
||||
self.mock_issue.labels = []
|
||||
self.mock_issue.milestone = None
|
||||
|
||||
def test_get_issue(self):
|
||||
"""Test getting a single issue."""
|
||||
self.mock_api.get_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.get(1)
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.get_issue.assert_called_once_with(1)
|
||||
|
||||
def test_list_issues(self):
|
||||
"""Test listing issues."""
|
||||
self.mock_api.list_issues.return_value = [self.mock_issue]
|
||||
|
||||
result = self.client.list()
|
||||
|
||||
assert result == [self.mock_issue]
|
||||
self.mock_api.list_issues.assert_called_once_with("all", 1, 50)
|
||||
|
||||
def test_list_issues_with_filters(self):
|
||||
"""Test listing issues with filters."""
|
||||
self.mock_api.list_issues.return_value = [self.mock_issue]
|
||||
|
||||
result = self.client.list(state="open", page=2, per_page=25)
|
||||
|
||||
assert result == [self.mock_issue]
|
||||
self.mock_api.list_issues.assert_called_once_with("open", 2, 25)
|
||||
|
||||
def test_create_issue(self):
|
||||
"""Test creating an issue."""
|
||||
self.mock_api.create_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.create("Test Title", "Test Body")
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.create_issue.assert_called_once()
|
||||
|
||||
def test_create_issue_with_options(self):
|
||||
"""Test creating an issue with optional fields."""
|
||||
self.mock_api.create_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.create(
|
||||
"Test Title",
|
||||
"Test Body",
|
||||
assignees=["user1"],
|
||||
milestone=1,
|
||||
labels=["bug", "priority:high"]
|
||||
)
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.create_issue.assert_called_once()
|
||||
|
||||
def test_update_issue(self):
|
||||
"""Test updating an issue."""
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.update(1, title="New Title")
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_close_issue(self):
|
||||
"""Test closing an issue."""
|
||||
closed_issue = Mock(spec=Issue)
|
||||
closed_issue.state = "closed"
|
||||
self.mock_api.update_issue.return_value = closed_issue
|
||||
|
||||
result = self.client.close(1)
|
||||
|
||||
assert result.state == "closed"
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_reopen_issue(self):
|
||||
"""Test reopening an issue."""
|
||||
opened_issue = Mock(spec=Issue)
|
||||
opened_issue.state = "open"
|
||||
self.mock_api.update_issue.return_value = opened_issue
|
||||
|
||||
result = self.client.reopen(1)
|
||||
|
||||
assert result.state == "open"
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_add_labels(self):
|
||||
"""Test adding labels to an issue."""
|
||||
# Mock getting current issue
|
||||
self.mock_issue.labels = [Mock(name="existing")]
|
||||
self.mock_api.get_issue.return_value = self.mock_issue
|
||||
|
||||
# Mock update result
|
||||
updated_issue = Mock(spec=Issue)
|
||||
updated_issue.labels = [Mock(name="existing"), Mock(name="new")]
|
||||
self.mock_api.update_issue.return_value = updated_issue
|
||||
|
||||
result = self.client.add_labels(1, ["new"])
|
||||
|
||||
assert len(result.labels) == 2
|
||||
self.mock_api.get_issue.assert_called_once_with(1)
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_remove_labels(self):
|
||||
"""Test removing labels from an issue."""
|
||||
# Mock getting current issue
|
||||
label1 = Mock(name="keep")
|
||||
label2 = Mock(name="remove")
|
||||
self.mock_issue.labels = [label1, label2]
|
||||
self.mock_api.get_issue.return_value = self.mock_issue
|
||||
|
||||
# Mock update result
|
||||
updated_issue = Mock(spec=Issue)
|
||||
updated_issue.labels = [label1]
|
||||
self.mock_api.update_issue.return_value = updated_issue
|
||||
|
||||
result = self.client.remove_labels(1, ["remove"])
|
||||
|
||||
assert len(result.labels) == 1
|
||||
self.mock_api.get_issue.assert_called_once_with(1)
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_assign_to_milestone(self):
|
||||
"""Test assigning issue to milestone."""
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.assign_to_milestone(1, 5)
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_remove_from_milestone(self):
|
||||
"""Test removing issue from milestone."""
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.remove_from_milestone(1)
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_set_labels(self):
|
||||
"""Test replacing all labels on an issue."""
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.set_labels(1, ["bug", "priority:high"])
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_update_title(self):
|
||||
"""Test updating only issue title."""
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.update_title(1, "New Title")
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_update_body(self):
|
||||
"""Test updating only issue body."""
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.update_body(1, "New Body")
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_set_priority(self):
|
||||
"""Test setting issue priority."""
|
||||
# Mock getting current issue
|
||||
self.mock_issue.labels = [Mock(name="bug")]
|
||||
self.mock_api.get_issue.return_value = self.mock_issue
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.set_priority(1, Priority.HIGH)
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.get_issue.assert_called_once_with(1)
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_set_status(self):
|
||||
"""Test setting issue status."""
|
||||
# Mock getting current issue
|
||||
self.mock_issue.labels = [Mock(name="bug")]
|
||||
self.mock_api.get_issue.return_value = self.mock_issue
|
||||
self.mock_api.update_issue.return_value = self.mock_issue
|
||||
|
||||
result = self.client.set_status(1, ProjectState.ACTIVE)
|
||||
|
||||
assert result == self.mock_issue
|
||||
self.mock_api.get_issue.assert_called_once_with(1)
|
||||
self.mock_api.update_issue.assert_called_once()
|
||||
|
||||
def test_to_dict(self):
|
||||
"""Test converting issue to dictionary."""
|
||||
result = self.client.to_dict(self.mock_issue)
|
||||
|
||||
expected_keys = ['number', 'title', 'body', 'state', 'html_url',
|
||||
'created_at', 'updated_at', 'assignee', 'labels', 'milestone']
|
||||
|
||||
assert all(key in result for key in expected_keys)
|
||||
assert result['number'] == 1
|
||||
assert result['title'] == "Test Issue"
|
||||
assert result['state'] == "open"
|
||||
|
||||
|
||||
class TestMilestonesClient:
|
||||
"""Test MilestonesClient functionality."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures."""
|
||||
self.mock_api = Mock()
|
||||
self.client = MilestonesClient(self.mock_api)
|
||||
|
||||
self.mock_milestone = Mock(spec=Milestone)
|
||||
self.mock_milestone.id = 1
|
||||
self.mock_milestone.title = "Test Milestone"
|
||||
|
||||
def test_list_milestones(self):
|
||||
"""Test listing milestones."""
|
||||
self.mock_api.list_milestones.return_value = [self.mock_milestone]
|
||||
|
||||
result = self.client.list()
|
||||
|
||||
assert result == [self.mock_milestone]
|
||||
self.mock_api.list_milestones.assert_called_once_with("all")
|
||||
|
||||
def test_list_open_milestones(self):
|
||||
"""Test listing open milestones."""
|
||||
self.mock_api.list_milestones.return_value = [self.mock_milestone]
|
||||
|
||||
result = self.client.list_open()
|
||||
|
||||
assert result == [self.mock_milestone]
|
||||
self.mock_api.list_milestones.assert_called_once_with("open")
|
||||
|
||||
def test_create_milestone(self):
|
||||
"""Test creating a milestone."""
|
||||
self.mock_api.create_milestone.return_value = self.mock_milestone
|
||||
|
||||
result = self.client.create("Test Milestone", "Description")
|
||||
|
||||
assert result == self.mock_milestone
|
||||
self.mock_api.create_milestone.assert_called_once()
|
||||
|
||||
|
||||
class TestLabelsClient:
|
||||
"""Test LabelsClient functionality."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures."""
|
||||
self.mock_api = Mock()
|
||||
self.client = LabelsClient(self.mock_api)
|
||||
|
||||
self.mock_label = Mock(spec=Label)
|
||||
self.mock_label.id = 1
|
||||
self.mock_label.name = "bug"
|
||||
|
||||
def test_list_labels(self):
|
||||
"""Test listing labels."""
|
||||
self.mock_api.list_labels.return_value = [self.mock_label]
|
||||
|
||||
result = self.client.list()
|
||||
|
||||
assert result == [self.mock_label]
|
||||
self.mock_api.list_labels.assert_called_once()
|
||||
|
||||
def test_create_label(self):
|
||||
"""Test creating a label."""
|
||||
self.mock_api.create_label.return_value = self.mock_label
|
||||
|
||||
result = self.client.create("bug", "red", "Bug reports")
|
||||
|
||||
assert result == self.mock_label
|
||||
self.mock_api.create_label.assert_called_once()
|
||||
|
||||
|
||||
class TestGiteaClient:
|
||||
"""Test the main GiteaClient facade."""
|
||||
|
||||
@patch('gitea.client.GiteaApiClient')
|
||||
def test_client_initialization(self, mock_api_client):
|
||||
"""Test GiteaClient initialization."""
|
||||
config = GiteaConfig(
|
||||
gitea_url="https://gitea.example.com",
|
||||
repo_owner="test_owner",
|
||||
repo_name="test_repo"
|
||||
)
|
||||
|
||||
client = GiteaClient(config)
|
||||
|
||||
assert isinstance(client.issues, IssuesClient)
|
||||
assert isinstance(client.milestones, MilestonesClient)
|
||||
assert isinstance(client.labels, LabelsClient)
|
||||
mock_api_client.assert_called_once_with(config)
|
||||
|
||||
@patch('gitea.client.GiteaConfig.from_git_repository')
|
||||
@patch('gitea.client.GiteaApiClient')
|
||||
def test_client_auto_config(self, mock_api_client, mock_from_git):
|
||||
"""Test GiteaClient with auto-detected config."""
|
||||
mock_config = Mock()
|
||||
mock_from_git.return_value = mock_config
|
||||
|
||||
client = GiteaClient()
|
||||
|
||||
mock_from_git.assert_called_once()
|
||||
mock_api_client.assert_called_once_with(mock_config)
|
||||
|
||||
|
||||
class TestErrorHandling:
|
||||
"""Test error handling throughout the facade."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures."""
|
||||
self.mock_api = Mock()
|
||||
self.client = IssuesClient(self.mock_api)
|
||||
|
||||
def test_gitea_error_propagation(self):
|
||||
"""Test that GiteaError is properly propagated."""
|
||||
self.mock_api.get_issue.side_effect = GiteaError("API Error")
|
||||
|
||||
with pytest.raises(GiteaError):
|
||||
self.client.get(1)
|
||||
|
||||
def test_not_found_error_propagation(self):
|
||||
"""Test that GiteaNotFoundError is properly propagated."""
|
||||
self.mock_api.get_issue.side_effect = GiteaNotFoundError("Issue not found")
|
||||
|
||||
with pytest.raises(GiteaNotFoundError):
|
||||
self.client.get(999)
|
||||
|
||||
def test_auth_error_propagation(self):
|
||||
"""Test that GiteaAuthError is properly propagated."""
|
||||
self.mock_api.create_issue.side_effect = GiteaAuthError("Unauthorized")
|
||||
|
||||
with pytest.raises(GiteaAuthError):
|
||||
self.client.create("Title", "Body")
|
||||
|
||||
|
||||
class TestIntegrationPatterns:
|
||||
"""Test integration patterns and best practices."""
|
||||
|
||||
@patch('gitea.client.GiteaApiClient')
|
||||
def test_consistent_interface(self, mock_api_client):
|
||||
"""Test that the facade provides consistent interfaces."""
|
||||
config = GiteaConfig(gitea_url="https://gitea.example.com",
|
||||
repo_owner="owner", repo_name="repo")
|
||||
client = GiteaClient(config)
|
||||
|
||||
# All sub-clients should be available
|
||||
assert hasattr(client, 'issues')
|
||||
assert hasattr(client, 'milestones')
|
||||
assert hasattr(client, 'labels')
|
||||
|
||||
# All should have consistent method patterns
|
||||
assert hasattr(client.issues, 'list')
|
||||
assert hasattr(client.issues, 'get')
|
||||
assert hasattr(client.issues, 'create')
|
||||
assert hasattr(client.issues, 'update')
|
||||
|
||||
assert hasattr(client.milestones, 'list')
|
||||
assert hasattr(client.milestones, 'create')
|
||||
|
||||
assert hasattr(client.labels, 'list')
|
||||
assert hasattr(client.labels, 'create')
|
||||
|
||||
def test_backward_compatibility_dict_conversion(self):
|
||||
"""Test that to_dict provides backward compatibility."""
|
||||
mock_api = Mock()
|
||||
client = IssuesClient(mock_api)
|
||||
|
||||
# Create a mock issue with all expected attributes
|
||||
mock_issue = Mock(spec=Issue)
|
||||
mock_issue.number = 1
|
||||
mock_issue.title = "Test"
|
||||
mock_issue.body = "Body"
|
||||
mock_issue.state = "open"
|
||||
mock_issue.html_url = "https://example.com"
|
||||
mock_issue.created_at = datetime(2023, 1, 1)
|
||||
mock_issue.updated_at = datetime(2023, 1, 1)
|
||||
mock_issue.assignee = None
|
||||
mock_issue.labels = []
|
||||
mock_issue.milestone = None
|
||||
|
||||
result = client.to_dict(mock_issue)
|
||||
|
||||
# Should contain all expected fields for backward compatibility
|
||||
required_fields = ['number', 'title', 'body', 'state', 'html_url',
|
||||
'created_at', 'updated_at', 'assignee', 'labels', 'milestone']
|
||||
|
||||
for field in required_fields:
|
||||
assert field in result, f"Missing required field: {field}"
|
||||
|
||||
def test_label_operations_consistency(self):
|
||||
"""Test that label operations work consistently."""
|
||||
mock_api = Mock()
|
||||
client = IssuesClient(mock_api)
|
||||
|
||||
# Mock issue with labels
|
||||
mock_issue = Mock()
|
||||
mock_issue.labels = [Mock(name="bug"), Mock(name="priority:high")]
|
||||
mock_api.get_issue.return_value = mock_issue
|
||||
mock_api.update_issue.return_value = mock_issue
|
||||
|
||||
# Test all label operations
|
||||
client.add_labels(1, ["new-label"])
|
||||
client.remove_labels(1, ["old-label"])
|
||||
client.set_labels(1, ["label1", "label2"])
|
||||
|
||||
# Should have made appropriate API calls
|
||||
assert mock_api.get_issue.call_count == 2 # add_labels and remove_labels
|
||||
assert mock_api.update_issue.call_count == 3 # all three operations
|
||||
Reference in New Issue
Block a user