Files
markitect-main/tests/test_issue_writer.py
tegwick 72f341279a feat: Implement comprehensive IssueCreator system and create CLI roadmap issues
IssueCreator Implementation:
- Add tddai/issue_creator.py with full POST API functionality for issue creation
- Support multiple creation methods: basic, enhancement, bug, template-based
- Include structured issue formatting with acceptance criteria and dependencies
- Template system with variable substitution for reusable issue creation

Authentication Fix:
- Fix critical authentication bug: use GITEA_API_TOKEN instead of GITEA_TOKEN
- Update both IssueCreator and IssueWriter for consistency
- Update all tests and documentation to reflect correct environment variable

Comprehensive Test Suite:
- Add 15 unit tests for IssueCreator (tests/test_issue_creator.py)
- Add 5 integration tests for full API lifecycle (tests/test_issue_integration.py)
- Create test_environment_variable_detection to prevent future auth issues
- Total 33 tests covering complete issue handling workflow

CLI Integration:
- Enhance tddai_cli.py with 3 new commands: create-issue, create-enhancement, create-from-template
- Add comprehensive argument parsing with optional fields and priority support
- Include user-friendly output with next step guidance
- Update package exports to include IssueCreator

CLI Roadmap Execution:
- Successfully create 8 CLI implementation issues (#12-#19) in Gitea
- Resolve mismatch between NEXT.md roadmap and actual Gitea issues
- Issues prioritized for core USPs: Database Query CLI and AST Query CLI
- Remove local MISSING_ISSUES.md file after successful creation

Framework Maturity:
- Complete CRUD operations for issue management (Create, Read, Update, Delete)
- Robust error handling and API integration patterns
- Full authentication and environment variable management
- Ready for production CLI implementation workflow

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-24 23:36:07 +02:00

203 lines
7.8 KiB
Python

"""
Tests for IssueWriter functionality.
"""
import json
import subprocess
import pytest
from unittest.mock import patch, MagicMock
from tddai.issue_writer import IssueWriter
from tddai.exceptions import IssueError
from tddai.config import TddaiConfig
from pathlib import Path
class TestIssueWriter:
"""Test suite for IssueWriter 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 IssueWriter initialization with auth token."""
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
assert writer.auth_token == "test-token"
def test_init_without_auth_token_uses_env(self):
"""Test IssueWriter uses environment variable when no token provided."""
config = self._get_test_config()
with patch.dict('os.environ', {'GITEA_API_TOKEN': 'env-token'}):
writer = IssueWriter(config=config)
assert writer.auth_token == "env-token"
def test_update_issue_without_auth_token_raises_error(self):
"""Test that updating without auth token raises IssueError."""
config = self._get_test_config()
with patch.dict('os.environ', {}, clear=True):
writer = IssueWriter(config=config, auth_token=None)
with pytest.raises(IssueError, match="Authentication token required"):
writer.update_issue(1, {'title': 'New Title'})
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_success(self, mock_run):
"""Test successful issue update via PATCH."""
# Mock successful response
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({
'number': 1,
'title': 'Updated Title',
'body': 'Updated body',
'state': 'open'
})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
result = writer.update_issue(1, {'title': 'Updated Title'})
assert result['number'] == 1
assert result['title'] == 'Updated Title'
# Verify curl command was called correctly
mock_run.assert_called_once()
call_args = mock_run.call_args[0][0]
assert 'curl' in call_args
assert '-X' in call_args
assert 'PATCH' in call_args
assert 'Authorization: token test-token' in ' '.join(call_args)
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_with_error_response(self, mock_run):
"""Test issue update with API error response."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({'message': 'Issue not found'})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
with pytest.raises(IssueError, match="Failed to update issue #1: Issue not found"):
writer.update_issue(1, {'title': 'New Title'})
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_subprocess_error(self, mock_run):
"""Test issue update with subprocess error."""
mock_run.side_effect = subprocess.CalledProcessError(1, 'curl')
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
with pytest.raises(IssueError, match="Failed to update issue #1"):
writer.update_issue(1, {'title': 'New Title'})
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_json_decode_error(self, mock_run):
"""Test issue update with invalid JSON response."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "invalid json"
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
with pytest.raises(IssueError, match="Failed to parse response data"):
writer.update_issue(1, {'title': 'New Title'})
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_title(self, mock_run):
"""Test updating only issue title."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({'number': 1, 'title': 'New Title'})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
result = writer.update_issue_title(1, 'New Title')
assert result['title'] == 'New Title'
# Verify the correct data was sent
call_args = mock_run.call_args[0][0]
json_data_index = call_args.index('-d') + 1
sent_data = json.loads(call_args[json_data_index])
assert sent_data == {'title': 'New Title'}
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_body(self, mock_run):
"""Test updating only issue body."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({'number': 1, 'body': 'New body content'})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
result = writer.update_issue_body(1, 'New body content')
assert result['body'] == 'New body content'
@patch('tddai.issue_writer.subprocess.run')
def test_update_issue_state_valid(self, mock_run):
"""Test updating issue state with valid state."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({'number': 1, 'state': 'closed'})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
result = writer.update_issue_state(1, 'closed')
assert result['state'] == 'closed'
def test_update_issue_state_invalid(self):
"""Test updating issue state with invalid state."""
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
with pytest.raises(IssueError, match="Invalid state 'invalid'"):
writer.update_issue_state(1, 'invalid')
@patch('tddai.issue_writer.subprocess.run')
def test_close_issue(self, mock_run):
"""Test closing an issue."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({'number': 1, 'state': 'closed'})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
result = writer.close_issue(1)
assert result['state'] == 'closed'
@patch('tddai.issue_writer.subprocess.run')
def test_reopen_issue(self, mock_run):
"""Test reopening an issue."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = json.dumps({'number': 1, 'state': 'open'})
mock_result.stderr = ''
mock_run.return_value = mock_result
config = self._get_test_config()
writer = IssueWriter(config=config, auth_token="test-token")
result = writer.reopen_issue(1)
assert result['state'] == 'open'