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.
Files changed (184) hide show
  1. atdd/__init__.py +6 -0
  2. atdd/__main__.py +4 -0
  3. atdd/cli.py +404 -0
  4. atdd/coach/__init__.py +0 -0
  5. atdd/coach/commands/__init__.py +0 -0
  6. atdd/coach/commands/add_persistence_metadata.py +215 -0
  7. atdd/coach/commands/analyze_migrations.py +188 -0
  8. atdd/coach/commands/consumers.py +720 -0
  9. atdd/coach/commands/infer_governance_status.py +149 -0
  10. atdd/coach/commands/initializer.py +177 -0
  11. atdd/coach/commands/interface.py +1078 -0
  12. atdd/coach/commands/inventory.py +565 -0
  13. atdd/coach/commands/migration.py +240 -0
  14. atdd/coach/commands/registry.py +1560 -0
  15. atdd/coach/commands/session.py +430 -0
  16. atdd/coach/commands/sync.py +405 -0
  17. atdd/coach/commands/test_interface.py +399 -0
  18. atdd/coach/commands/test_runner.py +141 -0
  19. atdd/coach/commands/tests/__init__.py +1 -0
  20. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  21. atdd/coach/commands/traceability.py +4264 -0
  22. atdd/coach/conventions/session.convention.yaml +754 -0
  23. atdd/coach/overlays/__init__.py +2 -0
  24. atdd/coach/overlays/claude.md +2 -0
  25. atdd/coach/schemas/config.schema.json +34 -0
  26. atdd/coach/schemas/manifest.schema.json +101 -0
  27. atdd/coach/templates/ATDD.md +282 -0
  28. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  29. atdd/coach/utils/__init__.py +0 -0
  30. atdd/coach/utils/graph/__init__.py +0 -0
  31. atdd/coach/utils/graph/urn.py +875 -0
  32. atdd/coach/validators/__init__.py +0 -0
  33. atdd/coach/validators/shared_fixtures.py +365 -0
  34. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  35. atdd/coach/validators/test_registry.py +575 -0
  36. atdd/coach/validators/test_session_validation.py +1183 -0
  37. atdd/coach/validators/test_traceability.py +448 -0
  38. atdd/coach/validators/test_update_feature_paths.py +108 -0
  39. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  40. atdd/coder/__init__.py +1 -0
  41. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  42. atdd/coder/conventions/backend.convention.yaml +460 -0
  43. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  44. atdd/coder/conventions/commons.convention.yaml +460 -0
  45. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  46. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  47. atdd/coder/conventions/design.convention.yaml +327 -0
  48. atdd/coder/conventions/design.recipe.yaml +273 -0
  49. atdd/coder/conventions/dto.convention.yaml +660 -0
  50. atdd/coder/conventions/frontend.convention.yaml +542 -0
  51. atdd/coder/conventions/green.convention.yaml +1012 -0
  52. atdd/coder/conventions/presentation.convention.yaml +587 -0
  53. atdd/coder/conventions/refactor.convention.yaml +535 -0
  54. atdd/coder/conventions/technology.convention.yaml +206 -0
  55. atdd/coder/conventions/tests/__init__.py +0 -0
  56. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  57. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  58. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  59. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  60. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  61. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  62. atdd/coder/conventions/train.convention.yaml +325 -0
  63. atdd/coder/conventions/verification.protocol.yaml +53 -0
  64. atdd/coder/schemas/design_system.schema.json +361 -0
  65. atdd/coder/validators/__init__.py +0 -0
  66. atdd/coder/validators/test_commons_structure.py +485 -0
  67. atdd/coder/validators/test_complexity.py +416 -0
  68. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  69. atdd/coder/validators/test_design_system_compliance.py +413 -0
  70. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  71. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  72. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  73. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  74. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  75. atdd/coder/validators/test_import_boundaries.py +396 -0
  76. atdd/coder/validators/test_init_file_urns.py +593 -0
  77. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  78. atdd/coder/validators/test_presentation_convention.py +260 -0
  79. atdd/coder/validators/test_python_architecture.py +674 -0
  80. atdd/coder/validators/test_quality_metrics.py +420 -0
  81. atdd/coder/validators/test_station_master_pattern.py +244 -0
  82. atdd/coder/validators/test_train_infrastructure.py +454 -0
  83. atdd/coder/validators/test_train_urns.py +293 -0
  84. atdd/coder/validators/test_typescript_architecture.py +616 -0
  85. atdd/coder/validators/test_usecase_structure.py +421 -0
  86. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  87. atdd/conftest.py +126 -0
  88. atdd/planner/__init__.py +1 -0
  89. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  90. atdd/planner/conventions/appendix.convention.yaml +187 -0
  91. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  92. atdd/planner/conventions/component.convention.yaml +670 -0
  93. atdd/planner/conventions/criteria.convention.yaml +141 -0
  94. atdd/planner/conventions/feature.convention.yaml +371 -0
  95. atdd/planner/conventions/interface.convention.yaml +382 -0
  96. atdd/planner/conventions/steps.convention.yaml +141 -0
  97. atdd/planner/conventions/train.convention.yaml +552 -0
  98. atdd/planner/conventions/wagon.convention.yaml +275 -0
  99. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  100. atdd/planner/schemas/acceptance.schema.json +336 -0
  101. atdd/planner/schemas/appendix.schema.json +78 -0
  102. atdd/planner/schemas/component.schema.json +114 -0
  103. atdd/planner/schemas/feature.schema.json +197 -0
  104. atdd/planner/schemas/train.schema.json +192 -0
  105. atdd/planner/schemas/wagon.schema.json +281 -0
  106. atdd/planner/schemas/wmbt.schema.json +59 -0
  107. atdd/planner/validators/__init__.py +0 -0
  108. atdd/planner/validators/conftest.py +5 -0
  109. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  110. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  111. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  112. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  113. atdd/planner/validators/test_plan_wagons.py +174 -0
  114. atdd/planner/validators/test_train_validation.py +514 -0
  115. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  116. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  117. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  118. atdd/tester/__init__.py +1 -0
  119. atdd/tester/conventions/artifact.convention.yaml +257 -0
  120. atdd/tester/conventions/contract.convention.yaml +1009 -0
  121. atdd/tester/conventions/filename.convention.yaml +555 -0
  122. atdd/tester/conventions/migration.convention.yaml +509 -0
  123. atdd/tester/conventions/red.convention.yaml +797 -0
  124. atdd/tester/conventions/routing.convention.yaml +51 -0
  125. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  126. atdd/tester/schemas/a11y.tmpl.json +17 -0
  127. atdd/tester/schemas/artifact.schema.json +189 -0
  128. atdd/tester/schemas/contract.schema.json +591 -0
  129. atdd/tester/schemas/contract.tmpl.json +95 -0
  130. atdd/tester/schemas/db.tmpl.json +20 -0
  131. atdd/tester/schemas/e2e.tmpl.json +17 -0
  132. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  133. atdd/tester/schemas/event.tmpl.json +17 -0
  134. atdd/tester/schemas/http.tmpl.json +19 -0
  135. atdd/tester/schemas/job.tmpl.json +18 -0
  136. atdd/tester/schemas/load.tmpl.json +21 -0
  137. atdd/tester/schemas/metric.tmpl.json +19 -0
  138. atdd/tester/schemas/pack.schema.json +139 -0
  139. atdd/tester/schemas/realtime.tmpl.json +20 -0
  140. atdd/tester/schemas/rls.tmpl.json +18 -0
  141. atdd/tester/schemas/script.tmpl.json +16 -0
  142. atdd/tester/schemas/sec.tmpl.json +18 -0
  143. atdd/tester/schemas/storage.tmpl.json +18 -0
  144. atdd/tester/schemas/telemetry.schema.json +128 -0
  145. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  146. atdd/tester/schemas/test_filename.schema.json +194 -0
  147. atdd/tester/schemas/test_intent.schema.json +179 -0
  148. atdd/tester/schemas/unit.tmpl.json +18 -0
  149. atdd/tester/schemas/visual.tmpl.json +18 -0
  150. atdd/tester/schemas/ws.tmpl.json +17 -0
  151. atdd/tester/utils/__init__.py +0 -0
  152. atdd/tester/utils/filename.py +300 -0
  153. atdd/tester/validators/__init__.py +0 -0
  154. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  155. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  156. atdd/tester/validators/conftest.py +5 -0
  157. atdd/tester/validators/coverage_gap_report.py +321 -0
  158. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  159. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  160. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  161. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  162. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  163. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  164. atdd/tester/validators/test_contracts_structure.py +200 -0
  165. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  166. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  167. atdd/tester/validators/test_fixture_validity.py +372 -0
  168. atdd/tester/validators/test_isolation.py +487 -0
  169. atdd/tester/validators/test_migration_coverage.py +204 -0
  170. atdd/tester/validators/test_migration_criteria.py +276 -0
  171. atdd/tester/validators/test_migration_generation.py +116 -0
  172. atdd/tester/validators/test_python_test_naming.py +410 -0
  173. atdd/tester/validators/test_red_layer_validation.py +95 -0
  174. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  175. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  176. atdd/tester/validators/test_telemetry_structure.py +634 -0
  177. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  178. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  179. atdd-0.2.1.dist-info/METADATA +221 -0
  180. atdd-0.2.1.dist-info/RECORD +184 -0
  181. atdd-0.2.1.dist-info/WHEEL +5 -0
  182. atdd-0.2.1.dist-info/entry_points.txt +2 -0
  183. atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
  184. atdd-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,165 @@
1
+ """
2
+ SPEC-CODER-CONV-0013, SPEC-CODER-CONV-0014: Component URN naming migration tests for coder
3
+
4
+ Tests for migrating component URN pattern in coder conventions.
5
+ """
6
+ import pytest
7
+ import yaml
8
+ from pathlib import Path
9
+
10
+ REPO_ROOT = Path(__file__).resolve().parents[5]
11
+ COMPONENT_NAMING_PATH = REPO_ROOT / "atdd/coder/conventions/component-naming.convention.yaml"
12
+ GREEN_CONV_PATH = REPO_ROOT / "atdd/coder/conventions/green.convention.yaml"
13
+
14
+
15
+ def test_coder_convention_uses_colon_hierarchy():
16
+ """
17
+ SPEC-CODER-CONV-0013: Update coder component-naming.convention.yaml with new URN pattern
18
+
19
+ Verify that the URN pattern uses colons for hierarchy.
20
+ """
21
+ with open(COMPONENT_NAMING_PATH) as f:
22
+ convention = yaml.safe_load(f)
23
+
24
+ pattern = convention.get("urn_naming", {}).get("pattern", "")
25
+
26
+ # Should use colon after wagon and feature
27
+ assert "component:{wagon}:{feature}" in pattern, \
28
+ f"Pattern should use colons for hierarchy: {pattern}"
29
+
30
+ # Should NOT use dot between wagon and feature
31
+ assert "component:{wagon}.{feature}" not in pattern, \
32
+ f"Pattern should not use dots between wagon and feature: {pattern}"
33
+
34
+
35
+ def test_coder_artifact_examples_use_new_format():
36
+ """
37
+ SPEC-CODER-CONV-0013: All artifact derivation examples updated with new format
38
+
39
+ Verify all artifact_derivation examples use colon-based hierarchy.
40
+ """
41
+ with open(COMPONENT_NAMING_PATH) as f:
42
+ convention = yaml.safe_load(f)
43
+
44
+ artifact_derivation = convention.get("artifact_derivation", {})
45
+ # Navigate to the nested structure: rules > capability_suffix > by_artifact_type
46
+ rules = artifact_derivation.get("rules", {})
47
+ capability_suffix = rules.get("capability_suffix", {})
48
+ by_artifact_type = capability_suffix.get("by_artifact_type", {})
49
+
50
+ # Collect all example URNs from all artifact types
51
+ all_urns = []
52
+ for artifact_type, config in by_artifact_type.items():
53
+ examples = config.get("examples", [])
54
+ for example in examples:
55
+ urn = example.get("urn", "")
56
+ if urn:
57
+ all_urns.append((artifact_type, urn))
58
+
59
+ assert len(all_urns) > 0, "Should have artifact derivation examples"
60
+
61
+ for artifact_type, urn in all_urns:
62
+ # Check for colon format (look for pattern: component:word:word)
63
+ # After "component:" there should be wagon, then colon, then feature
64
+ parts = urn.split(":")
65
+ assert len(parts) >= 3, \
66
+ f"URN should have at least 3 colon-separated parts: {urn}"
67
+
68
+ # First part should be "component"
69
+ assert parts[0] == "component", f"URN should start with 'component:': {urn}"
70
+
71
+ # Should have wagon and feature separated by colon
72
+ # Format: component:wagon:feature.rest
73
+ wagon_feature = f"{parts[1]}:{parts[2].split('.')[0]}"
74
+ assert ":" in wagon_feature, \
75
+ f"{artifact_type} URN should use colon between wagon and feature: {urn}"
76
+
77
+ # Should NOT contain old format component:wagon.feature
78
+ assert not urn.startswith(f"component:{parts[1]}."), \
79
+ f"{artifact_type} URN should not use old dot format after wagon: {urn}"
80
+
81
+
82
+ def test_coder_complete_example_updated():
83
+ """
84
+ SPEC-CODER-CONV-0013: Complete example section updated with all four component URNs
85
+
86
+ Verify the complete_example section has all URNs in new format.
87
+ """
88
+ with open(COMPONENT_NAMING_PATH) as f:
89
+ convention = yaml.safe_load(f)
90
+
91
+ complete_example = convention.get("complete_example", {})
92
+ components = complete_example.get("components", [])
93
+
94
+ assert len(components) >= 4, "Should have at least 4 component examples"
95
+
96
+ for component in components:
97
+ urn = component.get("urn", "")
98
+ name = component.get("name", "")
99
+
100
+ # All URNs should use colon format
101
+ parts = urn.split(":")
102
+ assert len(parts) >= 3, \
103
+ f"{name} URN should have at least 3 colon-separated parts: {urn}"
104
+
105
+ # Should be component:wagon:feature.component
106
+ wagon = parts[1]
107
+ feature_and_rest = parts[2]
108
+
109
+ # Verify no dot immediately after component: (old format)
110
+ assert not urn.startswith(f"component:{wagon}."), \
111
+ f"{name} URN should not use old format component:wagon.feature: {urn}"
112
+
113
+
114
+ def test_green_convention_examples_updated():
115
+ """
116
+ SPEC-CODER-CONV-0014: Update coder green.convention.yaml component URN examples
117
+
118
+ Verify green convention examples use new colon hierarchy.
119
+ """
120
+ with open(GREEN_CONV_PATH) as f:
121
+ convention = yaml.safe_load(f)
122
+
123
+ # urn_naming is nested under green_phase
124
+ green_phase = convention.get("green_phase", {})
125
+ examples = green_phase.get("urn_naming", {}).get("examples", [])
126
+
127
+ assert len(examples) > 0, "Should have example URNs in green convention"
128
+
129
+ for example in examples:
130
+ urn = example.get("urn", "")
131
+ wagon = example.get("wagon", "")
132
+ feature = example.get("feature", "")
133
+
134
+ # Should use new format: component:wagon:feature
135
+ expected_prefix = f"component:{wagon}:{feature}"
136
+ assert urn.startswith(expected_prefix), \
137
+ f"URN should use colon hierarchy:\n URN: {urn}\n Expected prefix: {expected_prefix}"
138
+
139
+ # Should NOT use old format: component:wagon.feature
140
+ old_format = f"component:{wagon}.{feature}"
141
+ assert not urn.startswith(old_format), \
142
+ f"URN should not use old dot format: {urn}"
143
+
144
+
145
+ def test_green_convention_pattern_correct():
146
+ """
147
+ SPEC-CODER-CONV-0014: Green convention pattern field updated correctly
148
+
149
+ Verify the pattern field in green.convention.yaml uses new format.
150
+ """
151
+ with open(GREEN_CONV_PATH) as f:
152
+ convention = yaml.safe_load(f)
153
+
154
+ # urn_naming is nested under green_phase
155
+ green_phase = convention.get("green_phase", {})
156
+ pattern = green_phase.get("urn_naming", {}).get("pattern", "")
157
+ description = green_phase.get("urn_naming", {}).get("description", "")
158
+
159
+ # Pattern should use colons for hierarchy
160
+ assert "component:{wagon}:{feature}" in pattern, \
161
+ f"Pattern should use colons: {pattern}"
162
+
163
+ # Description should mention hierarchy via colons
164
+ assert "hierarchy" in description.lower() or "colon" in description.lower(), \
165
+ f"Description should mention hierarchy or colons: {description}"
@@ -0,0 +1,286 @@
1
+ """
2
+ Tests for thinness recipe.
3
+
4
+ Tests SPEC-CODER-UTL-0153 to 042, 053, 056, 057, 058
5
+ ATDD: These tests define expected behavior of thinness 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
14
+ def load_recipe(recipe_name: str) -> Dict[str, Any]:
15
+ """
16
+ Load recipe YAML file.
17
+
18
+ SPEC-CODER-UTL-0153: Load thinness 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-0154: Detect when thinness recipe applies
34
+ """
35
+ recipe = load_recipe(recipe_name)
36
+
37
+ if recipe_name == "thinness":
38
+ # thinness applies when thinness check fails
39
+ return smells.get("thinness", {}).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-0155, 041, 042: 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
+ "next_action": "verify_tests" if step == 1 else ("continue" if step < len(steps) else "verify_final")
66
+ }
67
+
68
+
69
+ def select_recipe(smells: Dict[str, Any]) -> Dict[str, Any]:
70
+ """
71
+ Select recipe based on smell detection.
72
+
73
+ SPEC-CODER-UTL-0168: Map handler smell to thinness recipe
74
+ """
75
+ # Priority 1: Handler smell → thinness
76
+ if smells.get("thinness", {}).get("passed", True) is False:
77
+ return {"recipe": "thinness", "priority": 1}
78
+
79
+ # Priority 2: Complexity smell → specification
80
+ if smells.get("complexity", {}).get("passed", True) is False:
81
+ return {"recipe": "specification", "priority": 2}
82
+
83
+ # Priority 3: Missing adapter → adapter
84
+ if smells.get("missing_adapter", False):
85
+ return {"recipe": "adapter", "priority": 3}
86
+
87
+ return {"recipe": None, "priority": 0}
88
+
89
+
90
+ def verify_recipe_step(step_result: Dict[str, Any], test_status: str) -> bool:
91
+ """
92
+ Verify recipe step maintains GREEN tests.
93
+
94
+ SPEC-CODER-UTL-0171: Verify recipe step maintains GREEN tests
95
+ SPEC-CODER-UTL-0173: Rollback on step failure
96
+ """
97
+ if test_status == "GREEN":
98
+ return True
99
+
100
+ # RED tests trigger rollback
101
+ if test_status == "RED":
102
+ # This would call rollback_refactor_step() in real usage
103
+ return False
104
+
105
+ return False
106
+
107
+
108
+ def verify_recipe_final(recipe_name: str, results: Dict[str, Any]) -> Dict[str, Any]:
109
+ """
110
+ Verify final state after all recipe steps.
111
+
112
+ SPEC-CODER-UTL-0172: Verify final state after all recipe steps
113
+ """
114
+ verification = {"success": True, "checks": []}
115
+
116
+ if recipe_name == "thinness":
117
+ # Final verification: thinness.check() should pass
118
+ verification["checks"].append({
119
+ "check": "thinness.check()",
120
+ "expected": "passed=true"
121
+ })
122
+
123
+ return verification
124
+
125
+
126
+ # TESTS
127
+
128
+ class TestThinHandlerRecipeLoading:
129
+ """Test SPEC-CODER-UTL-0153: Load thinness recipe"""
130
+
131
+ def test_load_recipe(self):
132
+ """Should load thinness recipe YAML"""
133
+ recipe = load_recipe("thinness")
134
+
135
+ assert recipe is not None
136
+ assert recipe["recipe"] == "thinness"
137
+ assert recipe["pattern"] == "Thin Handler + Use Case"
138
+ assert "steps" in recipe
139
+ assert len(recipe["steps"]) == 3
140
+
141
+
142
+ class TestThinHandlerRecipeApplies:
143
+ """Test SPEC-CODER-UTL-0154: Detect when thinness recipe applies"""
144
+
145
+ def test_recipe_applies(self):
146
+ """Should return true when thinness check fails"""
147
+ smells = {
148
+ "thinness": {
149
+ "passed": False,
150
+ "smells": [
151
+ {"line": 10, "reason": "Business logic in handler"}
152
+ ]
153
+ }
154
+ }
155
+
156
+ result = check_recipe_applies("thinness", smells)
157
+
158
+ assert result is True
159
+
160
+ def test_recipe_not_applies(self):
161
+ """Should return false when thinness check passes"""
162
+ smells = {
163
+ "thinness": {
164
+ "passed": True,
165
+ "smells": []
166
+ }
167
+ }
168
+
169
+ result = check_recipe_applies("thinness", smells)
170
+
171
+ assert result is False
172
+
173
+
174
+ class TestThinHandlerRecipeSteps:
175
+ """Test SPEC-CODER-UTL-0155, 041, 042: Execute recipe steps"""
176
+
177
+ def test_execute_step_1(self):
178
+ """Should execute step 1: create use case"""
179
+ context = {"file_path": "presentation/handlers/order.py"}
180
+
181
+ result = execute_recipe_step("thinness", 1, context)
182
+
183
+ assert result["success"] is True
184
+ assert "use case" in result["what"].lower()
185
+ assert "application/" in result["where"]
186
+ assert result["next_action"] == "verify_tests"
187
+
188
+ def test_execute_step_2(self):
189
+ """Should execute step 2: define port"""
190
+ context = {"usecase_created": True}
191
+
192
+ result = execute_recipe_step("thinness", 2, context)
193
+
194
+ assert result["success"] is True
195
+ assert "port" in result["what"].lower()
196
+ assert "application/ports/" in result["where"]
197
+ assert result["next_action"] == "continue"
198
+
199
+ def test_execute_step_3(self):
200
+ """Should execute step 3: thin the handler"""
201
+ context = {"usecase_created": True, "port_defined": True}
202
+
203
+ result = execute_recipe_step("thinness", 3, context)
204
+
205
+ assert result["success"] is True
206
+ assert "thin" in result["what"].lower() or "handler" in result["what"].lower()
207
+ assert result["next_action"] == "verify_final"
208
+
209
+
210
+ class TestThinHandlerRecipeSelection:
211
+ """Test SPEC-CODER-UTL-0168: Map handler smell to thinness recipe"""
212
+
213
+ def test_select_from_smell(self):
214
+ """Should select thinness recipe when handler smell detected"""
215
+ smells = {
216
+ "thinness": {
217
+ "passed": False,
218
+ "smells": [{"line": 10, "reason": "Business logic"}]
219
+ }
220
+ }
221
+
222
+ result = select_recipe(smells)
223
+
224
+ assert result["recipe"] == "thinness"
225
+ assert result["priority"] == 1
226
+
227
+
228
+ class TestThinHandlerRecipeVerification:
229
+ """Test SPEC-CODER-UTL-0171, 057, 058: Recipe verification"""
230
+
231
+ def test_verify_step_green(self):
232
+ """Should return true when tests are GREEN"""
233
+ step_result = {"success": True, "step": 1}
234
+
235
+ result = verify_recipe_step(step_result, "GREEN")
236
+
237
+ assert result is True
238
+
239
+ def test_rollback_on_failure(self):
240
+ """Should return false and trigger rollback when tests are RED"""
241
+ step_result = {"success": True, "step": 1}
242
+
243
+ result = verify_recipe_step(step_result, "RED")
244
+
245
+ assert result is False
246
+
247
+ def test_verify_final(self):
248
+ """Should verify final state with thinness check"""
249
+ results = {"all_steps_completed": True}
250
+
251
+ verification = verify_recipe_final("thinness", results)
252
+
253
+ assert verification["success"] is True
254
+ assert len(verification["checks"]) > 0
255
+ assert any("thinness" in check["check"] for check in verification["checks"])
256
+
257
+
258
+ class TestThinHandlerRecipeIntegration:
259
+ """Integration tests for full recipe workflow"""
260
+
261
+ def test_full_recipe_workflow(self):
262
+ """Should execute full recipe from detection to verification"""
263
+ # 1. Detect smell
264
+ smells = {"thinness": {"passed": False, "smells": [{"line": 10, "reason": "Business logic"}]}}
265
+
266
+ # 2. Select recipe
267
+ selected = select_recipe(smells)
268
+ assert selected["recipe"] == "thinness"
269
+
270
+ # 3. Load recipe
271
+ recipe = load_recipe("thinness")
272
+ assert recipe is not None
273
+
274
+ # 4. Execute steps
275
+ context = {}
276
+ for step in range(1, 4):
277
+ result = execute_recipe_step("thinness", step, context)
278
+ assert result["success"] is True
279
+
280
+ # Verify step
281
+ verified = verify_recipe_step(result, "GREEN")
282
+ assert verified is True
283
+
284
+ # 5. Final verification
285
+ verification = verify_recipe_final("thinness", {"all_steps": True})
286
+ assert verification["success"] is True
@@ -0,0 +1,82 @@
1
+ recipe: thinness
2
+ pattern: "Thin Handler + Use Case"
3
+ category: presentation_application
4
+ source: "Clean Architecture (Robert C. Martin)"
5
+ utils: "thinness.py"
6
+
7
+ applies_when:
8
+ - "Handler contains business logic"
9
+
10
+ steps:
11
+ - step: 1
12
+ what: "Create use case in application layer"
13
+ where: "application/{feature}_usecase.py"
14
+ template: |
15
+ class {Action}UseCase:
16
+ """Execute {action} business logic."""
17
+
18
+ def __init__(self, repo: {Resource}Repository):
19
+ self.repo = repo
20
+
21
+ def execute(self, input: {Action}Input) -> {Resource}:
22
+ # Business logic here
23
+ result = {Resource}.create(input)
24
+ self.repo.save(result)
25
+ return result
26
+
27
+ verify:
28
+ - run: pytest -xvs tests/application/test_{feature}_usecase.py
29
+ expect: GREEN
30
+
31
+ - step: 2
32
+ what: "Define port interface (if not exists)"
33
+ where: "application/ports/{resource}_repository.py"
34
+ template: |
35
+ class {Resource}Repository:
36
+ """Port interface for {resource} persistence."""
37
+
38
+ def save(self, entity: {Resource}) -> None:
39
+ raise NotImplementedError
40
+
41
+ def find_by_id(self, id: {Resource}Id) -> {Resource}:
42
+ raise NotImplementedError
43
+
44
+ - step: 3
45
+ what: "Thin the handler (parse → call → format)"
46
+ where: "presentation/handlers/{feature}_handler.py"
47
+ before: |
48
+ # Fat handler with business logic
49
+ async def handle_create_order(req: Request) -> Response:
50
+ order_data = req.body
51
+ # Validation logic
52
+ if not order_data.get('customer_id'):
53
+ return Response(400, "Missing customer")
54
+ # Business logic (BAD - should be in use case)
55
+ total = sum(item['price'] * item['qty'] for item in order_data['items'])
56
+ discount = total * 0.1 if total > 100 else 0
57
+ final_total = total - discount
58
+ # Persistence (BAD - should be behind port)
59
+ db.execute("INSERT INTO orders ...")
60
+ return Response(201, {"id": order_id})
61
+
62
+ after: |
63
+ # Thin handler
64
+ async def handle_create_order(req: Request) -> Response:
65
+ # Parse
66
+ input = CreateOrderDTO.parse(req.body)
67
+
68
+ # Call use case
69
+ order = create_order_usecase.execute(input)
70
+
71
+ # Format
72
+ return Response(201, CreateOrderDTO.from_domain(order))
73
+
74
+ verify:
75
+ - run: pytest -xvs tests/presentation/test_{feature}_handler.py
76
+ expect: GREEN
77
+
78
+ final_verify:
79
+ - "Handler has NO business logic (only parse/call/format)"
80
+ - "Use case orchestrates domain + ports"
81
+ - "All tests GREEN"
82
+ - "Coverage maintained or improved"