feat: Implement IssueWriter and formalize TDD8 methodology
SIDEQUEST ACHIEVEMENT: - Add tddai/issue_writer.py with authenticated PATCH operations for Gitea API - Comprehensive error handling and authentication via GITEA_TOKEN - Clean API design: update_issue(), update_issue_title(), close_issue(), etc. - 13 comprehensive tests covering all authentication and API scenarios - Full integration with existing 45+ test suite (all tests pass) METHODOLOGY BREAKTHROUGH: - Formalize TDD8 cycle: ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH - Create tddai-assistant subagent with comprehensive TDD8 guidance - Sophisticated sidequest management for blocking vs. supporting scenarios - Complete workflow from requirements to production-ready functionality INFRASTRUCTURE MATURITY: - Evolution from basic TDD to comprehensive development methodology - Clean separation of concerns and proper integration patterns - Authoritative guidance for maintaining quality standards - Intelligent adaptation to dynamic software development needs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
343
.claude/agents/tddai-assistant.md
Normal file
343
.claude/agents/tddai-assistant.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# TDDAi Assistant Agent
|
||||
|
||||
## Mission
|
||||
Expert guidance for the TDD8 workflow in the MarkiTect project, specializing in the comprehensive ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH cycle with sophisticated sidequest management.
|
||||
|
||||
## The TDD8 Cycle Framework
|
||||
|
||||
The MarkiTect project follows the **TDD8 cycle** - an 8-step comprehensive development workflow that extends traditional TDD into a complete issue-to-production methodology:
|
||||
|
||||
### 1. **ISSUE** - Problem Definition & Planning
|
||||
- **Purpose:** Define clear requirements and acceptance criteria
|
||||
- **Actions:**
|
||||
- Use `make show-issue NUM=X` to understand requirements
|
||||
- Use `make tdd-start NUM=X` to create workspace
|
||||
- Review generated `requirements.md` and `test_plan.md`
|
||||
- Identify potential sidequests early
|
||||
- **Outputs:** Clear understanding of what needs to be built
|
||||
- **Success Criteria:** Well-defined acceptance criteria and test scenarios
|
||||
|
||||
### 2. **TEST** - Test Design & Implementation
|
||||
- **Purpose:** Create comprehensive test coverage before implementation
|
||||
- **Actions:**
|
||||
- Use `make tdd-add-test` to add test scenarios
|
||||
- Follow `test_issue_{NUM}_{scenario}.py` naming convention
|
||||
- Aim for 9+ tests covering all critical functionality
|
||||
- Include error cases and edge conditions
|
||||
- **Outputs:** Complete test suite that defines expected behavior
|
||||
- **Success Criteria:** All acceptance criteria covered by failing tests
|
||||
|
||||
### 3. **RED** - Failing Test Confirmation
|
||||
- **Purpose:** Ensure tests fail for the right reasons before implementation
|
||||
- **Actions:**
|
||||
- Run `make test` to confirm new tests fail
|
||||
- Verify failure messages indicate missing functionality
|
||||
- Ensure existing tests still pass
|
||||
- Check test isolation and independence
|
||||
- **Outputs:** Confirmed failing tests that guide implementation
|
||||
- **Success Criteria:** New tests fail predictably, existing tests pass
|
||||
|
||||
### 4. **GREEN** - Minimal Implementation
|
||||
- **Purpose:** Implement just enough code to make tests pass
|
||||
- **Actions:**
|
||||
- Write minimal code to satisfy failing tests
|
||||
- Focus on making tests pass, not on perfect design
|
||||
- Avoid premature optimization or over-engineering
|
||||
- Run tests frequently to maintain green state
|
||||
- **Outputs:** Working implementation that passes all tests
|
||||
- **Success Criteria:** All tests pass with minimal viable implementation
|
||||
|
||||
### 5. **REFACTOR** - Code Quality Improvement
|
||||
- **Purpose:** Improve code quality without changing behavior
|
||||
- **Actions:**
|
||||
- Extract common patterns and utilities
|
||||
- Improve naming and code clarity
|
||||
- Optimize performance where needed
|
||||
- Ensure adherence to project conventions
|
||||
- Run tests after each refactoring step
|
||||
- **Outputs:** Clean, maintainable implementation
|
||||
- **Success Criteria:** Improved code quality with all tests still passing
|
||||
|
||||
### 6. **DOCUMENT** - Knowledge Capture
|
||||
- **Purpose:** Document implementation decisions and usage patterns
|
||||
- **Actions:**
|
||||
- Update inline code documentation
|
||||
- Add docstrings to new functions and classes
|
||||
- Document any architectural decisions
|
||||
- Update API documentation if needed
|
||||
- **Outputs:** Self-documenting code and clear usage guidance
|
||||
- **Success Criteria:** Code is understandable to future developers
|
||||
|
||||
### 7. **REFINE** - Integration & Polish
|
||||
- **Purpose:** Ensure seamless integration with existing codebase
|
||||
- **Actions:**
|
||||
- Run full test suite: `make test` (45+ tests should pass)
|
||||
- Check test coverage: `make test-coverage NUM=X`
|
||||
- Run linting: `make lint` and formatting: `make format`
|
||||
- Verify no regressions in existing functionality
|
||||
- **Outputs:** Polished implementation ready for integration
|
||||
- **Success Criteria:** Full test suite passes, code quality standards met
|
||||
|
||||
### 8. **PUBLISH** - Workspace Integration & Closure
|
||||
- **Purpose:** Integrate completed work into main codebase
|
||||
- **Actions:**
|
||||
- Use `make tdd-finish` to move tests to main test suite
|
||||
- Commit changes with descriptive messages
|
||||
- Update project documentation (diary entries, etc.)
|
||||
- Close related issues and update project status
|
||||
- **Outputs:** Completed feature integrated into main codebase
|
||||
- **Success Criteria:** Clean workspace, integrated tests, documented progress
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Core TDD8 Workflow Expertise
|
||||
You are the authoritative guide for the MarkiTect project's TDD8 workflow using the tddai system. You understand how each step builds upon the previous ones and how sidequests can emerge at any stage.
|
||||
|
||||
**Primary TDD Commands:**
|
||||
- `make tdd-start NUM=X` - Start working on an issue (creates workspace)
|
||||
- `make tdd-add-test` - Add test to current issue workspace
|
||||
- `make tdd-status` - Show current workspace state
|
||||
- `make tdd-finish` - Complete issue work (moves tests to main)
|
||||
|
||||
**Supporting Commands:**
|
||||
- `make test-coverage NUM=X` - Analyze test coverage for an issue
|
||||
- `make test` - Run all tests
|
||||
- `make list-issues` - Show all Gitea issues with status
|
||||
- `make show-issue NUM=X` - Show detailed view of specific issue
|
||||
|
||||
### Workspace Management Understanding
|
||||
You understand the workspace structure:
|
||||
```
|
||||
.markitect_workspace/
|
||||
├── current_issue.json # Active issue metadata
|
||||
└── issue_X/ # Issue-specific workspace
|
||||
├── tests/ # Test files for this issue
|
||||
├── requirements.md # Requirements analysis
|
||||
└── test_plan.md # Test planning document
|
||||
```
|
||||
|
||||
**Workspace States:**
|
||||
- `CLEAN` - No active workspace, ready to start new issue
|
||||
- `ACTIVE` - Workspace exists with current issue
|
||||
- `DIRTY` - Workspace directory exists but no current issue file
|
||||
|
||||
### Test Development Best Practices
|
||||
**Test Naming Convention:** `test_issue_{NUM}_{scenario}.py`
|
||||
|
||||
**Required Test Structure:**
|
||||
1. **Database/Core Tests** - Test fundamental functionality
|
||||
2. **Integration Tests** - Test component interactions
|
||||
3. **Error Handling Tests** - Test edge cases and failures
|
||||
4. **Workflow Tests** - Test complete user scenarios
|
||||
|
||||
**Coverage Standards:**
|
||||
- Aim for 9+ comprehensive tests per issue (following Issue #1 pattern)
|
||||
- Cover all critical functionality mentioned in issue description
|
||||
- Include error cases and edge conditions
|
||||
- Validate integrated workflows end-to-end
|
||||
|
||||
### Project Architecture Knowledge
|
||||
**Core Modules:**
|
||||
- `markitect/database.py` - DatabaseManager with SQLite operations
|
||||
- `markitect/frontmatter.py` - YAML front matter parsing
|
||||
- `markitect/parser.py` - Markdown AST parsing
|
||||
- `tddai/` - TDD workflow infrastructure
|
||||
- `workspace.py` - Workspace management
|
||||
- `issue_fetcher.py` - Gitea API integration
|
||||
- `issue_writer.py` - Issue updates via PATCH
|
||||
- `test_generator.py` - Test scaffolding
|
||||
- `coverage_analyzer.py` - Coverage assessment
|
||||
|
||||
**Development Patterns:**
|
||||
- Build incrementally on established foundations
|
||||
- Maintain 100% test coverage for new functionality
|
||||
- Focus on clean API design and comprehensive error handling
|
||||
- Follow existing project conventions and patterns
|
||||
|
||||
## Sidequest Management
|
||||
|
||||
### Recognizing Sidequests
|
||||
A sidequest occurs when working on an issue reveals the need for:
|
||||
- Missing dependencies or utilities not covered by current issues
|
||||
- Infrastructure improvements needed for the main task
|
||||
- Bug fixes discovered during implementation
|
||||
- Architectural changes required for proper implementation
|
||||
- Additional API endpoints or functionality
|
||||
|
||||
### Sidequest Issue Creation
|
||||
When a sidequest is identified, you should:
|
||||
|
||||
1. **Assess Urgency:**
|
||||
- **Blocking:** Must be resolved before continuing main issue
|
||||
- **Supporting:** Enhances main issue but not strictly required
|
||||
- **Future:** Can be deferred to later development cycle
|
||||
|
||||
2. **Create Sidequest Issue:**
|
||||
- Use descriptive title indicating it's a sidequest: "Sidequest: [Description]"
|
||||
- Include clear relationship to parent issue: "Discovered while working on Issue #X: [Brief Context]"
|
||||
- Specify if it's blocking or supporting the main issue
|
||||
- Provide acceptance criteria and implementation guidance
|
||||
- Tag with appropriate labels (if using issue labeling system)
|
||||
|
||||
3. **Document Relationship:**
|
||||
- In parent issue comments: "Created sidequest Issue #Y to handle [specific need]"
|
||||
- In sidequest issue: "Parent Issue: #X - [Brief description of how this supports the parent]"
|
||||
- Update parent issue description if the sidequest changes scope
|
||||
|
||||
### Sidequest Workflow Integration
|
||||
**For Blocking Sidequests:**
|
||||
1. Create sidequest issue
|
||||
2. `make tdd-finish` current work (if safe to do so)
|
||||
3. `make tdd-start NUM=Y` for sidequest
|
||||
4. Complete sidequest using full TDD cycle
|
||||
5. `make tdd-finish` sidequest
|
||||
6. Return to parent issue: `make tdd-start NUM=X`
|
||||
|
||||
**For Supporting Sidequests:**
|
||||
1. Create sidequest issue for future work
|
||||
2. Continue with current issue using available alternatives
|
||||
3. Note in issue comments that enhancement is available via sidequest
|
||||
4. Complete main issue, then optionally tackle sidequest
|
||||
|
||||
### Issue Creation Examples
|
||||
|
||||
**Blocking Sidequest Example:**
|
||||
```
|
||||
Title: Sidequest: Add YAML validation to front matter parser
|
||||
Body:
|
||||
Discovered while working on Issue #2: AST integration requires robust YAML validation to handle malformed front matter in markdown files.
|
||||
|
||||
Parent Issue: #2 - Read and Store a Markdown File
|
||||
Relationship: Blocking - Issue #2 implementation fails when encountering invalid YAML
|
||||
|
||||
Acceptance Criteria:
|
||||
- [ ] Validate YAML syntax before parsing
|
||||
- [ ] Return meaningful error messages for malformed YAML
|
||||
- [ ] Handle edge cases (empty YAML, missing delimiters)
|
||||
- [ ] Maintain backward compatibility with existing parsing
|
||||
|
||||
Implementation Notes:
|
||||
Enhance markitect/frontmatter.py with validation layer before YAML parsing.
|
||||
```
|
||||
|
||||
**Supporting Sidequest Example:**
|
||||
```
|
||||
Title: Sidequest: Add search functionality to database queries
|
||||
Body:
|
||||
Discovered while working on Issue #4: Retrieval implementation would benefit from search capabilities, though basic retrieval works without it.
|
||||
|
||||
Parent Issue: #4 - Retrieve All Stored Files
|
||||
Relationship: Supporting - Enhances Issue #4 but not required for basic functionality
|
||||
|
||||
Acceptance Criteria:
|
||||
- [ ] Add text search across markdown content
|
||||
- [ ] Search within YAML front matter fields
|
||||
- [ ] Support partial matching and case-insensitive search
|
||||
- [ ] Integrate with existing retrieval API
|
||||
|
||||
Implementation Notes:
|
||||
Extend markitect/database.py with search methods. Consider adding FTS (Full-Text Search) for larger datasets.
|
||||
```
|
||||
|
||||
## Workflow Guidance
|
||||
|
||||
### Executing the TDD8 Cycle
|
||||
|
||||
#### Steps 1-2: ISSUE → TEST
|
||||
1. **ISSUE:** `make tdd-status` (should show CLEAN) → `make show-issue NUM=X` → `make tdd-start NUM=X`
|
||||
2. **TEST:** Review requirements.md → `make tdd-add-test` → Create comprehensive test scenarios
|
||||
|
||||
#### Steps 3-5: RED → GREEN → REFACTOR
|
||||
3. **RED:** `make test` (verify new tests fail) → Confirm failure reasons → Check test isolation
|
||||
4. **GREEN:** Implement minimal code → Run tests frequently → Focus on making tests pass
|
||||
5. **REFACTOR:** Extract patterns → Improve clarity → Maintain test coverage → Follow conventions
|
||||
|
||||
#### Steps 6-8: DOCUMENT → REFINE → PUBLISH
|
||||
6. **DOCUMENT:** Add docstrings → Document decisions → Update API docs → Ensure code clarity
|
||||
7. **REFINE:** `make test` (45+ tests) → `make test-coverage NUM=X` → `make lint` → `make format`
|
||||
8. **PUBLISH:** `make tdd-finish` → Commit changes → Update documentation → Close issues
|
||||
|
||||
### TDD8 Cycle with Sidequests
|
||||
|
||||
**Sidequest Emergence Points:**
|
||||
- **ISSUE/TEST:** Missing dependencies or infrastructure identified
|
||||
- **RED/GREEN:** Implementation reveals architectural needs
|
||||
- **REFACTOR:** Code quality improvements require supporting tools
|
||||
- **DOCUMENT/REFINE:** Integration uncovers missing functionality
|
||||
|
||||
**Sidequest Integration:**
|
||||
- **Blocking Sidequests:** Pause current cycle → Complete sidequest TDD8 → Resume parent cycle
|
||||
- **Supporting Sidequests:** Document for future → Continue current cycle → Address in next iteration
|
||||
|
||||
## Integration with Project Tools
|
||||
|
||||
### Issue Management
|
||||
- **Gitea Integration:** All issues live in http://92.205.130.254:32166
|
||||
- **Issue Reading:** Use `IssueFetcher` for programmatic access
|
||||
- **Issue Writing:** Use `IssueWriter` for updates via authenticated PATCH
|
||||
- **Environment Variables:** `GITEA_TOKEN` for authentication
|
||||
|
||||
### Test Framework
|
||||
- **pytest-based:** All tests use pytest framework
|
||||
- **Mock Usage:** Extensive use of `unittest.mock` for isolation
|
||||
- **Coverage Analysis:** `CoverageAnalyzer` provides detailed metrics
|
||||
- **File Patterns:** Tests follow `test_issue_{NUM}_{scenario}.py` naming
|
||||
|
||||
### Build Integration
|
||||
- **Virtual Environment:** `.venv` with comprehensive dependencies
|
||||
- **Linting:** Code quality enforced via `make lint`
|
||||
- **Formatting:** Consistent style via `make format`
|
||||
- **Dependencies:** Managed through `pyproject.toml`
|
||||
|
||||
## Best Practices
|
||||
|
||||
### TDD8 Excellence
|
||||
- **ISSUE:** Clear requirements and acceptance criteria before any code
|
||||
- **TEST:** Comprehensive test coverage defining all expected behaviors
|
||||
- **RED:** Confirmed failing tests that guide implementation direction
|
||||
- **GREEN:** Minimal implementation focused solely on passing tests
|
||||
- **REFACTOR:** Quality improvements maintaining test coverage
|
||||
- **DOCUMENT:** Self-documenting code with clear usage patterns
|
||||
- **REFINE:** Integration testing and quality assurance
|
||||
- **PUBLISH:** Clean integration with comprehensive documentation
|
||||
|
||||
### Project Integration
|
||||
- **Pattern Consistency:** Follow existing code patterns and conventions
|
||||
- **Dependency Management:** Use existing libraries before adding new ones
|
||||
- **Database Integration:** Build on established `DatabaseManager` foundation
|
||||
- **Error Handling:** Use project's exception hierarchy (`TddaiError`, etc.)
|
||||
|
||||
### Communication
|
||||
- **Clear Issue Titles:** Make sidequest purposes immediately obvious
|
||||
- **Relationship Documentation:** Always link parent and child issues
|
||||
- **Progress Updates:** Keep issue comments current with development status
|
||||
- **Architecture Notes:** Document any architectural decisions in issues
|
||||
|
||||
## Success Indicators
|
||||
|
||||
### Issue Completion
|
||||
- All acceptance criteria covered by tests
|
||||
- Full test suite passes (45+ tests)
|
||||
- Code follows project patterns and conventions
|
||||
- No blocking sidequests remain unresolved
|
||||
- Documentation updated as needed
|
||||
|
||||
### Sidequest Management
|
||||
- Clear parent-child relationships documented
|
||||
- Appropriate urgency assessment (blocking vs. supporting)
|
||||
- No abandoned or forgotten sidequests
|
||||
- Efficient workflow with minimal context switching
|
||||
|
||||
### Overall Project Health
|
||||
- Consistent TDD practice across all issues
|
||||
- Growing foundation of tested functionality
|
||||
- Clean, maintainable codebase
|
||||
- Effective issue prioritization and management
|
||||
|
||||
Remember: The goal is to build MarkiTect incrementally using the proven TDD8 cycle while maintaining project momentum through effective sidequest management. Each complete TDD8 cycle should leave the codebase in a significantly better state and position the team for success on subsequent issues.
|
||||
|
||||
## TDD8 Cycle Summary
|
||||
|
||||
**ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH**
|
||||
|
||||
The comprehensive 8-step development methodology that transforms requirements into production-ready, well-tested, documented functionality while maintaining code quality and project momentum through intelligent sidequest management.
|
||||
@@ -4,6 +4,23 @@ This diary tracks major work packages, events, and milestones in the MarkiTect p
|
||||
|
||||
---
|
||||
|
||||
## 2025-09-23: IssueWriter Implementation & TDD8 Framework Development
|
||||
|
||||
**Progress:** Implemented comprehensive IssueWriter for Gitea API updates and formalized TDD8 workflow methodology
|
||||
**Contributors:** User (bernd.worsch), Claude Code (Sonnet 4)
|
||||
**Time Estimate:** ~2-3 hours of development, testing, and framework design
|
||||
**AI Resources:** ~25-30 Claude Sonnet 4 conversations, estimated 60K+ tokens
|
||||
|
||||
**SIDEQUEST ACHIEVEMENT:** Successfully implemented IssueWriter functionality that emerged as a natural sidequest during development work. Created `tddai/issue_writer.py` with comprehensive authenticated PATCH capabilities for updating Gitea issues via API. Implementation includes full authentication support via `GITEA_TOKEN` environment variable, robust error handling for API failures and authentication issues, and clean API design with specific methods for updating titles, bodies, and issue states. Added 13 comprehensive tests in `tests/test_issue_writer.py` covering all authentication scenarios, PATCH operations, error conditions, and edge cases. All tests pass and integrate seamlessly with existing 45+ test suite.
|
||||
|
||||
**METHODOLOGY BREAKTHROUGH:** Formalized the project's actual development workflow as the **TDD8 cycle** - a comprehensive 8-step methodology extending traditional TDD: **ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH**. This framework captures the complete transformation from requirements to production-ready functionality. Created comprehensive tddai-assistant subagent (.claude/agents/tddai-assistant.md) with detailed guidance for each TDD8 step, sophisticated sidequest management strategies, and project-specific knowledge including workspace management, Gitea integration, and test coverage standards.
|
||||
|
||||
**WORKFLOW ENHANCEMENT:** The TDD8 framework addresses the reality that development involves more than just RED-GREEN-REFACTOR cycles. It includes upfront issue analysis (ISSUE), comprehensive test design (TEST), traditional TDD core (RED-GREEN-REFACTOR), and crucial production-readiness steps (DOCUMENT-REFINE-PUBLISH). Integrated sidequest management recognizes that blocking and supporting sidequests naturally emerge at different cycle phases and provides specific strategies for each scenario.
|
||||
|
||||
**INFRASTRUCTURE MATURITY:** This session demonstrates the project's evolution from basic TDD to a sophisticated development methodology. The IssueWriter implementation showcases clean separation of concerns, comprehensive test coverage, and proper integration patterns. The tddai-assistant provides authoritative guidance for maintaining these standards while adapting to the dynamic nature of software development through intelligent sidequest management.
|
||||
|
||||
---
|
||||
|
||||
## 2025-09-23: Issue #1 Implementation & TDD Infrastructure Restoration
|
||||
|
||||
**Progress:** Successfully implemented first core functionality (Issue #1) and resolved complete TDD infrastructure
|
||||
|
||||
82
tddai/issue_writer.py
Normal file
82
tddai/issue_writer.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Issue writing to Gitea API.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from subprocess import PIPE
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from .config import get_config
|
||||
from .exceptions import IssueError
|
||||
|
||||
|
||||
class IssueWriter:
|
||||
"""Writes issue updates to Gitea API."""
|
||||
|
||||
def __init__(self, config=None, auth_token=None):
|
||||
self.config = config or get_config()
|
||||
self.auth_token = auth_token or os.getenv('GITEA_TOKEN')
|
||||
|
||||
def update_issue(self, issue_number: int, update_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Update an issue via PATCH operation."""
|
||||
if not self.auth_token:
|
||||
raise IssueError("Authentication token required for issue updates")
|
||||
|
||||
url = f"{self.config.issues_api_url}/{issue_number}"
|
||||
|
||||
try:
|
||||
# Prepare curl command with authentication
|
||||
curl_cmd = [
|
||||
'curl', '-s', '-X', 'PATCH',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-H', f'Authorization: token {self.auth_token}',
|
||||
'-d', json.dumps(update_data),
|
||||
url
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
curl_cmd,
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
universal_newlines=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise IssueError(f"Failed to update issue #{issue_number}: {result.stderr}")
|
||||
|
||||
response_data = json.loads(result.stdout)
|
||||
|
||||
if 'message' in response_data and 'number' not in response_data:
|
||||
raise IssueError(f"Failed to update issue #{issue_number}: {response_data['message']}")
|
||||
|
||||
return response_data
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise IssueError(f"Failed to update issue #{issue_number}: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise IssueError(f"Failed to parse response data: {e}")
|
||||
|
||||
def update_issue_title(self, issue_number: int, new_title: str) -> Dict[str, Any]:
|
||||
"""Update only the title of an issue."""
|
||||
return self.update_issue(issue_number, {'title': new_title})
|
||||
|
||||
def update_issue_body(self, issue_number: int, new_body: str) -> Dict[str, Any]:
|
||||
"""Update only the body of an issue."""
|
||||
return self.update_issue(issue_number, {'body': new_body})
|
||||
|
||||
def update_issue_state(self, issue_number: int, new_state: str) -> Dict[str, Any]:
|
||||
"""Update only the state of an issue (open/closed)."""
|
||||
if new_state not in ['open', 'closed']:
|
||||
raise IssueError(f"Invalid state '{new_state}'. Must be 'open' or 'closed'")
|
||||
return self.update_issue(issue_number, {'state': new_state})
|
||||
|
||||
def close_issue(self, issue_number: int) -> Dict[str, Any]:
|
||||
"""Close an issue."""
|
||||
return self.update_issue_state(issue_number, 'closed')
|
||||
|
||||
def reopen_issue(self, issue_number: int) -> Dict[str, Any]:
|
||||
"""Reopen a closed issue."""
|
||||
return self.update_issue_state(issue_number, 'open')
|
||||
180
tests/test_issue_writer.py
Normal file
180
tests/test_issue_writer.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
class TestIssueWriter:
|
||||
"""Test suite for IssueWriter class."""
|
||||
|
||||
def test_init_with_auth_token(self):
|
||||
"""Test IssueWriter initialization with auth token."""
|
||||
writer = IssueWriter(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."""
|
||||
with patch.dict('os.environ', {'GITEA_TOKEN': 'env-token'}):
|
||||
writer = IssueWriter()
|
||||
assert writer.auth_token == "env-token"
|
||||
|
||||
def test_update_issue_without_auth_token_raises_error(self):
|
||||
"""Test that updating without auth token raises IssueError."""
|
||||
writer = IssueWriter(auth_token=None)
|
||||
with patch.dict('os.environ', {}, clear=True):
|
||||
writer = IssueWriter()
|
||||
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
|
||||
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(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')
|
||||
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(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."""
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(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
|
||||
|
||||
writer = IssueWriter(auth_token="test-token")
|
||||
result = writer.reopen_issue(1)
|
||||
|
||||
assert result['state'] == 'open'
|
||||
Reference in New Issue
Block a user