""" Unit tests for algorithm modules: BaseAlgorithm, SM2Algorithm """ import pytest from datetime import datetime, timezone from src.heurams.kernel.algorithms.base import BaseAlgorithm from src.heurams.kernel.algorithms.sm2 import SM2Algorithm from src.heurams.kernel.particles.electron import Electron from src.heurams.kernel.particles.orbital import Orbital class TestBaseAlgorithm: """Test cases for BaseAlgorithm class.""" def test_base_algorithm_abstract_methods(self): """Test that BaseAlgorithm cannot be instantiated directly.""" with pytest.raises(TypeError): BaseAlgorithm() class TestSM2Algorithm: """Test cases for SM2Algorithm class.""" def test_sm2_algorithm_creation(self): """Test SM2Algorithm creation.""" algorithm = SM2Algorithm() assert algorithm.name == "sm2" assert algorithm.version == "1.0" def test_sm2_calculate_interval_learning_phase(self): """Test interval calculation in learning phase.""" algorithm = SM2Algorithm() electron = Electron(repetitions=0) orbital = Orbital(learning_steps=[1, 10]) interval = algorithm.calculate_interval(electron, orbital, quality=3) assert interval == 1 # First learning step def test_sm2_calculate_interval_graduation(self): """Test interval calculation when graduating.""" algorithm = SM2Algorithm() electron = Electron(repetitions=1) orbital = Orbital(learning_steps=[1, 10], graduating_interval=1) interval = algorithm.calculate_interval(electron, orbital, quality=4) assert interval == 1 # Graduating interval def test_sm2_calculate_interval_review_phase(self): """Test interval calculation in review phase.""" algorithm = SM2Algorithm() electron = Electron(ease=2.5, interval=10, repetitions=5) orbital = Orbital() interval = algorithm.calculate_interval(electron, orbital, quality=4) # Should be: 10 * 2.5 = 25 assert interval == 25 def test_sm2_calculate_ease_increase(self): """Test ease calculation with increase.""" algorithm = SM2Algorithm() electron = Electron(ease=2.5) new_ease = algorithm.calculate_ease(electron, quality=5) # Should be: 2.5 + 0.1 - 0.08 + 0.02 = 2.54 assert new_ease == pytest.approx(2.54) def test_sm2_calculate_ease_decrease(self): """Test ease calculation with decrease.""" algorithm = SM2Algorithm() electron = Electron(ease=2.5) new_ease = algorithm.calculate_ease(electron, quality=2) # Should be: 2.5 + 0.1 - 0.16 + 0.02 = 2.46 assert new_ease == pytest.approx(2.46) def test_sm2_calculate_ease_minimum(self): """Test ease calculation with minimum bound.""" algorithm = SM2Algorithm() electron = Electron(ease=1.3) # Very low ease new_ease = algorithm.calculate_ease(electron, quality=0) # Should be clamped to minimum 1.3 assert new_ease == 1.3 def test_sm2_calculate_repetitions_reset(self): """Test repetition calculation with reset.""" algorithm = SM2Algorithm() electron = Electron(repetitions=5) new_repetitions = algorithm.calculate_repetitions(electron, quality=1) assert new_repetitions == 0 # Reset on failure def test_sm2_calculate_repetitions_increment(self): """Test repetition calculation with increment.""" algorithm = SM2Algorithm() electron = Electron(repetitions=2) new_repetitions = algorithm.calculate_repetitions(electron, quality=3) assert new_repetitions == 3 # Increment on success def test_sm2_process_review_quality_1(self): """Test complete review process with quality 1.""" algorithm = SM2Algorithm() electron = Electron(ease=2.5, interval=10, repetitions=5) orbital = Orbital() new_electron = algorithm.process_review(electron, orbital, 1) assert new_electron.repetitions == 0 assert new_electron.interval == 1 assert new_electron.ease == 2.5 def test_sm2_process_review_quality_3(self): """Test complete review process with quality 3.""" algorithm = SM2Algorithm() electron = Electron(ease=2.5, interval=1, repetitions=0) orbital = Orbital(learning_steps=[1, 10]) new_electron = algorithm.process_review(electron, orbital, 3) assert new_electron.repetitions == 1 assert new_electron.interval == 1 assert new_electron.ease == pytest.approx(2.54) def test_sm2_process_review_quality_5(self): """Test complete review process with quality 5.""" algorithm = SM2Algorithm() electron = Electron(ease=2.5, interval=10, repetitions=5) orbital = Orbital() new_electron = algorithm.process_review(electron, orbital, 5) assert new_electron.repetitions == 6 assert new_electron.interval == 25 # 10 * 2.5 assert new_electron.ease == pytest.approx(2.54) def test_sm2_get_next_review_date(self): """Test next review date calculation.""" algorithm = SM2Algorithm() electron = Electron(interval=5) # Mock current time current_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) next_review = algorithm.get_next_review_date(electron, current_time) expected_date = datetime(2024, 1, 6, 12, 0, 0, tzinfo=timezone.utc) assert next_review == expected_date def test_sm2_get_next_review_date_no_interval(self): """Test next review date with zero interval.""" algorithm = SM2Algorithm() electron = Electron(interval=0) current_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) next_review = algorithm.get_next_review_date(electron, current_time) assert next_review == current_time def test_sm2_algorithm_boundary_conditions(self): """Test boundary conditions for SM2 algorithm.""" algorithm = SM2Algorithm() # Test with minimum ease electron = Electron(ease=1.3) orbital = Orbital() new_electron = algorithm.process_review(electron, orbital, 0) assert new_electron.ease == 1.3 # Should not go below minimum # Test with maximum repetitions electron = Electron(repetitions=100) new_electron = algorithm.process_review(electron, orbital, 4) assert new_electron.repetitions == 101 # Should continue incrementing def test_sm2_algorithm_validation(self): """Test input validation for SM2 algorithm.""" algorithm = SM2Algorithm() electron = Electron() orbital = Orbital() # Test invalid quality values with pytest.raises(ValueError): algorithm.process_review(electron, orbital, -1) with pytest.raises(ValueError): algorithm.process_review(electron, orbital, 6) # Test with None electron with pytest.raises(TypeError): algorithm.process_review(None, orbital, 3) # Test with None orbital with pytest.raises(TypeError): algorithm.process_review(electron, None, 3)