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