#!/usr/bin/env python3 """ Tests for pattern_recognizer.py - Design pattern detection. Test Coverage: - SingletonDetector (4 tests) - FactoryDetector (4 tests) - ObserverDetector (3 tests) - PatternRecognizer Integration (4 tests) - Multi-Language Support (3 tests) """ import unittest import sys import os # Add src to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) from skill_seekers.cli.pattern_recognizer import ( SingletonDetector, FactoryDetector, ObserverDetector, StrategyDetector, DecoratorDetector, BuilderDetector, AdapterDetector, CommandDetector, TemplateMethodDetector, ChainOfResponsibilityDetector, PatternRecognizer, LanguageAdapter, PatternInstance ) class TestSingletonDetector(unittest.TestCase): """Tests for Singleton pattern detection""" def setUp(self): self.detector = SingletonDetector(depth='deep') self.recognizer = PatternRecognizer(depth='deep') def test_surface_detection_by_name(self): """Test surface detection using class name""" code = """ class DatabaseSingleton: def __init__(self): self.connection = None """ report = self.recognizer.analyze_file('test.py', code, 'Python') self.assertEqual(len(report.patterns), 1) pattern = report.patterns[0] self.assertEqual(pattern.pattern_type, 'Singleton') self.assertGreaterEqual(pattern.confidence, 0.6) self.assertIn('Singleton', pattern.class_name) def test_deep_detection_with_instance_method(self): """Test deep detection with getInstance() method""" code = """ class Database: def getInstance(self): return self._instance def __init__(self): self._instance = None """ report = self.recognizer.analyze_file('test.py', code, 'Python') # May or may not detect based on getInstance alone # Checking that analysis completes successfully self.assertIsNotNone(report) self.assertEqual(report.language, 'Python') def test_python_singleton_with_new(self): """Test Python-specific __new__ singleton pattern""" code = """ class Config: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance """ report = self.recognizer.analyze_file('test.py', code, 'Python') # Detection may vary based on __new__ method signatures from CodeAnalyzer # Main check: analysis completes successfully self.assertIsNotNone(report) self.assertGreaterEqual(report.total_classes, 1) def test_java_singleton_pattern(self): """Test Java-style Singleton pattern""" code = """ public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } """ report = self.recognizer.analyze_file('test.java', code, 'Java') # May detect Singleton based on getInstance method # Since CodeAnalyzer uses regex for Java, detection may vary self.assertIsNotNone(report) class TestFactoryDetector(unittest.TestCase): """Tests for Factory pattern detection""" def setUp(self): self.detector = FactoryDetector(depth='deep') self.recognizer = PatternRecognizer(depth='deep') def test_surface_detection_by_name(self): """Test surface detection using class name""" code = """ class CarFactory: def create_car(self, type): pass """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Factory'] self.assertGreater(len(patterns), 0) pattern = patterns[0] # Confidence may be adjusted by deep detection self.assertGreaterEqual(pattern.confidence, 0.5) self.assertIn('Factory', pattern.class_name) def test_factory_method_detection(self): """Test detection of create/make methods""" code = """ class VehicleFactory: def create(self, vehicle_type): if vehicle_type == 'car': return Car() elif vehicle_type == 'truck': return Truck() def make_vehicle(self, specs): return Vehicle(specs) """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Factory'] self.assertGreater(len(patterns), 0) pattern = patterns[0] self.assertIn('create', ' '.join(pattern.evidence).lower()) def test_abstract_factory_multiple_methods(self): """Test Abstract Factory with multiple creation methods""" code = """ class UIFactory: def create_button(self): pass def create_window(self): pass def create_menu(self): pass """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Factory'] self.assertGreater(len(patterns), 0) pattern = patterns[0] self.assertGreaterEqual(pattern.confidence, 0.5) def test_parameterized_factory(self): """Test parameterized factory pattern""" code = """ class ShapeFactory: def create_shape(self, shape_type, *args): if shape_type == 'circle': return Circle(*args) elif shape_type == 'square': return Square(*args) return None """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Factory'] self.assertGreater(len(patterns), 0) class TestObserverDetector(unittest.TestCase): """Tests for Observer pattern detection""" def setUp(self): self.detector = ObserverDetector(depth='deep') self.recognizer = PatternRecognizer(depth='deep') def test_observer_triplet_detection(self): """Test classic attach/detach/notify triplet""" code = """ class Subject: def __init__(self): self.observers = [] def attach(self, observer): self.observers.append(observer) def detach(self, observer): self.observers.remove(observer) def notify(self): for observer in self.observers: observer.update() """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Observer'] self.assertGreater(len(patterns), 0) pattern = patterns[0] self.assertGreaterEqual(pattern.confidence, 0.8) evidence_str = ' '.join(pattern.evidence).lower() self.assertTrue( 'attach' in evidence_str and 'detach' in evidence_str and 'notify' in evidence_str ) def test_pubsub_pattern(self): """Test publish/subscribe variant""" code = """ class EventBus: def subscribe(self, event, handler): pass def unsubscribe(self, event, handler): pass def publish(self, event, data): pass """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Observer'] self.assertGreater(len(patterns), 0) def test_event_emitter_pattern(self): """Test EventEmitter-style observer""" code = """ class EventEmitter: def on(self, event, listener): pass def off(self, event, listener): pass def emit(self, event, *args): pass """ report = self.recognizer.analyze_file('test.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Observer'] self.assertGreater(len(patterns), 0) class TestPatternRecognizerIntegration(unittest.TestCase): """Integration tests for PatternRecognizer""" def setUp(self): self.recognizer = PatternRecognizer(depth='deep') def test_analyze_singleton_code(self): """Test end-to-end Singleton analysis""" code = """ class ConfigManager: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def getInstance(self): return self._instance """ report = self.recognizer.analyze_file('config.py', code, 'Python') self.assertEqual(report.file_path, 'config.py') self.assertEqual(report.language, 'Python') self.assertGreater(len(report.patterns), 0) self.assertGreater(report.total_classes, 0) def test_analyze_factory_code(self): """Test end-to-end Factory analysis""" code = """ class AnimalFactory: def create_animal(self, animal_type): if animal_type == 'dog': return Dog() elif animal_type == 'cat': return Cat() return None """ report = self.recognizer.analyze_file('factory.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Factory'] self.assertGreater(len(patterns), 0) def test_analyze_observer_code(self): """Test end-to-end Observer analysis""" code = """ class WeatherStation: def __init__(self): self.observers = [] def attach(self, observer): self.observers.append(observer) def detach(self, observer): self.observers.remove(observer) def notify(self): for obs in self.observers: obs.update(self.temperature) """ report = self.recognizer.analyze_file('weather.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Observer'] self.assertGreater(len(patterns), 0) def test_pattern_report_summary(self): """Test PatternReport.get_summary() method""" code = """ class LoggerSingleton: _instance = None def getInstance(self): return self._instance class LoggerFactory: def create_logger(self, type): return Logger(type) """ report = self.recognizer.analyze_file('logging.py', code, 'Python') summary = report.get_summary() self.assertIsInstance(summary, dict) # Summary returns pattern counts by type (e.g., {'Singleton': 1, 'Factory': 1}) if summary: # Check that at least one pattern type is in summary total_count = sum(summary.values()) self.assertGreater(total_count, 0) class TestMultiLanguageSupport(unittest.TestCase): """Tests for multi-language pattern detection""" def setUp(self): self.recognizer = PatternRecognizer(depth='deep') def test_python_patterns(self): """Test Python-specific patterns""" code = """ class DatabaseConnection: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance """ report = self.recognizer.analyze_file('db.py', code, 'Python') # Detection depends on CodeAnalyzer's ability to parse __new__ method # Main check: analysis completes successfully self.assertIsNotNone(report) self.assertEqual(report.language, 'Python') def test_javascript_patterns(self): """Test JavaScript-specific patterns""" code = """ const singleton = (function() { let instance; function createInstance() { return { name: 'Singleton' }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); """ # Note: CodeAnalyzer uses regex for JavaScript, so detection may be limited report = self.recognizer.analyze_file('app.js', code, 'JavaScript') self.assertIsNotNone(report) def test_java_patterns(self): """Test Java-specific patterns""" code = """ public class Logger { private static Logger instance; private Logger() {} public static Logger getInstance() { if (instance == null) { instance = new Logger(); } return instance; } } """ report = self.recognizer.analyze_file('Logger.java', code, 'Java') self.assertIsNotNone(report) class TestExtendedPatternDetectors(unittest.TestCase): """Tests for extended pattern detectors (Builder, Adapter, Command, etc.)""" def setUp(self): self.recognizer = PatternRecognizer(depth='deep') def test_builder_pattern(self): """Test Builder pattern detection""" code = """ class QueryBuilder: def __init__(self): self.query = {} def where(self, condition): self.query['where'] = condition return self def orderBy(self, field): self.query['order'] = field return self def build(self): return Query(self.query) """ report = self.recognizer.analyze_file('query.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Builder'] self.assertGreater(len(patterns), 0) def test_adapter_pattern(self): """Test Adapter pattern detection""" code = """ class DatabaseAdapter: def __init__(self, adaptee): self.adaptee = adaptee def query(self, sql): return self.adaptee.execute(sql) def connect(self): return self.adaptee.open_connection() """ report = self.recognizer.analyze_file('adapter.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Adapter'] self.assertGreater(len(patterns), 0) def test_command_pattern(self): """Test Command pattern detection""" code = """ class SaveCommand: def __init__(self, receiver): self.receiver = receiver def execute(self): self.receiver.save() def undo(self): self.receiver.revert() """ report = self.recognizer.analyze_file('command.py', code, 'Python') patterns = [p for p in report.patterns if p.pattern_type == 'Command'] self.assertGreater(len(patterns), 0) class TestLanguageAdapter(unittest.TestCase): """Tests for language-specific adaptations""" def test_python_decorator_boost(self): """Test Python @decorator syntax boost""" pattern = PatternInstance( pattern_type='Decorator', category='Structural', confidence=0.6, location='test.py', class_name='LogDecorator', evidence=['Uses @decorator syntax'] ) adapted = LanguageAdapter.adapt_for_language(pattern, 'Python') self.assertGreater(adapted.confidence, 0.6) self.assertIn('Python @decorator', ' '.join(adapted.evidence)) def test_javascript_module_pattern(self): """Test JavaScript module pattern boost""" pattern = PatternInstance( pattern_type='Singleton', category='Creational', confidence=0.5, location='app.js', class_name='App', evidence=['Has getInstance', 'module pattern detected'] ) adapted = LanguageAdapter.adapt_for_language(pattern, 'JavaScript') self.assertGreater(adapted.confidence, 0.5) def test_no_pattern_returns_none(self): """Test None input returns None""" result = LanguageAdapter.adapt_for_language(None, 'Python') self.assertIsNone(result) if __name__ == '__main__': # Run tests with verbose output unittest.main(verbosity=2)