Files
markitect-main/tests/test_issue_59_plugin_manager.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

233 lines
8.5 KiB
Python

"""
Tests for Issue #59 - Issue Management Plugin Manager
This module contains tests for the plugin manager that handles
backend discovery, loading, and configuration for the unified
issue management CLI.
"""
import pytest
from unittest.mock import Mock, patch
from pathlib import Path
from typing import Dict, Any
# Import the classes we'll implement
# Note: These imports will fail initially (RED phase)
from markitect.issues.manager import IssuePluginManager
from markitect.issues.base import IssueBackend
from markitect.issues.plugins.gitea import GiteaPlugin
from markitect.issues.plugins.local import LocalPlugin
from markitect.issues.exceptions import PluginNotFoundError, ConfigurationError
class TestIssuePluginManager:
"""Test suite for the issue plugin manager."""
def test_manager_initialization_with_default_config(self):
"""Test plugin manager initializes with default configuration."""
manager = IssuePluginManager()
assert manager is not None
assert hasattr(manager, 'config')
assert hasattr(manager, 'plugins')
def test_manager_initialization_with_custom_config_path(self):
"""Test plugin manager accepts custom config path."""
config_path = "/custom/path/config.yml"
with patch.object(IssuePluginManager, '_load_config') as mock_load:
mock_load.return_value = {'default_backend': 'gitea'}
manager = IssuePluginManager(config_path)
mock_load.assert_called_once_with(config_path)
def test_plugin_discovery_finds_available_backends(self):
"""Test plugin discovery locates all available backend plugins."""
manager = IssuePluginManager()
# Should discover at least gitea and local plugins
assert 'gitea' in manager.plugins
assert 'local' in manager.plugins
assert len(manager.plugins) >= 2
def test_get_default_backend_when_none_specified(self):
"""Test getting backend instance uses default from config."""
with patch.object(IssuePluginManager, '_load_config') as mock_load:
mock_load.return_value = {
'default_backend': 'gitea',
'backends': {'gitea': {'url': 'http://test.com'}}
}
manager = IssuePluginManager()
backend = manager.get_backend()
assert isinstance(backend, IssueBackend)
def test_get_specific_backend_override(self):
"""Test getting specific backend overrides default config."""
with patch.object(IssuePluginManager, '_load_config') as mock_load:
mock_load.return_value = {
'default_backend': 'gitea',
'backends': {
'gitea': {'url': 'http://test.com'},
'local': {'directory': '.issues'}
}
}
manager = IssuePluginManager()
backend = manager.get_backend('local')
assert isinstance(backend, IssueBackend)
def test_get_unknown_backend_raises_error(self):
"""Test requesting unknown backend raises appropriate error."""
manager = IssuePluginManager()
with pytest.raises(PluginNotFoundError):
manager.get_backend('nonexistent')
def test_config_loading_with_missing_file(self):
"""Test configuration loading handles missing config file gracefully."""
manager = IssuePluginManager()
# Should have default configuration
assert manager.config is not None
assert 'default_backend' in manager.config
def test_config_loading_with_invalid_yaml(self):
"""Test configuration loading handles invalid YAML gracefully."""
with patch('builtins.open', side_effect=Exception("Invalid YAML")):
manager = IssuePluginManager()
# Should fall back to default configuration
assert manager.config is not None
class TestPluginInterface:
"""Test suite for the abstract plugin interface."""
def test_abstract_backend_cannot_be_instantiated(self):
"""Test abstract IssueBackend cannot be instantiated directly."""
with pytest.raises(TypeError):
IssueBackend()
def test_plugin_must_implement_all_abstract_methods(self):
"""Test concrete plugins must implement all abstract methods."""
class IncompletePlugin(IssueBackend):
def list_issues(self, state=None):
return []
# Missing other required methods
with pytest.raises(TypeError):
IncompletePlugin()
def test_complete_plugin_implementation_works(self):
"""Test properly implemented plugin can be instantiated."""
class CompletePlugin(IssueBackend):
def list_issues(self, state=None):
return []
def get_issue(self, issue_id):
return Mock()
def create_issue(self, title, body, **kwargs):
return Mock()
def add_comment(self, issue_id, comment):
return {}
def close_issue(self, issue_id):
return Mock()
def update_issue(self, issue_id, **kwargs):
return Mock()
# Should not raise any errors
plugin = CompletePlugin({})
assert isinstance(plugin, IssueBackend)
class TestPluginConfiguration:
"""Test suite for plugin configuration management."""
def test_backend_receives_configuration_on_initialization(self):
"""Test backend plugins receive their configuration during init."""
config = {
'default_backend': 'gitea',
'backends': {
'gitea': {'url': 'http://test.com', 'repo': 'test/repo'}
}
}
with patch.object(IssuePluginManager, '_load_config', return_value=config):
with patch.object(IssuePluginManager, '_discover_plugins') as mock_discover:
# Mock the plugin class to verify config is passed
mock_plugin_class = Mock()
mock_discover.return_value = {'gitea': mock_plugin_class}
manager = IssuePluginManager()
manager.get_backend('gitea')
# Verify plugin was initialized with backend config
mock_plugin_class.assert_called_once_with({'url': 'http://test.com', 'repo': 'test/repo'})
def test_missing_backend_config_uses_empty_dict(self):
"""Test backend initialization with missing config uses empty dict."""
config = {
'default_backend': 'local',
'backends': {} # No local backend config
}
with patch.object(IssuePluginManager, '_load_config', return_value=config):
with patch.object(IssuePluginManager, '_discover_plugins') as mock_discover:
mock_plugin_class = Mock()
mock_discover.return_value = {'local': mock_plugin_class}
manager = IssuePluginManager()
manager.get_backend('local')
# Should initialize with empty config
mock_plugin_class.assert_called_once_with({})
def test_config_validation_rejects_invalid_backend_names(self):
"""Test configuration validation rejects invalid backend names."""
config = {
'default_backend': 'invalid-backend-name',
'backends': {}
}
with patch.object(IssuePluginManager, '_load_config', return_value=config):
manager = IssuePluginManager()
with pytest.raises(PluginNotFoundError):
manager.get_backend()
class TestErrorHandling:
"""Test suite for error handling scenarios."""
def test_plugin_loading_failure_provides_helpful_error(self):
"""Test plugin loading failures provide helpful error messages."""
manager = IssuePluginManager()
with pytest.raises(PluginNotFoundError) as exc_info:
manager.get_backend('nonexistent')
assert 'nonexistent' in str(exc_info.value)
assert 'backend' in str(exc_info.value).lower()
def test_configuration_error_for_malformed_config(self):
"""Test configuration errors for malformed configuration."""
# This will be implemented when we add config validation
pass
def test_graceful_degradation_on_plugin_import_failure(self):
"""Test system handles plugin import failures gracefully."""
# Mock import failure for one plugin
with patch('importlib.import_module', side_effect=ImportError("Mock import failure")):
manager = IssuePluginManager()
# Should still work with available plugins
assert manager.plugins is not None