fix: Resolve failing tests after CLI and error handling refactoring
Fix all test failures introduced by recent architectural changes: • Issue Creator Tests: - Fixed mock API responses to include required fields (created_at, updated_at, html_url) - Added input validation for empty titles back to issue creator - Updated test expectations to match new error handling patterns - Created helper function for complete mock responses • Issue Fetcher Test: - Updated mock target from tddai.issue_fetcher.subprocess to gitea.http_client.subprocess - Fixed test assertions to match new error handling with specific exception chaining - Test now properly validates API error translation • Makefile Integration Test: - Implemented lazy initialization in tddai_cli.py to prevent import-time configuration errors - Replaced eager CLI framework initialization with _get_cli() lazy pattern - Preserves normal CLI functionality while fixing test environment compatibility • Result: All 171 tests now pass (169 passed, 2 skipped) • Maintains backward compatibility of CLI interface • Validates that refactored error handling works correctly 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,10 @@ class IssueCreator:
|
||||
Raises:
|
||||
IssueError: If creation fails
|
||||
"""
|
||||
# Validate input
|
||||
if not title or not title.strip():
|
||||
raise IssueError("Issue title cannot be empty")
|
||||
|
||||
try:
|
||||
issue = self.gitea_client.issues.create(
|
||||
title=title,
|
||||
|
||||
49
tddai_cli.py
49
tddai_cli.py
@@ -15,48 +15,55 @@ sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from cli import CLIFramework
|
||||
|
||||
# Initialize CLI framework
|
||||
cli = CLIFramework()
|
||||
# Lazy initialization of CLI framework
|
||||
_cli_framework = None
|
||||
|
||||
def _get_cli():
|
||||
"""Get CLI framework instance (lazy initialization)."""
|
||||
global _cli_framework
|
||||
if _cli_framework is None:
|
||||
_cli_framework = CLIFramework()
|
||||
return _cli_framework
|
||||
|
||||
|
||||
def workspace_status():
|
||||
"""Show current workspace status."""
|
||||
cli.workspace_status()
|
||||
_get_cli().workspace_status()
|
||||
|
||||
|
||||
def start_issue(issue_number: int):
|
||||
"""Start working on an issue."""
|
||||
cli.start_issue(issue_number)
|
||||
_get_cli().start_issue(issue_number)
|
||||
|
||||
|
||||
def finish_issue():
|
||||
"""Finish current issue workspace."""
|
||||
cli.finish_issue()
|
||||
_get_cli().finish_issue()
|
||||
|
||||
|
||||
def add_test_guidance():
|
||||
"""Show guidance for adding tests."""
|
||||
cli.add_test_guidance()
|
||||
_get_cli().add_test_guidance()
|
||||
|
||||
|
||||
def list_issues():
|
||||
"""List all issues."""
|
||||
cli.list_issues()
|
||||
_get_cli().list_issues()
|
||||
|
||||
|
||||
def list_open_issues():
|
||||
"""List only open issues."""
|
||||
cli.list_open_issues()
|
||||
_get_cli().list_open_issues()
|
||||
|
||||
|
||||
def show_issue(issue_number: int):
|
||||
"""Show detailed issue information."""
|
||||
cli.show_issue(issue_number)
|
||||
_get_cli().show_issue(issue_number)
|
||||
|
||||
|
||||
def create_issue(title: str, body: str, issue_type: str = "enhancement"):
|
||||
"""Create a new issue."""
|
||||
cli.create_issue(title, body, issue_type)
|
||||
_get_cli().create_issue(title, body, issue_type)
|
||||
|
||||
|
||||
def create_enhancement_issue(title: str, use_case: str, technical_requirements: str = "",
|
||||
@@ -73,7 +80,7 @@ def create_enhancement_issue(title: str, use_case: str, technical_requirements:
|
||||
if dependencies:
|
||||
deps_list = [line.strip() for line in dependencies.split('\n') if line.strip()]
|
||||
|
||||
cli.create_enhancement_issue(
|
||||
_get_cli().create_enhancement_issue(
|
||||
title=title,
|
||||
use_case=use_case,
|
||||
technical_requirements=technical_requirements,
|
||||
@@ -85,52 +92,52 @@ def create_enhancement_issue(title: str, use_case: str, technical_requirements:
|
||||
|
||||
def create_from_template(template_file: str, **kwargs):
|
||||
"""Create issue from template file."""
|
||||
cli.create_from_template(template_file, **kwargs)
|
||||
_get_cli().create_from_template(template_file, **kwargs)
|
||||
|
||||
|
||||
def analyze_coverage(issue_number: int):
|
||||
"""Analyze test coverage for a specific issue."""
|
||||
cli.analyze_coverage(issue_number)
|
||||
_get_cli().analyze_coverage(issue_number)
|
||||
|
||||
|
||||
def setup_project_management():
|
||||
"""Setup project management labels and milestones."""
|
||||
cli.setup_project_management()
|
||||
_get_cli().setup_project_management()
|
||||
|
||||
|
||||
def move_issue_to_state(issue_number: int, state: str):
|
||||
"""Move issue to a specific project state."""
|
||||
cli.move_issue_to_state(issue_number, state)
|
||||
_get_cli().move_issue_to_state(issue_number, state)
|
||||
|
||||
|
||||
def set_issue_priority(issue_number: int, priority: str):
|
||||
"""Set issue priority."""
|
||||
cli.set_issue_priority(issue_number, priority)
|
||||
_get_cli().set_issue_priority(issue_number, priority)
|
||||
|
||||
|
||||
def create_milestone(title: str, description: str = ""):
|
||||
"""Create a new milestone (project)."""
|
||||
cli.create_milestone(title, description)
|
||||
_get_cli().create_milestone(title, description)
|
||||
|
||||
|
||||
def list_milestones():
|
||||
"""List all milestones."""
|
||||
cli.list_milestones()
|
||||
_get_cli().list_milestones()
|
||||
|
||||
|
||||
def assign_issue_to_milestone(issue_number: int, milestone_id: int):
|
||||
"""Assign issue to a milestone."""
|
||||
cli.assign_issue_to_milestone(issue_number, milestone_id)
|
||||
_get_cli().assign_issue_to_milestone(issue_number, milestone_id)
|
||||
|
||||
|
||||
def project_overview():
|
||||
"""Show project management overview."""
|
||||
cli.project_overview()
|
||||
_get_cli().project_overview()
|
||||
|
||||
|
||||
def issue_index(format_type="tsv", sort_by="number", filter_state=None, filter_priority=None, include_state=False):
|
||||
"""Output compact index of all issues for Unix processing."""
|
||||
cli.issue_index(
|
||||
_get_cli().issue_index(
|
||||
format_type=format_type,
|
||||
sort_by=sort_by,
|
||||
filter_state=filter_state,
|
||||
|
||||
@@ -108,16 +108,16 @@ class TestWorkspaceCreation:
|
||||
with pytest.raises(WorkspaceError, match="Workspace already active"):
|
||||
manager.create_workspace(second_issue_data)
|
||||
|
||||
@patch('tddai.issue_fetcher.subprocess.run')
|
||||
@patch('gitea.http_client.subprocess.run')
|
||||
def test_issue_fetcher_handles_invalid_issue(self, mock_run, temp_workspace):
|
||||
"""Test error handling for invalid issue numbers."""
|
||||
# Mock curl response for non-existent issue
|
||||
mock_run.return_value.returncode = 0
|
||||
mock_run.return_value.stdout = '{"message": "404 Not Found"}'
|
||||
# Mock curl response for non-existent issue (404 error)
|
||||
from subprocess import CalledProcessError
|
||||
mock_run.side_effect = CalledProcessError(22, 'curl') # HTTP 404 error
|
||||
|
||||
fetcher = IssueFetcher(temp_workspace)
|
||||
|
||||
with pytest.raises(IssueError, match="not found"):
|
||||
with pytest.raises(IssueError, match="API error fetching issue.*HTTP request failed"):
|
||||
fetcher.fetch_issue(999)
|
||||
|
||||
def test_workspace_cleanup(self, temp_workspace, mock_issue_data):
|
||||
|
||||
@@ -25,6 +25,18 @@ class TestIssueCreator:
|
||||
repo_name="test_repo"
|
||||
)
|
||||
|
||||
def _get_complete_mock_response(self, number: int, title: str = "Test Issue", body: str = "Test description"):
|
||||
"""Get a complete mock API response with all required fields."""
|
||||
return {
|
||||
"number": number,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"state": "open",
|
||||
"created_at": "2025-09-26T10:00:00Z",
|
||||
"updated_at": "2025-09-26T10:00:00Z",
|
||||
"html_url": f"http://gitea.example.com/repo/issues/{number}"
|
||||
}
|
||||
|
||||
def test_init_with_auth_token(self):
|
||||
"""Test IssueCreator initialization with auth token."""
|
||||
config = self._get_test_config()
|
||||
@@ -62,7 +74,10 @@ class TestIssueCreator:
|
||||
"number": 123,
|
||||
"title": "Test Issue",
|
||||
"body": "Test description",
|
||||
"state": "open"
|
||||
"state": "open",
|
||||
"created_at": "2025-09-26T10:00:00Z",
|
||||
"updated_at": "2025-09-26T10:00:00Z",
|
||||
"html_url": "http://gitea.example.com/repo/issues/123"
|
||||
}
|
||||
|
||||
mock_run.return_value = MagicMock(
|
||||
@@ -73,7 +88,19 @@ class TestIssueCreator:
|
||||
|
||||
result = creator.create_issue("Test Issue", "Test description")
|
||||
|
||||
assert result == mock_response
|
||||
# Verify the result has the expected structure (transformed by issue creator)
|
||||
expected_result = {
|
||||
'number': 123,
|
||||
'title': "Test Issue",
|
||||
'body': "Test description",
|
||||
'state': "open",
|
||||
'html_url': "http://gitea.example.com/repo/issues/123",
|
||||
'created_at': "2025-09-26T10:00:00", # ISO format from datetime parsing
|
||||
'updated_at': "2025-09-26T10:00:00",
|
||||
'assignee': None,
|
||||
'labels': []
|
||||
}
|
||||
assert result == expected_result
|
||||
mock_run.assert_called_once()
|
||||
|
||||
# Check curl command structure
|
||||
@@ -144,7 +171,7 @@ class TestIssueCreator:
|
||||
stderr=""
|
||||
)
|
||||
|
||||
with pytest.raises(IssueError, match="Failed to parse response data"):
|
||||
with pytest.raises(IssueError, match="Failed to create issue.*parse.*response"):
|
||||
creator.create_issue("Test Issue", "Test description")
|
||||
|
||||
@patch('subprocess.run')
|
||||
@@ -153,7 +180,7 @@ class TestIssueCreator:
|
||||
config = self._get_test_config()
|
||||
creator = IssueCreator(config=config, auth_token="test-token")
|
||||
|
||||
mock_response = {"number": 124}
|
||||
mock_response = self._get_complete_mock_response(124)
|
||||
mock_run.return_value = MagicMock(
|
||||
returncode=0,
|
||||
stdout=json.dumps(mock_response),
|
||||
@@ -183,7 +210,7 @@ class TestIssueCreator:
|
||||
config = self._get_test_config()
|
||||
creator = IssueCreator(config=config, auth_token="test-token")
|
||||
|
||||
mock_response = {"number": 125}
|
||||
mock_response = self._get_complete_mock_response(125)
|
||||
mock_run.return_value = MagicMock(
|
||||
returncode=0,
|
||||
stdout=json.dumps(mock_response),
|
||||
@@ -218,7 +245,7 @@ class TestIssueCreator:
|
||||
config = self._get_test_config()
|
||||
creator = IssueCreator(config=config, auth_token="test-token")
|
||||
|
||||
mock_response = {"number": 126}
|
||||
mock_response = self._get_complete_mock_response(126)
|
||||
mock_run.return_value = MagicMock(
|
||||
returncode=0,
|
||||
stdout=json.dumps(mock_response),
|
||||
@@ -255,7 +282,7 @@ class TestIssueCreator:
|
||||
config = self._get_test_config()
|
||||
creator = IssueCreator(config=config, auth_token="test-token")
|
||||
|
||||
mock_response = {"number": 127}
|
||||
mock_response = self._get_complete_mock_response(127)
|
||||
mock_run.return_value = MagicMock(
|
||||
returncode=0,
|
||||
stdout=json.dumps(mock_response),
|
||||
|
||||
Reference in New Issue
Block a user