fishertools 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fishertools/__init__.py +82 -0
- fishertools/config/__init__.py +24 -0
- fishertools/config/manager.py +247 -0
- fishertools/config/models.py +96 -0
- fishertools/config/parser.py +265 -0
- fishertools/decorators.py +93 -0
- fishertools/documentation/__init__.py +38 -0
- fishertools/documentation/api.py +242 -0
- fishertools/documentation/generator.py +502 -0
- fishertools/documentation/models.py +126 -0
- fishertools/documentation/visual.py +583 -0
- fishertools/errors/__init__.py +29 -0
- fishertools/errors/exceptions.py +191 -0
- fishertools/errors/explainer.py +303 -0
- fishertools/errors/formatters.py +386 -0
- fishertools/errors/models.py +228 -0
- fishertools/errors/patterns.py +119 -0
- fishertools/errors/recovery.py +467 -0
- fishertools/examples/__init__.py +22 -0
- fishertools/examples/models.py +118 -0
- fishertools/examples/repository.py +770 -0
- fishertools/helpers.py +116 -0
- fishertools/integration.py +451 -0
- fishertools/learn/__init__.py +18 -0
- fishertools/learn/examples.py +550 -0
- fishertools/learn/tips.py +281 -0
- fishertools/learning/__init__.py +32 -0
- fishertools/learning/core.py +349 -0
- fishertools/learning/models.py +112 -0
- fishertools/learning/progress.py +314 -0
- fishertools/learning/session.py +500 -0
- fishertools/learning/tutorial.py +626 -0
- fishertools/legacy/__init__.py +76 -0
- fishertools/legacy/deprecated.py +261 -0
- fishertools/legacy/deprecation.py +149 -0
- fishertools/safe/__init__.py +16 -0
- fishertools/safe/collections.py +242 -0
- fishertools/safe/files.py +240 -0
- fishertools/safe/strings.py +15 -0
- fishertools/utils.py +57 -0
- fishertools-0.2.1.dist-info/METADATA +256 -0
- fishertools-0.2.1.dist-info/RECORD +81 -0
- fishertools-0.2.1.dist-info/WHEEL +5 -0
- fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
- fishertools-0.2.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +6 -0
- tests/conftest.py +25 -0
- tests/test_config/__init__.py +3 -0
- tests/test_config/test_basic_config.py +57 -0
- tests/test_config/test_config_error_handling.py +287 -0
- tests/test_config/test_config_properties.py +435 -0
- tests/test_documentation/__init__.py +3 -0
- tests/test_documentation/test_documentation_properties.py +253 -0
- tests/test_documentation/test_visual_documentation_properties.py +444 -0
- tests/test_errors/__init__.py +3 -0
- tests/test_errors/test_api.py +301 -0
- tests/test_errors/test_error_handling.py +354 -0
- tests/test_errors/test_explainer.py +173 -0
- tests/test_errors/test_formatters.py +338 -0
- tests/test_errors/test_models.py +248 -0
- tests/test_errors/test_patterns.py +270 -0
- tests/test_examples/__init__.py +3 -0
- tests/test_examples/test_example_repository_properties.py +204 -0
- tests/test_examples/test_specific_examples.py +303 -0
- tests/test_integration.py +298 -0
- tests/test_integration_enhancements.py +462 -0
- tests/test_learn/__init__.py +3 -0
- tests/test_learn/test_examples.py +221 -0
- tests/test_learn/test_tips.py +285 -0
- tests/test_learning/__init__.py +3 -0
- tests/test_learning/test_interactive_learning_properties.py +337 -0
- tests/test_learning/test_learning_system_properties.py +194 -0
- tests/test_learning/test_progress_tracking_properties.py +279 -0
- tests/test_legacy/__init__.py +3 -0
- tests/test_legacy/test_backward_compatibility.py +236 -0
- tests/test_legacy/test_deprecation_warnings.py +208 -0
- tests/test_safe/__init__.py +3 -0
- tests/test_safe/test_collections_properties.py +189 -0
- tests/test_safe/test_files.py +104 -0
- tests/test_structure.py +58 -0
- tests/test_structure_enhancements.py +115 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests for fishertools enhancements.
|
|
3
|
+
|
|
4
|
+
Tests the interaction between all enhancement components:
|
|
5
|
+
- Learning System integration with Tutorial Engine and Example Repository
|
|
6
|
+
- Documentation Generator integration with Visual Documentation
|
|
7
|
+
- Error recovery and graceful degradation
|
|
8
|
+
- End-to-end learning scenarios
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
import tempfile
|
|
13
|
+
import os
|
|
14
|
+
from unittest.mock import Mock, patch
|
|
15
|
+
from fishertools.integration import FishertoolsIntegration, get_integration, reset_integration
|
|
16
|
+
from fishertools.learning.models import DifficultyLevel
|
|
17
|
+
from fishertools.errors.recovery import ErrorSeverity, RecoveryStrategy
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestComponentIntegration:
|
|
21
|
+
"""Test integration between different components."""
|
|
22
|
+
|
|
23
|
+
def setup_method(self):
|
|
24
|
+
"""Set up test environment."""
|
|
25
|
+
reset_integration()
|
|
26
|
+
self.integration = FishertoolsIntegration(project_name="test_project")
|
|
27
|
+
|
|
28
|
+
def teardown_method(self):
|
|
29
|
+
"""Clean up after tests."""
|
|
30
|
+
reset_integration()
|
|
31
|
+
|
|
32
|
+
def test_learning_system_tutorial_engine_integration(self):
|
|
33
|
+
"""Test that Learning System properly integrates with Tutorial Engine."""
|
|
34
|
+
# Start a learning session
|
|
35
|
+
session = self.integration.start_learning_session("variables", "beginner", "test_user")
|
|
36
|
+
|
|
37
|
+
assert session is not None
|
|
38
|
+
assert session.topic == "variables"
|
|
39
|
+
assert session.level == DifficultyLevel.BEGINNER
|
|
40
|
+
assert len(session.exercises) > 0
|
|
41
|
+
|
|
42
|
+
# Verify tutorial engine is connected
|
|
43
|
+
assert self.integration.learning_system._tutorial_engine is not None
|
|
44
|
+
assert self.integration.learning_system._tutorial_engine == self.integration.tutorial_engine
|
|
45
|
+
|
|
46
|
+
def test_learning_system_example_repository_integration(self):
|
|
47
|
+
"""Test that Learning System integrates with Example Repository."""
|
|
48
|
+
# Get examples for a topic
|
|
49
|
+
examples = self.integration.example_repository.get_examples_by_topic("lists")
|
|
50
|
+
assert len(examples) > 0
|
|
51
|
+
|
|
52
|
+
# Start session and verify examples are used
|
|
53
|
+
session = self.integration.start_learning_session("lists", "beginner", "test_user")
|
|
54
|
+
|
|
55
|
+
# Should have interactive session if examples are available
|
|
56
|
+
if hasattr(session, 'interactive_session') and session.interactive_session:
|
|
57
|
+
assert session.interactive_session is not None
|
|
58
|
+
|
|
59
|
+
def test_tutorial_engine_example_repository_integration(self):
|
|
60
|
+
"""Test that Tutorial Engine can access Example Repository."""
|
|
61
|
+
# Set up the integration
|
|
62
|
+
self.integration.tutorial_engine._example_repository = self.integration.example_repository
|
|
63
|
+
|
|
64
|
+
# Test getting related examples
|
|
65
|
+
related_examples = self.integration.tutorial_engine.get_related_examples("variables")
|
|
66
|
+
|
|
67
|
+
# Should return examples if available
|
|
68
|
+
assert isinstance(related_examples, list)
|
|
69
|
+
|
|
70
|
+
def test_documentation_generator_visual_integration(self):
|
|
71
|
+
"""Test that Documentation Generator integrates with Visual Documentation."""
|
|
72
|
+
if not self.integration.doc_generator or not self.integration.visual_docs:
|
|
73
|
+
pytest.skip("Documentation components not available")
|
|
74
|
+
|
|
75
|
+
# Set up integration
|
|
76
|
+
self.integration.doc_generator._visual_docs = self.integration.visual_docs
|
|
77
|
+
|
|
78
|
+
# Test enhanced documentation generation
|
|
79
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
80
|
+
test_module = os.path.join(temp_dir, "test_module.py")
|
|
81
|
+
with open(test_module, 'w') as f:
|
|
82
|
+
f.write('''
|
|
83
|
+
"""Test module for documentation."""
|
|
84
|
+
|
|
85
|
+
def test_function(param1: str, param2: int = 0) -> str:
|
|
86
|
+
"""Test function with parameters.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
param1: First parameter
|
|
90
|
+
param2: Second parameter
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
str: Result string
|
|
94
|
+
"""
|
|
95
|
+
return f"{param1}_{param2}"
|
|
96
|
+
''')
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
result = self.integration.doc_generator.generate_enhanced_documentation([test_module])
|
|
100
|
+
|
|
101
|
+
assert 'sphinx_docs' in result
|
|
102
|
+
assert 'visual_artifacts' in result
|
|
103
|
+
assert 'enhanced_files' in result
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
# Documentation generation might fail in test environment
|
|
107
|
+
pytest.skip(f"Documentation generation failed: {e}")
|
|
108
|
+
|
|
109
|
+
def test_session_manager_integration(self):
|
|
110
|
+
"""Test that Session Manager integrates with other components."""
|
|
111
|
+
if not self.integration.session_manager:
|
|
112
|
+
pytest.skip("Session manager not available")
|
|
113
|
+
|
|
114
|
+
# Create session from example
|
|
115
|
+
examples = self.integration.example_repository.get_examples_by_topic("variables")
|
|
116
|
+
if examples:
|
|
117
|
+
session = self.integration.session_manager.create_session_from_example(
|
|
118
|
+
"test_user", examples[0]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert session is not None
|
|
122
|
+
assert len(session.exercises) > 0
|
|
123
|
+
assert session.topic in examples[0].topics
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestEndToEndScenarios:
|
|
127
|
+
"""Test complete end-to-end learning scenarios."""
|
|
128
|
+
|
|
129
|
+
def setup_method(self):
|
|
130
|
+
"""Set up test environment."""
|
|
131
|
+
reset_integration()
|
|
132
|
+
self.integration = FishertoolsIntegration(project_name="test_project")
|
|
133
|
+
|
|
134
|
+
def teardown_method(self):
|
|
135
|
+
"""Clean up after tests."""
|
|
136
|
+
reset_integration()
|
|
137
|
+
|
|
138
|
+
def test_complete_learning_flow(self):
|
|
139
|
+
"""Test a complete learning flow from start to finish."""
|
|
140
|
+
user_id = "test_learner"
|
|
141
|
+
topic = "variables"
|
|
142
|
+
|
|
143
|
+
# 1. Start learning session
|
|
144
|
+
session = self.integration.start_learning_session(topic, "beginner", user_id)
|
|
145
|
+
assert session is not None
|
|
146
|
+
|
|
147
|
+
# 2. Get step-by-step explanation for code
|
|
148
|
+
code = "name = 'Alice'"
|
|
149
|
+
explanation_result = self.integration.explain_code_with_examples(code, include_visuals=False)
|
|
150
|
+
|
|
151
|
+
assert 'step_explanations' in explanation_result
|
|
152
|
+
assert 'related_examples' in explanation_result
|
|
153
|
+
assert 'concepts_covered' in explanation_result
|
|
154
|
+
assert len(explanation_result['step_explanations']) > 0
|
|
155
|
+
|
|
156
|
+
# 3. Get learning recommendations
|
|
157
|
+
recommendations = self.integration.get_learning_recommendations(user_id, topic)
|
|
158
|
+
|
|
159
|
+
assert 'next_topics' in recommendations
|
|
160
|
+
assert 'recommended_examples' in recommendations
|
|
161
|
+
assert 'progress_summary' in recommendations
|
|
162
|
+
|
|
163
|
+
def test_learning_with_progress_tracking(self):
|
|
164
|
+
"""Test learning scenario with progress tracking."""
|
|
165
|
+
user_id = "progress_user"
|
|
166
|
+
|
|
167
|
+
# Start multiple learning sessions
|
|
168
|
+
topics = ["variables", "lists", "functions"]
|
|
169
|
+
|
|
170
|
+
for topic in topics:
|
|
171
|
+
session = self.integration.start_learning_session(topic, "beginner", user_id)
|
|
172
|
+
assert session is not None
|
|
173
|
+
|
|
174
|
+
# Simulate completing the topic
|
|
175
|
+
if self.integration.learning_system:
|
|
176
|
+
self.integration.learning_system.track_progress(user_id, topic, True)
|
|
177
|
+
|
|
178
|
+
# Get final recommendations
|
|
179
|
+
recommendations = self.integration.get_learning_recommendations(user_id)
|
|
180
|
+
|
|
181
|
+
if recommendations.get('progress_summary'):
|
|
182
|
+
progress = recommendations['progress_summary']
|
|
183
|
+
assert 'completed_topics' in progress
|
|
184
|
+
assert len(progress['completed_topics']) <= len(topics)
|
|
185
|
+
|
|
186
|
+
def test_code_explanation_with_examples(self):
|
|
187
|
+
"""Test comprehensive code explanation with examples."""
|
|
188
|
+
# Test with different types of code
|
|
189
|
+
test_codes = [
|
|
190
|
+
"x = 5",
|
|
191
|
+
"numbers = [1, 2, 3]",
|
|
192
|
+
"def greet(name): return f'Hello, {name}!'",
|
|
193
|
+
"for item in [1, 2, 3]: print(item)"
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
for code in test_codes:
|
|
197
|
+
result = self.integration.explain_code_with_examples(code, include_visuals=False)
|
|
198
|
+
|
|
199
|
+
assert 'step_explanations' in result
|
|
200
|
+
assert 'related_examples' in result
|
|
201
|
+
assert 'concepts_covered' in result
|
|
202
|
+
|
|
203
|
+
# Should have at least one explanation
|
|
204
|
+
assert len(result['step_explanations']) > 0
|
|
205
|
+
|
|
206
|
+
# Should identify some concepts
|
|
207
|
+
assert len(result['concepts_covered']) > 0
|
|
208
|
+
|
|
209
|
+
def test_documentation_generation_flow(self):
|
|
210
|
+
"""Test complete documentation generation flow."""
|
|
211
|
+
if not self.integration.doc_generator:
|
|
212
|
+
pytest.skip("Documentation generator not available")
|
|
213
|
+
|
|
214
|
+
# Create a temporary module to document
|
|
215
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
216
|
+
test_module = os.path.join(temp_dir, "example_module.py")
|
|
217
|
+
with open(test_module, 'w') as f:
|
|
218
|
+
f.write('''
|
|
219
|
+
"""Example module for testing documentation generation."""
|
|
220
|
+
|
|
221
|
+
class ExampleClass:
|
|
222
|
+
"""An example class for demonstration."""
|
|
223
|
+
|
|
224
|
+
def __init__(self, name: str):
|
|
225
|
+
"""Initialize with a name."""
|
|
226
|
+
self.name = name
|
|
227
|
+
|
|
228
|
+
def greet(self) -> str:
|
|
229
|
+
"""Return a greeting message."""
|
|
230
|
+
return f"Hello, {self.name}!"
|
|
231
|
+
|
|
232
|
+
def example_function(x: int, y: int = 0) -> int:
|
|
233
|
+
"""Add two numbers together.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
x: First number
|
|
237
|
+
y: Second number (default 0)
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
int: Sum of x and y
|
|
241
|
+
"""
|
|
242
|
+
return x + y
|
|
243
|
+
''')
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
# Generate comprehensive documentation
|
|
247
|
+
result = self.integration.generate_comprehensive_documentation([test_module])
|
|
248
|
+
|
|
249
|
+
assert 'sphinx_docs' in result
|
|
250
|
+
assert 'visual_artifacts' in result
|
|
251
|
+
assert 'publish_result' in result
|
|
252
|
+
|
|
253
|
+
# Check that documentation was generated
|
|
254
|
+
sphinx_docs = result['sphinx_docs']
|
|
255
|
+
assert sphinx_docs.source_files
|
|
256
|
+
assert 'index.rst' in sphinx_docs.source_files
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
# Documentation generation might fail in test environment
|
|
260
|
+
pytest.skip(f"Documentation generation failed: {e}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class TestErrorRecoveryIntegration:
|
|
264
|
+
"""Test error recovery and graceful degradation."""
|
|
265
|
+
|
|
266
|
+
def setup_method(self):
|
|
267
|
+
"""Set up test environment."""
|
|
268
|
+
reset_integration()
|
|
269
|
+
|
|
270
|
+
def teardown_method(self):
|
|
271
|
+
"""Clean up after tests."""
|
|
272
|
+
reset_integration()
|
|
273
|
+
|
|
274
|
+
def test_graceful_degradation_on_component_failure(self):
|
|
275
|
+
"""Test that system degrades gracefully when components fail."""
|
|
276
|
+
# Mock a component initialization failure
|
|
277
|
+
with patch('fishertools.learning.TutorialEngine') as mock_tutorial:
|
|
278
|
+
mock_tutorial.side_effect = Exception("Tutorial engine failed")
|
|
279
|
+
|
|
280
|
+
# Should still initialize with minimal components
|
|
281
|
+
integration = FishertoolsIntegration(project_name="test_project")
|
|
282
|
+
|
|
283
|
+
# Should have basic learning system
|
|
284
|
+
assert integration.learning_system is not None
|
|
285
|
+
assert integration.example_repository is not None
|
|
286
|
+
|
|
287
|
+
def test_error_recovery_in_learning_session(self):
|
|
288
|
+
"""Test error recovery during learning session creation."""
|
|
289
|
+
integration = FishertoolsIntegration(project_name="test_project")
|
|
290
|
+
|
|
291
|
+
# Mock session manager to fail
|
|
292
|
+
if integration.session_manager:
|
|
293
|
+
with patch.object(integration.session_manager, 'create_session') as mock_create:
|
|
294
|
+
mock_create.side_effect = Exception("Session creation failed")
|
|
295
|
+
|
|
296
|
+
# Should still create basic tutorial session
|
|
297
|
+
session = integration.start_learning_session("variables", "beginner", "test_user")
|
|
298
|
+
assert session is not None
|
|
299
|
+
|
|
300
|
+
def test_error_recovery_in_documentation_generation(self):
|
|
301
|
+
"""Test error recovery during documentation generation."""
|
|
302
|
+
integration = FishertoolsIntegration(project_name="test_project")
|
|
303
|
+
|
|
304
|
+
if not integration.doc_generator:
|
|
305
|
+
pytest.skip("Documentation generator not available")
|
|
306
|
+
|
|
307
|
+
# Test with invalid module path
|
|
308
|
+
try:
|
|
309
|
+
result = integration.generate_comprehensive_documentation(["/nonexistent/module.py"])
|
|
310
|
+
# Should either succeed with error handling or raise appropriate exception
|
|
311
|
+
assert result is not None or True # Either works or fails gracefully
|
|
312
|
+
except Exception as e:
|
|
313
|
+
# Should be a fishertools error, not a raw exception
|
|
314
|
+
assert "fishertools" in str(type(e)).lower() or "documentation" in str(e).lower()
|
|
315
|
+
|
|
316
|
+
def test_configuration_error_recovery(self):
|
|
317
|
+
"""Test recovery from configuration errors."""
|
|
318
|
+
# Test with invalid configuration path
|
|
319
|
+
integration = FishertoolsIntegration(
|
|
320
|
+
config_path="/nonexistent/config.json",
|
|
321
|
+
project_name="test_project"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Should initialize with default configuration
|
|
325
|
+
assert integration.config is not None
|
|
326
|
+
assert integration.config_manager is not None
|
|
327
|
+
|
|
328
|
+
def test_recovery_manager_integration(self):
|
|
329
|
+
"""Test that recovery manager is properly integrated."""
|
|
330
|
+
integration = FishertoolsIntegration(project_name="test_project")
|
|
331
|
+
|
|
332
|
+
assert integration.recovery_manager is not None
|
|
333
|
+
|
|
334
|
+
# Test error statistics
|
|
335
|
+
stats = integration.recovery_manager.get_error_statistics()
|
|
336
|
+
assert 'total_errors' in stats
|
|
337
|
+
assert 'error_counts_by_type' in stats
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class TestSystemStatus:
|
|
341
|
+
"""Test system status and health checks."""
|
|
342
|
+
|
|
343
|
+
def setup_method(self):
|
|
344
|
+
"""Set up test environment."""
|
|
345
|
+
reset_integration()
|
|
346
|
+
self.integration = FishertoolsIntegration(project_name="test_project")
|
|
347
|
+
|
|
348
|
+
def teardown_method(self):
|
|
349
|
+
"""Clean up after tests."""
|
|
350
|
+
reset_integration()
|
|
351
|
+
|
|
352
|
+
def test_system_status_reporting(self):
|
|
353
|
+
"""Test that system status is properly reported."""
|
|
354
|
+
status = self.integration.get_system_status()
|
|
355
|
+
|
|
356
|
+
# Should have status for all components
|
|
357
|
+
expected_components = [
|
|
358
|
+
'learning_system',
|
|
359
|
+
'documentation_generator',
|
|
360
|
+
'example_repository',
|
|
361
|
+
'visual_documentation',
|
|
362
|
+
'configuration_manager'
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
for component in expected_components:
|
|
366
|
+
assert component in status
|
|
367
|
+
assert status[component] in ['initialized', 'failed']
|
|
368
|
+
|
|
369
|
+
# Should have configuration info
|
|
370
|
+
assert 'current_config' in status
|
|
371
|
+
assert 'total_examples' in status
|
|
372
|
+
|
|
373
|
+
def test_global_integration_instance(self):
|
|
374
|
+
"""Test global integration instance management."""
|
|
375
|
+
# Get global instance
|
|
376
|
+
global_integration = get_integration(project_name="global_test")
|
|
377
|
+
assert global_integration is not None
|
|
378
|
+
|
|
379
|
+
# Should return same instance on subsequent calls
|
|
380
|
+
same_integration = get_integration()
|
|
381
|
+
assert same_integration is global_integration
|
|
382
|
+
|
|
383
|
+
# Reset and get new instance
|
|
384
|
+
reset_integration()
|
|
385
|
+
new_integration = get_integration(project_name="new_test")
|
|
386
|
+
assert new_integration is not global_integration
|
|
387
|
+
|
|
388
|
+
def test_convenience_functions(self):
|
|
389
|
+
"""Test convenience functions work properly."""
|
|
390
|
+
from fishertools.integration import start_learning, explain_code, get_recommendations
|
|
391
|
+
|
|
392
|
+
# Test start_learning convenience function
|
|
393
|
+
session = start_learning("variables", "beginner", "convenience_user")
|
|
394
|
+
assert session is not None
|
|
395
|
+
|
|
396
|
+
# Test explain_code convenience function
|
|
397
|
+
result = explain_code("x = 42", include_visuals=False)
|
|
398
|
+
assert 'step_explanations' in result
|
|
399
|
+
|
|
400
|
+
# Test get_recommendations convenience function
|
|
401
|
+
recommendations = get_recommendations("convenience_user", "variables")
|
|
402
|
+
assert 'next_topics' in recommendations
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class TestConfigurationIntegration:
|
|
406
|
+
"""Test configuration system integration."""
|
|
407
|
+
|
|
408
|
+
def setup_method(self):
|
|
409
|
+
"""Set up test environment."""
|
|
410
|
+
reset_integration()
|
|
411
|
+
|
|
412
|
+
def teardown_method(self):
|
|
413
|
+
"""Clean up after tests."""
|
|
414
|
+
reset_integration()
|
|
415
|
+
|
|
416
|
+
def test_configuration_update_integration(self):
|
|
417
|
+
"""Test that configuration updates are applied to all components."""
|
|
418
|
+
integration = FishertoolsIntegration(project_name="config_test")
|
|
419
|
+
|
|
420
|
+
# Update configuration
|
|
421
|
+
new_config = {
|
|
422
|
+
'default_level': 'intermediate',
|
|
423
|
+
'docs_output_dir': 'custom_docs'
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
integration.update_configuration(new_config)
|
|
428
|
+
|
|
429
|
+
# Verify configuration was updated
|
|
430
|
+
assert integration.config.default_level == 'intermediate'
|
|
431
|
+
|
|
432
|
+
# Verify it was applied to components
|
|
433
|
+
if integration.doc_generator:
|
|
434
|
+
assert integration.doc_generator.output_dir == 'custom_docs'
|
|
435
|
+
|
|
436
|
+
except Exception as e:
|
|
437
|
+
# Configuration update might fail in test environment
|
|
438
|
+
pytest.skip(f"Configuration update failed: {e}")
|
|
439
|
+
|
|
440
|
+
def test_configuration_validation_integration(self):
|
|
441
|
+
"""Test configuration validation during integration."""
|
|
442
|
+
# Test with invalid configuration
|
|
443
|
+
invalid_config = {
|
|
444
|
+
'default_level': 'invalid_level',
|
|
445
|
+
'explanation_verbosity': 'invalid_verbosity'
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
integration = FishertoolsIntegration(project_name="validation_test")
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
integration.update_configuration(invalid_config)
|
|
452
|
+
# Should either succeed with validation or raise appropriate error
|
|
453
|
+
except ValueError as e:
|
|
454
|
+
# Expected for invalid configuration
|
|
455
|
+
assert "configuration" in str(e).lower() or "invalid" in str(e).lower()
|
|
456
|
+
except Exception as e:
|
|
457
|
+
# Other exceptions should be handled gracefully
|
|
458
|
+
assert integration.config is not None # Should still have valid config
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
if __name__ == "__main__":
|
|
462
|
+
pytest.main([__file__])
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the examples module in fishertools.learn.
|
|
3
|
+
|
|
4
|
+
Tests the generate_example function and related utilities for
|
|
5
|
+
generating educational code examples.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from fishertools.learn.examples import (
|
|
10
|
+
generate_example,
|
|
11
|
+
list_available_concepts,
|
|
12
|
+
get_concept_info,
|
|
13
|
+
CODE_EXAMPLES
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestGenerateExample:
|
|
18
|
+
"""Test the generate_example function."""
|
|
19
|
+
|
|
20
|
+
def test_generate_example_valid_concept(self):
|
|
21
|
+
"""Test generating example for a valid concept."""
|
|
22
|
+
result = generate_example("variables")
|
|
23
|
+
|
|
24
|
+
# Should return a formatted string with content
|
|
25
|
+
assert isinstance(result, str)
|
|
26
|
+
assert len(result) > 0
|
|
27
|
+
|
|
28
|
+
# Should contain expected sections
|
|
29
|
+
assert "Переменные и типы данных" in result
|
|
30
|
+
assert "Описание:" in result
|
|
31
|
+
assert "Пример кода:" in result
|
|
32
|
+
assert "Совет:" in result
|
|
33
|
+
|
|
34
|
+
# Should contain actual code content
|
|
35
|
+
assert "name = " in result
|
|
36
|
+
assert "print(" in result
|
|
37
|
+
|
|
38
|
+
def test_generate_example_invalid_concept(self):
|
|
39
|
+
"""Test generating example for an invalid concept."""
|
|
40
|
+
result = generate_example("nonexistent_concept")
|
|
41
|
+
|
|
42
|
+
# Should return error message
|
|
43
|
+
assert "❌ Концепция 'nonexistent_concept' не найдена" in result
|
|
44
|
+
assert "📚 Доступные концепции:" in result
|
|
45
|
+
|
|
46
|
+
def test_generate_example_case_insensitive(self):
|
|
47
|
+
"""Test that concept matching is case insensitive."""
|
|
48
|
+
result1 = generate_example("VARIABLES")
|
|
49
|
+
result2 = generate_example("variables")
|
|
50
|
+
result3 = generate_example("Variables")
|
|
51
|
+
|
|
52
|
+
# All should produce the same result
|
|
53
|
+
assert "Переменные и типы данных" in result1
|
|
54
|
+
assert "Переменные и типы данных" in result2
|
|
55
|
+
assert "Переменные и типы данных" in result3
|
|
56
|
+
|
|
57
|
+
def test_generate_example_whitespace_handling(self):
|
|
58
|
+
"""Test that whitespace is handled correctly."""
|
|
59
|
+
result = generate_example(" variables ")
|
|
60
|
+
|
|
61
|
+
# Should work despite extra whitespace
|
|
62
|
+
assert "Переменные и типы данных" in result
|
|
63
|
+
|
|
64
|
+
def test_all_concepts_generate_valid_examples(self):
|
|
65
|
+
"""Test that all available concepts generate valid examples."""
|
|
66
|
+
concepts = list_available_concepts()
|
|
67
|
+
|
|
68
|
+
for concept in concepts:
|
|
69
|
+
result = generate_example(concept)
|
|
70
|
+
|
|
71
|
+
# Each should return a valid formatted example
|
|
72
|
+
assert isinstance(result, str)
|
|
73
|
+
assert len(result) > 100 # Should be substantial content
|
|
74
|
+
assert "Описание:" in result
|
|
75
|
+
assert "Пример кода:" in result
|
|
76
|
+
assert "Совет:" in result
|
|
77
|
+
|
|
78
|
+
# Should not contain error messages (but may contain ❌ in educational content)
|
|
79
|
+
assert "не найдена" not in result
|
|
80
|
+
assert "Доступные концепции:" not in result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TestListAvailableConcepts:
|
|
84
|
+
"""Test the list_available_concepts function."""
|
|
85
|
+
|
|
86
|
+
def test_returns_list(self):
|
|
87
|
+
"""Test that function returns a list."""
|
|
88
|
+
result = list_available_concepts()
|
|
89
|
+
assert isinstance(result, list)
|
|
90
|
+
|
|
91
|
+
def test_contains_expected_concepts(self):
|
|
92
|
+
"""Test that list contains expected concepts."""
|
|
93
|
+
concepts = list_available_concepts()
|
|
94
|
+
|
|
95
|
+
# Should contain core Python concepts
|
|
96
|
+
expected_concepts = [
|
|
97
|
+
"variables", "lists", "dictionaries",
|
|
98
|
+
"functions", "loops", "conditionals", "file_operations"
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for concept in expected_concepts:
|
|
102
|
+
assert concept in concepts
|
|
103
|
+
|
|
104
|
+
def test_all_concepts_have_data(self):
|
|
105
|
+
"""Test that all listed concepts have corresponding data."""
|
|
106
|
+
concepts = list_available_concepts()
|
|
107
|
+
|
|
108
|
+
for concept in concepts:
|
|
109
|
+
assert concept in CODE_EXAMPLES
|
|
110
|
+
assert "title" in CODE_EXAMPLES[concept]
|
|
111
|
+
assert "description" in CODE_EXAMPLES[concept]
|
|
112
|
+
assert "code" in CODE_EXAMPLES[concept]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TestGetConceptInfo:
|
|
116
|
+
"""Test the get_concept_info function."""
|
|
117
|
+
|
|
118
|
+
def test_valid_concept_returns_info(self):
|
|
119
|
+
"""Test getting info for a valid concept."""
|
|
120
|
+
info = get_concept_info("variables")
|
|
121
|
+
|
|
122
|
+
assert info is not None
|
|
123
|
+
assert isinstance(info, dict)
|
|
124
|
+
assert "title" in info
|
|
125
|
+
assert "description" in info
|
|
126
|
+
|
|
127
|
+
# Should contain expected content
|
|
128
|
+
assert "Переменные и типы данных" in info["title"]
|
|
129
|
+
assert "Основы работы с переменными" in info["description"]
|
|
130
|
+
|
|
131
|
+
def test_invalid_concept_returns_none(self):
|
|
132
|
+
"""Test getting info for an invalid concept."""
|
|
133
|
+
info = get_concept_info("nonexistent")
|
|
134
|
+
assert info is None
|
|
135
|
+
|
|
136
|
+
def test_case_insensitive_lookup(self):
|
|
137
|
+
"""Test that concept lookup is case insensitive."""
|
|
138
|
+
info1 = get_concept_info("VARIABLES")
|
|
139
|
+
info2 = get_concept_info("variables")
|
|
140
|
+
|
|
141
|
+
assert info1 is not None
|
|
142
|
+
assert info2 is not None
|
|
143
|
+
assert info1["title"] == info2["title"]
|
|
144
|
+
|
|
145
|
+
def test_whitespace_handling(self):
|
|
146
|
+
"""Test that whitespace is handled in concept lookup."""
|
|
147
|
+
info = get_concept_info(" variables ")
|
|
148
|
+
|
|
149
|
+
assert info is not None
|
|
150
|
+
assert "Переменные и типы данных" in info["title"]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TestCodeExamplesData:
|
|
154
|
+
"""Test the CODE_EXAMPLES data structure."""
|
|
155
|
+
|
|
156
|
+
def test_all_examples_have_required_fields(self):
|
|
157
|
+
"""Test that all code examples have required fields."""
|
|
158
|
+
for concept, data in CODE_EXAMPLES.items():
|
|
159
|
+
assert "title" in data
|
|
160
|
+
assert "description" in data
|
|
161
|
+
assert "code" in data
|
|
162
|
+
|
|
163
|
+
# Fields should not be empty
|
|
164
|
+
assert len(data["title"]) > 0
|
|
165
|
+
assert len(data["description"]) > 0
|
|
166
|
+
assert len(data["code"]) > 0
|
|
167
|
+
|
|
168
|
+
def test_code_examples_contain_actual_code(self):
|
|
169
|
+
"""Test that code examples contain actual Python code."""
|
|
170
|
+
for concept, data in CODE_EXAMPLES.items():
|
|
171
|
+
code = data["code"]
|
|
172
|
+
|
|
173
|
+
# Should contain Python-like syntax
|
|
174
|
+
assert any(keyword in code for keyword in ["print(", "def ", "=", "if ", "for "])
|
|
175
|
+
|
|
176
|
+
def test_examples_are_educational(self):
|
|
177
|
+
"""Test that examples contain educational content."""
|
|
178
|
+
for concept, data in CODE_EXAMPLES.items():
|
|
179
|
+
code = data["code"]
|
|
180
|
+
|
|
181
|
+
# Should contain comments (educational explanations)
|
|
182
|
+
assert "#" in code
|
|
183
|
+
|
|
184
|
+
# Should contain Russian explanations
|
|
185
|
+
assert any(char in code for char in "абвгдеёжзийклмнопрстуфхцчшщъыьэюя")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class TestIntegration:
|
|
189
|
+
"""Integration tests for the examples module."""
|
|
190
|
+
|
|
191
|
+
def test_complete_workflow(self):
|
|
192
|
+
"""Test complete workflow of discovering and generating examples."""
|
|
193
|
+
# Get available concepts
|
|
194
|
+
concepts = list_available_concepts()
|
|
195
|
+
assert len(concepts) > 0
|
|
196
|
+
|
|
197
|
+
# Get info for first concept
|
|
198
|
+
first_concept = concepts[0]
|
|
199
|
+
info = get_concept_info(first_concept)
|
|
200
|
+
assert info is not None
|
|
201
|
+
|
|
202
|
+
# Generate example for the concept
|
|
203
|
+
example = generate_example(first_concept)
|
|
204
|
+
assert "❌" not in example # Should not be an error
|
|
205
|
+
assert info["title"] in example # Should contain the title
|
|
206
|
+
|
|
207
|
+
def test_error_handling_consistency(self):
|
|
208
|
+
"""Test that error handling is consistent across functions."""
|
|
209
|
+
invalid_concept = "definitely_not_a_concept"
|
|
210
|
+
|
|
211
|
+
# generate_example should return error message
|
|
212
|
+
example_result = generate_example(invalid_concept)
|
|
213
|
+
assert "❌" in example_result
|
|
214
|
+
|
|
215
|
+
# get_concept_info should return None
|
|
216
|
+
info_result = get_concept_info(invalid_concept)
|
|
217
|
+
assert info_result is None
|
|
218
|
+
|
|
219
|
+
# list_available_concepts should not include invalid concept
|
|
220
|
+
concepts = list_available_concepts()
|
|
221
|
+
assert invalid_concept not in concepts
|