File size: 13,650 Bytes
56fd459 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# Tests for ankigen_core/agents/feature_flags.py
import pytest
import os
from unittest.mock import patch, Mock
from dataclasses import dataclass
from ankigen_core.agents.feature_flags import (
AgentMode,
AgentFeatureFlags,
_env_bool,
get_feature_flags,
set_feature_flags,
reset_feature_flags
)
# Test AgentMode enum
def test_agent_mode_values():
"""Test AgentMode enum values"""
assert AgentMode.LEGACY.value == "legacy"
assert AgentMode.AGENT_ONLY.value == "agent_only"
assert AgentMode.HYBRID.value == "hybrid"
assert AgentMode.A_B_TEST.value == "a_b_test"
# Test AgentFeatureFlags
def test_agent_feature_flags_defaults():
"""Test AgentFeatureFlags with default values"""
flags = AgentFeatureFlags()
assert flags.mode == AgentMode.LEGACY
assert flags.enable_subject_expert_agent is False
assert flags.enable_pedagogical_agent is False
assert flags.enable_content_structuring_agent is False
assert flags.enable_generation_coordinator is False
assert flags.enable_content_accuracy_judge is False
assert flags.enable_pedagogical_judge is False
assert flags.enable_clarity_judge is False
assert flags.enable_technical_judge is False
assert flags.enable_completeness_judge is False
assert flags.enable_judge_coordinator is False
assert flags.enable_revision_agent is False
assert flags.enable_enhancement_agent is False
assert flags.enable_multi_agent_generation is False
assert flags.enable_parallel_judging is False
assert flags.enable_agent_handoffs is False
assert flags.enable_agent_tracing is True
assert flags.ab_test_ratio == 0.5
assert flags.ab_test_user_hash is None
assert flags.agent_timeout == 30.0
assert flags.max_agent_retries == 3
assert flags.enable_agent_caching is True
assert flags.min_judge_consensus == 0.6
assert flags.max_revision_iterations == 3
def test_agent_feature_flags_custom_values():
"""Test AgentFeatureFlags with custom values"""
flags = AgentFeatureFlags(
mode=AgentMode.AGENT_ONLY,
enable_subject_expert_agent=True,
enable_pedagogical_agent=True,
enable_content_accuracy_judge=True,
enable_multi_agent_generation=True,
ab_test_ratio=0.7,
agent_timeout=60.0,
max_agent_retries=5,
min_judge_consensus=0.8
)
assert flags.mode == AgentMode.AGENT_ONLY
assert flags.enable_subject_expert_agent is True
assert flags.enable_pedagogical_agent is True
assert flags.enable_content_accuracy_judge is True
assert flags.enable_multi_agent_generation is True
assert flags.ab_test_ratio == 0.7
assert flags.agent_timeout == 60.0
assert flags.max_agent_retries == 5
assert flags.min_judge_consensus == 0.8
@patch.dict(os.environ, {
'ANKIGEN_AGENT_MODE': 'agent_only',
'ANKIGEN_ENABLE_SUBJECT_EXPERT': 'true',
'ANKIGEN_ENABLE_PEDAGOGICAL_AGENT': '1',
'ANKIGEN_ENABLE_CONTENT_JUDGE': 'yes',
'ANKIGEN_ENABLE_MULTI_AGENT_GEN': 'on',
'ANKIGEN_AB_TEST_RATIO': '0.3',
'ANKIGEN_AGENT_TIMEOUT': '45.0',
'ANKIGEN_MAX_AGENT_RETRIES': '5',
'ANKIGEN_MIN_JUDGE_CONSENSUS': '0.7'
}, clear=False)
def test_agent_feature_flags_from_env():
"""Test loading AgentFeatureFlags from environment variables"""
flags = AgentFeatureFlags.from_env()
assert flags.mode == AgentMode.AGENT_ONLY
assert flags.enable_subject_expert_agent is True
assert flags.enable_pedagogical_agent is True
assert flags.enable_content_accuracy_judge is True
assert flags.enable_multi_agent_generation is True
assert flags.ab_test_ratio == 0.3
assert flags.agent_timeout == 45.0
assert flags.max_agent_retries == 5
assert flags.min_judge_consensus == 0.7
@patch.dict(os.environ, {}, clear=True)
def test_agent_feature_flags_from_env_defaults():
"""Test loading AgentFeatureFlags from environment with defaults"""
flags = AgentFeatureFlags.from_env()
assert flags.mode == AgentMode.LEGACY
assert flags.enable_subject_expert_agent is False
assert flags.ab_test_ratio == 0.5
assert flags.agent_timeout == 30.0
assert flags.max_agent_retries == 3
def test_should_use_agents_legacy_mode():
"""Test should_use_agents() in LEGACY mode"""
flags = AgentFeatureFlags(mode=AgentMode.LEGACY)
assert flags.should_use_agents() is False
def test_should_use_agents_agent_only_mode():
"""Test should_use_agents() in AGENT_ONLY mode"""
flags = AgentFeatureFlags(mode=AgentMode.AGENT_ONLY)
assert flags.should_use_agents() is True
def test_should_use_agents_hybrid_mode_no_agents():
"""Test should_use_agents() in HYBRID mode with no agents enabled"""
flags = AgentFeatureFlags(mode=AgentMode.HYBRID)
assert flags.should_use_agents() is False
def test_should_use_agents_hybrid_mode_with_generation_agent():
"""Test should_use_agents() in HYBRID mode with generation agent enabled"""
flags = AgentFeatureFlags(
mode=AgentMode.HYBRID,
enable_subject_expert_agent=True
)
assert flags.should_use_agents() is True
def test_should_use_agents_hybrid_mode_with_judge_agent():
"""Test should_use_agents() in HYBRID mode with judge agent enabled"""
flags = AgentFeatureFlags(
mode=AgentMode.HYBRID,
enable_content_accuracy_judge=True
)
assert flags.should_use_agents() is True
def test_should_use_agents_ab_test_mode_with_hash():
"""Test should_use_agents() in A_B_TEST mode with user hash"""
# Test hash that should result in False (< 50%)
flags = AgentFeatureFlags(
mode=AgentMode.A_B_TEST,
ab_test_ratio=0.5,
ab_test_user_hash="test_user_1" # This should hash to a value < 50%
)
# Hash is deterministic, so we can test specific values
import hashlib
hash_value = int(hashlib.md5("test_user_1".encode()).hexdigest(), 16)
expected_result = (hash_value % 100) < 50
assert flags.should_use_agents() == expected_result
def test_should_use_agents_ab_test_mode_without_hash():
"""Test should_use_agents() in A_B_TEST mode without user hash (random)"""
flags = AgentFeatureFlags(
mode=AgentMode.A_B_TEST,
ab_test_ratio=0.5
)
# Since it's random, we can't test the exact result, but we can test that it returns a boolean
with patch('random.random') as mock_random:
mock_random.return_value = 0.3 # < 0.5, should return True
assert flags.should_use_agents() is True
mock_random.return_value = 0.7 # > 0.5, should return False
assert flags.should_use_agents() is False
def test_get_enabled_agents():
"""Test get_enabled_agents() method"""
flags = AgentFeatureFlags(
enable_subject_expert_agent=True,
enable_pedagogical_agent=False,
enable_content_accuracy_judge=True,
enable_revision_agent=True
)
enabled = flags.get_enabled_agents()
assert enabled["subject_expert"] is True
assert enabled["pedagogical"] is False
assert enabled["content_accuracy_judge"] is True
assert enabled["revision_agent"] is True
assert enabled["enhancement_agent"] is False # Default false
def test_to_dict():
"""Test to_dict() method"""
flags = AgentFeatureFlags(
mode=AgentMode.HYBRID,
enable_subject_expert_agent=True,
enable_multi_agent_generation=True,
enable_agent_tracing=False,
ab_test_ratio=0.3,
agent_timeout=45.0,
max_agent_retries=5,
min_judge_consensus=0.7,
max_revision_iterations=2
)
result = flags.to_dict()
assert result["mode"] == "hybrid"
assert result["enabled_agents"]["subject_expert"] is True
assert result["workflow_features"]["multi_agent_generation"] is True
assert result["workflow_features"]["agent_tracing"] is False
assert result["ab_test_ratio"] == 0.3
assert result["performance_config"]["timeout"] == 45.0
assert result["performance_config"]["max_retries"] == 5
assert result["quality_thresholds"]["min_judge_consensus"] == 0.7
assert result["quality_thresholds"]["max_revision_iterations"] == 2
# Test _env_bool helper function
def test_env_bool_true_values():
"""Test _env_bool() with various true values"""
true_values = ["true", "True", "TRUE", "1", "yes", "Yes", "YES", "on", "On", "ON", "enabled", "ENABLED"]
for value in true_values:
with patch.dict(os.environ, {'TEST_VAR': value}):
assert _env_bool('TEST_VAR') is True
def test_env_bool_false_values():
"""Test _env_bool() with various false values"""
false_values = ["false", "False", "FALSE", "0", "no", "No", "NO", "off", "Off", "OFF", "disabled", "DISABLED", "random"]
for value in false_values:
with patch.dict(os.environ, {'TEST_VAR': value}):
assert _env_bool('TEST_VAR') is False
def test_env_bool_default_true():
"""Test _env_bool() with default=True"""
with patch.dict(os.environ, {}, clear=True):
assert _env_bool('NON_EXISTENT_VAR', default=True) is True
def test_env_bool_default_false():
"""Test _env_bool() with default=False"""
with patch.dict(os.environ, {}, clear=True):
assert _env_bool('NON_EXISTENT_VAR', default=False) is False
# Test global flag management functions
def test_get_feature_flags_first_call():
"""Test get_feature_flags() on first call"""
# Reset the global flag
reset_feature_flags()
with patch('ankigen_core.agents.feature_flags.AgentFeatureFlags.from_env') as mock_from_env:
mock_flags = AgentFeatureFlags(mode=AgentMode.AGENT_ONLY)
mock_from_env.return_value = mock_flags
flags = get_feature_flags()
assert flags == mock_flags
mock_from_env.assert_called_once()
def test_get_feature_flags_subsequent_calls():
"""Test get_feature_flags() on subsequent calls (should use cached value)"""
# Set a known flag first
test_flags = AgentFeatureFlags(mode=AgentMode.HYBRID)
set_feature_flags(test_flags)
with patch('ankigen_core.agents.feature_flags.AgentFeatureFlags.from_env') as mock_from_env:
flags1 = get_feature_flags()
flags2 = get_feature_flags()
assert flags1 == test_flags
assert flags2 == test_flags
# from_env should not be called since we already have cached flags
mock_from_env.assert_not_called()
def test_set_feature_flags():
"""Test set_feature_flags() function"""
test_flags = AgentFeatureFlags(
mode=AgentMode.AGENT_ONLY,
enable_subject_expert_agent=True
)
set_feature_flags(test_flags)
retrieved_flags = get_feature_flags()
assert retrieved_flags == test_flags
assert retrieved_flags.mode == AgentMode.AGENT_ONLY
assert retrieved_flags.enable_subject_expert_agent is True
def test_reset_feature_flags():
"""Test reset_feature_flags() function"""
# Set some flags first
test_flags = AgentFeatureFlags(mode=AgentMode.AGENT_ONLY)
set_feature_flags(test_flags)
# Verify they're set
assert get_feature_flags() == test_flags
# Reset
reset_feature_flags()
# Next call should reload from environment
with patch('ankigen_core.agents.feature_flags.AgentFeatureFlags.from_env') as mock_from_env:
mock_flags = AgentFeatureFlags(mode=AgentMode.HYBRID)
mock_from_env.return_value = mock_flags
flags = get_feature_flags()
assert flags == mock_flags
mock_from_env.assert_called_once()
# Integration tests for specific use cases
def test_feature_flags_production_config():
"""Test typical production configuration"""
flags = AgentFeatureFlags(
mode=AgentMode.HYBRID,
enable_subject_expert_agent=True,
enable_pedagogical_agent=True,
enable_content_accuracy_judge=True,
enable_judge_coordinator=True,
enable_multi_agent_generation=True,
enable_parallel_judging=True,
agent_timeout=60.0,
max_agent_retries=3,
min_judge_consensus=0.7
)
assert flags.should_use_agents() is True
enabled = flags.get_enabled_agents()
assert enabled["subject_expert"] is True
assert enabled["pedagogical"] is True
assert enabled["content_accuracy_judge"] is True
def test_feature_flags_development_config():
"""Test typical development configuration"""
flags = AgentFeatureFlags(
mode=AgentMode.AGENT_ONLY,
enable_subject_expert_agent=True,
enable_pedagogical_agent=True,
enable_content_accuracy_judge=True,
enable_pedagogical_judge=True,
enable_revision_agent=True,
enable_multi_agent_generation=True,
enable_agent_tracing=True,
agent_timeout=30.0,
max_agent_retries=2
)
assert flags.should_use_agents() is True
config_dict = flags.to_dict()
assert config_dict["mode"] == "agent_only"
assert config_dict["workflow_features"]["agent_tracing"] is True
def test_feature_flags_ab_test_consistency():
"""Test A/B test consistency with same user hash"""
flags = AgentFeatureFlags(
mode=AgentMode.A_B_TEST,
ab_test_ratio=0.5,
ab_test_user_hash="consistent_user"
)
# Multiple calls with same hash should return same result
result1 = flags.should_use_agents()
result2 = flags.should_use_agents()
result3 = flags.should_use_agents()
assert result1 == result2 == result3 |