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.
Files changed (81) hide show
  1. fishertools/__init__.py +82 -0
  2. fishertools/config/__init__.py +24 -0
  3. fishertools/config/manager.py +247 -0
  4. fishertools/config/models.py +96 -0
  5. fishertools/config/parser.py +265 -0
  6. fishertools/decorators.py +93 -0
  7. fishertools/documentation/__init__.py +38 -0
  8. fishertools/documentation/api.py +242 -0
  9. fishertools/documentation/generator.py +502 -0
  10. fishertools/documentation/models.py +126 -0
  11. fishertools/documentation/visual.py +583 -0
  12. fishertools/errors/__init__.py +29 -0
  13. fishertools/errors/exceptions.py +191 -0
  14. fishertools/errors/explainer.py +303 -0
  15. fishertools/errors/formatters.py +386 -0
  16. fishertools/errors/models.py +228 -0
  17. fishertools/errors/patterns.py +119 -0
  18. fishertools/errors/recovery.py +467 -0
  19. fishertools/examples/__init__.py +22 -0
  20. fishertools/examples/models.py +118 -0
  21. fishertools/examples/repository.py +770 -0
  22. fishertools/helpers.py +116 -0
  23. fishertools/integration.py +451 -0
  24. fishertools/learn/__init__.py +18 -0
  25. fishertools/learn/examples.py +550 -0
  26. fishertools/learn/tips.py +281 -0
  27. fishertools/learning/__init__.py +32 -0
  28. fishertools/learning/core.py +349 -0
  29. fishertools/learning/models.py +112 -0
  30. fishertools/learning/progress.py +314 -0
  31. fishertools/learning/session.py +500 -0
  32. fishertools/learning/tutorial.py +626 -0
  33. fishertools/legacy/__init__.py +76 -0
  34. fishertools/legacy/deprecated.py +261 -0
  35. fishertools/legacy/deprecation.py +149 -0
  36. fishertools/safe/__init__.py +16 -0
  37. fishertools/safe/collections.py +242 -0
  38. fishertools/safe/files.py +240 -0
  39. fishertools/safe/strings.py +15 -0
  40. fishertools/utils.py +57 -0
  41. fishertools-0.2.1.dist-info/METADATA +256 -0
  42. fishertools-0.2.1.dist-info/RECORD +81 -0
  43. fishertools-0.2.1.dist-info/WHEEL +5 -0
  44. fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
  45. fishertools-0.2.1.dist-info/top_level.txt +2 -0
  46. tests/__init__.py +6 -0
  47. tests/conftest.py +25 -0
  48. tests/test_config/__init__.py +3 -0
  49. tests/test_config/test_basic_config.py +57 -0
  50. tests/test_config/test_config_error_handling.py +287 -0
  51. tests/test_config/test_config_properties.py +435 -0
  52. tests/test_documentation/__init__.py +3 -0
  53. tests/test_documentation/test_documentation_properties.py +253 -0
  54. tests/test_documentation/test_visual_documentation_properties.py +444 -0
  55. tests/test_errors/__init__.py +3 -0
  56. tests/test_errors/test_api.py +301 -0
  57. tests/test_errors/test_error_handling.py +354 -0
  58. tests/test_errors/test_explainer.py +173 -0
  59. tests/test_errors/test_formatters.py +338 -0
  60. tests/test_errors/test_models.py +248 -0
  61. tests/test_errors/test_patterns.py +270 -0
  62. tests/test_examples/__init__.py +3 -0
  63. tests/test_examples/test_example_repository_properties.py +204 -0
  64. tests/test_examples/test_specific_examples.py +303 -0
  65. tests/test_integration.py +298 -0
  66. tests/test_integration_enhancements.py +462 -0
  67. tests/test_learn/__init__.py +3 -0
  68. tests/test_learn/test_examples.py +221 -0
  69. tests/test_learn/test_tips.py +285 -0
  70. tests/test_learning/__init__.py +3 -0
  71. tests/test_learning/test_interactive_learning_properties.py +337 -0
  72. tests/test_learning/test_learning_system_properties.py +194 -0
  73. tests/test_learning/test_progress_tracking_properties.py +279 -0
  74. tests/test_legacy/__init__.py +3 -0
  75. tests/test_legacy/test_backward_compatibility.py +236 -0
  76. tests/test_legacy/test_deprecation_warnings.py +208 -0
  77. tests/test_safe/__init__.py +3 -0
  78. tests/test_safe/test_collections_properties.py +189 -0
  79. tests/test_safe/test_files.py +104 -0
  80. tests/test_structure.py +58 -0
  81. tests/test_structure_enhancements.py +115 -0
@@ -0,0 +1,285 @@
1
+ """
2
+ Unit tests for the tips module in fishertools.learn.
3
+
4
+ Tests the show_best_practice function and related utilities for
5
+ displaying Python best practices.
6
+ """
7
+
8
+ import pytest
9
+ from io import StringIO
10
+ import sys
11
+ from fishertools.learn.tips import (
12
+ show_best_practice,
13
+ list_available_topics,
14
+ get_topic_summary,
15
+ BEST_PRACTICES
16
+ )
17
+
18
+
19
+ class TestShowBestPractice:
20
+ """Test the show_best_practice function."""
21
+
22
+ def capture_output(self, func, *args, **kwargs):
23
+ """Helper method to capture print output."""
24
+ old_stdout = sys.stdout
25
+ sys.stdout = captured_output = StringIO()
26
+ try:
27
+ func(*args, **kwargs)
28
+ return captured_output.getvalue()
29
+ finally:
30
+ sys.stdout = old_stdout
31
+
32
+ def test_show_valid_topic(self):
33
+ """Test showing best practices for a valid topic."""
34
+ output = self.capture_output(show_best_practice, "variables")
35
+
36
+ # Should contain expected sections
37
+ assert "Переменные в Python" in output
38
+ assert "ЛУЧШИЕ ПРАКТИКИ:" in output
39
+ assert "ПРИМЕР КОДА:" in output
40
+
41
+ # Should contain practical advice
42
+ assert "snake_case" in output
43
+ assert "описательные имена" in output
44
+
45
+ # Should contain code examples
46
+ assert "age = " in output or "name = " in output
47
+
48
+ def test_show_invalid_topic(self):
49
+ """Test showing best practices for an invalid topic."""
50
+ output = self.capture_output(show_best_practice, "nonexistent_topic")
51
+
52
+ # Should show error message
53
+ assert "❌ Тема 'nonexistent_topic' не найдена" in output
54
+ assert "📚 Доступные темы:" in output
55
+
56
+ def test_case_insensitive_topic(self):
57
+ """Test that topic matching is case insensitive."""
58
+ output1 = self.capture_output(show_best_practice, "VARIABLES")
59
+ output2 = self.capture_output(show_best_practice, "variables")
60
+
61
+ # Both should show the same content
62
+ assert "Переменные в Python" in output1
63
+ assert "Переменные в Python" in output2
64
+
65
+ def test_whitespace_handling(self):
66
+ """Test that whitespace is handled correctly."""
67
+ output = self.capture_output(show_best_practice, " variables ")
68
+
69
+ # Should work despite extra whitespace
70
+ assert "Переменные в Python" in output
71
+
72
+ def test_all_topics_display_correctly(self):
73
+ """Test that all available topics display correctly."""
74
+ topics = list_available_topics()
75
+
76
+ for topic in topics:
77
+ output = self.capture_output(show_best_practice, topic)
78
+
79
+ # Each should produce valid output
80
+ assert len(output) > 100 # Should be substantial content
81
+ assert "ЛУЧШИЕ ПРАКТИКИ:" in output
82
+ assert "ПРИМЕР КОДА:" in output
83
+
84
+ # Should not contain error messages (but may contain ❌ in educational content)
85
+ assert "не найдена" not in output
86
+ assert "Доступные темы:" not in output
87
+
88
+
89
+ class TestListAvailableTopics:
90
+ """Test the list_available_topics function."""
91
+
92
+ def test_returns_list(self):
93
+ """Test that function returns a list."""
94
+ result = list_available_topics()
95
+ assert isinstance(result, list)
96
+
97
+ def test_contains_expected_topics(self):
98
+ """Test that list contains expected topics."""
99
+ topics = list_available_topics()
100
+
101
+ # Should contain core Python topics
102
+ expected_topics = [
103
+ "variables", "functions", "lists",
104
+ "dictionaries", "error_handling"
105
+ ]
106
+
107
+ for topic in expected_topics:
108
+ assert topic in topics
109
+
110
+ def test_all_topics_have_data(self):
111
+ """Test that all listed topics have corresponding data."""
112
+ topics = list_available_topics()
113
+
114
+ for topic in topics:
115
+ assert topic in BEST_PRACTICES
116
+ assert "title" in BEST_PRACTICES[topic]
117
+ assert "practices" in BEST_PRACTICES[topic]
118
+ assert "example" in BEST_PRACTICES[topic]
119
+
120
+
121
+ class TestGetTopicSummary:
122
+ """Test the get_topic_summary function."""
123
+
124
+ def test_valid_topic_returns_summary(self):
125
+ """Test getting summary for a valid topic."""
126
+ summary = get_topic_summary("variables")
127
+
128
+ assert isinstance(summary, str)
129
+ assert len(summary) > 0
130
+ assert "Переменные в Python" in summary
131
+
132
+ def test_invalid_topic_returns_error(self):
133
+ """Test getting summary for an invalid topic."""
134
+ summary = get_topic_summary("nonexistent")
135
+ assert "не найдена" in summary
136
+
137
+ def test_case_insensitive_lookup(self):
138
+ """Test that topic lookup is case insensitive."""
139
+ summary1 = get_topic_summary("VARIABLES")
140
+ summary2 = get_topic_summary("variables")
141
+
142
+ assert summary1 == summary2
143
+ assert "Переменные в Python" in summary1
144
+
145
+ def test_whitespace_handling(self):
146
+ """Test that whitespace is handled in topic lookup."""
147
+ summary = get_topic_summary(" variables ")
148
+
149
+ assert "Переменные в Python" in summary
150
+
151
+
152
+ class TestBestPracticesData:
153
+ """Test the BEST_PRACTICES data structure."""
154
+
155
+ def test_all_practices_have_required_fields(self):
156
+ """Test that all best practices have required fields."""
157
+ for topic, data in BEST_PRACTICES.items():
158
+ assert "title" in data
159
+ assert "practices" in data
160
+ assert "example" in data
161
+
162
+ # Fields should not be empty
163
+ assert len(data["title"]) > 0
164
+ assert len(data["practices"]) > 0
165
+ assert len(data["example"]) > 0
166
+
167
+ def test_practices_contain_educational_content(self):
168
+ """Test that practices contain educational content."""
169
+ for topic, data in BEST_PRACTICES.items():
170
+ practices = data["practices"]
171
+
172
+ # Should contain visual indicators
173
+ assert "🔹" in practices or "✅" in practices or "❌" in practices
174
+
175
+ # Should contain Russian explanations
176
+ assert any(char in practices for char in "абвгдеёжзийклмнопрстуфхцчшщъыьэюя")
177
+
178
+ def test_examples_contain_actual_code(self):
179
+ """Test that examples contain actual Python code."""
180
+ for topic, data in BEST_PRACTICES.items():
181
+ example = data["example"]
182
+
183
+ # Should contain Python-like syntax
184
+ assert any(keyword in example for keyword in ["def ", "=", "print(", "if ", "for "])
185
+
186
+ def test_examples_are_educational(self):
187
+ """Test that examples contain educational content."""
188
+ for topic, data in BEST_PRACTICES.items():
189
+ example = data["example"]
190
+
191
+ # Should contain comments (educational explanations)
192
+ assert "#" in example or '"""' in example
193
+
194
+ # Should contain Russian explanations in comments or strings
195
+ assert any(char in example for char in "абвгдеёжзийклмнопрстуфхцчшщъыьэюя")
196
+
197
+
198
+ class TestContentQuality:
199
+ """Test the quality and accuracy of educational content."""
200
+
201
+ def test_variables_topic_content(self):
202
+ """Test specific content for variables topic."""
203
+ data = BEST_PRACTICES["variables"]
204
+
205
+ # Should mention key concepts
206
+ assert "snake_case" in data["practices"]
207
+ assert "описательные имена" in data["practices"]
208
+ assert "константы" in data["practices"]
209
+
210
+ # Example should demonstrate good practices
211
+ example = data["example"]
212
+ assert "user_name" in example or "first_name" in example # snake_case
213
+ assert "MAX_" in example # constants
214
+
215
+ def test_functions_topic_content(self):
216
+ """Test specific content for functions topic."""
217
+ data = BEST_PRACTICES["functions"]
218
+
219
+ # Should mention key concepts
220
+ assert "docstring" in data["practices"]
221
+ assert "type hints" in data["practices"]
222
+
223
+ # Example should demonstrate good practices
224
+ example = data["example"]
225
+ assert '"""' in example # docstring
226
+ assert "->" in example or ":" in example # type hints or parameters
227
+
228
+ def test_error_handling_topic_content(self):
229
+ """Test specific content for error handling topic."""
230
+ data = BEST_PRACTICES["error_handling"]
231
+
232
+ # Should mention key concepts
233
+ assert "try" in data["practices"] or "except" in data["practices"]
234
+ assert "finally" in data["practices"] or "контекстные менеджеры" in data["practices"]
235
+
236
+ # Example should demonstrate error handling
237
+ example = data["example"]
238
+ assert "try:" in example
239
+ assert "except" in example
240
+
241
+
242
+ class TestIntegration:
243
+ """Integration tests for the tips module."""
244
+
245
+ def test_complete_workflow(self):
246
+ """Test complete workflow of discovering and showing best practices."""
247
+ # Get available topics
248
+ topics = list_available_topics()
249
+ assert len(topics) > 0
250
+
251
+ # Get summary for first topic
252
+ first_topic = topics[0]
253
+ summary = get_topic_summary(first_topic)
254
+ assert "не найдена" not in summary
255
+
256
+ # Show best practices for the topic
257
+ output = self.capture_output(show_best_practice, first_topic)
258
+ assert "не найдена" not in output # Should not be an error
259
+ assert summary in output # Should contain the title
260
+
261
+ def test_error_handling_consistency(self):
262
+ """Test that error handling is consistent across functions."""
263
+ invalid_topic = "definitely_not_a_topic"
264
+
265
+ # show_best_practice should display error message
266
+ output = self.capture_output(show_best_practice, invalid_topic)
267
+ assert "❌" in output
268
+
269
+ # get_topic_summary should return error message
270
+ summary = get_topic_summary(invalid_topic)
271
+ assert "не найдена" in summary
272
+
273
+ # list_available_topics should not include invalid topic
274
+ topics = list_available_topics()
275
+ assert invalid_topic not in topics
276
+
277
+ def capture_output(self, func, *args, **kwargs):
278
+ """Helper method to capture print output."""
279
+ old_stdout = sys.stdout
280
+ sys.stdout = captured_output = StringIO()
281
+ try:
282
+ func(*args, **kwargs)
283
+ return captured_output.getvalue()
284
+ finally:
285
+ sys.stdout = old_stdout
@@ -0,0 +1,3 @@
1
+ """
2
+ Tests for the learning system module.
3
+ """
@@ -0,0 +1,337 @@
1
+ """
2
+ Property-based tests for Interactive Learning completeness.
3
+
4
+ Feature: fishertools-enhancements
5
+ Property 5: Interactive Learning Completeness
6
+ Validates: Requirements 5.1, 5.2, 5.3, 5.4, 5.5
7
+ """
8
+
9
+ import pytest
10
+ from hypothesis import given, strategies as st, assume
11
+ from fishertools.learning.session import InteractiveSessionManager
12
+ from fishertools.learning.tutorial import TutorialEngine
13
+ from fishertools.learning.models import DifficultyLevel, ExerciseStatus
14
+
15
+
16
+ class TestInteractiveLearningCompleteness:
17
+ """Property tests for Interactive Learning completeness."""
18
+
19
+ def setup_method(self):
20
+ """Set up test fixtures."""
21
+ self.session_manager = InteractiveSessionManager()
22
+ self.tutorial_engine = TutorialEngine()
23
+
24
+ @given(
25
+ user_id=st.text(min_size=1, max_size=50).filter(lambda x: x.strip()),
26
+ topic=st.sampled_from(["variables", "lists", "functions", "loops", "conditionals"]),
27
+ level=st.sampled_from([DifficultyLevel.BEGINNER, DifficultyLevel.INTERMEDIATE, DifficultyLevel.ADVANCED])
28
+ )
29
+ def test_interactive_session_creation_completeness(self, user_id, topic, level):
30
+ """
31
+ Property 5: For any learning session, the Learning_System should
32
+ provide interactive exercises with proper session management.
33
+
34
+ **Validates: Requirements 5.1**
35
+ """
36
+ assume(len(user_id.strip()) > 0)
37
+
38
+ user_id = user_id.strip()
39
+
40
+ # Test session creation
41
+ session = self.session_manager.create_session(user_id, topic, level)
42
+
43
+ # Property: Session should be created successfully
44
+ assert session is not None, "Interactive session should be created"
45
+ assert session.session_id, "Session should have a unique ID"
46
+ assert session.topic == topic, "Session topic should match requested topic"
47
+ assert session.level == level, "Session level should match requested level"
48
+ assert isinstance(session.exercises, list), "Session should have exercises list"
49
+ assert len(session.exercises) > 0, "Session should have at least one exercise"
50
+ assert session.current_exercise_index == 0, "Should start with first exercise"
51
+ assert not session.is_completed, "New session should not be completed"
52
+
53
+ # Property: Session should be retrievable
54
+ retrieved_session = self.session_manager.get_session(session.session_id)
55
+ assert retrieved_session is not None, "Session should be retrievable"
56
+ assert retrieved_session.session_id == session.session_id, "Retrieved session should match"
57
+
58
+ # Property: Each exercise should be properly formed
59
+ for exercise in session.exercises:
60
+ assert exercise.id, "Exercise should have an ID"
61
+ assert exercise.title, "Exercise should have a title"
62
+ assert exercise.description, "Exercise should have a description"
63
+ assert exercise.starter_code is not None, "Exercise should have starter code"
64
+ assert isinstance(exercise.hints, list), "Exercise should have hints list"
65
+ assert exercise.difficulty_level == level, "Exercise difficulty should match session level"
66
+ assert exercise.topic == topic, "Exercise topic should match session topic"
67
+ assert exercise.status == ExerciseStatus.NOT_STARTED, "New exercises should not be started"
68
+
69
+ @given(
70
+ topic=st.sampled_from(["variables", "lists", "functions", "loops"]),
71
+ level=st.sampled_from([DifficultyLevel.BEGINNER, DifficultyLevel.INTERMEDIATE])
72
+ )
73
+ def test_exercise_creation_completeness(self, topic, level):
74
+ """
75
+ Property 5: For any learning session, the Learning_System should
76
+ create appropriate interactive exercises for the topic and level.
77
+
78
+ **Validates: Requirements 5.1, 5.2**
79
+ """
80
+ # Test exercise creation
81
+ exercise = self.tutorial_engine.create_interactive_exercise(topic, level)
82
+
83
+ # Property: Exercise should be created with all required components
84
+ assert exercise is not None, "Exercise should be created"
85
+ assert exercise.id, "Exercise should have a unique ID"
86
+ assert exercise.title, "Exercise should have a title"
87
+ assert exercise.description, "Exercise should have a description"
88
+ assert exercise.starter_code is not None, "Exercise should have starter code"
89
+ assert exercise.expected_output, "Exercise should have expected output"
90
+ assert isinstance(exercise.hints, list), "Exercise should have hints list"
91
+ assert len(exercise.hints) > 0, "Exercise should have at least one hint"
92
+ assert exercise.difficulty_level == level, "Exercise difficulty should match requested level"
93
+ assert exercise.topic == topic, "Exercise topic should match requested topic"
94
+ assert exercise.status == ExerciseStatus.NOT_STARTED, "New exercise should not be started"
95
+ assert exercise.attempts == 0, "New exercise should have 0 attempts"
96
+ assert exercise.max_attempts > 0, "Exercise should have maximum attempts limit"
97
+
98
+ @given(
99
+ topic=st.sampled_from(["variables", "lists", "functions"]),
100
+ solution=st.text(min_size=1, max_size=200)
101
+ )
102
+ def test_solution_validation_completeness(self, topic, solution):
103
+ """
104
+ Property 5: For any learning session, the Learning_System should
105
+ validate solutions and provide appropriate feedback.
106
+
107
+ **Validates: Requirements 5.2, 5.3**
108
+ """
109
+ assume(len(solution.strip()) > 0)
110
+
111
+ # Create exercise for testing
112
+ exercise = self.tutorial_engine.create_interactive_exercise(topic, DifficultyLevel.BEGINNER)
113
+
114
+ # Test solution validation
115
+ result = self.tutorial_engine.validate_solution(exercise, solution)
116
+
117
+ # Property: Validation should always return a result
118
+ assert result is not None, "Validation should return a result"
119
+ assert hasattr(result, 'is_correct'), "Result should have is_correct attribute"
120
+ assert hasattr(result, 'feedback'), "Result should have feedback attribute"
121
+ assert hasattr(result, 'errors'), "Result should have errors attribute"
122
+ assert hasattr(result, 'suggestions'), "Result should have suggestions attribute"
123
+
124
+ # Property: Feedback should be provided
125
+ assert isinstance(result.feedback, str), "Feedback should be a string"
126
+ assert len(result.feedback) > 0, "Feedback should not be empty"
127
+
128
+ # Property: Errors and suggestions should be lists
129
+ assert isinstance(result.errors, list), "Errors should be a list"
130
+ assert isinstance(result.suggestions, list), "Suggestions should be a list"
131
+
132
+ # Property: If incorrect, should provide helpful information
133
+ if not result.is_correct:
134
+ assert len(result.errors) > 0 or len(result.suggestions) > 0, "Should provide errors or suggestions for incorrect solutions"
135
+
136
+ @given(
137
+ topic=st.sampled_from(["variables", "lists", "functions"]),
138
+ attempt=st.text(max_size=100)
139
+ )
140
+ def test_hint_provision_completeness(self, topic, attempt):
141
+ """
142
+ Property 5: For any learning session, the Learning_System should
143
+ provide hints when users need help.
144
+
145
+ **Validates: Requirements 5.3**
146
+ """
147
+ # Create exercise for testing
148
+ exercise = self.tutorial_engine.create_interactive_exercise(topic, DifficultyLevel.BEGINNER)
149
+
150
+ # Test hint provision
151
+ hint = self.tutorial_engine.provide_hint(exercise, attempt)
152
+
153
+ # Property: Should always provide a hint
154
+ assert hint is not None, "Should provide a hint"
155
+ assert isinstance(hint, str), "Hint should be a string"
156
+ assert len(hint) > 0, "Hint should not be empty"
157
+
158
+ # Property: Hint should be helpful and relevant
159
+ assert len(hint) > 10, "Hint should be substantial enough to be helpful"
160
+
161
+ # Property: Multiple hint requests should work
162
+ hint2 = self.tutorial_engine.provide_hint(exercise, attempt + " more code")
163
+ assert hint2 is not None, "Should provide multiple hints"
164
+ assert isinstance(hint2, str), "Second hint should be a string"
165
+
166
+ @given(
167
+ topic=st.sampled_from(["variables", "lists", "functions"])
168
+ )
169
+ def test_solution_explanation_completeness(self, topic):
170
+ """
171
+ Property 5: For any learning session, the Learning_System should
172
+ provide detailed explanations of exercise solutions.
173
+
174
+ **Validates: Requirements 5.4**
175
+ """
176
+ # Create exercise for testing
177
+ exercise = self.tutorial_engine.create_interactive_exercise(topic, DifficultyLevel.BEGINNER)
178
+
179
+ # Test solution explanation
180
+ explanations = self.tutorial_engine.explain_solution(exercise)
181
+
182
+ # Property: Should provide explanations
183
+ assert explanations is not None, "Should provide solution explanations"
184
+ assert isinstance(explanations, list), "Explanations should be a list"
185
+
186
+ # Property: Each explanation should be well-formed
187
+ for explanation in explanations:
188
+ assert hasattr(explanation, 'step_number'), "Explanation should have step number"
189
+ assert hasattr(explanation, 'description'), "Explanation should have description"
190
+ assert hasattr(explanation, 'code_snippet'), "Explanation should have code snippet"
191
+ assert hasattr(explanation, 'related_concepts'), "Explanation should have related concepts"
192
+
193
+ assert explanation.step_number > 0, "Step number should be positive"
194
+ assert isinstance(explanation.description, str), "Description should be a string"
195
+ assert len(explanation.description) > 0, "Description should not be empty"
196
+ assert isinstance(explanation.code_snippet, str), "Code snippet should be a string"
197
+ assert isinstance(explanation.related_concepts, list), "Related concepts should be a list"
198
+
199
+ @given(
200
+ user_id=st.text(min_size=1, max_size=50).filter(lambda x: x.strip()),
201
+ topic=st.sampled_from(["variables", "lists", "functions"])
202
+ )
203
+ def test_additional_examples_provision_completeness(self, user_id, topic):
204
+ """
205
+ Property 5: For any learning session, the Learning_System should
206
+ provide additional examples when users struggle.
207
+
208
+ **Validates: Requirements 5.5**
209
+ """
210
+ assume(len(user_id.strip()) > 0)
211
+
212
+ user_id = user_id.strip()
213
+
214
+ # Create session
215
+ session = self.session_manager.create_session(user_id, topic, DifficultyLevel.BEGINNER)
216
+
217
+ # Test additional examples provision
218
+ examples = self.session_manager.provide_additional_examples(session.session_id, topic)
219
+
220
+ # Property: Should provide additional examples
221
+ assert examples is not None, "Should provide additional examples"
222
+ assert isinstance(examples, list), "Examples should be a list"
223
+ assert len(examples) > 0, "Should provide at least one example"
224
+
225
+ # Property: Each example should be helpful
226
+ for example in examples:
227
+ assert isinstance(example, str), "Each example should be a string"
228
+ assert len(example) > 0, "Examples should not be empty"
229
+ assert len(example) > 10, "Examples should be substantial enough to be helpful"
230
+
231
+ @given(
232
+ user_id=st.text(min_size=1, max_size=50).filter(lambda x: x.strip()),
233
+ topic=st.sampled_from(["variables", "lists", "functions"])
234
+ )
235
+ def test_session_workflow_completeness(self, user_id, topic):
236
+ """
237
+ Property 5: For any learning session, the complete workflow should
238
+ work from creation to completion.
239
+
240
+ **Validates: Requirements 5.1, 5.2, 5.3, 5.4, 5.5**
241
+ """
242
+ assume(len(user_id.strip()) > 0)
243
+
244
+ user_id = user_id.strip()
245
+
246
+ # Create session
247
+ session = self.session_manager.create_session(user_id, topic, DifficultyLevel.BEGINNER)
248
+
249
+ # Property: Should be able to get current exercise
250
+ current_exercise = self.session_manager.get_current_exercise(session.session_id)
251
+ assert current_exercise is not None, "Should have a current exercise"
252
+
253
+ # Property: Should be able to get session progress
254
+ progress = self.session_manager.get_session_progress(session.session_id)
255
+ assert progress is not None, "Should provide session progress"
256
+ assert 'session_id' in progress, "Progress should include session ID"
257
+ assert 'topic' in progress, "Progress should include topic"
258
+ assert 'current_exercise' in progress, "Progress should include current exercise"
259
+ assert 'total_exercises' in progress, "Progress should include total exercises"
260
+ assert 'progress_percentage' in progress, "Progress should include progress percentage"
261
+
262
+ # Property: Should be able to get hints
263
+ hint = self.session_manager.get_hint(session.session_id)
264
+ assert hint is not None, "Should provide hints"
265
+ assert isinstance(hint, str), "Hint should be a string"
266
+
267
+ # Property: Should be able to submit solutions (even if incorrect)
268
+ test_solution = "# test solution"
269
+ result = self.session_manager.submit_solution(session.session_id, test_solution)
270
+ assert result is not None, "Should validate solutions"
271
+
272
+ # Property: Should be able to complete session
273
+ summary = self.session_manager.complete_session(session.session_id)
274
+ assert summary is not None, "Should provide completion summary"
275
+ assert 'session_id' in summary, "Summary should include session ID"
276
+ assert 'topic' in summary, "Summary should include topic"
277
+ assert 'success_rate' in summary, "Summary should include success rate"
278
+ assert 'feedback' in summary, "Summary should include feedback"
279
+
280
+ def test_error_handling_completeness(self):
281
+ """
282
+ Property 5: Interactive learning system should handle errors gracefully.
283
+
284
+ **Validates: Requirements 5.1, 5.2, 5.3**
285
+ """
286
+ # Property: Invalid session IDs should be handled gracefully
287
+ assert self.session_manager.get_session("invalid_id") is None, "Invalid session ID should return None"
288
+ assert self.session_manager.get_hint("invalid_id") is None, "Invalid session ID should return None for hints"
289
+
290
+ # Property: Invalid user inputs should be handled gracefully
291
+ with pytest.raises(ValueError):
292
+ self.session_manager.create_session("", "topic", DifficultyLevel.BEGINNER)
293
+
294
+ with pytest.raises(ValueError):
295
+ self.session_manager.create_session("user", "", DifficultyLevel.BEGINNER)
296
+
297
+ # Property: Empty solutions should be handled gracefully
298
+ exercise = self.tutorial_engine.create_interactive_exercise("variables", DifficultyLevel.BEGINNER)
299
+ result = self.tutorial_engine.validate_solution(exercise, "")
300
+ assert result is not None, "Should handle empty solutions"
301
+ assert not result.is_correct, "Empty solution should be incorrect"
302
+ assert len(result.feedback) > 0, "Should provide feedback for empty solutions"
303
+
304
+ # Property: Invalid code should be handled gracefully
305
+ invalid_code_result = self.tutorial_engine.validate_solution(exercise, "invalid python code !!!")
306
+ assert invalid_code_result is not None, "Should handle invalid code"
307
+ assert not invalid_code_result.is_correct, "Invalid code should be incorrect"
308
+
309
+ @given(
310
+ topic=st.sampled_from(["variables", "lists", "functions"]),
311
+ code_line=st.text(min_size=1, max_size=100).filter(lambda x: x.strip())
312
+ )
313
+ def test_step_explanation_integration_completeness(self, topic, code_line):
314
+ """
315
+ Property 5: Interactive learning should integrate with step-by-step explanations.
316
+
317
+ **Validates: Requirements 5.4**
318
+ """
319
+ assume(len(code_line.strip()) > 0)
320
+
321
+ # Test step explanation generation
322
+ try:
323
+ explanation = self.tutorial_engine.generate_step_explanation(code_line.strip())
324
+
325
+ # Property: Should generate explanations for valid code
326
+ assert explanation is not None, "Should generate explanation"
327
+ assert hasattr(explanation, 'description'), "Should have description"
328
+ assert hasattr(explanation, 'code_snippet'), "Should have code snippet"
329
+ assert hasattr(explanation, 'related_concepts'), "Should have related concepts"
330
+
331
+ # Property: Explanation should be informative
332
+ assert len(explanation.description) > 0, "Description should not be empty"
333
+ assert explanation.code_snippet == code_line.strip(), "Code snippet should match input"
334
+
335
+ except ValueError:
336
+ # Invalid code should raise ValueError, which is acceptable
337
+ pass