devforgeai 1.0.4 → 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.
- package/CLAUDE.md +120 -0
- package/package.json +9 -1
- package/src/CLAUDE.md +699 -0
- package/src/claude/scripts/README.md +396 -0
- package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
- package/src/claude/scripts/check-hooks-fast.sh +70 -0
- package/src/claude/scripts/devforgeai-validate +6 -0
- package/src/claude/scripts/devforgeai_cli/README.md +531 -0
- package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
- package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
- package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
- package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
- package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
- package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
- package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
- package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
- package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
- package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
- package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
- package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
- package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
- package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
- package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
- package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
- package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
- package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
- package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
- package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
- package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
- package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
- package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
- package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
- package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
- package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
- package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
- package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
- package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
- package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
- package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
- package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
- package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
- package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
- package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
- package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
- package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
- package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
- package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
- package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
- package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
- package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
- package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
- package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
- package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
- package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
- package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
- package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
- package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
- package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
- package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
- package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
- package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
- package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
- package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
- package/src/claude/scripts/install_hooks.sh +186 -0
- package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
- package/src/claude/scripts/migrate-ac-headers.sh +122 -0
- package/src/claude/scripts/plan_file_kb.sh +704 -0
- package/src/claude/scripts/requirements.txt +8 -0
- package/src/claude/scripts/session_catalog.sh +543 -0
- package/src/claude/scripts/setup.py +55 -0
- package/src/claude/scripts/start-devforgeai.sh +16 -0
- package/src/claude/scripts/statusline.sh +27 -0
- package/src/claude/scripts/validate_deferrals.py +344 -0
- package/src/claude/skills/devforgeai-qa/SKILL.md +1 -1
- package/src/claude/skills/researching-market/SKILL.md +2 -1
- package/src/cli/lib/copier.js +13 -1
- package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
- package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
- 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.
|