Files
markitect-main/tests/test_mock_compatibility.py
tegwick 3af6fb9935 feat: Integrate Requirements Engineering Agent and fix Issue #59 test failures
## 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>
2025-10-02 00:45:06 +02:00

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'])