devforgeai 1.0.5 → 1.0.6

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 (131) hide show
  1. package/CLAUDE.md +120 -0
  2. package/package.json +9 -1
  3. package/src/CLAUDE.md +699 -0
  4. package/src/claude/scripts/README.md +396 -0
  5. package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
  6. package/src/claude/scripts/check-hooks-fast.sh +70 -0
  7. package/src/claude/scripts/devforgeai-validate +6 -0
  8. package/src/claude/scripts/devforgeai_cli/README.md +531 -0
  9. package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
  10. package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
  11. package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
  12. package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
  13. package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
  14. package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
  15. package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
  16. package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
  17. package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
  18. package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
  19. package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
  20. package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
  21. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
  22. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
  23. package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
  24. package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
  25. package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
  26. package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
  27. package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
  28. package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
  29. package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
  30. package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
  31. package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
  32. package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
  33. package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
  34. package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
  35. package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
  36. package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
  37. package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
  38. package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
  39. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
  40. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
  41. package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
  42. package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
  43. package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
  44. package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
  45. package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
  46. package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
  47. package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
  48. package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
  49. package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
  50. package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
  51. package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
  52. package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
  53. package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
  54. package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
  55. package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
  56. package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
  57. package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
  58. package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
  59. package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
  60. package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
  61. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
  62. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
  63. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
  64. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
  65. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
  66. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
  67. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
  68. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
  69. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
  70. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
  71. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
  72. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
  73. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
  74. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
  75. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
  76. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
  77. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
  78. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
  79. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
  80. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
  81. package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
  82. package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
  83. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
  84. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
  85. package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
  86. package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
  87. package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
  88. package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
  89. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
  90. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
  91. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
  92. package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
  93. package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
  94. package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
  95. package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
  96. package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
  97. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
  98. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
  99. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
  100. package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
  101. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
  102. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
  103. package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
  104. package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
  105. package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
  106. package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
  107. package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
  108. package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
  109. package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
  110. package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
  111. package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
  112. package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
  113. package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
  114. package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
  115. package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
  116. package/src/claude/scripts/install_hooks.sh +186 -0
  117. package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
  118. package/src/claude/scripts/migrate-ac-headers.sh +122 -0
  119. package/src/claude/scripts/plan_file_kb.sh +704 -0
  120. package/src/claude/scripts/requirements.txt +8 -0
  121. package/src/claude/scripts/session_catalog.sh +543 -0
  122. package/src/claude/scripts/setup.py +55 -0
  123. package/src/claude/scripts/start-devforgeai.sh +16 -0
  124. package/src/claude/scripts/statusline.sh +27 -0
  125. package/src/claude/scripts/validate_deferrals.py +344 -0
  126. package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
  127. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
  128. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
  129. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
  130. package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
  131. package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
@@ -0,0 +1,2652 @@
1
+ # Test Specifications for Configuration Management System
2
+
3
+ **Story:** STORY-011 Configuration Management
4
+ **Coverage Goal:** 95%+ per module (targeting 435+ untested statements)
5
+ **Framework:** pytest
6
+ **Date:** 2025-11-10
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ This document specifies comprehensive test outlines for 6 untested modules in the feedback configuration system. Tests are organized by module with detailed specifications for test methods, fixtures, and assertions.
13
+
14
+ **Total Lines of Code (LOC) to Cover:**
15
+ - config_manager.py: 161 statements
16
+ - hot_reload.py: 99 statements
17
+ - config_models.py: 85 statements
18
+ - skip_tracker.py: 78 statements
19
+ - config_schema.py: 4 statements
20
+ - config_defaults.py: 8 statements
21
+ - **TOTAL: 435 statements**
22
+
23
+ **Target Test Coverage Distribution:**
24
+ - Unit tests: 70% (305 statements)
25
+ - Integration tests: 20% (87 statements)
26
+ - Edge cases/error paths: 10% (43 statements)
27
+
28
+ ---
29
+
30
+ # 1. TEST_CONFIG_MANAGER.PY
31
+
32
+ **Target:** 150+ lines, 95% coverage of 161 statements
33
+ **Purpose:** Validate configuration loading, validation, merging, and hot-reload integration
34
+
35
+ ## Test Class: TestConfigurationManagerInitialization
36
+
37
+ Tests for initialization and setup phase of ConfigurationManager.
38
+
39
+ ### Fixtures Required
40
+ ```
41
+ - config_dir: temp directory for test config files
42
+ - logs_dir: temp directory for logs
43
+ - sample_yaml_file: valid YAML config file
44
+ - cleanup: auto-cleanup after tests
45
+ ```
46
+
47
+ ### Test Methods
48
+
49
+ #### test_init_with_default_paths
50
+ - **Purpose:** Verify default paths when no args provided
51
+ - **Setup:** Create ConfigurationManager() with no arguments
52
+ - **Assertions:**
53
+ - config_file_path == Path("devforgeai/config/feedback.yaml")
54
+ - logs_dir == Path("devforgeai/logs")
55
+ - _initialized == True
56
+ - _current_config is FeedbackConfiguration instance
57
+ - **Coverage:** Lines 34-68, 49-52, 82
58
+
59
+ #### test_init_with_custom_paths
60
+ - **Purpose:** Verify custom paths are respected
61
+ - **Setup:** ConfigurationManager(config_file_path=custom_path, logs_dir=custom_logs)
62
+ - **Assertions:**
63
+ - config_file_path == custom_path
64
+ - logs_dir == custom_logs
65
+ - _current_config loaded from custom location
66
+ - **Coverage:** Lines 34-52
67
+
68
+ #### test_init_creates_logging_directory
69
+ - **Purpose:** Verify logs directory created during init
70
+ - **Setup:** ConfigurationManager with non-existent logs_dir
71
+ - **Assertions:**
72
+ - logs_dir exists after init
73
+ - _config_errors_log exists in logs_dir
74
+ - **Coverage:** Lines 86-87
75
+
76
+ #### test_init_with_debug_flag
77
+ - **Purpose:** Verify DEBUG_FEEDBACK_CONFIG env var enables debug mode
78
+ - **Setup:** Set DEBUG_FEEDBACK_CONFIG=true, init manager
79
+ - **Assertions:**
80
+ - _debug == True
81
+ - Error logging includes print() output
82
+ - **Coverage:** Lines 62-103
83
+
84
+ #### test_init_loads_initial_configuration
85
+ - **Purpose:** Verify initial config loaded and validated
86
+ - **Setup:** Valid YAML file exists, init ConfigurationManager
87
+ - **Assertions:**
88
+ - _current_config is not None
89
+ - _current_config is FeedbackConfiguration
90
+ - get_configuration() returns same object
91
+ - **Coverage:** Lines 68
92
+
93
+ #### test_init_creates_skip_tracker
94
+ - **Purpose:** Verify SkipTracker initialized
95
+ - **Setup:** Init ConfigurationManager
96
+ - **Assertions:**
97
+ - _skip_tracker is not None
98
+ - _skip_tracker is SkipTracker instance
99
+ - skip_log_path == logs_dir / "feedback-skips.log"
100
+ - **Coverage:** Lines 71
101
+
102
+ #### test_init_starts_hot_reload_by_default
103
+ - **Purpose:** Verify hot-reload manager created and started
104
+ - **Setup:** ConfigurationManager(enable_hot_reload=True)
105
+ - **Assertions:**
106
+ - _hot_reload_manager is not None
107
+ - _hot_reload_manager.is_running() == True
108
+ - ConfigFileWatcher started
109
+ - **Coverage:** Lines 74-80
110
+
111
+ #### test_init_skips_hot_reload_when_disabled
112
+ - **Purpose:** Verify hot-reload not started if disabled
113
+ - **Setup:** ConfigurationManager(enable_hot_reload=False)
114
+ - **Assertions:**
115
+ - _hot_reload_manager is None
116
+ - No watcher thread running
117
+ - **Coverage:** Lines 74
118
+
119
+ #### test_init_thread_safety_lock
120
+ - **Purpose:** Verify initialization lock prevents race conditions
121
+ - **Setup:** Create two threads calling ConfigurationManager()
122
+ - **Assertions:**
123
+ - Both threads complete without race condition
124
+ - _initialized is True in both threads
125
+ - Single config instance (no duplicates)
126
+ - **Coverage:** Lines 60, 82
127
+
128
+ ---
129
+
130
+ ## Test Class: TestConfigurationLoading
131
+
132
+ Tests for YAML file loading and validation.
133
+
134
+ ### Fixtures Required
135
+ ```
136
+ - config_file: temp YAML file
137
+ - empty_config: empty YAML file
138
+ - invalid_yaml: malformed YAML
139
+ - manager: ConfigurationManager instance
140
+ ```
141
+
142
+ ### Test Methods
143
+
144
+ #### test_load_valid_yaml_file
145
+ - **Purpose:** Load valid YAML configuration file
146
+ - **Setup:** Create valid YAML with all sections
147
+ - **Assertions:**
148
+ - load_configuration() returns FeedbackConfiguration
149
+ - All sections populated correctly
150
+ - No errors logged
151
+ - **Coverage:** Lines 109-136, 226-248
152
+
153
+ #### test_load_missing_yaml_file
154
+ - **Purpose:** Handle missing config file gracefully
155
+ - **Setup:** No YAML file exists
156
+ - **Assertions:**
157
+ - load_configuration() returns default config
158
+ - No error exception raised
159
+ - get_default_config() values used
160
+ - **Coverage:** Lines 119-120, 240-253
161
+
162
+ #### test_load_invalid_yaml_syntax
163
+ - **Purpose:** Handle malformed YAML
164
+ - **Setup:** YAML file with syntax error
165
+ - **Assertions:**
166
+ - yaml.YAMLError raised
167
+ - Error logged to config-errors.log
168
+ - Error message contains "YAML parsing error"
169
+ - **Coverage:** Lines 131-133
170
+
171
+ #### test_load_yaml_with_ioerror
172
+ - **Purpose:** Handle file read errors
173
+ - **Setup:** Config file exists but unreadable (permission denied)
174
+ - **Assertions:**
175
+ - IOError/OSError raised
176
+ - Error logged to config-errors.log
177
+ - Error message contains "File read error"
178
+ - **Coverage:** Lines 134-136
179
+
180
+ #### test_load_yaml_without_pyyaml
181
+ - **Purpose:** Handle missing PyYAML gracefully
182
+ - **Setup:** Temporarily set yaml = None in module
183
+ - **Assertions:**
184
+ - _load_yaml_file() returns None
185
+ - Error logged: "PyYAML not available"
186
+ - load_configuration() falls back to defaults
187
+ - **Coverage:** Lines 123-125
188
+
189
+ #### test_load_empty_yaml_file
190
+ - **Purpose:** Handle empty YAML file
191
+ - **Setup:** YAML file with no content or only whitespace
192
+ - **Assertions:**
193
+ - Content parsed as empty dict {}
194
+ - Merged with defaults
195
+ - Default configuration returned
196
+ - **Coverage:** Lines 127-129
197
+
198
+ #### test_load_yaml_non_dict_content
199
+ - **Purpose:** Handle YAML that doesn't parse to dict
200
+ - **Setup:** YAML file contains array: [1, 2, 3]
201
+ - **Assertions:**
202
+ - Content recognized as non-dict
203
+ - Converted to empty dict
204
+ - Defaults applied
205
+ - **Coverage:** Lines 129
206
+
207
+ #### test_load_configuration_exception_recovery
208
+ - **Purpose:** Return defaults if any exception during load
209
+ - **Setup:** Trigger exception in any load phase
210
+ - **Assertions:**
211
+ - load_configuration() catches exception
212
+ - Error logged to config-errors.log
213
+ - Returns FeedbackConfiguration() (defaults)
214
+ - **Coverage:** Lines 250-253
215
+
216
+ ---
217
+
218
+ ## Test Class: TestConfigurationMerging
219
+
220
+ Tests for merging loaded config with defaults.
221
+
222
+ ### Fixtures Required
223
+ ```
224
+ - manager: ConfigurationManager instance
225
+ ```
226
+
227
+ ### Test Methods
228
+
229
+ #### test_merge_none_with_defaults
230
+ - **Purpose:** None config merged to defaults
231
+ - **Setup:** loaded_config = None
232
+ - **Assertions:**
233
+ - _merge_with_defaults(None) returns default config
234
+ - All defaults present
235
+ - **Coverage:** Lines 160-161
236
+
237
+ #### test_merge_partial_config
238
+ - **Purpose:** Partial config merged with defaults
239
+ - **Setup:** loaded_config = {"enabled": False}
240
+ - **Assertions:**
241
+ - enabled == False
242
+ - All other fields have defaults
243
+ - **Coverage:** Lines 151-173
244
+
245
+ #### test_merge_complete_config
246
+ - **Purpose:** Complete config overrides all defaults
247
+ - **Setup:** loaded_config with all sections defined
248
+ - **Assertions:**
249
+ - All loaded values present
250
+ - No defaults override custom values
251
+ - **Coverage:** Lines 151-173
252
+
253
+ #### test_merge_nested_conversation_settings
254
+ - **Purpose:** Nested section merged correctly
255
+ - **Setup:** loaded_config["conversation_settings"] = {"max_questions": 10}
256
+ - **Assertions:**
257
+ - max_questions == 10
258
+ - allow_skip == default (True)
259
+ - Other defaults preserved
260
+ - **Coverage:** Lines 138-150, 170-171
261
+
262
+ #### test_merge_nested_skip_tracking
263
+ - **Purpose:** Skip tracking section merged
264
+ - **Setup:** loaded_config["skip_tracking"] = {"enabled": False}
265
+ - **Assertions:**
266
+ - enabled == False
267
+ - max_consecutive_skips == default
268
+ - reset_on_positive == default
269
+ - **Coverage:** Lines 138-150, 170-171
270
+
271
+ #### test_merge_nested_templates
272
+ - **Purpose:** Template settings merged
273
+ - **Setup:** loaded_config["templates"] = {"tone": "detailed"}
274
+ - **Assertions:**
275
+ - tone == "detailed"
276
+ - format == default ("structured")
277
+ - **Coverage:** Lines 138-150, 170-171
278
+
279
+ #### test_merge_nested_config_non_dict
280
+ - **Purpose:** Non-dict nested section handled
281
+ - **Setup:** loaded_config["conversation_settings"] = "invalid"
282
+ - **Assertions:**
283
+ - Section not merged (skipped)
284
+ - Defaults used for conversation_settings
285
+ - **Coverage:** Lines 146
286
+
287
+ #### test_merge_with_extra_top_level_keys
288
+ - **Purpose:** Extra keys in loaded config merged
289
+ - **Setup:** loaded_config = {"enabled": True, "custom_key": "value"}
290
+ - **Assertions:**
291
+ - custom_key present in merged config
292
+ - Standard keys merged correctly
293
+ - **Coverage:** Lines 167
294
+
295
+ ---
296
+
297
+ ## Test Class: TestConfigurationValidation
298
+
299
+ Tests for configuration validation and data model conversion.
300
+
301
+ ### Fixtures Required
302
+ ```
303
+ - manager: ConfigurationManager instance
304
+ - valid_dict: valid configuration dictionary
305
+ ```
306
+
307
+ ### Test Methods
308
+
309
+ #### test_dict_to_configuration_valid
310
+ - **Purpose:** Convert valid dict to FeedbackConfiguration
311
+ - **Setup:** valid config dict with all sections
312
+ - **Assertions:**
313
+ - FeedbackConfiguration created
314
+ - All nested objects instantiated
315
+ - Validation passes
316
+ - **Coverage:** Lines 192-224
317
+
318
+ #### test_dict_to_configuration_parses_nested
319
+ - **Purpose:** Nested sections parsed to dataclasses
320
+ - **Setup:** config_dict["conversation_settings"] = dict
321
+ - **Assertions:**
322
+ - conversation_settings is ConversationSettings
323
+ - skip_tracking is SkipTrackingSettings
324
+ - templates is TemplateSettings
325
+ - **Coverage:** Lines 206-208
326
+
327
+ #### test_dict_to_configuration_invalid_trigger_mode
328
+ - **Purpose:** Invalid trigger_mode raises ValueError
329
+ - **Setup:** config_dict["trigger_mode"] = "invalid-mode"
330
+ - **Assertions:**
331
+ - ValueError raised
332
+ - Error logged: "Invalid trigger_mode value"
333
+ - Error contains expected modes list
334
+ - **Coverage:** Lines 220-224
335
+
336
+ #### test_dict_to_configuration_missing_operations_specific_ops
337
+ - **Purpose:** Specific-operations mode requires operations list
338
+ - **Setup:** trigger_mode="specific-operations", operations=None
339
+ - **Assertions:**
340
+ - ValueError raised
341
+ - Error message references operations requirement
342
+ - Error logged
343
+ - **Coverage:** Lines 220-224
344
+
345
+ #### test_dict_to_configuration_invalid_enabled_type
346
+ - **Purpose:** Non-boolean enabled rejected
347
+ - **Setup:** config_dict["enabled"] = "true" (string)
348
+ - **Assertions:**
349
+ - ValueError raised
350
+ - Error includes type information
351
+ - **Coverage:** Lines 220-224
352
+
353
+ #### test_dict_to_configuration_defaults_missing_sections
354
+ - **Purpose:** Missing sections get defaults
355
+ - **Setup:** config_dict = {"enabled": True, "trigger_mode": "always"}
356
+ - **Assertions:**
357
+ - FeedbackConfiguration created with defaults
358
+ - conversation_settings uses defaults
359
+ - skip_tracking uses defaults
360
+ - templates uses defaults
361
+ - **Coverage:** Lines 206-208
362
+
363
+ #### test_parse_nested_settings_valid
364
+ - **Purpose:** Parse nested settings section
365
+ - **Setup:** Call _parse_nested_settings("conversation_settings", dict, ConversationSettings)
366
+ - **Assertions:**
367
+ - Returns ConversationSettings instance
368
+ - Fields populated from dict
369
+ - **Coverage:** Lines 175-190
370
+
371
+ #### test_parse_nested_settings_missing
372
+ - **Purpose:** Missing section returns None
373
+ - **Setup:** section_name not in config_dict
374
+ - **Assertions:**
375
+ - Returns None
376
+ - **Coverage:** Lines 175-190
377
+
378
+ #### test_parse_nested_settings_non_dict
379
+ - **Purpose:** Non-dict section returns None
380
+ - **Setup:** loaded_config["conversation_settings"] = "not a dict"
381
+ - **Assertions:**
382
+ - Returns None
383
+ - **Coverage:** Lines 188
384
+
385
+ ---
386
+
387
+ ## Test Class: TestConfigurationAccess
388
+
389
+ Tests for getter methods and configuration retrieval.
390
+
391
+ ### Fixtures Required
392
+ ```
393
+ - manager: ConfigurationManager instance
394
+ - sample_config: FeedbackConfiguration instance
395
+ ```
396
+
397
+ ### Test Methods
398
+
399
+ #### test_get_configuration_returns_current
400
+ - **Purpose:** Get current configuration
401
+ - **Setup:** manager.get_configuration()
402
+ - **Assertions:**
403
+ - Returns current FeedbackConfiguration
404
+ - Same object as _current_config
405
+ - **Coverage:** Lines 265-274
406
+
407
+ #### test_get_configuration_thread_safe
408
+ - **Purpose:** Concurrent reads are thread-safe
409
+ - **Setup:** 5 threads calling get_configuration()
410
+ - **Assertions:**
411
+ - All threads get valid config
412
+ - No race conditions
413
+ - **Coverage:** Lines 271
414
+
415
+ #### test_get_configuration_initializes_if_none
416
+ - **Purpose:** Load config if _current_config is None
417
+ - **Setup:** _current_config = None, call get_configuration()
418
+ - **Assertions:**
419
+ - Loads configuration
420
+ - Returns non-None FeedbackConfiguration
421
+ - **Coverage:** Lines 272-274
422
+
423
+ #### test_update_configuration
424
+ - **Purpose:** Update current configuration
425
+ - **Setup:** Create new config, call update_configuration(new_config)
426
+ - **Assertions:**
427
+ - _current_config updated
428
+ - get_configuration() returns new config
429
+ - **Coverage:** Lines 276-283
430
+
431
+ #### test_update_configuration_thread_safe
432
+ - **Purpose:** Update is thread-safe
433
+ - **Setup:** Update config in one thread while reading in another
434
+ - **Assertions:**
435
+ - All operations atomic
436
+ - No partial updates visible
437
+ - **Coverage:** Lines 282
438
+
439
+ #### test_is_enabled_true
440
+ - **Purpose:** Check if feedback enabled
441
+ - **Setup:** config.enabled = True
442
+ - **Assertions:**
443
+ - is_enabled() returns True
444
+ - **Coverage:** Lines 285-292
445
+
446
+ #### test_is_enabled_false
447
+ - **Purpose:** Check if feedback disabled
448
+ - **Setup:** config.enabled = False
449
+ - **Assertions:**
450
+ - is_enabled() returns False
451
+ - **Coverage:** Lines 285-292
452
+
453
+ #### test_get_trigger_mode
454
+ - **Purpose:** Retrieve trigger mode
455
+ - **Setup:** config.trigger_mode = "failures-only"
456
+ - **Assertions:**
457
+ - get_trigger_mode() returns "failures-only"
458
+ - **Coverage:** Lines 294-301
459
+
460
+ #### test_get_trigger_mode_variations
461
+ - **Purpose:** All trigger modes retrievable
462
+ - **Setup:** Test each trigger mode: always, failures-only, specific-operations, never
463
+ - **Assertions:**
464
+ - Each mode returned correctly
465
+ - **Coverage:** Lines 294-301
466
+
467
+ #### test_get_operations_returns_list
468
+ - **Purpose:** Get specific operations list
469
+ - **Setup:** config.operations = ["qa", "deployment"]
470
+ - **Assertions:**
471
+ - get_operations() returns ["qa", "deployment"]
472
+ - **Coverage:** Lines 303-310
473
+
474
+ #### test_get_operations_returns_none
475
+ - **Purpose:** Operations None when not specified
476
+ - **Setup:** config.operations = None
477
+ - **Assertions:**
478
+ - get_operations() returns None
479
+ - **Coverage:** Lines 303-310
480
+
481
+ #### test_get_conversation_settings
482
+ - **Purpose:** Retrieve conversation settings
483
+ - **Setup:** config has conversation_settings
484
+ - **Assertions:**
485
+ - get_conversation_settings() returns ConversationSettings
486
+ - All fields present
487
+ - **Coverage:** Lines 325-331
488
+
489
+ #### test_get_skip_tracking_settings
490
+ - **Purpose:** Retrieve skip tracking settings
491
+ - **Setup:** config has skip_tracking
492
+ - **Assertions:**
493
+ - get_skip_tracking_settings() returns SkipTrackingSettings
494
+ - **Coverage:** Lines 333-339
495
+
496
+ #### test_get_template_settings
497
+ - **Purpose:** Retrieve template settings
498
+ - **Setup:** config has templates
499
+ - **Assertions:**
500
+ - get_template_settings() returns TemplateSettings
501
+ - **Coverage:** Lines 341-347
502
+
503
+ #### test_get_nested_config_attribute
504
+ - **Purpose:** Get nested config by attribute name
505
+ - **Setup:** Call _get_nested_config("conversation_settings")
506
+ - **Assertions:**
507
+ - Returns ConversationSettings
508
+ - **Coverage:** Lines 312-323
509
+
510
+ #### test_get_nested_config_missing
511
+ - **Purpose:** Missing nested config returns None
512
+ - **Setup:** Call _get_nested_config("nonexistent")
513
+ - **Assertions:**
514
+ - Returns None
515
+ - **Coverage:** Lines 323
516
+
517
+ #### test_get_skip_tracker
518
+ - **Purpose:** Get skip tracker instance
519
+ - **Setup:** manager.get_skip_tracker()
520
+ - **Assertions:**
521
+ - Returns SkipTracker instance
522
+ - Same instance as _skip_tracker
523
+ - **Coverage:** Lines 349-355
524
+
525
+ ---
526
+
527
+ ## Test Class: TestHotReloadManagement
528
+
529
+ Tests for hot-reload lifecycle management.
530
+
531
+ ### Fixtures Required
532
+ ```
533
+ - manager: ConfigurationManager instance
534
+ - config_file: temp config file
535
+ ```
536
+
537
+ ### Test Methods
538
+
539
+ #### test_is_hot_reload_enabled_true
540
+ - **Purpose:** Check hot-reload status when enabled
541
+ - **Setup:** ConfigurationManager(enable_hot_reload=True)
542
+ - **Assertions:**
543
+ - is_hot_reload_enabled() returns True
544
+ - **Coverage:** Lines 357-365
545
+
546
+ #### test_is_hot_reload_enabled_false
547
+ - **Purpose:** Check hot-reload status when disabled
548
+ - **Setup:** ConfigurationManager(enable_hot_reload=False)
549
+ - **Assertions:**
550
+ - is_hot_reload_enabled() returns False
551
+ - **Coverage:** Lines 357-365
552
+
553
+ #### test_start_hot_reload
554
+ - **Purpose:** Start hot-reload if not running
555
+ - **Setup:** manager with hot-reload disabled, then start
556
+ - **Assertions:**
557
+ - start_hot_reload() starts manager
558
+ - is_hot_reload_enabled() returns True
559
+ - Watcher thread active
560
+ - **Coverage:** Lines 367-371
561
+
562
+ #### test_start_hot_reload_already_running
563
+ - **Purpose:** Starting already-running hot-reload is safe
564
+ - **Setup:** Hot-reload already running, call start_hot_reload()
565
+ - **Assertions:**
566
+ - No error
567
+ - Still running
568
+ - No duplicate threads
569
+ - **Coverage:** Lines 369-370
570
+
571
+ #### test_stop_hot_reload
572
+ - **Purpose:** Stop hot-reload
573
+ - **Setup:** manager with hot-reload enabled, call stop
574
+ - **Assertions:**
575
+ - stop_hot_reload() stops manager
576
+ - is_hot_reload_enabled() returns False
577
+ - Watcher thread terminated
578
+ - **Coverage:** Lines 373-376
579
+
580
+ #### test_stop_hot_reload_not_running
581
+ - **Purpose:** Stopping non-running hot-reload is safe
582
+ - **Setup:** hot-reload disabled, call stop_hot_reload()
583
+ - **Assertions:**
584
+ - No error
585
+ - **Coverage:** Lines 375
586
+
587
+ #### test_shutdown
588
+ - **Purpose:** Shutdown stops hot-reload
589
+ - **Setup:** manager.shutdown()
590
+ - **Assertions:**
591
+ - Hot-reload stopped
592
+ - is_hot_reload_enabled() returns False
593
+ - **Coverage:** Lines 378-380
594
+
595
+ ---
596
+
597
+ ## Test Class: TestConfigurationHotReload
598
+
599
+ Integration tests for hot-reload functionality.
600
+
601
+ ### Fixtures Required
602
+ ```
603
+ - temp_config_file: temp YAML file
604
+ - manager: ConfigurationManager with hot-reload
605
+ - time_control: sleep/time control for reliable testing
606
+ ```
607
+
608
+ ### Test Methods
609
+
610
+ #### test_hot_reload_detects_file_change
611
+ - **Purpose:** Configuration reloaded when file changes (AC-9)
612
+ - **Setup:** Start manager, modify config file, wait for detection
613
+ - **Assertions:**
614
+ - Change detected within 5 seconds (default timeout + poll interval)
615
+ - _reload_config_callback() called
616
+ - **Coverage:** Lines 255-263, 74-80 (integration)
617
+
618
+ #### test_hot_reload_updates_current_config
619
+ - **Purpose:** Configuration updated in memory after file change
620
+ - **Setup:** Start with enabled:true, change file to enabled:false
621
+ - **Assertions:**
622
+ - get_configuration().enabled changes from True to False
623
+ - is_enabled() reflects change
624
+ - **Coverage:** Lines 255-263, 74-80 (integration)
625
+
626
+ #### test_hot_reload_recovers_from_invalid_yaml
627
+ - **Purpose:** Previous config retained on invalid file change (AC-9)
628
+ - **Setup:** Valid config → modify to invalid YAML → wait
629
+ - **Assertions:**
630
+ - get_configuration() still returns valid config
631
+ - Error logged to config-errors.log
632
+ - No exception thrown
633
+ - **Coverage:** Lines 255-263, 131-133 (integration)
634
+
635
+ #### test_hot_reload_callback_exception_handling
636
+ - **Purpose:** Exception in reload callback handled
637
+ - **Setup:** Force exception in load_configuration()
638
+ - **Assertions:**
639
+ - Exception caught
640
+ - Previous config remains valid
641
+ - Callback can be called again
642
+ - **Coverage:** Lines 255-263 (integration)
643
+
644
+ ---
645
+
646
+ ## Test Class: TestConfigurationManager_GlobalSingleton
647
+
648
+ Tests for global configuration manager singleton.
649
+
650
+ ### Fixtures Required
651
+ ```
652
+ - cleanup: reset_config_manager() before/after
653
+ ```
654
+
655
+ ### Test Methods
656
+
657
+ #### test_get_config_manager_creates_instance
658
+ - **Purpose:** First call creates global instance
659
+ - **Setup:** reset_config_manager(), call get_config_manager()
660
+ - **Assertions:**
661
+ - Returns ConfigurationManager instance
662
+ - Same instance on second call
663
+ - **Coverage:** Lines 388-413
664
+
665
+ #### test_get_config_manager_singleton
666
+ - **Purpose:** Multiple calls return same instance
667
+ - **Setup:** Call get_config_manager() twice
668
+ - **Assertions:**
669
+ - Both calls return same object
670
+ - id(first) == id(second)
671
+ - **Coverage:** Lines 405-411
672
+
673
+ #### test_get_config_manager_thread_safe
674
+ - **Purpose:** Singleton creation is thread-safe
675
+ - **Setup:** 3 threads call get_config_manager() simultaneously
676
+ - **Assertions:**
677
+ - All threads get same instance
678
+ - No race conditions
679
+ - **Coverage:** Lines 405-411
680
+
681
+ #### test_get_config_manager_uses_provided_paths
682
+ - **Purpose:** Paths passed to first call only
683
+ - **Setup:** First call with custom paths, second call with different paths
684
+ - **Assertions:**
685
+ - First call paths used
686
+ - Second call paths ignored
687
+ - **Coverage:** Lines 397-411
688
+
689
+ #### test_reset_config_manager
690
+ - **Purpose:** Reset global instance for testing
691
+ - **Setup:** Create instance, reset, create again
692
+ - **Assertions:**
693
+ - reset_config_manager() stops hot-reload
694
+ - New call creates fresh instance
695
+ - **Coverage:** Lines 416-423
696
+
697
+ ---
698
+
699
+ ## Test Class: TestConfigurationManager_ErrorHandling
700
+
701
+ Tests for error handling and logging.
702
+
703
+ ### Fixtures Required
704
+ ```
705
+ - logs_dir: temp directory for logs
706
+ - manager: ConfigurationManager instance
707
+ ```
708
+
709
+ ### Test Methods
710
+
711
+ #### test_log_error_writes_to_file
712
+ - **Purpose:** Errors logged to config-errors.log
713
+ - **Setup:** Call _log_error("Test error")
714
+ - **Assertions:**
715
+ - File exists: logs_dir / "config-errors.log"
716
+ - File contains error message with timestamp
717
+ - **Coverage:** Lines 88-104
718
+
719
+ #### test_log_error_handles_write_failure
720
+ - **Purpose:** Log write failure doesn't crash
721
+ - **Setup:** Make logs_dir unwritable, call _log_error()
722
+ - **Assertions:**
723
+ - No exception raised
724
+ - Process continues normally
725
+ - **Coverage:** Lines 98-100
726
+
727
+ #### test_log_error_debug_mode
728
+ - **Purpose:** Debug mode prints to stdout
729
+ - **Setup:** DEBUG_FEEDBACK_CONFIG=true, _log_error()
730
+ - **Assertions:**
731
+ - stderr/stdout contains error message
732
+ - Message prefixed with "[CONFIG]"
733
+ - **Coverage:** Lines 102-103
734
+
735
+ #### test_ensure_config_directory_creates_path
736
+ - **Purpose:** Config directory created if missing
737
+ - **Setup:** Call _ensure_config_directory() with non-existent path
738
+ - **Assertions:**
739
+ - config_file_path.parent created
740
+ - **Coverage:** Lines 105-107
741
+
742
+ #### test_ensure_config_directory_exists
743
+ - **Purpose:** Existing directory not recreated
744
+ - **Setup:** Call _ensure_config_directory() twice
745
+ - **Assertions:**
746
+ - No error
747
+ - Directory exists after both calls
748
+ - **Coverage:** Lines 105-107
749
+
750
+ ---
751
+
752
+ ## Coverage Summary for config_manager.py
753
+
754
+ | Section | Lines | Coverage | Status |
755
+ |---------|-------|----------|--------|
756
+ | Initialization | 34-82 | ~100% | 10 tests |
757
+ | YAML Loading | 109-136 | ~100% | 8 tests |
758
+ | Merging | 138-173 | ~100% | 7 tests |
759
+ | Validation | 192-224 | ~100% | 7 tests |
760
+ | Getters | 265-347 | ~100% | 15 tests |
761
+ | Hot-Reload | 357-380 | ~100% | 7 tests |
762
+ | Callbacks | 255-263 | ~100% | 4 tests (integration) |
763
+ | Singleton | 388-423 | ~100% | 5 tests |
764
+ | Error Handling | 88-104 | ~100% | 5 tests |
765
+ | **TOTAL** | **161** | **95%+** | **68 tests** |
766
+
767
+ ---
768
+
769
+ # 2. TEST_HOT_RELOAD.PY
770
+
771
+ **Target:** 90+ lines, 95% coverage of 99 statements
772
+ **Purpose:** Validate file watching, change detection, and configuration reloading
773
+
774
+ ## Test Class: TestFileInfo
775
+
776
+ Tests for FileInfo named tuple.
777
+
778
+ ### Test Methods
779
+
780
+ #### test_fileinfo_creation
781
+ - **Purpose:** Create FileInfo instance
782
+ - **Setup:** FileInfo(mtime=1234567890.0, size=1024)
783
+ - **Assertions:**
784
+ - mtime == 1234567890.0
785
+ - size == 1024
786
+ - **Coverage:** Lines 16-20
787
+
788
+ #### test_fileinfo_named_tuple_properties
789
+ - **Purpose:** Access properties by name and index
790
+ - **Setup:** Create FileInfo(100.0, 200)
791
+ - **Assertions:**
792
+ - fi.mtime == 100.0 and fi[0] == 100.0
793
+ - fi.size == 200 and fi[1] == 200
794
+ - **Coverage:** Lines 16-20
795
+
796
+ ---
797
+
798
+ ## Test Class: TestConfigFileWatcher
799
+
800
+ Tests for file watching and change detection.
801
+
802
+ ### Fixtures Required
803
+ ```
804
+ - config_file: temp config file
805
+ - callback_mock: mock callback function
806
+ - watcher: ConfigFileWatcher instance
807
+ - cleanup: stop watcher after tests
808
+ ```
809
+
810
+ ### Test Methods
811
+
812
+ #### test_watcher_init
813
+ - **Purpose:** Initialize ConfigFileWatcher
814
+ - **Setup:** ConfigFileWatcher(config_file, callback)
815
+ - **Assertions:**
816
+ - config_file set
817
+ - on_change_callback set
818
+ - poll_interval == 0.5 (default)
819
+ - detection_timeout == 5.0 (default)
820
+ - _is_running == False
821
+ - **Coverage:** Lines 29-52
822
+
823
+ #### test_watcher_custom_poll_interval
824
+ - **Purpose:** Custom poll interval respected
825
+ - **Setup:** ConfigFileWatcher(..., poll_interval=0.1)
826
+ - **Assertions:**
827
+ - poll_interval == 0.1
828
+ - **Coverage:** Lines 29-48
829
+
830
+ #### test_watcher_custom_detection_timeout
831
+ - **Purpose:** Custom detection timeout respected
832
+ - **Setup:** ConfigFileWatcher(..., detection_timeout=10.0)
833
+ - **Assertions:**
834
+ - detection_timeout == 10.0
835
+ - **Coverage:** Lines 29-48
836
+
837
+ #### test_get_file_info_existing_file
838
+ - **Purpose:** Get file info for existing file
839
+ - **Setup:** config_file exists with known mtime/size
840
+ - **Assertions:**
841
+ - FileInfo.mtime == config_file.stat().st_mtime
842
+ - FileInfo.size == config_file.stat().st_size
843
+ - **Coverage:** Lines 55-67
844
+
845
+ #### test_get_file_info_missing_file
846
+ - **Purpose:** Get file info for non-existent file
847
+ - **Setup:** config_file doesn't exist
848
+ - **Assertions:**
849
+ - FileInfo(None, None) returned
850
+ - **Coverage:** Lines 55-67
851
+
852
+ #### test_get_file_info_ioerror
853
+ - **Purpose:** OSError/IOError handled gracefully
854
+ - **Setup:** config_file.stat() raises OSError
855
+ - **Assertions:**
856
+ - Returns FileInfo(None, None)
857
+ - No exception raised
858
+ - **Coverage:** Lines 65-67
859
+
860
+ #### test_has_file_changed_first_call
861
+ - **Purpose:** First call (no baseline) returns False
862
+ - **Setup:** _last_file_info = None, _has_file_changed(new_info)
863
+ - **Assertions:**
864
+ - Returns False
865
+ - **Coverage:** Lines 94-109
866
+
867
+ #### test_has_file_changed_mtime
868
+ - **Purpose:** Modification time change detected
869
+ - **Setup:** _last_file_info with mtime=100, current with mtime=200
870
+ - **Assertions:**
871
+ - Returns True
872
+ - **Coverage:** Lines 106-109
873
+
874
+ #### test_has_file_changed_size
875
+ - **Purpose:** File size change detected
876
+ - **Setup:** _last_file_info with size=1000, current with size=2000
877
+ - **Assertions:**
878
+ - Returns True
879
+ - **Coverage:** Lines 108-109
880
+
881
+ #### test_has_file_changed_no_change
882
+ - **Purpose:** No change detected correctly
883
+ - **Setup:** _last_file_info and current_info identical
884
+ - **Assertions:**
885
+ - Returns False
886
+ - **Coverage:** Lines 94-109
887
+
888
+ #### test_has_file_changed_last_info_none
889
+ - **Purpose:** No change when baseline is None
890
+ - **Setup:** _last_file_info = None, current_info with values
891
+ - **Assertions:**
892
+ - Returns False
893
+ - **Coverage:** Lines 103-104
894
+
895
+ #### test_has_file_changed_current_mtime_none
896
+ - **Purpose:** No change when current mtime is None
897
+ - **Setup:** _last_file_info has mtime, current_info.mtime = None
898
+ - **Assertions:**
899
+ - Returns False
900
+ - **Coverage:** Lines 106-107
901
+
902
+ #### test_has_file_changed_both_none
903
+ - **Purpose:** No change when both mtime values None
904
+ - **Setup:** Both _last_file_info.mtime and current_info.mtime = None
905
+ - **Assertions:**
906
+ - Returns False
907
+ - **Coverage:** Lines 106
908
+
909
+ #### test_start_watching
910
+ - **Purpose:** Start file watcher
911
+ - **Setup:** watcher.start()
912
+ - **Assertions:**
913
+ - _is_running == True
914
+ - _watch_thread is not None
915
+ - _watch_thread.is_alive() == True
916
+ - _stop_event cleared
917
+ - **Coverage:** Lines 111-120
918
+
919
+ #### test_start_watching_already_running
920
+ - **Purpose:** Starting already-running watcher is safe
921
+ - **Setup:** watcher.start(), then start again
922
+ - **Assertions:**
923
+ - No error
924
+ - Still running
925
+ - No duplicate threads (thread count == 1)
926
+ - **Coverage:** Lines 113-115
927
+
928
+ #### test_stop_watching
929
+ - **Purpose:** Stop file watcher
930
+ - **Setup:** watcher.start(), then watcher.stop()
931
+ - **Assertions:**
932
+ - _is_running == False
933
+ - _stop_event is set
934
+ - _watch_thread no longer alive (or None)
935
+ - **Coverage:** Lines 122-131
936
+
937
+ #### test_stop_watching_not_running
938
+ - **Purpose:** Stopping non-running watcher is safe
939
+ - **Setup:** watcher.stop() without start
940
+ - **Assertions:**
941
+ - No error
942
+ - _is_running == False
943
+ - **Coverage:** Lines 124-126
944
+
945
+ #### test_is_running_true
946
+ - **Purpose:** Check running status when active
947
+ - **Setup:** watcher.start(), check is_running()
948
+ - **Assertions:**
949
+ - is_running() returns True
950
+ - **Coverage:** Lines 133-140
951
+
952
+ #### test_is_running_false
953
+ - **Purpose:** Check running status when stopped
954
+ - **Setup:** watcher.stop(), check is_running()
955
+ - **Assertions:**
956
+ - is_running() returns False
957
+ - **Coverage:** Lines 133-140
958
+
959
+ #### test_watch_loop_initializes_baseline
960
+ - **Purpose:** Watch loop sets initial baseline
961
+ - **Setup:** Start watcher, observe _last_file_info
962
+ - **Assertions:**
963
+ - _last_file_info initialized in _watch_loop
964
+ - **Coverage:** Lines 69-72
965
+
966
+ #### test_watch_loop_detects_modification
967
+ - **Purpose:** File modification triggers callback
968
+ - **Setup:** Start watcher, modify config file
969
+ - **Assertions:**
970
+ - Callback called within poll_interval + tolerance
971
+ - Callback called with config_file path
972
+ - **Coverage:** Lines 74-88
973
+
974
+ #### test_watch_loop_callback_exception_ignored
975
+ - **Purpose:** Exception in callback doesn't crash watcher
976
+ - **Setup:** Callback raises exception
977
+ - **Assertions:**
978
+ - Watcher continues running
979
+ - Still detects further changes
980
+ - **Coverage:** Lines 83-87
981
+
982
+ #### test_watch_loop_exception_ignored
983
+ - **Purpose:** Exception in watch loop handled
984
+ - **Setup:** Force exception in _get_file_info
985
+ - **Assertions:**
986
+ - Watcher continues running
987
+ - No thread crash
988
+ - **Coverage:** Lines 90-92
989
+
990
+ #### test_watch_loop_thread_timing
991
+ - **Purpose:** Watch loop respects poll_interval
992
+ - **Setup:** Set poll_interval=0.1, modify file, measure detection time
993
+ - **Assertions:**
994
+ - Detection time ~poll_interval (within tolerance)
995
+ - Not polling more frequently than interval
996
+ - **Coverage:** Lines 76 (integration timing test)
997
+
998
+ #### test_watch_loop_respects_stop_event
999
+ - **Purpose:** Watch loop exits on stop signal
1000
+ - **Setup:** Start watcher, stop(), wait for thread
1001
+ - **Assertions:**
1002
+ - Thread joins within timeout
1003
+ - Loop exits cleanly
1004
+ - **Coverage:** Lines 74, 128-130
1005
+
1006
+ #### test_thread_safety_concurrent_start_stop
1007
+ - **Purpose:** Concurrent start/stop operations are safe
1008
+ - **Setup:** 3 threads: 1 start, 1 stop, 1 check status
1009
+ - **Assertions:**
1010
+ - All operations complete without race
1011
+ - Final state correct
1012
+ - **Coverage:** Lines 113, 124 (locking)
1013
+
1014
+ #### test_thread_safety_concurrent_is_running
1015
+ - **Purpose:** is_running() thread-safe during changes
1016
+ - **Setup:** One thread start/stop, others query status
1017
+ - **Assertions:**
1018
+ - All queries consistent
1019
+ - No race conditions
1020
+ - **Coverage:** Lines 139 (locking)
1021
+
1022
+ ---
1023
+
1024
+ ## Test Class: TestHotReloadManager
1025
+
1026
+ Tests for hot-reload coordination.
1027
+
1028
+ ### Fixtures Required
1029
+ ```
1030
+ - config_file: temp config file
1031
+ - load_config_callback: mock function returning config
1032
+ - manager: HotReloadManager instance
1033
+ ```
1034
+
1035
+ ### Test Methods
1036
+
1037
+ #### test_hotreload_manager_init
1038
+ - **Purpose:** Initialize HotReloadManager
1039
+ - **Setup:** HotReloadManager(config_file, callback)
1040
+ - **Assertions:**
1041
+ - config_file set
1042
+ - load_config_callback set
1043
+ - _watcher is None (not started)
1044
+ - _current_config is None
1045
+ - **Coverage:** Lines 149-165
1046
+
1047
+ #### test_start_creates_watcher
1048
+ - **Purpose:** Start creates ConfigFileWatcher
1049
+ - **Setup:** manager.start()
1050
+ - **Assertions:**
1051
+ - _watcher is not None
1052
+ - _watcher is ConfigFileWatcher
1053
+ - _watcher.is_running() == True
1054
+ - **Coverage:** Lines 182-192
1055
+
1056
+ #### test_start_already_running
1057
+ - **Purpose:** Starting already-running manager is safe
1058
+ - **Setup:** manager.start(), then start again
1059
+ - **Assertions:**
1060
+ - No error
1061
+ - Same _watcher instance
1062
+ - Still running
1063
+ - **Coverage:** Lines 184-186
1064
+
1065
+ #### test_on_config_change_callback
1066
+ - **Purpose:** File change triggers load_config_callback
1067
+ - **Setup:** Modify file while manager running
1068
+ - **Assertions:**
1069
+ - load_config_callback() called
1070
+ - _current_config updated
1071
+ - **Coverage:** Lines 167-180
1072
+
1073
+ #### test_on_config_change_updates_config
1074
+ - **Purpose:** Loaded config stored in _current_config
1075
+ - **Setup:** Mock load_config_callback to return new config
1076
+ - **Assertions:**
1077
+ - _current_config updated to new config
1078
+ - get_current_config() returns updated config
1079
+ - **Coverage:** Lines 174-177
1080
+
1081
+ #### test_on_config_change_exception_handling
1082
+ - **Purpose:** Exception in callback keeps previous config
1083
+ - **Setup:** load_config_callback raises exception
1084
+ - **Assertions:**
1085
+ - Exception caught
1086
+ - _current_config unchanged
1087
+ - Manager still running
1088
+ - **Coverage:** Lines 178-180
1089
+
1090
+ #### test_on_config_change_thread_safe
1091
+ - **Purpose:** Config change is atomic
1092
+ - **Setup:** Concurrent read while config_change modifies
1093
+ - **Assertions:**
1094
+ - No torn reads (partial config)
1095
+ - All readers see consistent state
1096
+ - **Coverage:** Lines 174 (locking)
1097
+
1098
+ #### test_stop_stops_watcher
1099
+ - **Purpose:** Stop stops the watcher
1100
+ - **Setup:** manager.start(), then manager.stop()
1101
+ - **Assertions:**
1102
+ - _watcher.is_running() == False
1103
+ - _watcher is None
1104
+ - **Coverage:** Lines 194-199
1105
+
1106
+ #### test_stop_not_running
1107
+ - **Purpose:** Stopping non-running manager is safe
1108
+ - **Setup:** manager.stop() without start
1109
+ - **Assertions:**
1110
+ - No error
1111
+ - _watcher is None
1112
+ - **Coverage:** Lines 196
1113
+
1114
+ #### test_is_running_true
1115
+ - **Purpose:** Check running status when active
1116
+ - **Setup:** manager.start(), check is_running()
1117
+ - **Assertions:**
1118
+ - is_running() returns True
1119
+ - **Coverage:** Lines 201-208
1120
+
1121
+ #### test_is_running_false
1122
+ - **Purpose:** Check running status when stopped
1123
+ - **Setup:** manager.stop(), check is_running()
1124
+ - **Assertions:**
1125
+ - is_running() returns False
1126
+ - **Coverage:** Lines 201-208
1127
+
1128
+ #### test_is_running_not_started
1129
+ - **Purpose:** is_running() returns False before start
1130
+ - **Setup:** manager.is_running() without start
1131
+ - **Assertions:**
1132
+ - Returns False
1133
+ - **Coverage:** Lines 206-207
1134
+
1135
+ #### test_get_current_config_returns_loaded
1136
+ - **Purpose:** Get current configuration
1137
+ - **Setup:** Set _current_config, call get_current_config()
1138
+ - **Assertions:**
1139
+ - Returns _current_config
1140
+ - **Coverage:** Lines 210-217
1141
+
1142
+ #### test_get_current_config_none
1143
+ - **Purpose:** Get current config when none loaded
1144
+ - **Setup:** _current_config = None
1145
+ - **Assertions:**
1146
+ - Returns None
1147
+ - **Coverage:** Lines 210-217
1148
+
1149
+ #### test_get_current_config_thread_safe
1150
+ - **Purpose:** Reading config is thread-safe
1151
+ - **Setup:** Concurrent reads/writes
1152
+ - **Assertions:**
1153
+ - All reads return consistent config
1154
+ - **Coverage:** Lines 216 (locking)
1155
+
1156
+ #### test_set_current_config
1157
+ - **Purpose:** Set current configuration
1158
+ - **Setup:** call set_current_config(new_config)
1159
+ - **Assertions:**
1160
+ - _current_config updated
1161
+ - get_current_config() returns new config
1162
+ - **Coverage:** Lines 219-226
1163
+
1164
+ #### test_set_current_config_thread_safe
1165
+ - **Purpose:** Setting config is thread-safe
1166
+ - **Setup:** Concurrent set/get operations
1167
+ - **Assertions:**
1168
+ - All operations atomic
1169
+ - No torn writes
1170
+ - **Coverage:** Lines 225 (locking)
1171
+
1172
+ ---
1173
+
1174
+ ## Test Class: TestHotReloadIntegration
1175
+
1176
+ Integration tests for hot-reload system.
1177
+
1178
+ ### Fixtures Required
1179
+ ```
1180
+ - temp_config_file: temp YAML file
1181
+ - cleanup: stop manager after tests
1182
+ ```
1183
+
1184
+ ### Test Methods
1185
+
1186
+ #### test_hotreload_full_lifecycle
1187
+ - **Purpose:** Complete hot-reload workflow
1188
+ - **Setup:** Start manager, modify file, wait, stop
1189
+ - **Assertions:**
1190
+ - Watcher detects change
1191
+ - Callback called
1192
+ - Config updated
1193
+ - Manager stops cleanly
1194
+ - **Coverage:** Lines full integration
1195
+
1196
+ #### test_hotreload_multiple_changes
1197
+ - **Purpose:** Multiple file changes detected
1198
+ - **Setup:** Start manager, modify file 3 times
1199
+ - **Assertions:**
1200
+ - Each change detected
1201
+ - Config updated each time
1202
+ - **Coverage:** Lines 74-88 (repeated)
1203
+
1204
+ #### test_hotreload_rapid_changes
1205
+ - **Purpose:** Rapid changes handled correctly
1206
+ - **Setup:** Modify file 10 times in 1 second
1207
+ - **Assertions:**
1208
+ - All changes eventually detected
1209
+ - No missed changes
1210
+ - No crashes
1211
+ - **Coverage:** Lines full integration
1212
+
1213
+ #### test_hotreload_callback_timing
1214
+ - **Purpose:** Callback timing within tolerance
1215
+ - **Setup:** Time callback execution
1216
+ - **Assertions:**
1217
+ - Callback completes <100ms typically
1218
+ - No excessive delays
1219
+ - **Coverage:** Lines 167-180 (performance)
1220
+
1221
+ ---
1222
+
1223
+ ## Coverage Summary for hot_reload.py
1224
+
1225
+ | Section | Lines | Coverage | Status |
1226
+ |---------|-------|----------|--------|
1227
+ | FileInfo | 16-20 | 100% | 2 tests |
1228
+ | Watcher Init | 29-52 | 100% | 3 tests |
1229
+ | File Info Methods | 55-67 | 100% | 3 tests |
1230
+ | Change Detection | 94-109 | 100% | 7 tests |
1231
+ | Start/Stop | 111-131 | 100% | 8 tests |
1232
+ | Is Running | 133-140 | 100% | 3 tests |
1233
+ | Watch Loop | 69-92 | 100% | 8 tests |
1234
+ | Manager Init | 149-165 | 100% | 1 test |
1235
+ | Manager Start/Stop | 182-199 | 100% | 6 tests |
1236
+ | Manager Config | 210-226 | 100% | 6 tests |
1237
+ | Config Change | 167-180 | 100% | 4 tests |
1238
+ | Integration | Full | 100% | 4 tests |
1239
+ | **TOTAL** | **99** | **95%+** | **55 tests** |
1240
+
1241
+ ---
1242
+
1243
+ # 3. TEST_CONFIG_MODELS.PY
1244
+
1245
+ **Target:** 80+ lines, 95% coverage of 85 statements
1246
+ **Purpose:** Validate data model initialization, validation, and error handling
1247
+
1248
+ ## Test Class: TestConversationSettings
1249
+
1250
+ Tests for ConversationSettings dataclass.
1251
+
1252
+ ### Fixtures Required
1253
+ ```
1254
+ - default_settings: ConversationSettings()
1255
+ ```
1256
+
1257
+ ### Test Methods
1258
+
1259
+ #### test_conversation_settings_init_defaults
1260
+ - **Purpose:** Initialize with defaults
1261
+ - **Setup:** ConversationSettings()
1262
+ - **Assertions:**
1263
+ - max_questions == 5
1264
+ - allow_skip == True
1265
+ - **Coverage:** Lines 40-48
1266
+
1267
+ #### test_conversation_settings_init_custom_values
1268
+ - **Purpose:** Initialize with custom values
1269
+ - **Setup:** ConversationSettings(max_questions=10, allow_skip=False)
1270
+ - **Assertions:**
1271
+ - max_questions == 10
1272
+ - allow_skip == False
1273
+ - **Coverage:** Lines 40-48
1274
+
1275
+ #### test_conversation_settings_post_init_valid
1276
+ - **Purpose:** Validation passes with valid values
1277
+ - **Setup:** ConversationSettings(max_questions=0, allow_skip=True)
1278
+ - **Assertions:**
1279
+ - No exception raised
1280
+ - Object created successfully
1281
+ - **Coverage:** Lines 50-59
1282
+
1283
+ #### test_conversation_settings_max_questions_negative
1284
+ - **Purpose:** Negative max_questions rejected
1285
+ - **Setup:** ConversationSettings(max_questions=-1)
1286
+ - **Assertions:**
1287
+ - ValueError raised
1288
+ - Message contains "non-negative integer"
1289
+ - **Coverage:** Lines 52-55
1290
+
1291
+ #### test_conversation_settings_max_questions_non_int
1292
+ - **Purpose:** Non-integer max_questions rejected
1293
+ - **Setup:** ConversationSettings(max_questions="5")
1294
+ - **Assertions:**
1295
+ - ValueError raised
1296
+ - Message contains type information
1297
+ - **Coverage:** Lines 52-55
1298
+
1299
+ #### test_conversation_settings_allow_skip_non_bool
1300
+ - **Purpose:** Non-boolean allow_skip rejected
1301
+ - **Setup:** ConversationSettings(allow_skip="true")
1302
+ - **Assertions:**
1303
+ - ValueError raised
1304
+ - Message contains boolean requirement
1305
+ - **Coverage:** Lines 56-59
1306
+
1307
+ #### test_conversation_settings_max_questions_zero
1308
+ - **Purpose:** max_questions=0 is valid (unlimited)
1309
+ - **Setup:** ConversationSettings(max_questions=0)
1310
+ - **Assertions:**
1311
+ - No error
1312
+ - max_questions == 0
1313
+ - **Coverage:** Lines 52
1314
+
1315
+ #### test_conversation_settings_max_questions_large
1316
+ - **Purpose:** Large max_questions values accepted
1317
+ - **Setup:** ConversationSettings(max_questions=9999)
1318
+ - **Assertions:**
1319
+ - No error
1320
+ - max_questions == 9999
1321
+ - **Coverage:** Lines 52-55
1322
+
1323
+ ---
1324
+
1325
+ ## Test Class: TestSkipTrackingSettings
1326
+
1327
+ Tests for SkipTrackingSettings dataclass.
1328
+
1329
+ ### Fixtures Required
1330
+ ```
1331
+ - default_settings: SkipTrackingSettings()
1332
+ ```
1333
+
1334
+ ### Test Methods
1335
+
1336
+ #### test_skip_tracking_settings_init_defaults
1337
+ - **Purpose:** Initialize with defaults
1338
+ - **Setup:** SkipTrackingSettings()
1339
+ - **Assertions:**
1340
+ - enabled == True
1341
+ - max_consecutive_skips == 3
1342
+ - reset_on_positive == True
1343
+ - **Coverage:** Lines 62-73
1344
+
1345
+ #### test_skip_tracking_settings_init_custom_values
1346
+ - **Purpose:** Initialize with custom values
1347
+ - **Setup:** SkipTrackingSettings(enabled=False, max_consecutive_skips=10, reset_on_positive=False)
1348
+ - **Assertions:**
1349
+ - Values set correctly
1350
+ - **Coverage:** Lines 62-73
1351
+
1352
+ #### test_skip_tracking_settings_enabled_non_bool
1353
+ - **Purpose:** Non-boolean enabled rejected
1354
+ - **Setup:** SkipTrackingSettings(enabled="true")
1355
+ - **Assertions:**
1356
+ - ValueError raised
1357
+ - **Coverage:** Lines 77-80
1358
+
1359
+ #### test_skip_tracking_settings_max_negative
1360
+ - **Purpose:** Negative max_consecutive_skips rejected
1361
+ - **Setup:** SkipTrackingSettings(max_consecutive_skips=-1)
1362
+ - **Assertions:**
1363
+ - ValueError raised
1364
+ - **Coverage:** Lines 81-84
1365
+
1366
+ #### test_skip_tracking_settings_max_non_int
1367
+ - **Purpose:** Non-integer max_consecutive_skips rejected
1368
+ - **Setup:** SkipTrackingSettings(max_consecutive_skips="3")
1369
+ - **Assertions:**
1370
+ - ValueError raised
1371
+ - **Coverage:** Lines 81-84
1372
+
1373
+ #### test_skip_tracking_settings_reset_non_bool
1374
+ - **Purpose:** Non-boolean reset_on_positive rejected
1375
+ - **Setup:** SkipTrackingSettings(reset_on_positive="true")
1376
+ - **Assertions:**
1377
+ - ValueError raised
1378
+ - **Coverage:** Lines 85-88
1379
+
1380
+ #### test_skip_tracking_settings_max_zero
1381
+ - **Purpose:** max_consecutive_skips=0 is valid (unlimited)
1382
+ - **Setup:** SkipTrackingSettings(max_consecutive_skips=0)
1383
+ - **Assertions:**
1384
+ - No error
1385
+ - **Coverage:** Lines 81
1386
+
1387
+ ---
1388
+
1389
+ ## Test Class: TestTemplateSettings
1390
+
1391
+ Tests for TemplateSettings dataclass.
1392
+
1393
+ ### Fixtures Required
1394
+ ```
1395
+ - default_settings: TemplateSettings()
1396
+ ```
1397
+
1398
+ ### Test Methods
1399
+
1400
+ #### test_template_settings_init_defaults
1401
+ - **Purpose:** Initialize with defaults
1402
+ - **Setup:** TemplateSettings()
1403
+ - **Assertions:**
1404
+ - format == "structured"
1405
+ - tone == "brief"
1406
+ - **Coverage:** Lines 91-100
1407
+
1408
+ #### test_template_settings_init_custom_values
1409
+ - **Purpose:** Initialize with custom values
1410
+ - **Setup:** TemplateSettings(format="free-text", tone="detailed")
1411
+ - **Assertions:**
1412
+ - format == "free-text"
1413
+ - tone == "detailed"
1414
+ - **Coverage:** Lines 91-100
1415
+
1416
+ #### test_template_settings_format_structured
1417
+ - **Purpose:** Structured format valid
1418
+ - **Setup:** TemplateSettings(format="structured")
1419
+ - **Assertions:**
1420
+ - No error
1421
+ - **Coverage:** Lines 102-108
1422
+
1423
+ #### test_template_settings_format_free_text
1424
+ - **Purpose:** Free-text format valid
1425
+ - **Setup:** TemplateSettings(format="free-text")
1426
+ - **Assertions:**
1427
+ - No error
1428
+ - **Coverage:** Lines 102-108
1429
+
1430
+ #### test_template_settings_format_invalid
1431
+ - **Purpose:** Invalid format rejected
1432
+ - **Setup:** TemplateSettings(format="invalid-format")
1433
+ - **Assertions:**
1434
+ - ValueError raised
1435
+ - Message lists valid options
1436
+ - **Coverage:** Lines 104-108
1437
+
1438
+ #### test_template_settings_tone_brief
1439
+ - **Purpose:** Brief tone valid
1440
+ - **Setup:** TemplateSettings(tone="brief")
1441
+ - **Assertions:**
1442
+ - No error
1443
+ - **Coverage:** Lines 110-114
1444
+
1445
+ #### test_template_settings_tone_detailed
1446
+ - **Purpose:** Detailed tone valid
1447
+ - **Setup:** TemplateSettings(tone="detailed")
1448
+ - **Assertions:**
1449
+ - No error
1450
+ - **Coverage:** Lines 110-114
1451
+
1452
+ #### test_template_settings_tone_invalid
1453
+ - **Purpose:** Invalid tone rejected
1454
+ - **Setup:** TemplateSettings(tone="invalid-tone")
1455
+ - **Assertions:**
1456
+ - ValueError raised
1457
+ - Message lists valid options
1458
+ - **Coverage:** Lines 110-114
1459
+
1460
+ #### test_template_settings_format_case_sensitive
1461
+ - **Purpose:** Format values are case-sensitive
1462
+ - **Setup:** TemplateSettings(format="Structured")
1463
+ - **Assertions:**
1464
+ - ValueError raised
1465
+ - **Coverage:** Lines 104-108
1466
+
1467
+ #### test_template_settings_tone_case_sensitive
1468
+ - **Purpose:** Tone values are case-sensitive
1469
+ - **Setup:** TemplateSettings(tone="Brief")
1470
+ - **Assertions:**
1471
+ - ValueError raised
1472
+ - **Coverage:** Lines 110-114
1473
+
1474
+ ---
1475
+
1476
+ ## Test Class: TestFeedbackConfiguration
1477
+
1478
+ Tests for main FeedbackConfiguration dataclass.
1479
+
1480
+ ### Fixtures Required
1481
+ ```
1482
+ - minimal_config: FeedbackConfiguration()
1483
+ - full_config: Complete FeedbackConfiguration with all sections
1484
+ ```
1485
+
1486
+ ### Test Methods
1487
+
1488
+ #### test_feedback_configuration_init_defaults
1489
+ - **Purpose:** Initialize with defaults
1490
+ - **Setup:** FeedbackConfiguration()
1491
+ - **Assertions:**
1492
+ - enabled == True
1493
+ - trigger_mode == "failures-only"
1494
+ - operations == None
1495
+ - All nested objects initialized with defaults
1496
+ - **Coverage:** Lines 117-151
1497
+
1498
+ #### test_feedback_configuration_init_custom_values
1499
+ - **Purpose:** Initialize with custom values
1500
+ - **Setup:** FeedbackConfiguration(enabled=False, trigger_mode="always")
1501
+ - **Assertions:**
1502
+ - Values set correctly
1503
+ - **Coverage:** Lines 117-151
1504
+
1505
+ #### test_feedback_configuration_normalize_nested_dict_conversation
1506
+ - **Purpose:** Dict conversation_settings converted to object
1507
+ - **Setup:** FeedbackConfiguration(conversation_settings={"max_questions": 10})
1508
+ - **Assertions:**
1509
+ - conversation_settings is ConversationSettings
1510
+ - max_questions == 10
1511
+ - **Coverage:** Lines 136-141
1512
+
1513
+ #### test_feedback_configuration_normalize_nested_dict_skip_tracking
1514
+ - **Purpose:** Dict skip_tracking converted to object
1515
+ - **Setup:** FeedbackConfiguration(skip_tracking={"enabled": False})
1516
+ - **Assertions:**
1517
+ - skip_tracking is SkipTrackingSettings
1518
+ - enabled == False
1519
+ - **Coverage:** Lines 143-146
1520
+
1521
+ #### test_feedback_configuration_normalize_nested_dict_templates
1522
+ - **Purpose:** Dict templates converted to object
1523
+ - **Setup:** FeedbackConfiguration(templates={"tone": "detailed"})
1524
+ - **Assertions:**
1525
+ - templates is TemplateSettings
1526
+ - tone == "detailed"
1527
+ - **Coverage:** Lines 148-151
1528
+
1529
+ #### test_feedback_configuration_normalize_nested_none_conversation
1530
+ - **Purpose:** None conversation_settings gets default
1531
+ - **Setup:** FeedbackConfiguration(conversation_settings=None)
1532
+ - **Assertions:**
1533
+ - conversation_settings is ConversationSettings
1534
+ - Has default values
1535
+ - **Coverage:** Lines 140-141
1536
+
1537
+ #### test_feedback_configuration_normalize_nested_none_skip_tracking
1538
+ - **Purpose:** None skip_tracking gets default
1539
+ - **Setup:** FeedbackConfiguration(skip_tracking=None)
1540
+ - **Assertions:**
1541
+ - skip_tracking is SkipTrackingSettings
1542
+ - Has default values
1543
+ - **Coverage:** Lines 145-146
1544
+
1545
+ #### test_feedback_configuration_normalize_nested_none_templates
1546
+ - **Purpose:** None templates gets default
1547
+ - **Setup:** FeedbackConfiguration(templates=None)
1548
+ - **Assertions:**
1549
+ - templates is TemplateSettings
1550
+ - Has default values
1551
+ - **Coverage:** Lines 151
1552
+
1553
+ #### test_feedback_configuration_validate_trigger_mode_always
1554
+ - **Purpose:** Trigger mode "always" valid
1555
+ - **Setup:** FeedbackConfiguration(trigger_mode="always")
1556
+ - **Assertions:**
1557
+ - No error
1558
+ - **Coverage:** Lines 153-159
1559
+
1560
+ #### test_feedback_configuration_validate_trigger_mode_failures_only
1561
+ - **Purpose:** Trigger mode "failures-only" valid
1562
+ - **Setup:** FeedbackConfiguration(trigger_mode="failures-only")
1563
+ - **Assertions:**
1564
+ - No error
1565
+ - **Coverage:** Lines 153-159
1566
+
1567
+ #### test_feedback_configuration_validate_trigger_mode_specific_ops
1568
+ - **Purpose:** Trigger mode "specific-operations" valid
1569
+ - **Setup:** FeedbackConfiguration(trigger_mode="specific-operations", operations=["qa"])
1570
+ - **Assertions:**
1571
+ - No error
1572
+ - **Coverage:** Lines 153-159
1573
+
1574
+ #### test_feedback_configuration_validate_trigger_mode_never
1575
+ - **Purpose:** Trigger mode "never" valid
1576
+ - **Setup:** FeedbackConfiguration(trigger_mode="never")
1577
+ - **Assertions:**
1578
+ - No error
1579
+ - **Coverage:** Lines 153-159
1580
+
1581
+ #### test_feedback_configuration_validate_trigger_mode_invalid
1582
+ - **Purpose:** Invalid trigger_mode rejected
1583
+ - **Setup:** FeedbackConfiguration(trigger_mode="invalid-mode")
1584
+ - **Assertions:**
1585
+ - ValueError raised
1586
+ - Message lists valid modes
1587
+ - **Coverage:** Lines 155-159
1588
+
1589
+ #### test_feedback_configuration_validate_trigger_mode_case_sensitive
1590
+ - **Purpose:** Trigger modes are case-sensitive
1591
+ - **Setup:** FeedbackConfiguration(trigger_mode="Always")
1592
+ - **Assertions:**
1593
+ - ValueError raised
1594
+ - **Coverage:** Lines 155-159
1595
+
1596
+ #### test_feedback_configuration_validate_enabled_true
1597
+ - **Purpose:** enabled=True valid
1598
+ - **Setup:** FeedbackConfiguration(enabled=True)
1599
+ - **Assertions:**
1600
+ - No error
1601
+ - **Coverage:** Lines 161-166
1602
+
1603
+ #### test_feedback_configuration_validate_enabled_false
1604
+ - **Purpose:** enabled=False valid
1605
+ - **Setup:** FeedbackConfiguration(enabled=False)
1606
+ - **Assertions:**
1607
+ - No error
1608
+ - **Coverage:** Lines 161-166
1609
+
1610
+ #### test_feedback_configuration_validate_enabled_non_bool
1611
+ - **Purpose:** Non-boolean enabled rejected
1612
+ - **Setup:** FeedbackConfiguration(enabled="true")
1613
+ - **Assertions:**
1614
+ - ValueError raised
1615
+ - **Coverage:** Lines 163-166
1616
+
1617
+ #### test_feedback_configuration_validate_operations_specific_ops_required
1618
+ - **Purpose:** specific-operations mode requires operations
1619
+ - **Setup:** FeedbackConfiguration(trigger_mode="specific-operations", operations=None)
1620
+ - **Assertions:**
1621
+ - ValueError raised
1622
+ - Message mentions operations requirement
1623
+ - **Coverage:** Lines 168-174
1624
+
1625
+ #### test_feedback_configuration_validate_operations_specific_ops_empty
1626
+ - **Purpose:** specific-operations mode requires non-empty operations
1627
+ - **Setup:** FeedbackConfiguration(trigger_mode="specific-operations", operations=[])
1628
+ - **Assertions:**
1629
+ - ValueError raised
1630
+ - **Coverage:** Lines 171-174
1631
+
1632
+ #### test_feedback_configuration_validate_operations_specific_ops_non_list
1633
+ - **Purpose:** specific-operations mode requires list operations
1634
+ - **Setup:** FeedbackConfiguration(trigger_mode="specific-operations", operations="qa")
1635
+ - **Assertions:**
1636
+ - ValueError raised
1637
+ - **Coverage:** Lines 171-174
1638
+
1639
+ #### test_feedback_configuration_validate_operations_other_modes
1640
+ - **Purpose:** Other modes don't require operations
1641
+ - **Setup:** FeedbackConfiguration(trigger_mode="always", operations=None)
1642
+ - **Assertions:**
1643
+ - No error
1644
+ - **Coverage:** Lines 170
1645
+
1646
+ #### test_feedback_configuration_validate_operations_list_with_values
1647
+ - **Purpose:** Valid operations list
1648
+ - **Setup:** FeedbackConfiguration(trigger_mode="specific-operations", operations=["qa", "dev"])
1649
+ - **Assertions:**
1650
+ - No error
1651
+ - operations == ["qa", "dev"]
1652
+ - **Coverage:** Lines 171-174
1653
+
1654
+ #### test_feedback_configuration_to_dict
1655
+ - **Purpose:** Convert configuration to dictionary
1656
+ - **Setup:** config.to_dict()
1657
+ - **Assertions:**
1658
+ - Returns dict
1659
+ - All fields present
1660
+ - Nested objects converted to dicts
1661
+ - **Coverage:** Lines 183-192
1662
+
1663
+ #### test_feedback_configuration_to_dict_contains_enabled
1664
+ - **Purpose:** to_dict includes enabled
1665
+ - **Setup:** config.to_dict()
1666
+ - **Assertions:**
1667
+ - dict["enabled"] == config.enabled
1668
+ - **Coverage:** Lines 186
1669
+
1670
+ #### test_feedback_configuration_to_dict_contains_trigger_mode
1671
+ - **Purpose:** to_dict includes trigger_mode
1672
+ - **Setup:** config.to_dict()
1673
+ - **Assertions:**
1674
+ - dict["trigger_mode"] == config.trigger_mode
1675
+ - **Coverage:** Lines 187
1676
+
1677
+ #### test_feedback_configuration_to_dict_contains_operations
1678
+ - **Purpose:** to_dict includes operations
1679
+ - **Setup:** config.to_dict()
1680
+ - **Assertions:**
1681
+ - dict["operations"] == config.operations
1682
+ - **Coverage:** Lines 188
1683
+
1684
+ #### test_feedback_configuration_to_dict_contains_nested
1685
+ - **Purpose:** to_dict includes nested objects as dicts
1686
+ - **Setup:** config.to_dict()
1687
+ - **Assertions:**
1688
+ - dict["conversation_settings"] is dict (not object)
1689
+ - dict["skip_tracking"] is dict
1690
+ - dict["templates"] is dict
1691
+ - **Coverage:** Lines 189-191
1692
+
1693
+ #### test_feedback_configuration_post_init_order
1694
+ - **Purpose:** Post-init runs in correct order
1695
+ - **Setup:** Create config with invalid nested object first
1696
+ - **Assertions:**
1697
+ - Normalization happens before validation
1698
+ - Validation catches normalized values
1699
+ - **Coverage:** Lines 176-181
1700
+
1701
+ #### test_feedback_configuration_full_integration
1702
+ - **Purpose:** Complete valid configuration
1703
+ - **Setup:** All fields set with valid values
1704
+ - **Assertions:**
1705
+ - No error
1706
+ - All nested objects present
1707
+ - Validation passes
1708
+ - **Coverage:** Lines 176-181 (integration)
1709
+
1710
+ ---
1711
+
1712
+ ## Test Class: TestEnumClasses
1713
+
1714
+ Tests for enum definitions.
1715
+
1716
+ ### Test Methods
1717
+
1718
+ #### test_trigger_mode_enum_values
1719
+ - **Purpose:** TriggerMode enum has correct values
1720
+ - **Setup:** Access TriggerMode enum members
1721
+ - **Assertions:**
1722
+ - TriggerMode.ALWAYS.value == "always"
1723
+ - TriggerMode.FAILURES_ONLY.value == "failures-only"
1724
+ - TriggerMode.SPECIFIC_OPS.value == "specific-operations"
1725
+ - TriggerMode.NEVER.value == "never"
1726
+ - **Coverage:** Lines 13-18
1727
+
1728
+ #### test_template_format_enum_values
1729
+ - **Purpose:** TemplateFormat enum has correct values
1730
+ - **Setup:** Access TemplateFormat enum members
1731
+ - **Assertions:**
1732
+ - TemplateFormat.STRUCTURED.value == "structured"
1733
+ - TemplateFormat.FREE_TEXT.value == "free-text"
1734
+ - **Coverage:** Lines 21-24
1735
+
1736
+ #### test_template_tone_enum_values
1737
+ - **Purpose:** TemplateTone enum has correct values
1738
+ - **Setup:** Access TemplateTone enum members
1739
+ - **Assertions:**
1740
+ - TemplateTone.BRIEF.value == "brief"
1741
+ - TemplateTone.DETAILED.value == "detailed"
1742
+ - **Coverage:** Lines 27-30
1743
+
1744
+ ---
1745
+
1746
+ ## Test Class: TestValidationConstants
1747
+
1748
+ Tests for validation constant definitions.
1749
+
1750
+ ### Test Methods
1751
+
1752
+ #### test_valid_template_formats_contains_all
1753
+ - **Purpose:** VALID_TEMPLATE_FORMATS has all formats
1754
+ - **Setup:** Check VALID_TEMPLATE_FORMATS set
1755
+ - **Assertions:**
1756
+ - "structured" in set
1757
+ - "free-text" in set
1758
+ - Length == 2
1759
+ - **Coverage:** Lines 34
1760
+
1761
+ #### test_valid_template_tones_contains_all
1762
+ - **Purpose:** VALID_TEMPLATE_TONES has all tones
1763
+ - **Setup:** Check VALID_TEMPLATE_TONES set
1764
+ - **Assertions:**
1765
+ - "brief" in set
1766
+ - "detailed" in set
1767
+ - Length == 2
1768
+ - **Coverage:** Lines 35
1769
+
1770
+ #### test_valid_trigger_modes_contains_all
1771
+ - **Purpose:** VALID_TRIGGER_MODES has all modes
1772
+ - **Setup:** Check VALID_TRIGGER_MODES set
1773
+ - **Assertions:**
1774
+ - "always" in set
1775
+ - "failures-only" in set
1776
+ - "specific-operations" in set
1777
+ - "never" in set
1778
+ - Length == 4
1779
+ - **Coverage:** Lines 36
1780
+
1781
+ ---
1782
+
1783
+ ## Coverage Summary for config_models.py
1784
+
1785
+ | Section | Lines | Coverage | Status |
1786
+ |---------|-------|----------|--------|
1787
+ | Enums | 13-30 | 100% | 3 tests |
1788
+ | Constants | 34-36 | 100% | 3 tests |
1789
+ | ConversationSettings | 40-59 | 100% | 8 tests |
1790
+ | SkipTrackingSettings | 62-88 | 100% | 7 tests |
1791
+ | TemplateSettings | 91-114 | 100% | 10 tests |
1792
+ | FeedbackConfiguration | 117-192 | 100% | 28 tests |
1793
+ | **TOTAL** | **85** | **95%+** | **59 tests** |
1794
+
1795
+ ---
1796
+
1797
+ # 4. TEST_SKIP_TRACKER.PY
1798
+
1799
+ **Target:** 75+ lines, 95% coverage of 78 statements
1800
+ **Purpose:** Validate skip tracking, atomicity, and persistence
1801
+
1802
+ ## Test Class: TestSkipTrackerInitialization
1803
+
1804
+ Tests for SkipTracker initialization and loading.
1805
+
1806
+ ### Fixtures Required
1807
+ ```
1808
+ - temp_log_path: temp file for skip log
1809
+ - tracker: SkipTracker instance
1810
+ ```
1811
+
1812
+ ### Test Methods
1813
+
1814
+ #### test_skip_tracker_init_default_path
1815
+ - **Purpose:** Initialize with default path
1816
+ - **Setup:** SkipTracker()
1817
+ - **Assertions:**
1818
+ - skip_log_path == SkipTracker.DEFAULT_SKIP_LOG_PATH
1819
+ - _skip_counters == {}
1820
+ - **Coverage:** Lines 27-40
1821
+
1822
+ #### test_skip_tracker_init_custom_path
1823
+ - **Purpose:** Initialize with custom path
1824
+ - **Setup:** SkipTracker(skip_log_path=custom_path)
1825
+ - **Assertions:**
1826
+ - skip_log_path == custom_path
1827
+ - _skip_counters == {}
1828
+ - **Coverage:** Lines 34-40
1829
+
1830
+ #### test_skip_tracker_loads_existing_counters
1831
+ - **Purpose:** Load counters from existing log
1832
+ - **Setup:** Create log file with skip entries, init SkipTracker
1833
+ - **Assertions:**
1834
+ - Counters loaded from file
1835
+ - _skip_counters populated correctly
1836
+ - **Coverage:** Lines 42-62
1837
+
1838
+ #### test_skip_tracker_loads_empty_log
1839
+ - **Purpose:** Handle empty log file
1840
+ - **Setup:** Empty log file exists, init SkipTracker
1841
+ - **Assertions:**
1842
+ - No error
1843
+ - _skip_counters == {}
1844
+ - **Coverage:** Lines 42-62
1845
+
1846
+ #### test_skip_tracker_loads_missing_log
1847
+ - **Purpose:** Handle missing log file gracefully
1848
+ - **Setup:** Log file doesn't exist, init SkipTracker
1849
+ - **Assertions:**
1850
+ - No error
1851
+ - _skip_counters == {}
1852
+ - **Coverage:** Lines 44
1853
+
1854
+ #### test_skip_tracker_loads_parses_format
1855
+ - **Purpose:** Parse log file format correctly
1856
+ - **Setup:** Log with format: "timestamp: operation: count, action=action"
1857
+ - **Assertions:**
1858
+ - Operation name extracted
1859
+ - Count parsed as integer
1860
+ - **Coverage:** Lines 47-59
1861
+
1862
+ #### test_skip_tracker_loads_skips_comments
1863
+ - **Purpose:** Skip comment lines in log
1864
+ - **Setup:** Log with comment lines starting with "#"
1865
+ - **Assertions:**
1866
+ - Comments ignored
1867
+ - Valid entries loaded
1868
+ - **Coverage:** Lines 49-50
1869
+
1870
+ #### test_skip_tracker_loads_skips_blank_lines
1871
+ - **Purpose:** Skip blank lines in log
1872
+ - **Setup:** Log with empty lines
1873
+ - **Assertions:**
1874
+ - Blank lines ignored
1875
+ - Valid entries loaded
1876
+ - **Coverage:** Lines 49
1877
+
1878
+ #### test_skip_tracker_loads_malformed_entries
1879
+ - **Purpose:** Malformed log entries ignored
1880
+ - **Setup:** Log with invalid format entries
1881
+ - **Assertions:**
1882
+ - Malformed entries skipped
1883
+ - Valid entries loaded
1884
+ - No error
1885
+ - **Coverage:** Lines 58-59
1886
+
1887
+ #### test_skip_tracker_loads_ioerror
1888
+ - **Purpose:** Handle file read error
1889
+ - **Setup:** Log file exists but unreadable
1890
+ - **Assertions:**
1891
+ - IOError caught
1892
+ - _skip_counters == {}
1893
+ - No exception raised
1894
+ - **Coverage:** Lines 60-62
1895
+
1896
+ #### test_skip_tracker_loads_uses_latest_count
1897
+ - **Purpose:** Use latest count for duplicate operations
1898
+ - **Setup:** Log has multiple entries for same operation
1899
+ - **Assertions:**
1900
+ - Latest count used
1901
+ - **Coverage:** Lines 57
1902
+
1903
+ #### test_skip_tracker_default_rating_threshold
1904
+ - **Purpose:** DEFAULT_RATING_THRESHOLD defined
1905
+ - **Setup:** Check SkipTracker.DEFAULT_RATING_THRESHOLD
1906
+ - **Assertions:**
1907
+ - DEFAULT_RATING_THRESHOLD == 4
1908
+ - **Coverage:** Lines 25
1909
+
1910
+ ---
1911
+
1912
+ ## Test Class: TestSkipTrackerIncrement
1913
+
1914
+ Tests for skip counter increment operations.
1915
+
1916
+ ### Fixtures Required
1917
+ ```
1918
+ - tracker: SkipTracker instance
1919
+ ```
1920
+
1921
+ ### Test Methods
1922
+
1923
+ #### test_increment_skip_new_operation
1924
+ - **Purpose:** Increment counter for new operation
1925
+ - **Setup:** increment_skip("test_op")
1926
+ - **Assertions:**
1927
+ - Returns 1
1928
+ - get_skip_count("test_op") == 1
1929
+ - **Coverage:** Lines 85-99
1930
+
1931
+ #### test_increment_skip_existing_operation
1932
+ - **Purpose:** Increment existing counter
1933
+ - **Setup:** increment twice for same operation
1934
+ - **Assertions:**
1935
+ - First call returns 1
1936
+ - Second call returns 2
1937
+ - **Coverage:** Lines 94-97
1938
+
1939
+ #### test_increment_skip_multiple_operations
1940
+ - **Purpose:** Track multiple operations independently
1941
+ - **Setup:** Increment "op1" and "op2"
1942
+ - **Assertions:**
1943
+ - get_skip_count("op1") == 1
1944
+ - get_skip_count("op2") == 1
1945
+ - **Coverage:** Lines 94-97
1946
+
1947
+ #### test_increment_skip_multiple_times
1948
+ - **Purpose:** Multiple increments accumulate
1949
+ - **Setup:** increment_skip("op") 5 times
1950
+ - **Assertions:**
1951
+ - Final count == 5
1952
+ - **Coverage:** Lines 94-97
1953
+
1954
+ #### test_increment_skip_logs_operation
1955
+ - **Purpose:** Skip logged to file
1956
+ - **Setup:** increment_skip("op"), check log file
1957
+ - **Assertions:**
1958
+ - Log file contains entry
1959
+ - Entry has: timestamp, operation name, count, action=skip
1960
+ - **Coverage:** Lines 98
1961
+
1962
+ #### test_increment_skip_thread_safe
1963
+ - **Purpose:** Concurrent increments are thread-safe
1964
+ - **Setup:** 10 threads each increment same operation 10 times
1965
+ - **Assertions:**
1966
+ - Final count == 100
1967
+ - No lost updates
1968
+ - **Coverage:** Lines 94 (locking)
1969
+
1970
+ ---
1971
+
1972
+ ## Test Class: TestSkipTrackerReset
1973
+
1974
+ Tests for skip counter reset operations.
1975
+
1976
+ ### Fixtures Required
1977
+ ```
1978
+ - tracker: SkipTracker instance with initial counts
1979
+ ```
1980
+
1981
+ ### Test Methods
1982
+
1983
+ #### test_reset_skip_counter
1984
+ - **Purpose:** Reset skip counter
1985
+ - **Setup:** increment "op", reset, check count
1986
+ - **Assertions:**
1987
+ - get_skip_count("op") == 0
1988
+ - **Coverage:** Lines 113-122
1989
+
1990
+ #### test_reset_skip_counter_nonexistent
1991
+ - **Purpose:** Reset non-existent operation safe
1992
+ - **Setup:** reset_skip_counter("nonexistent")
1993
+ - **Assertions:**
1994
+ - No error
1995
+ - Still 0 (or in _skip_counters as 0)
1996
+ - **Coverage:** Lines 120-121
1997
+
1998
+ #### test_reset_skip_counter_logs_operation
1999
+ - **Purpose:** Reset logged to file
2000
+ - **Setup:** reset_skip_counter("op"), check log
2001
+ - **Assertions:**
2002
+ - Log contains reset entry
2003
+ - action=reset
2004
+ - count == 0
2005
+ - **Coverage:** Lines 122
2006
+
2007
+ #### test_reset_skip_counter_thread_safe
2008
+ - **Purpose:** Reset is thread-safe
2009
+ - **Setup:** Concurrent increment and reset
2010
+ - **Assertions:**
2011
+ - No race conditions
2012
+ - Final state consistent
2013
+ - **Coverage:** Lines 119 (locking)
2014
+
2015
+ #### test_reset_skip_counter_multiple_operations
2016
+ - **Purpose:** Reset one operation, others unchanged
2017
+ - **Setup:** Increment "op1" and "op2", reset "op1"
2018
+ - **Assertions:**
2019
+ - get_skip_count("op1") == 0
2020
+ - get_skip_count("op2") > 0
2021
+ - **Coverage:** Lines 120-121
2022
+
2023
+ ---
2024
+
2025
+ ## Test Class: TestSkipTrackerLimitCheck
2026
+
2027
+ Tests for skip limit enforcement.
2028
+
2029
+ ### Fixtures Required
2030
+ ```
2031
+ - tracker: SkipTracker instance
2032
+ ```
2033
+
2034
+ ### Test Methods
2035
+
2036
+ #### test_check_skip_limit_not_reached
2037
+ - **Purpose:** Limit not reached returns False
2038
+ - **Setup:** increment 2 times, check_skip_limit with max=5
2039
+ - **Assertions:**
2040
+ - Returns False
2041
+ - **Coverage:** Lines 124-145
2042
+
2043
+ #### test_check_skip_limit_reached
2044
+ - **Purpose:** Limit reached returns True
2045
+ - **Setup:** increment 5 times, check_skip_limit with max=5
2046
+ - **Assertions:**
2047
+ - Returns True
2048
+ - **Coverage:** Lines 142-144
2049
+
2050
+ #### test_check_skip_limit_exceeded
2051
+ - **Purpose:** Exceeded limit returns True
2052
+ - **Setup:** increment 10 times, check_skip_limit with max=5
2053
+ - **Assertions:**
2054
+ - Returns True
2055
+ - **Coverage:** Lines 142-144
2056
+
2057
+ #### test_check_skip_limit_zero_unlimited
2058
+ - **Purpose:** max=0 means unlimited (always False)
2059
+ - **Setup:** increment 1000 times, check_skip_limit with max=0
2060
+ - **Assertions:**
2061
+ - Returns False
2062
+ - **Coverage:** Lines 136-138
2063
+
2064
+ #### test_check_skip_limit_logs_block
2065
+ - **Purpose:** Limit block logged to file
2066
+ - **Setup:** Reach limit, check_skip_limit, verify log
2067
+ - **Assertions:**
2068
+ - Log contains block entry
2069
+ - action=block
2070
+ - **Coverage:** Lines 143
2071
+
2072
+ #### test_check_skip_limit_thread_safe
2073
+ - **Purpose:** Limit check is thread-safe
2074
+ - **Setup:** Concurrent increment and limit check
2075
+ - **Assertions:**
2076
+ - No race conditions
2077
+ - Correct result
2078
+ - **Coverage:** Lines 140 (locking)
2079
+
2080
+ #### test_check_skip_limit_nonexistent_operation
2081
+ - **Purpose:** Non-existent operation has 0 count
2082
+ - **Setup:** check_skip_limit("new_op", max=5)
2083
+ - **Assertions:**
2084
+ - Returns False (0 < 5)
2085
+ - **Coverage:** Lines 141
2086
+
2087
+ ---
2088
+
2089
+ ## Test Class: TestSkipTrackerPositiveFeedback
2090
+
2091
+ Tests for positive feedback reset functionality.
2092
+
2093
+ ### Fixtures Required
2094
+ ```
2095
+ - tracker: SkipTracker instance
2096
+ ```
2097
+
2098
+ ### Test Methods
2099
+
2100
+ #### test_reset_on_positive_above_threshold
2101
+ - **Purpose:** Positive feedback resets counter
2102
+ - **Setup:** increment, reset_on_positive with rating=5 (default threshold=4)
2103
+ - **Assertions:**
2104
+ - get_skip_count("op") == 0
2105
+ - **Coverage:** Lines 147-160
2106
+
2107
+ #### test_reset_on_positive_at_threshold
2108
+ - **Purpose:** Rating at threshold resets counter
2109
+ - **Setup:** reset_on_positive with rating=4 (threshold=4)
2110
+ - **Assertions:**
2111
+ - Counter reset
2112
+ - **Coverage:** Lines 159
2113
+
2114
+ #### test_reset_on_positive_below_threshold
2115
+ - **Purpose:** Rating below threshold doesn't reset
2116
+ - **Setup:** increment 3 times, reset_on_positive with rating=3 (threshold=4)
2117
+ - **Assertions:**
2118
+ - get_skip_count("op") == 3 (unchanged)
2119
+ - **Coverage:** Lines 159
2120
+
2121
+ #### test_reset_on_positive_custom_threshold
2122
+ - **Purpose:** Custom rating threshold respected
2123
+ - **Setup:** reset_on_positive with rating=6, threshold=10
2124
+ - **Assertions:**
2125
+ - Counter NOT reset (6 < 10)
2126
+ - **Coverage:** Lines 156-157
2127
+
2128
+ #### test_reset_on_positive_zero_rating
2129
+ - **Purpose:** Rating of 0 is below threshold
2130
+ - **Setup:** reset_on_positive with rating=0
2131
+ - **Assertions:**
2132
+ - Counter NOT reset
2133
+ - **Coverage:** Lines 159
2134
+
2135
+ #### test_reset_on_positive_negative_rating
2136
+ - **Purpose:** Negative rating is below threshold
2137
+ - **Setup:** reset_on_positive with rating=-5
2138
+ - **Assertions:**
2139
+ - Counter NOT reset
2140
+ - **Coverage:** Lines 159
2141
+
2142
+ #### test_reset_on_positive_uses_default_threshold
2143
+ - **Purpose:** Default threshold used when not provided
2144
+ - **Setup:** reset_on_positive with rating=4, no threshold arg
2145
+ - **Assertions:**
2146
+ - Counter reset (4 >= 4 default)
2147
+ - **Coverage:** Lines 156-157
2148
+
2149
+ #### test_reset_on_positive_thread_safe
2150
+ - **Purpose:** Reset is thread-safe
2151
+ - **Setup:** Concurrent increment and reset_on_positive
2152
+ - **Assertions:**
2153
+ - No race conditions
2154
+ - **Coverage:** Lines 160 (locking via reset_skip_counter)
2155
+
2156
+ ---
2157
+
2158
+ ## Test Class: TestSkipTrackerCounterAccess
2159
+
2160
+ Tests for counter read and management.
2161
+
2162
+ ### Fixtures Required
2163
+ ```
2164
+ - tracker: SkipTracker instance
2165
+ ```
2166
+
2167
+ ### Test Methods
2168
+
2169
+ #### test_get_skip_count_existing
2170
+ - **Purpose:** Get count for existing operation
2171
+ - **Setup:** increment "op", get_skip_count("op")
2172
+ - **Assertions:**
2173
+ - Returns correct count
2174
+ - **Coverage:** Lines 101-111
2175
+
2176
+ #### test_get_skip_count_nonexistent
2177
+ - **Purpose:** Get count for non-existent operation
2178
+ - **Setup:** get_skip_count("nonexistent")
2179
+ - **Assertions:**
2180
+ - Returns 0
2181
+ - **Coverage:** Lines 111
2182
+
2183
+ #### test_get_skip_count_thread_safe
2184
+ - **Purpose:** Counter reads are thread-safe
2185
+ - **Setup:** Concurrent increment and get_skip_count
2186
+ - **Assertions:**
2187
+ - Consistent reads
2188
+ - **Coverage:** Lines 110 (locking)
2189
+
2190
+ #### test_get_all_counters
2191
+ - **Purpose:** Get copy of all counters
2192
+ - **Setup:** increment multiple operations, get_all_counters()
2193
+ - **Assertions:**
2194
+ - Returns dict with all operations
2195
+ - Changes to returned dict don't affect tracker
2196
+ - **Coverage:** Lines 162-169
2197
+
2198
+ #### test_get_all_counters_returns_copy
2199
+ - **Purpose:** Returned dict is a copy, not reference
2200
+ - **Setup:** Get all counters, modify returned dict, get again
2201
+ - **Assertions:**
2202
+ - Original counters unchanged
2203
+ - **Coverage:** Lines 169
2204
+
2205
+ #### test_get_all_counters_empty
2206
+ - **Purpose:** Empty counters returns empty dict
2207
+ - **Setup:** get_all_counters() with no operations
2208
+ - **Assertions:**
2209
+ - Returns {}
2210
+ - **Coverage:** Lines 162-169
2211
+
2212
+ #### test_get_all_counters_thread_safe
2213
+ - **Purpose:** Reading all counters is thread-safe
2214
+ - **Setup:** Concurrent modifications and reads
2215
+ - **Assertions:**
2216
+ - Consistent snapshots
2217
+ - **Coverage:** Lines 168 (locking)
2218
+
2219
+ #### test_clear_all_counters
2220
+ - **Purpose:** Clear all skip counters
2221
+ - **Setup:** increment multiple, clear_all_counters(), get_all_counters()
2222
+ - **Assertions:**
2223
+ - Returns {}
2224
+ - **Coverage:** Lines 171-177
2225
+
2226
+ #### test_clear_all_counters_thread_safe
2227
+ - **Purpose:** Clear is thread-safe
2228
+ - **Setup:** Concurrent operations and clear
2229
+ - **Assertions:**
2230
+ - No race conditions
2231
+ - **Coverage:** Lines 176 (locking)
2232
+
2233
+ ---
2234
+
2235
+ ## Test Class: TestSkipTrackerIntegration
2236
+
2237
+ Integration tests for complete workflows.
2238
+
2239
+ ### Fixtures Required
2240
+ ```
2241
+ - tracker: SkipTracker instance
2242
+ ```
2243
+
2244
+ ### Test Methods
2245
+
2246
+ #### test_skip_tracking_workflow_basic
2247
+ - **Purpose:** Complete skip tracking workflow
2248
+ - **Setup:** Increment → check limit → reset
2249
+ - **Assertions:**
2250
+ - Each step produces correct results
2251
+ - **Coverage:** Full integration
2252
+
2253
+ #### test_skip_tracking_workflow_with_positive_feedback
2254
+ - **Purpose:** Skip tracking with positive feedback reset
2255
+ - **Setup:** Increment → positive feedback → check count
2256
+ - **Assertions:**
2257
+ - Counter resets on positive feedback
2258
+ - **Coverage:** Lines 147-160 (integration)
2259
+
2260
+ #### test_skip_tracking_workflow_limit_then_positive
2261
+ - **Purpose:** Reach limit, then positive feedback
2262
+ - **Setup:** Increment to limit → positive feedback → check limit
2263
+ - **Assertions:**
2264
+ - Limit reached, then feedback resets
2265
+ - Limit no longer reached after reset
2266
+ - **Coverage:** Full integration
2267
+
2268
+ #### test_skip_tracking_concurrent_increments
2269
+ - **Purpose:** Multiple threads incrementing safely
2270
+ - **Setup:** 10 threads, each increment 10 times
2271
+ - **Assertions:**
2272
+ - Final count == 100
2273
+ - No lost updates
2274
+ - **Coverage:** Full integration (threading)
2275
+
2276
+ #### test_skip_tracking_log_persistence
2277
+ - **Purpose:** Counters persist across instances
2278
+ - **Setup:** Create tracker, increment, shutdown, new tracker
2279
+ - **Assertions:**
2280
+ - New tracker loads previous counts
2281
+ - **Coverage:** Full integration (persistence)
2282
+
2283
+ ---
2284
+
2285
+ ## Coverage Summary for skip_tracker.py
2286
+
2287
+ | Section | Lines | Coverage | Status |
2288
+ |---------|-------|----------|--------|
2289
+ | Initialization | 27-62 | 100% | 12 tests |
2290
+ | Increment | 85-99 | 100% | 6 tests |
2291
+ | Reset | 113-122 | 100% | 5 tests |
2292
+ | Limit Check | 124-145 | 100% | 7 tests |
2293
+ | Positive Feedback | 147-160 | 100% | 8 tests |
2294
+ | Counter Access | 101-177 | 100% | 8 tests |
2295
+ | Integration | Full | 100% | 5 tests |
2296
+ | **TOTAL** | **78** | **95%+** | **51 tests** |
2297
+
2298
+ ---
2299
+
2300
+ # 5. TEST_CONFIG_SCHEMA.PY
2301
+
2302
+ **Target:** 10+ lines, 95% coverage of 4 statements
2303
+ **Purpose:** Validate JSON Schema export and structure
2304
+
2305
+ ## Test Class: TestConfigSchema
2306
+
2307
+ Tests for schema retrieval and structure.
2308
+
2309
+ ### Test Methods
2310
+
2311
+ #### test_get_schema_returns_dict
2312
+ - **Purpose:** get_schema() returns dictionary
2313
+ - **Setup:** schema = get_schema()
2314
+ - **Assertions:**
2315
+ - isinstance(schema, dict)
2316
+ - **Coverage:** Lines 134-140
2317
+
2318
+ #### test_get_schema_returns_copy
2319
+ - **Purpose:** Returned schema is a copy, not reference
2320
+ - **Setup:** schema1 = get_schema(), modify, schema2 = get_schema()
2321
+ - **Assertions:**
2322
+ - schema1 != schema2 (different objects)
2323
+ - Original FEEDBACK_CONFIG_SCHEMA unchanged
2324
+ - **Coverage:** Lines 140
2325
+
2326
+ #### test_get_schema_has_required_top_level_keys
2327
+ - **Purpose:** Schema contains required top-level keys
2328
+ - **Setup:** schema = get_schema()
2329
+ - **Assertions:**
2330
+ - "$schema" in schema
2331
+ - "title" in schema
2332
+ - "description" in schema
2333
+ - "type" in schema
2334
+ - "properties" in schema
2335
+ - **Coverage:** Lines 12-131
2336
+
2337
+ #### test_get_schema_type_is_object
2338
+ - **Purpose:** Schema type is "object"
2339
+ - **Setup:** schema = get_schema()
2340
+ - **Assertions:**
2341
+ - schema["type"] == "object"
2342
+ - **Coverage:** Lines 16
2343
+
2344
+ #### test_get_schema_has_enabled_property
2345
+ - **Purpose:** Schema defines "enabled" property
2346
+ - **Setup:** schema = get_schema()
2347
+ - **Assertions:**
2348
+ - "enabled" in schema["properties"]
2349
+ - schema["properties"]["enabled"]["type"] == "boolean"
2350
+ - **Coverage:** Lines 18-22
2351
+
2352
+ #### test_get_schema_has_trigger_mode_property
2353
+ - **Purpose:** Schema defines "trigger_mode" property
2354
+ - **Setup:** schema = get_schema()
2355
+ - **Assertions:**
2356
+ - "trigger_mode" in schema["properties"]
2357
+ - schema["properties"]["trigger_mode"]["type"] == "string"
2358
+ - "enum" in schema["properties"]["trigger_mode"]
2359
+ - **Coverage:** Lines 23-28
2360
+
2361
+ #### test_get_schema_trigger_mode_enum_values
2362
+ - **Purpose:** Trigger mode enum has all valid values
2363
+ - **Setup:** schema = get_schema()
2364
+ - **Assertions:**
2365
+ - "always" in enum
2366
+ - "failures-only" in enum
2367
+ - "specific-operations" in enum
2368
+ - "never" in enum
2369
+ - **Coverage:** Lines 25
2370
+
2371
+ #### test_get_schema_has_operations_property
2372
+ - **Purpose:** Schema defines "operations" property
2373
+ - **Setup:** schema = get_schema()
2374
+ - **Assertions:**
2375
+ - "operations" in schema["properties"]
2376
+ - "array" in schema["properties"]["operations"]["type"]
2377
+ - **Coverage:** Lines 29-36
2378
+
2379
+ #### test_get_schema_has_conversation_settings
2380
+ - **Purpose:** Schema defines "conversation_settings"
2381
+ - **Setup:** schema = get_schema()
2382
+ - **Assertions:**
2383
+ - "conversation_settings" in schema["properties"]
2384
+ - "properties" in schema["properties"]["conversation_settings"]
2385
+ - "max_questions" and "allow_skip" defined
2386
+ - **Coverage:** Lines 37-57
2387
+
2388
+ #### test_get_schema_has_skip_tracking
2389
+ - **Purpose:** Schema defines "skip_tracking"
2390
+ - **Setup:** schema = get_schema()
2391
+ - **Assertions:**
2392
+ - "skip_tracking" in schema["properties"]
2393
+ - Contains "enabled", "max_consecutive_skips", "reset_on_positive"
2394
+ - **Coverage:** Lines 59-85
2395
+
2396
+ #### test_get_schema_has_templates
2397
+ - **Purpose:** Schema defines "templates"
2398
+ - **Setup:** schema = get_schema()
2399
+ - **Assertions:**
2400
+ - "templates" in schema["properties"]
2401
+ - Contains "format" and "tone"
2402
+ - **Coverage:** Lines 87-109
2403
+
2404
+ #### test_get_schema_has_default_values
2405
+ - **Purpose:** Schema includes default values
2406
+ - **Setup:** schema = get_schema()
2407
+ - **Assertions:**
2408
+ - "default" in schema
2409
+ - schema["default"]["enabled"] == True
2410
+ - schema["default"]["trigger_mode"] == "failures-only"
2411
+ - **Coverage:** Lines 113-130
2412
+
2413
+ #### test_get_schema_no_additional_properties
2414
+ - **Purpose:** Schema disallows additional properties
2415
+ - **Setup:** schema = get_schema()
2416
+ - **Assertions:**
2417
+ - schema["additionalProperties"] == False
2418
+ - **Coverage:** Lines 111
2419
+
2420
+ #### test_get_schema_json_schema_draft_07
2421
+ - **Purpose:** Schema specifies JSON Schema draft 07
2422
+ - **Setup:** schema = get_schema()
2423
+ - **Assertions:**
2424
+ - schema["$schema"] == "http://json-schema.org/draft-07/schema#"
2425
+ - **Coverage:** Lines 13
2426
+
2427
+ #### test_feedback_config_schema_global_defined
2428
+ - **Purpose:** Global FEEDBACK_CONFIG_SCHEMA defined
2429
+ - **Setup:** Check FEEDBACK_CONFIG_SCHEMA existence
2430
+ - **Assertions:**
2431
+ - FEEDBACK_CONFIG_SCHEMA is dict
2432
+ - Not empty
2433
+ - **Coverage:** Lines 12
2434
+
2435
+ ---
2436
+
2437
+ ## Coverage Summary for config_schema.py
2438
+
2439
+ | Section | Lines | Coverage | Status |
2440
+ |---------|-------|----------|--------|
2441
+ | Schema Retrieval | 134-140 | 100% | 2 tests |
2442
+ | Schema Structure | 12-131 | 100% | 13 tests |
2443
+ | **TOTAL** | **4** | **95%+** | **15 tests** |
2444
+
2445
+ ---
2446
+
2447
+ # 6. TEST_CONFIG_DEFAULTS.PY
2448
+
2449
+ **Target:** 15+ lines, 95% coverage of 8 statements
2450
+ **Purpose:** Validate default configuration values and retrieval
2451
+
2452
+ ## Test Class: TestConfigDefaults
2453
+
2454
+ Tests for default configuration access.
2455
+
2456
+ ### Test Methods
2457
+
2458
+ #### test_default_config_dict_exists
2459
+ - **Purpose:** DEFAULT_CONFIG_DICT defined
2460
+ - **Setup:** Check DEFAULT_CONFIG_DICT
2461
+ - **Assertions:**
2462
+ - DEFAULT_CONFIG_DICT is dict
2463
+ - Not empty
2464
+ - **Coverage:** Lines 11-28
2465
+
2466
+ #### test_default_config_dict_has_enabled
2467
+ - **Purpose:** Defaults include enabled field
2468
+ - **Setup:** DEFAULT_CONFIG_DICT
2469
+ - **Assertions:**
2470
+ - DEFAULT_CONFIG_DICT["enabled"] == True
2471
+ - **Coverage:** Lines 12
2472
+
2473
+ #### test_default_config_dict_has_trigger_mode
2474
+ - **Purpose:** Defaults include trigger_mode
2475
+ - **Setup:** DEFAULT_CONFIG_DICT
2476
+ - **Assertions:**
2477
+ - DEFAULT_CONFIG_DICT["trigger_mode"] == "failures-only"
2478
+ - **Coverage:** Lines 13
2479
+
2480
+ #### test_default_config_dict_has_operations
2481
+ - **Purpose:** Defaults include operations
2482
+ - **Setup:** DEFAULT_CONFIG_DICT
2483
+ - **Assertions:**
2484
+ - DEFAULT_CONFIG_DICT["operations"] == None
2485
+ - **Coverage:** Lines 14
2486
+
2487
+ #### test_default_config_dict_has_nested_sections
2488
+ - **Purpose:** Defaults include all nested sections
2489
+ - **Setup:** DEFAULT_CONFIG_DICT
2490
+ - **Assertions:**
2491
+ - "conversation_settings" in dict
2492
+ - "skip_tracking" in dict
2493
+ - "templates" in dict
2494
+ - **Coverage:** Lines 15-27
2495
+
2496
+ #### test_default_config_dict_conversation_settings
2497
+ - **Purpose:** Conversation settings have correct defaults
2498
+ - **Setup:** DEFAULT_CONFIG_DICT["conversation_settings"]
2499
+ - **Assertions:**
2500
+ - max_questions == 5
2501
+ - allow_skip == True
2502
+ - **Coverage:** Lines 15-17
2503
+
2504
+ #### test_default_config_dict_skip_tracking
2505
+ - **Purpose:** Skip tracking has correct defaults
2506
+ - **Setup:** DEFAULT_CONFIG_DICT["skip_tracking"]
2507
+ - **Assertions:**
2508
+ - enabled == True
2509
+ - max_consecutive_skips == 3
2510
+ - reset_on_positive == True
2511
+ - **Coverage:** Lines 19-22
2512
+
2513
+ #### test_default_config_dict_templates
2514
+ - **Purpose:** Templates have correct defaults
2515
+ - **Setup:** DEFAULT_CONFIG_DICT["templates"]
2516
+ - **Assertions:**
2517
+ - format == "structured"
2518
+ - tone == "brief"
2519
+ - **Coverage:** Lines 24-26
2520
+
2521
+ #### test_get_default_config_returns_dict
2522
+ - **Purpose:** get_default_config() returns dictionary
2523
+ - **Setup:** config = get_default_config()
2524
+ - **Assertions:**
2525
+ - isinstance(config, dict)
2526
+ - **Coverage:** Lines 31-37
2527
+
2528
+ #### test_get_default_config_returns_copy
2529
+ - **Purpose:** get_default_config() returns copy, not reference
2530
+ - **Setup:** config1 = get_default_config(), modify, config2 = get_default_config()
2531
+ - **Assertions:**
2532
+ - config1 != config2 (different objects)
2533
+ - DEFAULT_CONFIG_DICT unchanged
2534
+ - **Coverage:** Lines 37
2535
+
2536
+ #### test_get_default_config_has_all_fields
2537
+ - **Purpose:** Returned config has all required fields
2538
+ - **Setup:** config = get_default_config()
2539
+ - **Assertions:**
2540
+ - "enabled" in config
2541
+ - "trigger_mode" in config
2542
+ - All nested sections present
2543
+ - **Coverage:** Lines 31-37
2544
+
2545
+ #### test_get_default_nested_config_valid_section
2546
+ - **Purpose:** Get default for specific section
2547
+ - **Setup:** get_default_nested_config("conversation_settings")
2548
+ - **Assertions:**
2549
+ - Returns dict with max_questions and allow_skip
2550
+ - **Coverage:** Lines 40-58
2551
+
2552
+ #### test_get_default_nested_config_all_sections
2553
+ - **Purpose:** All sections accessible
2554
+ - **Setup:** Test each section: conversation_settings, skip_tracking, templates
2555
+ - **Assertions:**
2556
+ - Each returns dict with correct fields
2557
+ - **Coverage:** Lines 40-58
2558
+
2559
+ #### test_get_default_nested_config_invalid_section
2560
+ - **Purpose:** Invalid section raises error
2561
+ - **Setup:** get_default_nested_config("invalid_section")
2562
+ - **Assertions:**
2563
+ - ValueError raised
2564
+ - Message contains "Unknown configuration section"
2565
+ - Lists valid sections
2566
+ - **Coverage:** Lines 52-56
2567
+
2568
+ #### test_get_default_nested_config_returns_copy
2569
+ - **Purpose:** Returned section is a copy
2570
+ - **Setup:** config1 = get_default_nested_config(...), modify, config2 = get_default_nested_config(...)
2571
+ - **Assertions:**
2572
+ - config1 != config2
2573
+ - DEFAULT_CONFIG_DICT unchanged
2574
+ - **Coverage:** Lines 58
2575
+
2576
+ ---
2577
+
2578
+ ## Coverage Summary for config_defaults.py
2579
+
2580
+ | Section | Lines | Coverage | Status |
2581
+ |---------|-------|----------|--------|
2582
+ | DEFAULT_CONFIG_DICT | 11-28 | 100% | 8 tests |
2583
+ | get_default_config | 31-37 | 100% | 3 tests |
2584
+ | get_default_nested_config | 40-58 | 100% | 4 tests |
2585
+ | **TOTAL** | **8** | **95%+** | **15 tests** |
2586
+
2587
+ ---
2588
+
2589
+ # Summary & Implementation Guide
2590
+
2591
+ ## Overall Coverage
2592
+
2593
+ **Total Test Methods to Implement: 255+ tests**
2594
+
2595
+ - test_config_manager.py: 68 tests (161 statements)
2596
+ - test_hot_reload.py: 55 tests (99 statements)
2597
+ - test_config_models.py: 59 tests (85 statements)
2598
+ - test_skip_tracker.py: 51 tests (78 statements)
2599
+ - test_config_schema.py: 15 tests (4 statements)
2600
+ - test_config_defaults.py: 15 tests (8 statements)
2601
+ - **TOTAL: 263 tests** targeting **435 statements** (~50% coverage improvement)
2602
+
2603
+ ## Implementation Priority
2604
+
2605
+ **Phase 1 (Critical - AC 4,5,6,7,8):**
2606
+ 1. test_config_models.py (data validation, 59 tests)
2607
+ 2. test_config_manager.py - Validation section (7 tests)
2608
+ 3. test_config_defaults.py (15 tests)
2609
+
2610
+ **Phase 2 (High - AC 1,9):**
2611
+ 1. test_config_manager.py - Loading/Merging (15 tests)
2612
+ 2. test_hot_reload.py - File watching (55 tests)
2613
+
2614
+ **Phase 3 (Medium - AC 5):**
2615
+ 1. test_skip_tracker.py (51 tests)
2616
+
2617
+ **Phase 4 (Low):**
2618
+ 1. test_config_schema.py (15 tests)
2619
+
2620
+ ## Key Testing Patterns
2621
+
2622
+ 1. **Thread Safety Tests:** Use 3-5 concurrent threads per module
2623
+ 2. **File I/O Tests:** Use `pytest.tmp_path` fixture for temp directories
2624
+ 3. **Mock Tests:** Fixtures for mocking callbacks, file I/O
2625
+ 4. **Parametrized Tests:** Use `@pytest.mark.parametrize` for multiple trigger modes, formats, etc.
2626
+ 5. **Integration Tests:** End-to-end workflows combining multiple components
2627
+
2628
+ ## Pytest Fixture Setup
2629
+
2630
+ **Recommended fixture file (.conftest.py):**
2631
+
2632
+ ```python
2633
+ # conftest.py organization
2634
+ - temp_config_file (cleanup)
2635
+ - temp_logs_dir (cleanup)
2636
+ - config_manager instance
2637
+ - hot_reload_manager instance
2638
+ - skip_tracker instance
2639
+ - mock callbacks
2640
+ - sample YAML files (valid/invalid)
2641
+ ```
2642
+
2643
+ ## Performance Targets
2644
+
2645
+ - File I/O: <100ms per operation
2646
+ - Hot-reload detection: <5 seconds
2647
+ - Thread-safe operations: atomic updates
2648
+ - Skip tracking: <10ms per counter operation
2649
+
2650
+ ---
2651
+
2652
+ **Next Step:** Implement tests following this specification. Each test should be independent, use proper fixtures, and target specific lines of code for maximum coverage.