atdd 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atdd/__init__.py +6 -0
- atdd/__main__.py +4 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.2.1.dist-info/METADATA +221 -0
- atdd-0.2.1.dist-info/RECORD +184 -0
- atdd-0.2.1.dist-info/WHEEL +5 -0
- atdd-0.2.1.dist-info/entry_points.txt +2 -0
- atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
- atdd-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for complexity recipe.
|
|
3
|
+
|
|
4
|
+
Tests SPEC-CODER-UTL-0158 to 047, 054, 056, 057, 058
|
|
5
|
+
ATDD: These tests define expected behavior of complexity recipe BEFORE implementation.
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
import yaml
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Recipe loader utilities (shared with thinness tests)
|
|
14
|
+
def load_recipe(recipe_name: str) -> Dict[str, Any]:
|
|
15
|
+
"""
|
|
16
|
+
Load recipe YAML file.
|
|
17
|
+
|
|
18
|
+
SPEC-CODER-UTL-0158: Load complexity recipe
|
|
19
|
+
"""
|
|
20
|
+
recipe_path = Path(__file__).resolve().parents[1] / f"{recipe_name}.recipe.yaml"
|
|
21
|
+
|
|
22
|
+
if not recipe_path.exists():
|
|
23
|
+
raise FileNotFoundError(f"Recipe not found: {recipe_path}")
|
|
24
|
+
|
|
25
|
+
with open(recipe_path, 'r') as f:
|
|
26
|
+
return yaml.safe_load(f)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check_recipe_applies(recipe_name: str, smells: Dict[str, Any]) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Check if recipe applies based on smell detection results.
|
|
32
|
+
|
|
33
|
+
SPEC-CODER-UTL-0159: Detect when complexity recipe applies
|
|
34
|
+
"""
|
|
35
|
+
recipe = load_recipe(recipe_name)
|
|
36
|
+
|
|
37
|
+
if recipe_name == "complexity":
|
|
38
|
+
# complexity applies when complexity check fails
|
|
39
|
+
return smells.get("complexity", {}).get("passed", True) is False
|
|
40
|
+
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def execute_recipe_step(recipe_name: str, step: int, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Execute a recipe step.
|
|
47
|
+
|
|
48
|
+
SPEC-CODER-UTL-0160, 046, 047: Execute steps 1, 2, 3
|
|
49
|
+
"""
|
|
50
|
+
recipe = load_recipe(recipe_name)
|
|
51
|
+
steps = recipe.get("steps", [])
|
|
52
|
+
|
|
53
|
+
if step < 1 or step > len(steps):
|
|
54
|
+
return {"success": False, "error": f"Invalid step: {step}"}
|
|
55
|
+
|
|
56
|
+
step_def = steps[step - 1]
|
|
57
|
+
|
|
58
|
+
# Return step guidance
|
|
59
|
+
return {
|
|
60
|
+
"success": True,
|
|
61
|
+
"step": step,
|
|
62
|
+
"what": step_def.get("what", ""),
|
|
63
|
+
"where": step_def.get("where", ""),
|
|
64
|
+
"template": step_def.get("template", ""),
|
|
65
|
+
"example": step_def.get("example", ""),
|
|
66
|
+
"next_action": "continue" if step < len(steps) else "verify_final"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def select_recipe(smells: Dict[str, Any]) -> Dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Select recipe based on smell detection.
|
|
73
|
+
|
|
74
|
+
SPEC-CODER-UTL-0169: Map complexity smell to complexity recipe
|
|
75
|
+
"""
|
|
76
|
+
# Priority 1: Handler smell → thinness
|
|
77
|
+
if smells.get("thinness", {}).get("passed", True) is False:
|
|
78
|
+
return {"recipe": "thinness", "priority": 1}
|
|
79
|
+
|
|
80
|
+
# Priority 2: Complexity smell → complexity
|
|
81
|
+
if smells.get("complexity", {}).get("passed", True) is False:
|
|
82
|
+
return {"recipe": "complexity", "priority": 2}
|
|
83
|
+
|
|
84
|
+
# Priority 3: Missing adapter → adapter
|
|
85
|
+
if smells.get("missing_adapter", False):
|
|
86
|
+
return {"recipe": "adapter", "priority": 3}
|
|
87
|
+
|
|
88
|
+
return {"recipe": None, "priority": 0}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def verify_recipe_step(step_result: Dict[str, Any], test_status: str) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Verify recipe step maintains GREEN tests.
|
|
94
|
+
|
|
95
|
+
SPEC-CODER-UTL-0171: Verify recipe step maintains GREEN tests
|
|
96
|
+
SPEC-CODER-UTL-0173: Rollback on step failure
|
|
97
|
+
"""
|
|
98
|
+
if test_status == "GREEN":
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
# RED tests trigger rollback
|
|
102
|
+
if test_status == "RED":
|
|
103
|
+
# This would call rollback_refactor_step() in real usage
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def verify_recipe_final(recipe_name: str, results: Dict[str, Any]) -> Dict[str, Any]:
|
|
110
|
+
"""
|
|
111
|
+
Verify final state after all recipe steps.
|
|
112
|
+
|
|
113
|
+
SPEC-CODER-UTL-0172: Verify final state after all recipe steps
|
|
114
|
+
"""
|
|
115
|
+
verification = {"success": True, "checks": []}
|
|
116
|
+
|
|
117
|
+
if recipe_name == "complexity":
|
|
118
|
+
# Final verification: complexity.check() should pass
|
|
119
|
+
verification["checks"].append({
|
|
120
|
+
"check": "complexity.check()",
|
|
121
|
+
"expected": "passed=true"
|
|
122
|
+
})
|
|
123
|
+
verification["complexity_reduced"] = True
|
|
124
|
+
|
|
125
|
+
return verification
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# TESTS
|
|
129
|
+
|
|
130
|
+
class TestSpecificationRecipeLoading:
|
|
131
|
+
"""Test SPEC-CODER-UTL-0158: Load complexity recipe"""
|
|
132
|
+
|
|
133
|
+
def test_load_recipe(self):
|
|
134
|
+
"""Should load complexity recipe YAML"""
|
|
135
|
+
recipe = load_recipe("complexity")
|
|
136
|
+
|
|
137
|
+
assert recipe is not None
|
|
138
|
+
assert recipe["recipe"] == "complexity"
|
|
139
|
+
assert recipe["pattern"] == "Complexity Reduction (Split/Extract)"
|
|
140
|
+
assert "steps" in recipe
|
|
141
|
+
assert len(recipe["steps"]) == 3
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestSpecificationRecipeApplies:
|
|
145
|
+
"""Test SPEC-CODER-UTL-0159: Detect when complexity recipe applies"""
|
|
146
|
+
|
|
147
|
+
def test_recipe_applies(self):
|
|
148
|
+
"""Should return true when complexity check fails"""
|
|
149
|
+
smells = {
|
|
150
|
+
"complexity": {
|
|
151
|
+
"passed": False,
|
|
152
|
+
"violations": [
|
|
153
|
+
{"function": "calculateDiscount", "complexity": 12, "line": 45}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
result = check_recipe_applies("complexity", smells)
|
|
159
|
+
|
|
160
|
+
assert result is True
|
|
161
|
+
|
|
162
|
+
def test_recipe_not_applies(self):
|
|
163
|
+
"""Should return false when complexity check passes"""
|
|
164
|
+
smells = {
|
|
165
|
+
"complexity": {
|
|
166
|
+
"passed": True,
|
|
167
|
+
"violations": []
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
result = check_recipe_applies("complexity", smells)
|
|
172
|
+
|
|
173
|
+
assert result is False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class TestSpecificationRecipeSteps:
|
|
177
|
+
"""Test SPEC-CODER-UTL-0160, 046, 047: Execute recipe steps"""
|
|
178
|
+
|
|
179
|
+
def test_execute_step_1(self):
|
|
180
|
+
"""Should execute step 1: create base complexity"""
|
|
181
|
+
context = {"file_path": "domain/order.py"}
|
|
182
|
+
|
|
183
|
+
result = execute_recipe_step("complexity", 1, context)
|
|
184
|
+
|
|
185
|
+
assert result["success"] is True
|
|
186
|
+
assert "complexity" in result["what"].lower() or "base" in result["what"].lower()
|
|
187
|
+
assert "domain/complexity/" in result["where"]
|
|
188
|
+
assert "is_satisfied_by" in result["template"] or result["example"]
|
|
189
|
+
|
|
190
|
+
def test_execute_step_2(self):
|
|
191
|
+
"""Should execute step 2: extract complexitys"""
|
|
192
|
+
context = {"base_created": True}
|
|
193
|
+
|
|
194
|
+
result = execute_recipe_step("complexity", 2, context)
|
|
195
|
+
|
|
196
|
+
assert result["success"] is True
|
|
197
|
+
assert "extract" in result["what"].lower() or "boolean" in result["what"].lower()
|
|
198
|
+
assert "domain/complexity/" in result["where"]
|
|
199
|
+
|
|
200
|
+
def test_execute_step_3(self):
|
|
201
|
+
"""Should execute step 3: compose complexitys"""
|
|
202
|
+
context = {"base_created": True, "specs_extracted": True}
|
|
203
|
+
|
|
204
|
+
result = execute_recipe_step("complexity", 3, context)
|
|
205
|
+
|
|
206
|
+
assert result["success"] is True
|
|
207
|
+
assert "compose" in result["what"].lower() or "and_" in result["template"] or "or_" in result["template"]
|
|
208
|
+
assert result["next_action"] == "verify_final"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestSpecificationRecipeSelection:
|
|
212
|
+
"""Test SPEC-CODER-UTL-0169: Map complexity smell to complexity recipe"""
|
|
213
|
+
|
|
214
|
+
def test_select_from_smell(self):
|
|
215
|
+
"""Should select complexity recipe when complexity smell detected"""
|
|
216
|
+
smells = {
|
|
217
|
+
"complexity": {
|
|
218
|
+
"passed": False,
|
|
219
|
+
"violations": [{"function": "process", "complexity": 15}]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
result = select_recipe(smells)
|
|
224
|
+
|
|
225
|
+
assert result["recipe"] == "complexity"
|
|
226
|
+
assert result["priority"] == 2
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestSpecificationRecipeVerification:
|
|
230
|
+
"""Test SPEC-CODER-UTL-0171, 057, 058: Recipe verification"""
|
|
231
|
+
|
|
232
|
+
def test_verify_step_green(self):
|
|
233
|
+
"""Should return true when tests are GREEN"""
|
|
234
|
+
step_result = {"success": True, "step": 1}
|
|
235
|
+
|
|
236
|
+
result = verify_recipe_step(step_result, "GREEN")
|
|
237
|
+
|
|
238
|
+
assert result is True
|
|
239
|
+
|
|
240
|
+
def test_rollback_on_failure(self):
|
|
241
|
+
"""Should return false and trigger rollback when tests are RED"""
|
|
242
|
+
step_result = {"success": True, "step": 1}
|
|
243
|
+
|
|
244
|
+
result = verify_recipe_step(step_result, "RED")
|
|
245
|
+
|
|
246
|
+
assert result is False
|
|
247
|
+
|
|
248
|
+
def test_verify_final(self):
|
|
249
|
+
"""Should verify final state with complexity check"""
|
|
250
|
+
results = {"all_steps_completed": True}
|
|
251
|
+
|
|
252
|
+
verification = verify_recipe_final("complexity", results)
|
|
253
|
+
|
|
254
|
+
assert verification["success"] is True
|
|
255
|
+
assert verification["complexity_reduced"] is True
|
|
256
|
+
assert len(verification["checks"]) > 0
|
|
257
|
+
assert any("complexity" in check["check"] for check in verification["checks"])
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class TestSpecificationRecipeIntegration:
|
|
261
|
+
"""Integration tests for full recipe workflow"""
|
|
262
|
+
|
|
263
|
+
def test_full_recipe_workflow(self):
|
|
264
|
+
"""Should execute full recipe from detection to verification"""
|
|
265
|
+
# 1. Detect smell
|
|
266
|
+
smells = {"complexity": {"passed": False, "violations": [{"function": "foo", "complexity": 12}]}}
|
|
267
|
+
|
|
268
|
+
# 2. Select recipe
|
|
269
|
+
selected = select_recipe(smells)
|
|
270
|
+
assert selected["recipe"] == "complexity"
|
|
271
|
+
|
|
272
|
+
# 3. Load recipe
|
|
273
|
+
recipe = load_recipe("complexity")
|
|
274
|
+
assert recipe is not None
|
|
275
|
+
|
|
276
|
+
# 4. Execute steps
|
|
277
|
+
context = {}
|
|
278
|
+
for step in range(1, 4):
|
|
279
|
+
result = execute_recipe_step("complexity", step, context)
|
|
280
|
+
assert result["success"] is True
|
|
281
|
+
|
|
282
|
+
# Verify step
|
|
283
|
+
verified = verify_recipe_step(result, "GREEN")
|
|
284
|
+
assert verified is True
|
|
285
|
+
|
|
286
|
+
# 5. Final verification
|
|
287
|
+
verification = verify_recipe_final("complexity", {"all_steps": True})
|
|
288
|
+
assert verification["success"] is True
|
|
289
|
+
assert verification["complexity_reduced"] is True
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for component taxonomy adoption in conventions.
|
|
3
|
+
|
|
4
|
+
Tests SPEC-CODER-CONV-0006 through SPEC-CODER-CONV-0011
|
|
5
|
+
ATDD: Validates that conventions use generic component types with examples.
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
import yaml
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any, List
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Utility functions for loading YAML files
|
|
14
|
+
def load_yaml(file_path: Path) -> Dict[str, Any]:
|
|
15
|
+
"""Load and parse a YAML file."""
|
|
16
|
+
with open(file_path, 'r') as f:
|
|
17
|
+
return yaml.safe_load(f)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_project_root() -> Path:
|
|
21
|
+
"""Get the project root directory."""
|
|
22
|
+
# From tests/ directory, go up 4 levels: tests -> conventions -> coder -> atdd -> root
|
|
23
|
+
return Path(__file__).parent.parent.parent.parent.parent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_convention_backend() -> Dict[str, Any]:
|
|
27
|
+
"""Load the backend convention."""
|
|
28
|
+
conventions_dir = Path(__file__).parent.parent
|
|
29
|
+
path = conventions_dir / "backend.convention.yaml"
|
|
30
|
+
return load_yaml(path)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_convention_frontend() -> Dict[str, Any]:
|
|
34
|
+
"""Load the frontend convention."""
|
|
35
|
+
conventions_dir = Path(__file__).parent.parent
|
|
36
|
+
path = conventions_dir / "frontend.convention.yaml"
|
|
37
|
+
return load_yaml(path)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_component_type_names(layer_data: Dict[str, Any]) -> List[str]:
|
|
41
|
+
"""Extract component type names from a layer."""
|
|
42
|
+
component_types = layer_data.get("component_types", [])
|
|
43
|
+
return [ct["name"] for ct in component_types]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Expected generic component types for validation
|
|
47
|
+
BACKEND_GENERIC_TYPES = {
|
|
48
|
+
"presentation": ["controllers", "routes", "serializers", "validators", "middleware", "guards", "views"],
|
|
49
|
+
"application": ["use_cases", "handlers", "ports", "dtos", "policies", "workflows"],
|
|
50
|
+
"domain": ["entities", "value_objects", "aggregates", "services", "specifications", "events", "exceptions"],
|
|
51
|
+
"integration": ["repositories", "clients", "caches", "engines", "formatters", "notifiers", "queues", "stores", "mappers", "schedulers", "monitors"]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
FRONTEND_GENERIC_TYPES = {
|
|
55
|
+
"presentation": ["views", "components", "containers", "controllers", "routes", "layouts", "styles", "animations", "forms", "hooks", "directives", "filters"],
|
|
56
|
+
"application": ["use_cases", "ports", "policies", "dtos"],
|
|
57
|
+
"domain": ["entities", "value_objects", "services", "specifications", "exceptions"],
|
|
58
|
+
"integration": ["repositories", "clients", "stores", "serializers", "mappers", "interceptors", "caches", "synchronizers", "monitors", "validators", "workers", "connectors"]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestBackendConventionAdoptsTaxonomy:
|
|
63
|
+
"""Test SPEC-CODER-CONV-0006: Backend convention uses generic component types"""
|
|
64
|
+
|
|
65
|
+
def test_backend_convention_adopts_taxonomy_types(self):
|
|
66
|
+
"""Should have generic component types with examples for all 4 layers"""
|
|
67
|
+
convention = load_convention_backend()
|
|
68
|
+
backend = convention.get("backend", {})
|
|
69
|
+
layers = backend.get("layers", {})
|
|
70
|
+
|
|
71
|
+
for layer_name, expected_types in BACKEND_GENERIC_TYPES.items():
|
|
72
|
+
layer = layers.get(layer_name, {})
|
|
73
|
+
actual_types = get_component_type_names(layer)
|
|
74
|
+
|
|
75
|
+
# Verify all generic types are present
|
|
76
|
+
for expected_type in expected_types:
|
|
77
|
+
assert expected_type in actual_types, \
|
|
78
|
+
f"{layer_name} layer missing generic type '{expected_type}'"
|
|
79
|
+
|
|
80
|
+
# Verify each component type has required fields
|
|
81
|
+
component_types = layer.get("component_types", [])
|
|
82
|
+
for ct in component_types:
|
|
83
|
+
assert "name" in ct, f"Component type missing 'name' field"
|
|
84
|
+
assert "description" in ct, f"Component type {ct.get('name')} missing 'description'"
|
|
85
|
+
assert "suffix" in ct, f"Component type {ct.get('name')} missing 'suffix'"
|
|
86
|
+
assert "examples" in ct, f"Component type {ct.get('name')} missing 'examples'"
|
|
87
|
+
assert len(ct["examples"]) > 0, f"Component type {ct.get('name')} has empty examples"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestFrontendConventionAdoptsTaxonomy:
|
|
91
|
+
"""Test SPEC-CODER-CONV-0007: Frontend convention uses generic component types"""
|
|
92
|
+
|
|
93
|
+
def test_frontend_convention_adopts_taxonomy_types(self):
|
|
94
|
+
"""Should have generic component types with examples for all 4 layers"""
|
|
95
|
+
convention = load_convention_frontend()
|
|
96
|
+
frontend = convention.get("frontend", {})
|
|
97
|
+
layers = frontend.get("layers", {})
|
|
98
|
+
|
|
99
|
+
for layer_name, expected_types in FRONTEND_GENERIC_TYPES.items():
|
|
100
|
+
layer = layers.get(layer_name, {})
|
|
101
|
+
actual_types = get_component_type_names(layer)
|
|
102
|
+
|
|
103
|
+
# Verify all generic types are present
|
|
104
|
+
for expected_type in expected_types:
|
|
105
|
+
assert expected_type in actual_types, \
|
|
106
|
+
f"{layer_name} layer missing generic type '{expected_type}'"
|
|
107
|
+
|
|
108
|
+
# Verify each component type has required fields
|
|
109
|
+
component_types = layer.get("component_types", [])
|
|
110
|
+
for ct in component_types:
|
|
111
|
+
assert "name" in ct, f"Component type missing 'name' field"
|
|
112
|
+
assert "description" in ct, f"Component type {ct.get('name')} missing 'description'"
|
|
113
|
+
assert "suffix" in ct, f"Component type {ct.get('name')} missing 'suffix'"
|
|
114
|
+
assert "examples" in ct, f"Component type {ct.get('name')} missing 'examples'"
|
|
115
|
+
assert len(ct["examples"]) > 0, f"Component type {ct.get('name')} has empty examples"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestBackendPreservesDependencyRules:
|
|
119
|
+
"""Test SPEC-CODER-CONV-0008: Backend convention preserves dependency rules and CI enforcement"""
|
|
120
|
+
|
|
121
|
+
def test_backend_preserves_dependency_rules(self):
|
|
122
|
+
"""Should preserve dependency.allowed_edges, forbidden_examples, and ci_enforcement sections"""
|
|
123
|
+
convention = load_convention_backend()
|
|
124
|
+
backend = convention.get("backend", {})
|
|
125
|
+
|
|
126
|
+
# Verify dependency section exists
|
|
127
|
+
dependency = backend.get("dependency", {})
|
|
128
|
+
assert dependency is not None, "Backend dependency section missing"
|
|
129
|
+
|
|
130
|
+
# Verify allowed_edges preserved
|
|
131
|
+
allowed_edges = dependency.get("allowed_edges", [])
|
|
132
|
+
assert len(allowed_edges) > 0, "Backend allowed_edges empty or missing"
|
|
133
|
+
|
|
134
|
+
# Check specific expected edges
|
|
135
|
+
edge_from_values = [edge.get("from") for edge in allowed_edges]
|
|
136
|
+
assert "presentation" in edge_from_values, "Missing presentation edge"
|
|
137
|
+
assert "application" in edge_from_values, "Missing application edge"
|
|
138
|
+
assert "integration" in edge_from_values, "Missing integration edge"
|
|
139
|
+
|
|
140
|
+
# Verify forbidden_examples preserved
|
|
141
|
+
forbidden_examples = dependency.get("forbidden_examples", [])
|
|
142
|
+
assert len(forbidden_examples) > 0, "Backend forbidden_examples empty or missing"
|
|
143
|
+
|
|
144
|
+
# Verify ci_enforcement section exists
|
|
145
|
+
ci_enforcement = backend.get("ci_enforcement", {})
|
|
146
|
+
assert ci_enforcement is not None, "Backend ci_enforcement section missing"
|
|
147
|
+
|
|
148
|
+
# Verify ci_enforcement has required subsections
|
|
149
|
+
checks = ci_enforcement.get("checks", [])
|
|
150
|
+
assert len(checks) > 0, "Backend ci_enforcement.checks empty or missing"
|
|
151
|
+
|
|
152
|
+
tools = ci_enforcement.get("tools", [])
|
|
153
|
+
assert len(tools) > 0, "Backend ci_enforcement.tools empty or missing"
|
|
154
|
+
|
|
155
|
+
failure_policy = ci_enforcement.get("failure_policy")
|
|
156
|
+
assert failure_policy is not None, "Backend ci_enforcement.failure_policy missing"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TestFrontendPreservesDependencyRules:
|
|
160
|
+
"""Test SPEC-CODER-CONV-0009: Frontend convention preserves dependency rules and CI enforcement"""
|
|
161
|
+
|
|
162
|
+
def test_frontend_preserves_dependency_rules(self):
|
|
163
|
+
"""Should preserve dependency.allowed_edges, forbidden_examples, and ci_enforcement sections"""
|
|
164
|
+
convention = load_convention_frontend()
|
|
165
|
+
frontend = convention.get("frontend", {})
|
|
166
|
+
|
|
167
|
+
# Verify dependency section exists
|
|
168
|
+
dependency = frontend.get("dependency", {})
|
|
169
|
+
assert dependency is not None, "Frontend dependency section missing"
|
|
170
|
+
|
|
171
|
+
# Verify allowed_edges preserved
|
|
172
|
+
allowed_edges = dependency.get("allowed_edges", [])
|
|
173
|
+
assert len(allowed_edges) > 0, "Frontend allowed_edges empty or missing"
|
|
174
|
+
|
|
175
|
+
# Check specific expected edges
|
|
176
|
+
edge_from_values = [edge.get("from") for edge in allowed_edges]
|
|
177
|
+
assert "presentation" in edge_from_values, "Missing presentation edge"
|
|
178
|
+
assert "application" in edge_from_values, "Missing application edge"
|
|
179
|
+
assert "integration" in edge_from_values, "Missing integration edge"
|
|
180
|
+
|
|
181
|
+
# Verify forbidden_examples preserved
|
|
182
|
+
forbidden_examples = dependency.get("forbidden_examples", [])
|
|
183
|
+
assert len(forbidden_examples) > 0, "Frontend forbidden_examples empty or missing"
|
|
184
|
+
|
|
185
|
+
# Verify ci_enforcement section exists
|
|
186
|
+
ci_enforcement = frontend.get("ci_enforcement", {})
|
|
187
|
+
assert ci_enforcement is not None, "Frontend ci_enforcement section missing"
|
|
188
|
+
|
|
189
|
+
# Verify ci_enforcement has required subsections
|
|
190
|
+
checks = ci_enforcement.get("checks", [])
|
|
191
|
+
assert len(checks) > 0, "Frontend ci_enforcement.checks empty or missing"
|
|
192
|
+
|
|
193
|
+
tools = ci_enforcement.get("tools", [])
|
|
194
|
+
assert len(tools) > 0, "Frontend ci_enforcement.tools empty or missing"
|
|
195
|
+
|
|
196
|
+
failure_policy = ci_enforcement.get("failure_policy")
|
|
197
|
+
assert failure_policy is not None, "Frontend ci_enforcement.failure_policy missing"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TestTaxonomyUsesGenericNames:
|
|
201
|
+
"""Test SPEC-CODER-CONV-0010: Component types have generic names with example arrays"""
|
|
202
|
+
|
|
203
|
+
def test_taxonomy_uses_generic_names_with_examples(self):
|
|
204
|
+
"""Should verify conventions use generic names with examples arrays"""
|
|
205
|
+
backend_convention = load_convention_backend()
|
|
206
|
+
frontend_convention = load_convention_frontend()
|
|
207
|
+
|
|
208
|
+
# Test backend convention
|
|
209
|
+
backend = backend_convention.get("backend", {})
|
|
210
|
+
layers = backend.get("layers", {})
|
|
211
|
+
integration_layer = layers.get("integration", {})
|
|
212
|
+
component_types = integration_layer.get("component_types", [])
|
|
213
|
+
|
|
214
|
+
# Verify generic component types exist with examples
|
|
215
|
+
generic_types = ["repositories", "engines", "formatters", "clients", "caches"]
|
|
216
|
+
found_types = {ct["name"] for ct in component_types}
|
|
217
|
+
|
|
218
|
+
for generic_type in generic_types:
|
|
219
|
+
assert generic_type in found_types, f"Generic type '{generic_type}' not found in backend convention"
|
|
220
|
+
|
|
221
|
+
# Find the component type and verify it has examples
|
|
222
|
+
ct = next((ct for ct in component_types if ct["name"] == generic_type), None)
|
|
223
|
+
assert ct is not None, f"Component type {generic_type} not found"
|
|
224
|
+
assert "examples" in ct, f"Component type {generic_type} missing examples array"
|
|
225
|
+
assert len(ct["examples"]) > 0, f"Component type {generic_type} has empty examples array"
|
|
226
|
+
assert "description" in ct, f"Component type {generic_type} missing description"
|
|
227
|
+
|
|
228
|
+
# Test frontend convention
|
|
229
|
+
frontend = frontend_convention.get("frontend", {})
|
|
230
|
+
layers = frontend.get("layers", {})
|
|
231
|
+
presentation_layer = layers.get("presentation", {})
|
|
232
|
+
component_types = presentation_layer.get("component_types", [])
|
|
233
|
+
|
|
234
|
+
# Verify generic component types exist with examples
|
|
235
|
+
generic_types = ["views", "components", "controllers"]
|
|
236
|
+
found_types = {ct["name"] for ct in component_types}
|
|
237
|
+
|
|
238
|
+
for generic_type in generic_types:
|
|
239
|
+
assert generic_type in found_types, f"Generic type '{generic_type}' not found in frontend convention"
|
|
240
|
+
|
|
241
|
+
# Find the component type and verify it has examples
|
|
242
|
+
ct = next((ct for ct in component_types if ct["name"] == generic_type), None)
|
|
243
|
+
assert ct is not None, f"Component type {generic_type} not found"
|
|
244
|
+
assert "examples" in ct, f"Component type {generic_type} missing examples array"
|
|
245
|
+
assert len(ct["examples"]) > 0, f"Component type {generic_type} has empty examples array"
|
|
246
|
+
assert "description" in ct, f"Component type {generic_type} missing description"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class TestConventionsExcludeUrnAndOrganization:
|
|
250
|
+
"""Test SPEC-CODER-CONV-0011: Conventions exclude URN and organization sections"""
|
|
251
|
+
|
|
252
|
+
def test_conventions_exclude_urn_and_organization(self):
|
|
253
|
+
"""Should verify conventions don't have urn_pattern, urn_examples, organization, or framework_adaptations"""
|
|
254
|
+
backend_convention = load_convention_backend()
|
|
255
|
+
frontend_convention = load_convention_frontend()
|
|
256
|
+
|
|
257
|
+
# Test backend convention
|
|
258
|
+
assert "urn_pattern" not in backend_convention, "Backend convention should not have urn_pattern"
|
|
259
|
+
assert "urn_examples" not in backend_convention, "Backend convention should not have urn_examples"
|
|
260
|
+
assert "organization" not in backend_convention, "Backend convention should not have organization section"
|
|
261
|
+
assert "framework_adaptations" not in backend_convention, "Backend convention should not have framework_adaptations"
|
|
262
|
+
|
|
263
|
+
# Test frontend convention
|
|
264
|
+
assert "urn_pattern" not in frontend_convention, "Frontend convention should not have urn_pattern"
|
|
265
|
+
assert "urn_examples" not in frontend_convention, "Frontend convention should not have urn_examples"
|
|
266
|
+
assert "organization" not in frontend_convention, "Frontend convention should not have organization section"
|
|
267
|
+
assert "framework_adaptations" not in frontend_convention, "Frontend convention should not have framework_adaptations"
|
|
268
|
+
|
|
269
|
+
# Verify conventions only have expected top-level keys
|
|
270
|
+
expected_backend_keys = {"version", "name", "description", "backend"}
|
|
271
|
+
actual_backend_keys = set(backend_convention.keys())
|
|
272
|
+
assert actual_backend_keys == expected_backend_keys, \
|
|
273
|
+
f"Backend convention has unexpected keys: {actual_backend_keys - expected_backend_keys}"
|
|
274
|
+
|
|
275
|
+
expected_frontend_keys = {"version", "name", "description", "frontend"}
|
|
276
|
+
actual_frontend_keys = set(frontend_convention.keys())
|
|
277
|
+
assert actual_frontend_keys == expected_frontend_keys, \
|
|
278
|
+
f"Frontend convention has unexpected keys: {actual_frontend_keys - expected_frontend_keys}"
|