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