""" Tests for IssueCreator functionality. """ import json import subprocess import pytest from unittest.mock import patch, MagicMock, mock_open from pathlib import Path from tddai.issue_creator import IssueCreator from tddai.exceptions import IssueError from tddai.config import TddaiConfig class TestIssueCreator: """Test suite for IssueCreator class.""" def _get_test_config(self): """Get a valid test configuration.""" return TddaiConfig( workspace_dir=Path(".test_workspace"), gitea_url="http://localhost:3000", repo_owner="test_owner", repo_name="test_repo" ) def test_init_with_auth_token(self): """Test IssueCreator initialization with auth token.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") assert creator.config == config assert creator.auth_token == "test-token" def test_init_with_env_token(self): """Test IssueCreator initialization with environment token.""" config = self._get_test_config() with patch.dict('os.environ', {'GITEA_API_TOKEN': 'env-token'}): creator = IssueCreator(config=config) assert creator.auth_token == 'env-token' def test_init_without_token(self): """Test IssueCreator initialization without token.""" config = self._get_test_config() creator = IssueCreator(config=config) assert creator.auth_token is None @patch('subprocess.run') def test_create_issue_success(self, mock_run): """Test successful issue creation.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") # Mock successful API response mock_response = { "number": 123, "title": "Test Issue", "body": "Test description", "state": "open" } mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps(mock_response), stderr="" ) result = creator.create_issue("Test Issue", "Test description") assert result == mock_response mock_run.assert_called_once() # Check curl command structure call_args = mock_run.call_args[0][0] assert 'curl' in call_args assert '-X' in call_args assert 'POST' in call_args assert 'Authorization: token test-token' in ' '.join(call_args) def test_create_issue_without_auth_token(self): """Test issue creation without authentication token.""" config = self._get_test_config() creator = IssueCreator(config=config) with pytest.raises(IssueError, match="Authentication token required"): creator.create_issue("Test Issue", "Test description") def test_create_issue_empty_title(self): """Test issue creation with empty title.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") with pytest.raises(IssueError, match="Issue title cannot be empty"): creator.create_issue("", "Test description") with pytest.raises(IssueError, match="Issue title cannot be empty"): creator.create_issue(" ", "Test description") @patch('subprocess.run') def test_create_issue_api_error(self, mock_run): """Test issue creation with API error response.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") # Mock API error response mock_response = {"message": "Repository not found"} mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps(mock_response), stderr="" ) with pytest.raises(IssueError, match="Failed to create issue: Repository not found"): creator.create_issue("Test Issue", "Test description") @patch('subprocess.run') def test_create_issue_subprocess_error(self, mock_run): """Test issue creation with subprocess error.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") mock_run.side_effect = subprocess.CalledProcessError(1, 'curl') with pytest.raises(IssueError, match="Failed to create issue"): creator.create_issue("Test Issue", "Test description") @patch('subprocess.run') def test_create_issue_json_error(self, mock_run): """Test issue creation with invalid JSON response.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") mock_run.return_value = MagicMock( returncode=0, stdout="invalid json", stderr="" ) with pytest.raises(IssueError, match="Failed to parse response data"): creator.create_issue("Test Issue", "Test description") @patch('subprocess.run') def test_create_issue_with_optional_fields(self, mock_run): """Test issue creation with optional fields.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") mock_response = {"number": 124} mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps(mock_response), stderr="" ) creator.create_issue( "Test Issue", "Test description", assignees=["user1"], milestone=1, labels=["bug", "high"] ) # Check that JSON data includes optional fields call_args = mock_run.call_args[0][0] json_data_index = call_args.index('-d') + 1 json_data = json.loads(call_args[json_data_index]) assert json_data['assignees'] == ["user1"] assert json_data['milestone'] == 1 assert json_data['labels'] == ["bug", "high"] @patch('subprocess.run') def test_create_enhancement_issue(self, mock_run): """Test creating enhancement issue with structured format.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") mock_response = {"number": 125} mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps(mock_response), stderr="" ) result = creator.create_enhancement_issue( title="Add CLI Support", use_case="User needs command-line interface", technical_requirements="Implement Click framework", acceptance_criteria=["CLI entry point works", "Commands have help text"], dependencies=["Issue #1 - Database"], priority="High" ) # Verify structure of created issue call_args = mock_run.call_args[0][0] json_data_index = call_args.index('-d') + 1 json_data = json.loads(call_args[json_data_index]) assert "UseCase: User needs command-line interface" in json_data['body'] assert "Technical Requirements:" in json_data['body'] assert "- [ ] CLI entry point works" in json_data['body'] assert "- [ ] Commands have help text" in json_data['body'] assert "Dependencies:" in json_data['body'] assert "- Issue #1 - Database" in json_data['body'] assert json_data['labels'] == ["high", "enhancement"] @patch('subprocess.run') def test_create_bug_issue(self, mock_run): """Test creating bug issue with structured format.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") mock_response = {"number": 126} mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps(mock_response), stderr="" ) result = creator.create_bug_issue( title="CLI crashes on empty input", description="The CLI tool crashes when given empty input", steps_to_reproduce=["Run CLI command", "Provide empty input", "Observe crash"], expected_behavior="Should show help message", actual_behavior="Application crashes", environment="Python 3.8, Linux" ) # Verify structure of created bug issue call_args = mock_run.call_args[0][0] json_data_index = call_args.index('-d') + 1 json_data = json.loads(call_args[json_data_index]) assert "The CLI tool crashes when given empty input" in json_data['body'] assert "Steps to Reproduce:" in json_data['body'] assert "1. Run CLI command" in json_data['body'] assert "2. Provide empty input" in json_data['body'] assert "Expected Behavior: Should show help message" in json_data['body'] assert "Actual Behavior: Application crashes" in json_data['body'] assert "Environment: Python 3.8, Linux" in json_data['body'] assert json_data['labels'] == ["bug"] @patch('subprocess.run') @patch('builtins.open', new_callable=mock_open, read_data="Title: Template Issue\nTemplate body content with {variable}") def test_create_from_template(self, mock_file, mock_run): """Test creating issue from template file.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") mock_response = {"number": 127} mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps(mock_response), stderr="" ) result = creator.create_from_template( "template.md", variable="test value" ) # Verify template processing call_args = mock_run.call_args[0][0] json_data_index = call_args.index('-d') + 1 json_data = json.loads(call_args[json_data_index]) assert json_data['title'] == "Template Issue" assert "Template body content with test value" in json_data['body'] def test_create_from_template_file_not_found(self): """Test creating issue from non-existent template.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") with pytest.raises(IssueError, match="Template file not found"): creator.create_from_template("nonexistent.md") @patch('builtins.open', new_callable=mock_open, read_data="") def test_create_from_empty_template(self, mock_file): """Test creating issue from empty template.""" config = self._get_test_config() creator = IssueCreator(config=config, auth_token="test-token") with pytest.raises(IssueError, match="Template file is empty"): creator.create_from_template("empty.md")