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,435 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property-based tests for configuration management.
|
|
3
|
+
|
|
4
|
+
Feature: fishertools-enhancements
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import tempfile
|
|
9
|
+
import os
|
|
10
|
+
from hypothesis import given, strategies as st, assume
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from fishertools.config.models import LearningConfig
|
|
14
|
+
from fishertools.config.manager import ConfigurationManager
|
|
15
|
+
from fishertools.config.parser import ConfigurationParser
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_valid_json(text: str) -> bool:
|
|
19
|
+
"""Helper function to check if text is valid JSON."""
|
|
20
|
+
try:
|
|
21
|
+
json.loads(text)
|
|
22
|
+
return True
|
|
23
|
+
except (json.JSONDecodeError, ValueError):
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Strategy for generating valid LearningConfig objects
|
|
28
|
+
@st.composite
|
|
29
|
+
def learning_config_strategy(draw):
|
|
30
|
+
"""Generate valid LearningConfig objects for property testing."""
|
|
31
|
+
return LearningConfig(
|
|
32
|
+
default_level=draw(st.sampled_from(["beginner", "intermediate", "advanced"])),
|
|
33
|
+
explanation_verbosity=draw(st.sampled_from(["brief", "detailed", "comprehensive"])),
|
|
34
|
+
visual_aids_enabled=draw(st.booleans()),
|
|
35
|
+
diagram_style=draw(st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')))),
|
|
36
|
+
color_scheme=draw(st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')))),
|
|
37
|
+
progress_tracking_enabled=draw(st.booleans()),
|
|
38
|
+
save_progress_locally=draw(st.booleans()),
|
|
39
|
+
suggested_topics_count=draw(st.integers(min_value=1, max_value=10)),
|
|
40
|
+
max_examples_per_topic=draw(st.integers(min_value=1, max_value=20)),
|
|
41
|
+
exercise_difficulty_progression=draw(st.lists(
|
|
42
|
+
st.sampled_from(["beginner", "intermediate", "advanced"]),
|
|
43
|
+
min_size=1, max_size=5
|
|
44
|
+
)),
|
|
45
|
+
readthedocs_project=draw(st.one_of(
|
|
46
|
+
st.none(),
|
|
47
|
+
st.text(min_size=1, max_size=50, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd', 'Pc')))
|
|
48
|
+
)),
|
|
49
|
+
sphinx_theme=draw(st.text(min_size=1, max_size=30, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd', 'Pc')))),
|
|
50
|
+
enable_interactive_sessions=draw(st.booleans()),
|
|
51
|
+
session_timeout_minutes=draw(st.integers(min_value=1, max_value=180)),
|
|
52
|
+
max_hint_count=draw(st.integers(min_value=0, max_value=10))
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Strategy for generating valid configuration dictionaries
|
|
57
|
+
@st.composite
|
|
58
|
+
def valid_config_dict_strategy(draw):
|
|
59
|
+
"""Generate valid configuration dictionaries for property testing."""
|
|
60
|
+
return {
|
|
61
|
+
"default_level": draw(st.sampled_from(["beginner", "intermediate", "advanced"])),
|
|
62
|
+
"explanation_verbosity": draw(st.sampled_from(["brief", "detailed", "comprehensive"])),
|
|
63
|
+
"visual_aids_enabled": draw(st.booleans()),
|
|
64
|
+
"diagram_style": draw(st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')))),
|
|
65
|
+
"color_scheme": draw(st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')))),
|
|
66
|
+
"progress_tracking_enabled": draw(st.booleans()),
|
|
67
|
+
"save_progress_locally": draw(st.booleans()),
|
|
68
|
+
"suggested_topics_count": draw(st.integers(min_value=1, max_value=10)),
|
|
69
|
+
"max_examples_per_topic": draw(st.integers(min_value=1, max_value=20)),
|
|
70
|
+
"exercise_difficulty_progression": draw(st.lists(
|
|
71
|
+
st.sampled_from(["beginner", "intermediate", "advanced"]),
|
|
72
|
+
min_size=1, max_size=5
|
|
73
|
+
)),
|
|
74
|
+
"readthedocs_project": draw(st.one_of(
|
|
75
|
+
st.none(),
|
|
76
|
+
st.text(min_size=1, max_size=50, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd', 'Pc')))
|
|
77
|
+
)),
|
|
78
|
+
"sphinx_theme": draw(st.text(min_size=1, max_size=30, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd', 'Pc')))),
|
|
79
|
+
"enable_interactive_sessions": draw(st.booleans()),
|
|
80
|
+
"session_timeout_minutes": draw(st.integers(min_value=1, max_value=180)),
|
|
81
|
+
"max_hint_count": draw(st.integers(min_value=0, max_value=10))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Strategy for generating invalid configuration dictionaries
|
|
86
|
+
@st.composite
|
|
87
|
+
def invalid_config_dict_strategy(draw):
|
|
88
|
+
"""Generate invalid configuration dictionaries for property testing."""
|
|
89
|
+
config = {}
|
|
90
|
+
|
|
91
|
+
# Add some valid fields
|
|
92
|
+
if draw(st.booleans()):
|
|
93
|
+
config["default_level"] = draw(st.sampled_from(["beginner", "intermediate", "advanced"]))
|
|
94
|
+
|
|
95
|
+
# Add invalid fields with wrong types or values
|
|
96
|
+
invalid_choices = draw(st.sampled_from([
|
|
97
|
+
"wrong_type_level",
|
|
98
|
+
"wrong_type_verbosity",
|
|
99
|
+
"wrong_type_bool",
|
|
100
|
+
"wrong_type_int",
|
|
101
|
+
"invalid_enum_value",
|
|
102
|
+
"missing_required"
|
|
103
|
+
]))
|
|
104
|
+
|
|
105
|
+
if invalid_choices == "wrong_type_level":
|
|
106
|
+
config["default_level"] = draw(st.integers()) # Should be string
|
|
107
|
+
elif invalid_choices == "wrong_type_verbosity":
|
|
108
|
+
config["explanation_verbosity"] = draw(st.integers()) # Should be string
|
|
109
|
+
elif invalid_choices == "wrong_type_bool":
|
|
110
|
+
config["visual_aids_enabled"] = draw(st.text()) # Should be bool
|
|
111
|
+
elif invalid_choices == "wrong_type_int":
|
|
112
|
+
config["suggested_topics_count"] = draw(st.text()) # Should be int
|
|
113
|
+
elif invalid_choices == "invalid_enum_value":
|
|
114
|
+
config["default_level"] = draw(st.text().filter(lambda x: x not in ["beginner", "intermediate", "advanced"]))
|
|
115
|
+
config["explanation_verbosity"] = "detailed" # Add required field
|
|
116
|
+
elif invalid_choices == "missing_required":
|
|
117
|
+
# Don't add required fields
|
|
118
|
+
config["visual_aids_enabled"] = True
|
|
119
|
+
|
|
120
|
+
return config
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TestConfigurationRoundTrip:
|
|
124
|
+
"""Property tests for configuration round-trip serialization."""
|
|
125
|
+
|
|
126
|
+
@given(config=learning_config_strategy())
|
|
127
|
+
def test_json_round_trip_property(self, config):
|
|
128
|
+
"""
|
|
129
|
+
Property 8: Configuration Serialization Round-trip (JSON)
|
|
130
|
+
|
|
131
|
+
For any valid configuration object, parsing then formatting then parsing
|
|
132
|
+
should produce an equivalent configuration object.
|
|
133
|
+
|
|
134
|
+
Validates: Requirements 7.4
|
|
135
|
+
"""
|
|
136
|
+
# Feature: fishertools-enhancements, Property 8: Configuration Serialization Round-trip
|
|
137
|
+
|
|
138
|
+
parser = ConfigurationParser()
|
|
139
|
+
manager = ConfigurationManager()
|
|
140
|
+
|
|
141
|
+
# Format config to JSON
|
|
142
|
+
json_content = parser.format_to_json(config)
|
|
143
|
+
|
|
144
|
+
# Parse JSON back to dict
|
|
145
|
+
parsed_dict = parser.parse_json(json_content)
|
|
146
|
+
|
|
147
|
+
# Convert back to LearningConfig
|
|
148
|
+
reconstructed_config = manager._dict_to_config(parsed_dict)
|
|
149
|
+
|
|
150
|
+
# Verify equivalence
|
|
151
|
+
assert reconstructed_config.default_level == config.default_level
|
|
152
|
+
assert reconstructed_config.explanation_verbosity == config.explanation_verbosity
|
|
153
|
+
assert reconstructed_config.visual_aids_enabled == config.visual_aids_enabled
|
|
154
|
+
assert reconstructed_config.diagram_style == config.diagram_style
|
|
155
|
+
assert reconstructed_config.color_scheme == config.color_scheme
|
|
156
|
+
assert reconstructed_config.progress_tracking_enabled == config.progress_tracking_enabled
|
|
157
|
+
assert reconstructed_config.save_progress_locally == config.save_progress_locally
|
|
158
|
+
assert reconstructed_config.suggested_topics_count == config.suggested_topics_count
|
|
159
|
+
assert reconstructed_config.max_examples_per_topic == config.max_examples_per_topic
|
|
160
|
+
assert reconstructed_config.exercise_difficulty_progression == config.exercise_difficulty_progression
|
|
161
|
+
assert reconstructed_config.readthedocs_project == config.readthedocs_project
|
|
162
|
+
assert reconstructed_config.sphinx_theme == config.sphinx_theme
|
|
163
|
+
assert reconstructed_config.enable_interactive_sessions == config.enable_interactive_sessions
|
|
164
|
+
assert reconstructed_config.session_timeout_minutes == config.session_timeout_minutes
|
|
165
|
+
assert reconstructed_config.max_hint_count == config.max_hint_count
|
|
166
|
+
|
|
167
|
+
@given(config=learning_config_strategy())
|
|
168
|
+
def test_yaml_round_trip_property(self, config):
|
|
169
|
+
"""
|
|
170
|
+
Property 8: Configuration Serialization Round-trip (YAML)
|
|
171
|
+
|
|
172
|
+
For any valid configuration object, parsing then formatting then parsing
|
|
173
|
+
should produce an equivalent configuration object.
|
|
174
|
+
|
|
175
|
+
Validates: Requirements 7.4
|
|
176
|
+
"""
|
|
177
|
+
# Feature: fishertools-enhancements, Property 8: Configuration Serialization Round-trip
|
|
178
|
+
|
|
179
|
+
parser = ConfigurationParser()
|
|
180
|
+
manager = ConfigurationManager()
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Format config to YAML
|
|
184
|
+
yaml_content = parser.format_to_yaml(config)
|
|
185
|
+
|
|
186
|
+
# Parse YAML back to dict
|
|
187
|
+
parsed_dict = parser.parse_yaml(yaml_content)
|
|
188
|
+
|
|
189
|
+
# Convert back to LearningConfig
|
|
190
|
+
reconstructed_config = manager._dict_to_config(parsed_dict)
|
|
191
|
+
|
|
192
|
+
# Verify equivalence
|
|
193
|
+
assert reconstructed_config.default_level == config.default_level
|
|
194
|
+
assert reconstructed_config.explanation_verbosity == config.explanation_verbosity
|
|
195
|
+
assert reconstructed_config.visual_aids_enabled == config.visual_aids_enabled
|
|
196
|
+
assert reconstructed_config.diagram_style == config.diagram_style
|
|
197
|
+
assert reconstructed_config.color_scheme == config.color_scheme
|
|
198
|
+
assert reconstructed_config.progress_tracking_enabled == config.progress_tracking_enabled
|
|
199
|
+
assert reconstructed_config.save_progress_locally == config.save_progress_locally
|
|
200
|
+
assert reconstructed_config.suggested_topics_count == config.suggested_topics_count
|
|
201
|
+
assert reconstructed_config.max_examples_per_topic == config.max_examples_per_topic
|
|
202
|
+
assert reconstructed_config.exercise_difficulty_progression == config.exercise_difficulty_progression
|
|
203
|
+
assert reconstructed_config.readthedocs_project == config.readthedocs_project
|
|
204
|
+
assert reconstructed_config.sphinx_theme == config.sphinx_theme
|
|
205
|
+
assert reconstructed_config.enable_interactive_sessions == config.enable_interactive_sessions
|
|
206
|
+
assert reconstructed_config.session_timeout_minutes == config.session_timeout_minutes
|
|
207
|
+
assert reconstructed_config.max_hint_count == config.max_hint_count
|
|
208
|
+
|
|
209
|
+
except ValueError as e:
|
|
210
|
+
if "YAML support not available" in str(e):
|
|
211
|
+
pytest.skip("YAML support not available")
|
|
212
|
+
else:
|
|
213
|
+
raise
|
|
214
|
+
|
|
215
|
+
@given(config=learning_config_strategy())
|
|
216
|
+
def test_file_round_trip_property_json(self, config):
|
|
217
|
+
"""
|
|
218
|
+
Property 8: Configuration Serialization Round-trip (File I/O JSON)
|
|
219
|
+
|
|
220
|
+
For any valid configuration object, saving to file then loading
|
|
221
|
+
should produce an equivalent configuration object.
|
|
222
|
+
|
|
223
|
+
Validates: Requirements 7.4
|
|
224
|
+
"""
|
|
225
|
+
# Feature: fishertools-enhancements, Property 8: Configuration Serialization Round-trip
|
|
226
|
+
|
|
227
|
+
manager = ConfigurationManager()
|
|
228
|
+
|
|
229
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
230
|
+
temp_path = f.name
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
# Save config to file
|
|
234
|
+
manager.save_config(config, temp_path)
|
|
235
|
+
|
|
236
|
+
# Load config from file
|
|
237
|
+
loaded_config = manager.load_config(temp_path)
|
|
238
|
+
|
|
239
|
+
# Verify equivalence
|
|
240
|
+
assert loaded_config.default_level == config.default_level
|
|
241
|
+
assert loaded_config.explanation_verbosity == config.explanation_verbosity
|
|
242
|
+
assert loaded_config.visual_aids_enabled == config.visual_aids_enabled
|
|
243
|
+
assert loaded_config.diagram_style == config.diagram_style
|
|
244
|
+
assert loaded_config.color_scheme == config.color_scheme
|
|
245
|
+
assert loaded_config.progress_tracking_enabled == config.progress_tracking_enabled
|
|
246
|
+
assert loaded_config.save_progress_locally == config.save_progress_locally
|
|
247
|
+
assert loaded_config.suggested_topics_count == config.suggested_topics_count
|
|
248
|
+
assert loaded_config.max_examples_per_topic == config.max_examples_per_topic
|
|
249
|
+
assert loaded_config.exercise_difficulty_progression == config.exercise_difficulty_progression
|
|
250
|
+
assert loaded_config.readthedocs_project == config.readthedocs_project
|
|
251
|
+
assert loaded_config.sphinx_theme == config.sphinx_theme
|
|
252
|
+
assert loaded_config.enable_interactive_sessions == config.enable_interactive_sessions
|
|
253
|
+
assert loaded_config.session_timeout_minutes == config.session_timeout_minutes
|
|
254
|
+
assert loaded_config.max_hint_count == config.max_hint_count
|
|
255
|
+
|
|
256
|
+
finally:
|
|
257
|
+
# Clean up
|
|
258
|
+
if os.path.exists(temp_path):
|
|
259
|
+
os.unlink(temp_path)
|
|
260
|
+
|
|
261
|
+
@given(config=learning_config_strategy())
|
|
262
|
+
def test_file_round_trip_property_yaml(self, config):
|
|
263
|
+
"""
|
|
264
|
+
Property 8: Configuration Serialization Round-trip (File I/O YAML)
|
|
265
|
+
|
|
266
|
+
For any valid configuration object, saving to file then loading
|
|
267
|
+
should produce an equivalent configuration object.
|
|
268
|
+
|
|
269
|
+
Validates: Requirements 7.4
|
|
270
|
+
"""
|
|
271
|
+
# Feature: fishertools-enhancements, Property 8: Configuration Serialization Round-trip
|
|
272
|
+
|
|
273
|
+
manager = ConfigurationManager()
|
|
274
|
+
|
|
275
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
|
276
|
+
temp_path = f.name
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
# Save config to file
|
|
280
|
+
manager.save_config(config, temp_path)
|
|
281
|
+
|
|
282
|
+
# Load config from file
|
|
283
|
+
loaded_config = manager.load_config(temp_path)
|
|
284
|
+
|
|
285
|
+
# Verify equivalence
|
|
286
|
+
assert loaded_config.default_level == config.default_level
|
|
287
|
+
assert loaded_config.explanation_verbosity == config.explanation_verbosity
|
|
288
|
+
assert loaded_config.visual_aids_enabled == config.visual_aids_enabled
|
|
289
|
+
assert loaded_config.diagram_style == config.diagram_style
|
|
290
|
+
assert loaded_config.color_scheme == config.color_scheme
|
|
291
|
+
assert loaded_config.progress_tracking_enabled == config.progress_tracking_enabled
|
|
292
|
+
assert loaded_config.save_progress_locally == config.save_progress_locally
|
|
293
|
+
assert loaded_config.suggested_topics_count == config.suggested_topics_count
|
|
294
|
+
assert loaded_config.max_examples_per_topic == config.max_examples_per_topic
|
|
295
|
+
assert loaded_config.exercise_difficulty_progression == config.exercise_difficulty_progression
|
|
296
|
+
assert loaded_config.readthedocs_project == config.readthedocs_project
|
|
297
|
+
assert loaded_config.sphinx_theme == config.sphinx_theme
|
|
298
|
+
assert loaded_config.enable_interactive_sessions == config.enable_interactive_sessions
|
|
299
|
+
assert loaded_config.session_timeout_minutes == config.session_timeout_minutes
|
|
300
|
+
assert loaded_config.max_hint_count == config.max_hint_count
|
|
301
|
+
|
|
302
|
+
except ValueError as e:
|
|
303
|
+
if "YAML support not available" in str(e):
|
|
304
|
+
pytest.skip("YAML support not available")
|
|
305
|
+
else:
|
|
306
|
+
raise
|
|
307
|
+
finally:
|
|
308
|
+
# Clean up
|
|
309
|
+
if os.path.exists(temp_path):
|
|
310
|
+
os.unlink(temp_path)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class TestConfigurationParsingRobustness:
|
|
314
|
+
"""Property tests for configuration parsing robustness."""
|
|
315
|
+
|
|
316
|
+
@given(config_dict=valid_config_dict_strategy())
|
|
317
|
+
def test_valid_config_parsing_property(self, config_dict):
|
|
318
|
+
"""
|
|
319
|
+
Property 7: Configuration Parsing Robustness (Valid Configs)
|
|
320
|
+
|
|
321
|
+
For any valid configuration dictionary, the parser should successfully
|
|
322
|
+
parse it and validation should pass.
|
|
323
|
+
|
|
324
|
+
Validates: Requirements 7.1, 7.2, 7.5
|
|
325
|
+
"""
|
|
326
|
+
# Feature: fishertools-enhancements, Property 7: Configuration Parsing Robustness
|
|
327
|
+
|
|
328
|
+
parser = ConfigurationParser()
|
|
329
|
+
manager = ConfigurationManager()
|
|
330
|
+
|
|
331
|
+
# Validation should pass for valid configs
|
|
332
|
+
validation_result = parser.validate_structure(config_dict)
|
|
333
|
+
assert validation_result.is_valid, f"Valid config failed validation: {validation_result.errors}"
|
|
334
|
+
|
|
335
|
+
# Should be able to convert to LearningConfig without errors
|
|
336
|
+
learning_config = manager._dict_to_config(config_dict)
|
|
337
|
+
assert isinstance(learning_config, LearningConfig)
|
|
338
|
+
|
|
339
|
+
# JSON serialization should work
|
|
340
|
+
json_content = parser.format_to_json(learning_config)
|
|
341
|
+
assert isinstance(json_content, str)
|
|
342
|
+
assert len(json_content) > 0
|
|
343
|
+
|
|
344
|
+
# JSON parsing should work
|
|
345
|
+
parsed_dict = parser.parse_json(json_content)
|
|
346
|
+
assert isinstance(parsed_dict, dict)
|
|
347
|
+
|
|
348
|
+
@given(config_dict=invalid_config_dict_strategy())
|
|
349
|
+
def test_invalid_config_parsing_property(self, config_dict):
|
|
350
|
+
"""
|
|
351
|
+
Property 7: Configuration Parsing Robustness (Invalid Configs)
|
|
352
|
+
|
|
353
|
+
For any invalid configuration dictionary, the parser should detect
|
|
354
|
+
validation errors and provide descriptive error messages.
|
|
355
|
+
|
|
356
|
+
Validates: Requirements 7.1, 7.2, 7.5
|
|
357
|
+
"""
|
|
358
|
+
# Feature: fishertools-enhancements, Property 7: Configuration Parsing Robustness
|
|
359
|
+
|
|
360
|
+
parser = ConfigurationParser()
|
|
361
|
+
|
|
362
|
+
# Validation should fail for invalid configs
|
|
363
|
+
validation_result = parser.validate_structure(config_dict)
|
|
364
|
+
|
|
365
|
+
# Should have validation errors
|
|
366
|
+
assert not validation_result.is_valid or len(validation_result.errors) > 0
|
|
367
|
+
|
|
368
|
+
# Error messages should be descriptive
|
|
369
|
+
for error in validation_result.errors:
|
|
370
|
+
assert isinstance(error.message, str)
|
|
371
|
+
assert len(error.message) > 0
|
|
372
|
+
assert isinstance(error.field_path, str)
|
|
373
|
+
assert len(error.field_path) > 0
|
|
374
|
+
|
|
375
|
+
def test_malformed_json_parsing_property(self):
|
|
376
|
+
"""
|
|
377
|
+
Property 7: Configuration Parsing Robustness (Malformed JSON)
|
|
378
|
+
|
|
379
|
+
For any malformed JSON string, the parser should raise a ValueError
|
|
380
|
+
with a descriptive error message.
|
|
381
|
+
|
|
382
|
+
Validates: Requirements 7.1, 7.2, 7.5
|
|
383
|
+
"""
|
|
384
|
+
# Feature: fishertools-enhancements, Property 7: Configuration Parsing Robustness
|
|
385
|
+
|
|
386
|
+
parser = ConfigurationParser()
|
|
387
|
+
|
|
388
|
+
# Test with known malformed JSON strings
|
|
389
|
+
malformed_examples = [
|
|
390
|
+
'{"key": value}', # Missing quotes around value
|
|
391
|
+
'{"key": "value",}', # Trailing comma
|
|
392
|
+
'{key: "value"}', # Missing quotes around key
|
|
393
|
+
'{"key": "value"', # Missing closing brace
|
|
394
|
+
'{"key": "value"}}', # Extra closing brace
|
|
395
|
+
'{"key": "value" "key2": "value2"}', # Missing comma
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
for malformed_json in malformed_examples:
|
|
399
|
+
# Should raise ValueError for malformed JSON
|
|
400
|
+
with pytest.raises(ValueError) as exc_info:
|
|
401
|
+
parser.parse_json(malformed_json)
|
|
402
|
+
|
|
403
|
+
# Error message should be descriptive
|
|
404
|
+
error_message = str(exc_info.value)
|
|
405
|
+
assert "Invalid JSON configuration" in error_message
|
|
406
|
+
assert len(error_message) > 0
|
|
407
|
+
|
|
408
|
+
@given(config_dict=valid_config_dict_strategy())
|
|
409
|
+
def test_config_change_application_property(self, config_dict):
|
|
410
|
+
"""
|
|
411
|
+
Property 7: Configuration Parsing Robustness (Dynamic Changes)
|
|
412
|
+
|
|
413
|
+
For any valid configuration, the system should be able to apply
|
|
414
|
+
configuration changes dynamically without errors.
|
|
415
|
+
|
|
416
|
+
Validates: Requirements 7.5
|
|
417
|
+
"""
|
|
418
|
+
# Feature: fishertools-enhancements, Property 7: Configuration Parsing Robustness
|
|
419
|
+
|
|
420
|
+
manager = ConfigurationManager()
|
|
421
|
+
|
|
422
|
+
# Create LearningConfig from dict
|
|
423
|
+
learning_config = manager._dict_to_config(config_dict)
|
|
424
|
+
|
|
425
|
+
# Should be able to apply configuration without errors
|
|
426
|
+
manager.apply_config(learning_config)
|
|
427
|
+
|
|
428
|
+
# Should be able to retrieve current config
|
|
429
|
+
current_config = manager.get_current_config()
|
|
430
|
+
assert current_config is not None
|
|
431
|
+
assert isinstance(current_config, LearningConfig)
|
|
432
|
+
|
|
433
|
+
# Current config should match applied config
|
|
434
|
+
assert current_config.default_level == learning_config.default_level
|
|
435
|
+
assert current_config.explanation_verbosity == learning_config.explanation_verbosity
|