## Major Integration - ✅ Integrated Requirements Engineering Agent into development workflow - ✅ Enhanced Makefile with requirements validation targets - ✅ Added pre-commit validation with mock compatibility checking - ✅ Enhanced TDD workflow to include foundation analysis ## Test Fixes - ✅ Fixed GiteaPlugin missing _add_comment_async method - ✅ Fixed LocalPlugin config.yml file not found errors in tests - ✅ Enhanced mock objects in CLI tests with proper domain model attributes - ✅ All Issue #59 tests now passing (38/38 tests pass) ## New Capabilities - `make validate-requirements` - Foundation analysis before development - `make check-interface-compatibility INTERFACE=Name` - Interface compatibility checking - `make generate-dev-checklist FEATURE='Name'` - Development checklist generation - `make validate-mocks` - Mock object compatibility validation - `make pre-commit-validate` - Complete pre-commit validation workflow ## Problem Prevention This integration prevents the exact interface compatibility issues and mock object mismatches that caused hours of debugging in Issue #59. The Requirements Engineering Agent provides proactive foundation analysis and catches problems before they occur. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
223 lines
8.7 KiB
Python
223 lines
8.7 KiB
Python
"""
|
|
Mock Compatibility Validation Tests
|
|
|
|
Validates that test mocks match actual domain models and prevent
|
|
the interface compatibility issues encountered in Issue #59.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock
|
|
from datetime import datetime, timezone
|
|
from typing import List
|
|
|
|
# Import domain models for validation
|
|
from domain.issues.models import Issue, IssueState, Label
|
|
|
|
|
|
class TestMockCompatibility:
|
|
"""Validate that test mocks match actual domain models."""
|
|
|
|
def test_issue_mock_has_all_required_attributes(self):
|
|
"""Test that Issue mocks include all required attributes."""
|
|
# Create a real Issue to get expected attributes
|
|
real_issue = Issue(
|
|
number=1,
|
|
title="Real Issue",
|
|
state=IssueState.OPEN,
|
|
labels=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Create mock with spec
|
|
mock_issue = Mock(spec=Issue)
|
|
mock_issue.number = 1
|
|
mock_issue.title = "Mock Issue"
|
|
mock_issue.state = IssueState.OPEN
|
|
mock_issue.labels = []
|
|
mock_issue.created_at = datetime.now(timezone.utc)
|
|
mock_issue.updated_at = datetime.now(timezone.utc)
|
|
mock_issue.milestone = None
|
|
mock_issue.assignee = None
|
|
|
|
# Verify critical attributes match
|
|
real_attrs = {attr for attr in dir(real_issue) if not attr.startswith('_')}
|
|
mock_attrs = {attr for attr in dir(mock_issue) if not attr.startswith('_')}
|
|
|
|
missing_attrs = real_attrs - mock_attrs
|
|
# Filter out methods - we only care about data attributes
|
|
critical_missing = [attr for attr in missing_attrs
|
|
if not callable(getattr(real_issue, attr, None))]
|
|
|
|
assert not critical_missing, f"Mock missing critical attributes: {critical_missing}"
|
|
|
|
def test_issue_mock_uses_correct_types(self):
|
|
"""Test that Issue mocks use correct types."""
|
|
mock_issue = Mock(spec=Issue)
|
|
mock_issue.state = IssueState.OPEN # Should be enum, not string
|
|
|
|
assert isinstance(mock_issue.state, IssueState), "State should be IssueState enum"
|
|
|
|
def test_issue_mock_correct_pattern_from_issue_59_fix(self):
|
|
"""Test the correct mock pattern that fixes Issue #59 problems."""
|
|
# ✅ CORRECT pattern - what we learned from Issue #59
|
|
mock_datetime = Mock()
|
|
mock_datetime.strftime.return_value = "2023-01-01 00:00"
|
|
|
|
mock_issue = Mock(spec=Issue) # Use spec parameter!
|
|
mock_issue.number = 59
|
|
mock_issue.title = "Test Issue"
|
|
mock_issue._body = "Test issue body" # CLI expects _body attribute
|
|
mock_issue.state = Mock()
|
|
mock_issue.state.value = "open" # CLI converts state.value
|
|
mock_issue.created_at = mock_datetime # Must support strftime()
|
|
mock_issue.updated_at = mock_datetime
|
|
mock_issue.labels = []
|
|
mock_issue.assignee = None
|
|
mock_issue.milestone = None
|
|
|
|
# Attributes that the view layer expects (learned from Issue #59)
|
|
mock_issue.state_label = "OPEN"
|
|
mock_issue.priority_label = "Normal"
|
|
mock_issue.type_labels = []
|
|
mock_issue.other_labels = []
|
|
mock_issue.html_url = ""
|
|
mock_issue.kanban_column = "To Do"
|
|
|
|
# Verify it behaves like Issue #59 tests expect
|
|
assert mock_issue.number == 59
|
|
assert mock_issue.state.value == "open"
|
|
assert mock_issue.created_at.strftime('%Y-%m-%d') == "2023-01-01 00:00"
|
|
assert isinstance(mock_issue.labels, list)
|
|
|
|
def test_label_mock_has_correct_attributes(self):
|
|
"""Test that Label mocks match domain model."""
|
|
real_label = Label(name="bug", color="#ff0000", description="Bug label")
|
|
|
|
mock_label = Mock(spec=Label)
|
|
mock_label.name = "bug"
|
|
mock_label.color = "#ff0000"
|
|
mock_label.description = "Bug label"
|
|
|
|
# Verify key attributes exist
|
|
assert hasattr(mock_label, 'name')
|
|
assert hasattr(mock_label, 'color')
|
|
assert hasattr(mock_label, 'description')
|
|
|
|
def test_enum_vs_string_validation(self):
|
|
"""Validate that enums are used instead of strings (Issue #59 lesson)."""
|
|
# ❌ WRONG - what caused Issue #59 problems
|
|
wrong_mock = Mock()
|
|
wrong_mock.state = "open" # String instead of enum!
|
|
|
|
# ✅ CORRECT - proper enum usage
|
|
correct_mock = Mock(spec=Issue)
|
|
correct_mock.state = IssueState.OPEN
|
|
|
|
# Verify correct usage
|
|
assert isinstance(correct_mock.state, IssueState)
|
|
assert not isinstance(wrong_mock.state, IssueState)
|
|
|
|
# This test serves as documentation of the correct pattern
|
|
|
|
|
|
class TestIssue59Prevention:
|
|
"""Specific tests to prevent Issue #59 problems from recurring."""
|
|
|
|
def test_plugin_manager_mock_discovery_pattern(self):
|
|
"""Test the correct plugin manager mocking pattern."""
|
|
from markitect.issues.manager import IssuePluginManager
|
|
|
|
# ✅ CORRECT: Mock at _discover_plugins level (learned from Issue #59)
|
|
from unittest.mock import patch
|
|
with patch.object(IssuePluginManager, '_discover_plugins') as mock_discover:
|
|
mock_plugin_class = Mock()
|
|
mock_discover.return_value = {'gitea': mock_plugin_class}
|
|
|
|
manager = IssuePluginManager()
|
|
|
|
# This pattern works because it mocks at the right level
|
|
assert 'gitea' in manager.plugins
|
|
mock_plugin_class.assert_not_called() # Only called when get_backend() is used
|
|
|
|
def test_cli_mock_realistic_attributes(self):
|
|
"""Test CLI layer mocks have all required attributes for views."""
|
|
# These are the attributes the CLI view layer expects (learned from Issue #59)
|
|
required_view_attributes = [
|
|
'number', 'title', 'state', 'created_at', 'updated_at',
|
|
'labels', 'assignee', 'milestone', '_body'
|
|
]
|
|
|
|
# Additional attributes the view expects (discovered during Issue #59 fixing)
|
|
view_layer_attributes = [
|
|
'state_label', 'priority_label', 'type_labels',
|
|
'other_labels', 'html_url', 'kanban_column'
|
|
]
|
|
|
|
mock_issue = Mock(spec=Issue)
|
|
|
|
# Set all required attributes
|
|
for attr in required_view_attributes:
|
|
setattr(mock_issue, attr, None) # Set to None or appropriate default
|
|
|
|
for attr in view_layer_attributes:
|
|
setattr(mock_issue, attr, None)
|
|
|
|
# Verify all critical attributes exist
|
|
for attr in required_view_attributes + view_layer_attributes:
|
|
assert hasattr(mock_issue, attr), f"Mock missing required attribute: {attr}"
|
|
|
|
def test_datetime_mock_strftime_support(self):
|
|
"""Test datetime mocks support strftime (Issue #59 requirement)."""
|
|
# The view layer calls created_at.strftime() - mocks must support this
|
|
mock_datetime = Mock()
|
|
mock_datetime.strftime.return_value = "2023-01-01"
|
|
|
|
mock_issue = Mock(spec=Issue)
|
|
mock_issue.created_at = mock_datetime
|
|
mock_issue.updated_at = mock_datetime
|
|
|
|
# Verify strftime works
|
|
assert mock_issue.created_at.strftime('%Y-%m-%d') == "2023-01-01"
|
|
assert mock_issue.updated_at.strftime('%Y-%m-%d') == "2023-01-01"
|
|
|
|
|
|
class TestMockGuidelines:
|
|
"""Tests that demonstrate correct mock patterns for future development."""
|
|
|
|
def test_correct_mock_creation_pattern(self):
|
|
"""Demonstrate the correct way to create mocks."""
|
|
# ✅ ALWAYS use spec= parameter
|
|
mock_issue = Mock(spec=Issue)
|
|
|
|
# ✅ Use actual enums, not strings
|
|
mock_issue.state = IssueState.OPEN
|
|
|
|
# ✅ Include all required attributes based on domain model analysis
|
|
mock_issue.number = 1
|
|
mock_issue.title = "Test"
|
|
mock_issue.labels = []
|
|
mock_issue.created_at = datetime.now(timezone.utc)
|
|
mock_issue.updated_at = datetime.now(timezone.utc)
|
|
|
|
# Verify this is the correct pattern
|
|
assert hasattr(mock_issue, '_spec_class') # Mock(spec=X) creates this
|
|
assert isinstance(mock_issue.state, IssueState)
|
|
|
|
def test_integration_test_mocking_strategy(self):
|
|
"""Demonstrate proper mocking for integration tests."""
|
|
# For integration tests, create mocks that match the actual interface contracts
|
|
from markitect.issues.base import IssueBackend
|
|
|
|
mock_backend = Mock(spec=IssueBackend)
|
|
mock_backend.list_issues.return_value = []
|
|
mock_backend.get_issue.return_value = Mock(spec=Issue)
|
|
|
|
# Verify the mock supports the interface
|
|
assert hasattr(mock_backend, 'list_issues')
|
|
assert hasattr(mock_backend, 'get_issue')
|
|
assert callable(mock_backend.list_issues)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v']) |