atdd 0.1.0__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 (183) hide show
  1. atdd/__init__.py +0 -0
  2. atdd/cli.py +404 -0
  3. atdd/coach/__init__.py +0 -0
  4. atdd/coach/commands/__init__.py +0 -0
  5. atdd/coach/commands/add_persistence_metadata.py +215 -0
  6. atdd/coach/commands/analyze_migrations.py +188 -0
  7. atdd/coach/commands/consumers.py +720 -0
  8. atdd/coach/commands/infer_governance_status.py +149 -0
  9. atdd/coach/commands/initializer.py +177 -0
  10. atdd/coach/commands/interface.py +1078 -0
  11. atdd/coach/commands/inventory.py +565 -0
  12. atdd/coach/commands/migration.py +240 -0
  13. atdd/coach/commands/registry.py +1560 -0
  14. atdd/coach/commands/session.py +430 -0
  15. atdd/coach/commands/sync.py +405 -0
  16. atdd/coach/commands/test_interface.py +399 -0
  17. atdd/coach/commands/test_runner.py +141 -0
  18. atdd/coach/commands/tests/__init__.py +1 -0
  19. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  20. atdd/coach/commands/traceability.py +4264 -0
  21. atdd/coach/conventions/session.convention.yaml +754 -0
  22. atdd/coach/overlays/__init__.py +2 -0
  23. atdd/coach/overlays/claude.md +2 -0
  24. atdd/coach/schemas/config.schema.json +34 -0
  25. atdd/coach/schemas/manifest.schema.json +101 -0
  26. atdd/coach/templates/ATDD.md +282 -0
  27. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  28. atdd/coach/utils/__init__.py +0 -0
  29. atdd/coach/utils/graph/__init__.py +0 -0
  30. atdd/coach/utils/graph/urn.py +875 -0
  31. atdd/coach/validators/__init__.py +0 -0
  32. atdd/coach/validators/shared_fixtures.py +365 -0
  33. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  34. atdd/coach/validators/test_registry.py +575 -0
  35. atdd/coach/validators/test_session_validation.py +1183 -0
  36. atdd/coach/validators/test_traceability.py +448 -0
  37. atdd/coach/validators/test_update_feature_paths.py +108 -0
  38. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  39. atdd/coder/__init__.py +1 -0
  40. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  41. atdd/coder/conventions/backend.convention.yaml +460 -0
  42. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  43. atdd/coder/conventions/commons.convention.yaml +460 -0
  44. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  45. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  46. atdd/coder/conventions/design.convention.yaml +327 -0
  47. atdd/coder/conventions/design.recipe.yaml +273 -0
  48. atdd/coder/conventions/dto.convention.yaml +660 -0
  49. atdd/coder/conventions/frontend.convention.yaml +542 -0
  50. atdd/coder/conventions/green.convention.yaml +1012 -0
  51. atdd/coder/conventions/presentation.convention.yaml +587 -0
  52. atdd/coder/conventions/refactor.convention.yaml +535 -0
  53. atdd/coder/conventions/technology.convention.yaml +206 -0
  54. atdd/coder/conventions/tests/__init__.py +0 -0
  55. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  56. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  57. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  58. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  59. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  60. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  61. atdd/coder/conventions/train.convention.yaml +325 -0
  62. atdd/coder/conventions/verification.protocol.yaml +53 -0
  63. atdd/coder/schemas/design_system.schema.json +361 -0
  64. atdd/coder/validators/__init__.py +0 -0
  65. atdd/coder/validators/test_commons_structure.py +485 -0
  66. atdd/coder/validators/test_complexity.py +416 -0
  67. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  68. atdd/coder/validators/test_design_system_compliance.py +413 -0
  69. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  70. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  71. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  72. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  73. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  74. atdd/coder/validators/test_import_boundaries.py +396 -0
  75. atdd/coder/validators/test_init_file_urns.py +593 -0
  76. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  77. atdd/coder/validators/test_presentation_convention.py +260 -0
  78. atdd/coder/validators/test_python_architecture.py +674 -0
  79. atdd/coder/validators/test_quality_metrics.py +420 -0
  80. atdd/coder/validators/test_station_master_pattern.py +244 -0
  81. atdd/coder/validators/test_train_infrastructure.py +454 -0
  82. atdd/coder/validators/test_train_urns.py +293 -0
  83. atdd/coder/validators/test_typescript_architecture.py +616 -0
  84. atdd/coder/validators/test_usecase_structure.py +421 -0
  85. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  86. atdd/conftest.py +126 -0
  87. atdd/planner/__init__.py +1 -0
  88. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  89. atdd/planner/conventions/appendix.convention.yaml +187 -0
  90. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  91. atdd/planner/conventions/component.convention.yaml +670 -0
  92. atdd/planner/conventions/criteria.convention.yaml +141 -0
  93. atdd/planner/conventions/feature.convention.yaml +371 -0
  94. atdd/planner/conventions/interface.convention.yaml +382 -0
  95. atdd/planner/conventions/steps.convention.yaml +141 -0
  96. atdd/planner/conventions/train.convention.yaml +552 -0
  97. atdd/planner/conventions/wagon.convention.yaml +275 -0
  98. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  99. atdd/planner/schemas/acceptance.schema.json +336 -0
  100. atdd/planner/schemas/appendix.schema.json +78 -0
  101. atdd/planner/schemas/component.schema.json +114 -0
  102. atdd/planner/schemas/feature.schema.json +197 -0
  103. atdd/planner/schemas/train.schema.json +192 -0
  104. atdd/planner/schemas/wagon.schema.json +281 -0
  105. atdd/planner/schemas/wmbt.schema.json +59 -0
  106. atdd/planner/validators/__init__.py +0 -0
  107. atdd/planner/validators/conftest.py +5 -0
  108. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  109. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  110. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  111. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  112. atdd/planner/validators/test_plan_wagons.py +174 -0
  113. atdd/planner/validators/test_train_validation.py +514 -0
  114. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  115. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  116. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  117. atdd/tester/__init__.py +1 -0
  118. atdd/tester/conventions/artifact.convention.yaml +257 -0
  119. atdd/tester/conventions/contract.convention.yaml +1009 -0
  120. atdd/tester/conventions/filename.convention.yaml +555 -0
  121. atdd/tester/conventions/migration.convention.yaml +509 -0
  122. atdd/tester/conventions/red.convention.yaml +797 -0
  123. atdd/tester/conventions/routing.convention.yaml +51 -0
  124. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  125. atdd/tester/schemas/a11y.tmpl.json +17 -0
  126. atdd/tester/schemas/artifact.schema.json +189 -0
  127. atdd/tester/schemas/contract.schema.json +591 -0
  128. atdd/tester/schemas/contract.tmpl.json +95 -0
  129. atdd/tester/schemas/db.tmpl.json +20 -0
  130. atdd/tester/schemas/e2e.tmpl.json +17 -0
  131. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  132. atdd/tester/schemas/event.tmpl.json +17 -0
  133. atdd/tester/schemas/http.tmpl.json +19 -0
  134. atdd/tester/schemas/job.tmpl.json +18 -0
  135. atdd/tester/schemas/load.tmpl.json +21 -0
  136. atdd/tester/schemas/metric.tmpl.json +19 -0
  137. atdd/tester/schemas/pack.schema.json +139 -0
  138. atdd/tester/schemas/realtime.tmpl.json +20 -0
  139. atdd/tester/schemas/rls.tmpl.json +18 -0
  140. atdd/tester/schemas/script.tmpl.json +16 -0
  141. atdd/tester/schemas/sec.tmpl.json +18 -0
  142. atdd/tester/schemas/storage.tmpl.json +18 -0
  143. atdd/tester/schemas/telemetry.schema.json +128 -0
  144. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  145. atdd/tester/schemas/test_filename.schema.json +194 -0
  146. atdd/tester/schemas/test_intent.schema.json +179 -0
  147. atdd/tester/schemas/unit.tmpl.json +18 -0
  148. atdd/tester/schemas/visual.tmpl.json +18 -0
  149. atdd/tester/schemas/ws.tmpl.json +17 -0
  150. atdd/tester/utils/__init__.py +0 -0
  151. atdd/tester/utils/filename.py +300 -0
  152. atdd/tester/validators/__init__.py +0 -0
  153. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  154. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  155. atdd/tester/validators/conftest.py +5 -0
  156. atdd/tester/validators/coverage_gap_report.py +321 -0
  157. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  158. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  159. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  160. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  161. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  162. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  163. atdd/tester/validators/test_contracts_structure.py +200 -0
  164. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  165. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  166. atdd/tester/validators/test_fixture_validity.py +372 -0
  167. atdd/tester/validators/test_isolation.py +487 -0
  168. atdd/tester/validators/test_migration_coverage.py +204 -0
  169. atdd/tester/validators/test_migration_criteria.py +276 -0
  170. atdd/tester/validators/test_migration_generation.py +116 -0
  171. atdd/tester/validators/test_python_test_naming.py +410 -0
  172. atdd/tester/validators/test_red_layer_validation.py +95 -0
  173. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  174. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  175. atdd/tester/validators/test_telemetry_structure.py +634 -0
  176. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  177. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  178. atdd-0.1.0.dist-info/METADATA +191 -0
  179. atdd-0.1.0.dist-info/RECORD +183 -0
  180. atdd-0.1.0.dist-info/WHEEL +5 -0
  181. atdd-0.1.0.dist-info/entry_points.txt +2 -0
  182. atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
  183. atdd-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,410 @@
1
+ """
2
+ Test Python test files follow naming conventions.
3
+
4
+ Validates:
5
+ - Test files are named test_*.py
6
+ - Test files are in test/ or tests/ directories (accept both singular and plural)
7
+ - Test files have mandatory slugs (test_{wmbt}_{harness}_{nnn}_{slug}.py)
8
+ - Test functions start with test_
9
+ - Test classes start with Test
10
+
11
+ Inspired by: .claude/utils/tester/filename.py
12
+ But: Self-contained, no utility dependencies
13
+ """
14
+
15
+ import pytest
16
+ import re
17
+ from pathlib import Path
18
+
19
+
20
+ # Path constants
21
+ REPO_ROOT = Path(__file__).resolve().parents[4]
22
+ PYTHON_DIR = REPO_ROOT / "python"
23
+
24
+
25
+ def find_test_files() -> list:
26
+ """
27
+ Find all test files in python/ directory.
28
+
29
+ Returns:
30
+ List of Path objects pointing to test files
31
+ """
32
+ if not PYTHON_DIR.exists():
33
+ return []
34
+
35
+ test_files = []
36
+
37
+ # Find test files
38
+ for py_file in PYTHON_DIR.rglob("*.py"):
39
+ # Check if in test directory or named test_*
40
+ if '/test/' in str(py_file) or py_file.name.startswith('test_'):
41
+ test_files.append(py_file)
42
+
43
+ return test_files
44
+
45
+
46
+ def extract_test_functions(file_path: Path) -> list:
47
+ """
48
+ Extract test function names from Python file.
49
+
50
+ Args:
51
+ file_path: Path to test file
52
+
53
+ Returns:
54
+ List of function names
55
+ """
56
+ try:
57
+ with open(file_path, 'r', encoding='utf-8') as f:
58
+ content = f.read()
59
+ except Exception:
60
+ return []
61
+
62
+ # Match: def test_something(...):
63
+ # or: async def test_something(...):
64
+ functions = re.findall(r'(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', content)
65
+
66
+ return functions
67
+
68
+
69
+ def extract_test_classes(file_path: Path) -> list:
70
+ """
71
+ Extract test class names from Python file.
72
+
73
+ Args:
74
+ file_path: Path to test file
75
+
76
+ Returns:
77
+ List of class names
78
+ """
79
+ try:
80
+ with open(file_path, 'r', encoding='utf-8') as f:
81
+ content = f.read()
82
+ except Exception:
83
+ return []
84
+
85
+ # Match: class TestSomething(...):
86
+ classes = re.findall(r'class\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[:\(]', content)
87
+
88
+ return classes
89
+
90
+
91
+ @pytest.mark.tester
92
+ def test_python_test_files_named_correctly():
93
+ """
94
+ SPEC-TESTER-NAMING-0001: Python test files follow naming convention.
95
+
96
+ Convention:
97
+ - Test files must be named test_*.py
98
+ - Test files should be in test/ or tests/ directories (both accepted)
99
+
100
+ Given: Python test files
101
+ When: Checking file names
102
+ Then: All test files follow convention
103
+ """
104
+ test_files = find_test_files()
105
+
106
+ if not test_files:
107
+ pytest.skip("No Python test files found")
108
+
109
+ violations = []
110
+
111
+ for test_file in test_files:
112
+ filename = test_file.name
113
+
114
+ # Skip special pytest files
115
+ if filename in ['conftest.py', '__init__.py']:
116
+ continue
117
+
118
+ # Check naming convention
119
+ if not filename.startswith('test_') and not filename.endswith('_test.py'):
120
+ violations.append(
121
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
122
+ f" Issue: Test file should be named test_*.py\\n"
123
+ f" Found: {filename}"
124
+ )
125
+
126
+ # Check if in test/ or tests/ directory (accept both singular and plural)
127
+ if '/test/' not in str(test_file) and '/tests/' not in str(test_file):
128
+ violations.append(
129
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
130
+ f" Issue: Test file should be in test/ or tests/ directory\\n"
131
+ f" Found: Not in test/ or tests/ directory"
132
+ )
133
+
134
+ if violations:
135
+ pytest.fail(
136
+ f"\\n\\nFound {len(violations)} naming violations:\\n\\n" +
137
+ "\\n\\n".join(violations[:10]) +
138
+ (f"\\n\\n... and {len(violations) - 10} more" if len(violations) > 10 else "")
139
+ )
140
+
141
+
142
+ @pytest.mark.tester
143
+ def test_python_test_functions_named_correctly():
144
+ """
145
+ SPEC-TESTER-NAMING-0002: Python test functions follow naming convention.
146
+
147
+ Convention:
148
+ - Test functions must start with test_
149
+ - Test functions should use snake_case
150
+ - Test functions should be descriptive
151
+
152
+ Given: Python test files
153
+ When: Checking function names
154
+ Then: All test functions follow convention
155
+ """
156
+ test_files = find_test_files()
157
+
158
+ if not test_files:
159
+ pytest.skip("No Python test files found")
160
+
161
+ violations = []
162
+
163
+ for test_file in test_files:
164
+ functions = extract_test_functions(test_file)
165
+
166
+ for func_name in functions:
167
+ # Skip helper functions (private)
168
+ if func_name.startswith('_'):
169
+ continue
170
+
171
+ # Skip fixture functions
172
+ if func_name in ['setup', 'teardown', 'setup_class', 'teardown_class',
173
+ 'setup_method', 'teardown_method']:
174
+ continue
175
+
176
+ # Check if test function follows convention
177
+ if not func_name.startswith('test_'):
178
+ # Not a test function (could be helper)
179
+ continue
180
+
181
+ # Check snake_case (allow uppercase for WMBT codes like E003, UNIT, etc.)
182
+ # Pattern accepts: test_e003_unit_001_... OR test_E003_UNIT_001_...
183
+ if not re.match(r'^test_[a-zA-Z0-9_]+$', func_name):
184
+ violations.append(
185
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
186
+ f" Function: {func_name}\\n"
187
+ f" Issue: Test function should start with test_ and use alphanumeric_underscore pattern"
188
+ )
189
+
190
+ # Check if too short
191
+ if len(func_name) < 10:
192
+ violations.append(
193
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
194
+ f" Function: {func_name}\\n"
195
+ f" Issue: Test function name too short (should be descriptive)"
196
+ )
197
+
198
+ if violations:
199
+ pytest.fail(
200
+ f"\\n\\nFound {len(violations)} function naming violations:\\n\\n" +
201
+ "\\n\\n".join(violations[:10]) +
202
+ (f"\\n\\n... and {len(violations) - 10} more" if len(violations) > 10 else "")
203
+ )
204
+
205
+
206
+ @pytest.mark.tester
207
+ def test_python_test_classes_named_correctly():
208
+ """
209
+ SPEC-TESTER-NAMING-0003: Python test classes follow naming convention.
210
+
211
+ Convention:
212
+ - Test classes must start with Test
213
+ - Test classes should use PascalCase
214
+ - Test classes should be descriptive
215
+
216
+ Given: Python test files
217
+ When: Checking class names
218
+ Then: All test classes follow convention
219
+ """
220
+ test_files = find_test_files()
221
+
222
+ if not test_files:
223
+ pytest.skip("No Python test files found")
224
+
225
+ violations = []
226
+
227
+ for test_file in test_files:
228
+ classes = extract_test_classes(test_file)
229
+
230
+ for class_name in classes:
231
+ # Skip if not a test class (could be helper)
232
+ if not class_name.startswith('Test'):
233
+ continue
234
+
235
+ # Check PascalCase
236
+ if not re.match(r'^Test[A-Z][a-zA-Z0-9]*$', class_name):
237
+ violations.append(
238
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
239
+ f" Class: {class_name}\\n"
240
+ f" Issue: Test class should use PascalCase after 'Test'"
241
+ )
242
+
243
+ # Check if too short
244
+ if len(class_name) < 8: # Test + at least 3 chars
245
+ violations.append(
246
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
247
+ f" Class: {class_name}\\n"
248
+ f" Issue: Test class name too short (should be descriptive)"
249
+ )
250
+
251
+ if violations:
252
+ pytest.fail(
253
+ f"\\n\\nFound {len(violations)} class naming violations:\\n\\n" +
254
+ "\\n\\n".join(violations[:10]) +
255
+ (f"\\n\\n... and {len(violations) - 10} more" if len(violations) > 10 else "")
256
+ )
257
+
258
+
259
+ @pytest.mark.tester
260
+ def test_python_test_files_have_mandatory_slugs():
261
+ """
262
+ SPEC-TESTER-NAMING-0004: Python test files in feature-based structure have mandatory slugs.
263
+
264
+ Convention (from filename.convention.yaml):
265
+ - Pattern: test_{wmbt_lower}_{harness_lower}_{nnn}_{slug_snake}.py
266
+ - Slug is MANDATORY (not optional)
267
+ - Slug derived from acceptance.identity.purpose
268
+ - Example: test_l001_unit_001_uuid_v7_generation_completes_within.py
269
+
270
+ Given: Python test files in python/{wagon}/{feature}/test/ directories
271
+ When: Checking file names
272
+ Then: All test files include mandatory slug component
273
+ """
274
+ test_files = find_test_files()
275
+
276
+ if not test_files:
277
+ pytest.skip("No Python test files found")
278
+
279
+ violations = []
280
+
281
+ # Pattern: test_{wmbt}_{harness}_{nnn}_{slug}.py
282
+ # WMBT: 1-4 letters (L, P, C, etc.) followed by 3 digits
283
+ # Harness: unit, integration, load, http, e2e, etc.
284
+ # NNN: 3 digits (001, 002, etc.)
285
+ # Slug: descriptive snake_case (MANDATORY)
286
+ slug_pattern = re.compile(
287
+ r'^test_([a-z]\d{3})_([a-z]+)_(\d{3})_([a-z][a-z0-9_]+)\.py$'
288
+ )
289
+
290
+ for test_file in test_files:
291
+ filename = test_file.name
292
+
293
+ # Skip special pytest files and wagon-level tests
294
+ if filename in ['conftest.py', '__init__.py', 'test_contracts.py', 'test_telemetry.py']:
295
+ continue
296
+
297
+ # Only check files in feature-based structure: python/{wagon}/{feature}/test/
298
+ test_path_str = str(test_file)
299
+ if '/python/' not in test_path_str or '/test/' not in test_path_str:
300
+ continue
301
+
302
+ # Check if in feature-based directory structure
303
+ parts = test_file.parts
304
+ try:
305
+ python_idx = parts.index('python')
306
+ # Feature-based: python/{wagon}/{feature}/test/
307
+ if len(parts) > python_idx + 3 and parts[python_idx + 3] == 'test':
308
+ # This is a feature-based test file
309
+ match = slug_pattern.match(filename)
310
+
311
+ if not match:
312
+ # Try to parse what we have
313
+ basic_pattern = re.compile(r'^test_([a-z]\d{3})_([a-z]+)_(\d{3})\.py$')
314
+ basic_match = basic_pattern.match(filename)
315
+
316
+ if basic_match:
317
+ wmbt = basic_match.group(1)
318
+ harness = basic_match.group(2)
319
+ nnn = basic_match.group(3)
320
+ violations.append(
321
+ f"{test_file.relative_to(REPO_ROOT)}\n"
322
+ f" Issue: Missing mandatory slug component\n"
323
+ f" Pattern: test_{{wmbt}}_{{harness}}_{{nnn}}_{{slug}}.py\n"
324
+ f" Found: test_{wmbt}_{harness}_{nnn}.py (no slug)\n"
325
+ f" Expected: test_{wmbt}_{harness}_{nnn}_<descriptive_slug>.py\n"
326
+ f" Note: Slug must be derived from acceptance.identity.purpose"
327
+ )
328
+ else:
329
+ violations.append(
330
+ f"{test_file.relative_to(REPO_ROOT)}\n"
331
+ f" Issue: Does not match required pattern\n"
332
+ f" Pattern: test_{{wmbt}}_{{harness}}_{{nnn}}_{{slug}}.py\n"
333
+ f" Found: {filename}\n"
334
+ f" Note: All 4 components required (wmbt, harness, nnn, slug)"
335
+ )
336
+ except (ValueError, IndexError):
337
+ # Not in feature-based structure, skip
338
+ continue
339
+
340
+ if violations:
341
+ pytest.fail(
342
+ f"\n\nFound {len(violations)} test files without mandatory slugs:\n\n" +
343
+ "\n\n".join(violations[:10]) +
344
+ (f"\n\n... and {len(violations) - 10} more" if len(violations) > 10 else "") +
345
+ "\n\nSlug Convention:\n" +
346
+ "- Slugs are MANDATORY (not optional)\n" +
347
+ "- Derived from acceptance.identity.purpose field\n" +
348
+ "- Process: Remove 'Verify', lowercase, replace spaces with underscores\n" +
349
+ "- Example: 'Verify UUID v7 generation completes within' → 'uuid_v7_generation_completes_within'"
350
+ )
351
+
352
+
353
+ @pytest.mark.tester
354
+ def test_python_test_files_are_in_correct_locations():
355
+ """
356
+ SPEC-TESTER-NAMING-0004: Test files are in correct locations.
357
+
358
+ Convention:
359
+ - Test files should be in {module}/test/ directory
360
+ - Test files should mirror source structure when possible
361
+
362
+ Given: Python test files
363
+ When: Checking locations
364
+ Then: Test files are in appropriate test/ directories
365
+ """
366
+ test_files = find_test_files()
367
+
368
+ if not test_files:
369
+ pytest.skip("No Python test files found")
370
+
371
+ violations = []
372
+
373
+ for test_file in test_files:
374
+ # Check if in test/ or tests/ directory (accept both)
375
+ if '/test/' not in str(test_file) and '/tests/' not in str(test_file):
376
+ violations.append(
377
+ f"{test_file.relative_to(REPO_ROOT)}\\n"
378
+ f" Issue: Test file not in test/ or tests/ directory"
379
+ )
380
+
381
+ # Check if test file has corresponding source structure
382
+ # Example: module/test/test_foo.py should have module/src/foo.py
383
+ # Accept both test/ and tests/ directories
384
+ if ('/test/' in str(test_file) or '/tests/' in str(test_file)) and test_file.name.startswith('test_'):
385
+ # Get potential source file name
386
+ source_name = test_file.name.replace('test_', '', 1)
387
+ test_dir = test_file.parent
388
+
389
+ # Check for src/ sibling
390
+ module_root = test_dir.parent
391
+ src_dir = module_root / 'src'
392
+
393
+ if src_dir.exists():
394
+ # Look for corresponding source file
395
+ potential_source = src_dir / source_name
396
+ # Also check subdirectories (domain, application, etc.)
397
+ source_exists = potential_source.exists() or \
398
+ any((src_dir / subdir / source_name).exists()
399
+ for subdir in ['domain', 'application', 'integration', 'presentation'])
400
+
401
+ if not source_exists and source_name != '__init__.py':
402
+ # This might be okay (integration tests, etc.) so just warn
403
+ pass # Don't fail, just note
404
+
405
+ if violations:
406
+ pytest.fail(
407
+ f"\\n\\nFound {len(violations)} location violations:\\n\\n" +
408
+ "\\n\\n".join(violations[:10]) +
409
+ (f"\\n\\n... and {len(violations) - 10} more" if len(violations) > 10 else "")
410
+ )
@@ -0,0 +1,95 @@
1
+ """
2
+ SPEC-TESTER-CONV-0082: RED convention rejects tests not in layer structure
3
+
4
+ Test that red.convention.yaml validates tests are in correct layer directories.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ RED_CONVENTION = REPO_ROOT / "atdd" / "tester" / "conventions" / "red.convention.yaml"
14
+
15
+
16
+ @pytest.mark.tester
17
+ def test_rejects_non_layered_python_tests():
18
+ """
19
+ SPEC-TESTER-CONV-0082: RED convention rejects Python tests not in layer structure.
20
+
21
+ Given: Test file generated outside layer directory
22
+ When: Validating test structure
23
+ Then: Convention validation fails with error message
24
+ """
25
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
26
+
27
+ with open(RED_CONVENTION, 'r') as f:
28
+ convention = yaml.safe_load(f)
29
+
30
+ structure = convention.get('layer_structure') or convention.get('test_structure')
31
+ assert structure is not None, "Convention must define test structure"
32
+
33
+ python_config = structure.get('python', {})
34
+
35
+ # Check for validation rules
36
+ validation = python_config.get('validation') or convention.get('validation', {})
37
+
38
+ # Should reject tests without layer directory
39
+ assert 'require_layer_directory' in validation or \
40
+ validation.get('enforce_layer_structure') == True, \
41
+ "Convention must enforce layer directory requirement"
42
+
43
+ # Check for error message configuration
44
+ assert 'error_messages' in validation or 'messages' in validation, \
45
+ "Convention must define error messages for violations"
46
+
47
+
48
+ @pytest.mark.tester
49
+ def test_rejects_non_layered_flutter_tests():
50
+ """
51
+ SPEC-TESTER-CONV-0082: RED convention rejects Flutter tests not in layer structure.
52
+
53
+ Given: Flutter test file outside layer directory
54
+ When: Validating test structure
55
+ Then: Convention validation fails
56
+ """
57
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
58
+
59
+ with open(RED_CONVENTION, 'r') as f:
60
+ convention = yaml.safe_load(f)
61
+
62
+ structure = convention.get('layer_structure') or convention.get('test_structure')
63
+ flutter_config = structure.get('flutter') or structure.get('dart', {})
64
+
65
+ validation = flutter_config.get('validation') or convention.get('validation', {})
66
+
67
+ # Should reject tests without layer directory
68
+ assert 'require_layer_directory' in validation or \
69
+ validation.get('enforce_layer_structure') == True, \
70
+ "Convention must enforce layer directory requirement for Flutter"
71
+
72
+
73
+ @pytest.mark.tester
74
+ def test_rejects_non_layered_supabase_tests():
75
+ """
76
+ SPEC-TESTER-CONV-0082: RED convention rejects Supabase tests not in layer structure.
77
+
78
+ Given: Supabase test file outside layer directory
79
+ When: Validating test structure
80
+ Then: Convention validation fails
81
+ """
82
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
83
+
84
+ with open(RED_CONVENTION, 'r') as f:
85
+ convention = yaml.safe_load(f)
86
+
87
+ structure = convention.get('layer_structure') or convention.get('test_structure')
88
+ supabase_config = structure.get('supabase') or structure.get('typescript', {})
89
+
90
+ validation = supabase_config.get('validation') or convention.get('validation', {})
91
+
92
+ # Should reject tests without layer directory
93
+ assert 'require_layer_directory' in validation or \
94
+ validation.get('enforce_layer_structure') == True, \
95
+ "Convention must enforce layer directory requirement for Supabase"
@@ -0,0 +1,87 @@
1
+ """
2
+ SPEC-TESTER-CONV-0079: RED convention defines 4-layer test structure for Python
3
+
4
+ Test that red.convention.yaml enforces 4-layer test structure for Python.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ RED_CONVENTION = REPO_ROOT / "atdd" / "tester" / "conventions" / "red.convention.yaml"
14
+
15
+
16
+ @pytest.mark.tester
17
+ def test_red_defines_python_layer_structure():
18
+ """
19
+ SPEC-TESTER-CONV-0079: RED convention defines 4-layer test structure for Python.
20
+
21
+ Given: red.convention.yaml exists
22
+ When: Reading Python test structure configuration
23
+ Then: Convention specifies test structure python/{wagon}/{feature}/tests/{layer}/
24
+ """
25
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
26
+
27
+ with open(RED_CONVENTION, 'r') as f:
28
+ convention = yaml.safe_load(f)
29
+
30
+ # Check for layer structure definition
31
+ assert 'layer_structure' in convention or 'test_structure' in convention, \
32
+ "Convention must define layer_structure or test_structure"
33
+
34
+ # Get structure config
35
+ structure = convention.get('layer_structure') or convention.get('test_structure')
36
+
37
+ # Check Python-specific configuration
38
+ assert 'python' in structure, "Convention must define Python test structure"
39
+
40
+ python_config = structure['python']
41
+
42
+ # Verify 4 layers are defined
43
+ assert 'layers' in python_config, "Python config must define layers"
44
+ layers = python_config['layers']
45
+
46
+ expected_layers = {'presentation', 'application', 'domain', 'integration'}
47
+ assert set(layers) == expected_layers, \
48
+ f"Layers must be {expected_layers}, got {set(layers)}"
49
+
50
+ # Verify test path pattern
51
+ assert 'test_path_pattern' in python_config, "Python config must define test_path_pattern"
52
+ pattern = python_config['test_path_pattern']
53
+
54
+ assert '{wagon}' in pattern, "Pattern must include {wagon} placeholder"
55
+ assert '{feature}' in pattern, "Pattern must include {feature} placeholder"
56
+ assert '{layer}' in pattern, "Pattern must include {layer} placeholder"
57
+ assert 'tests' in pattern, "Pattern must include 'tests' directory"
58
+
59
+ # Verify it matches expected pattern
60
+ expected_pattern = "python/{wagon}/{feature}/tests/{layer}/"
61
+ assert pattern == expected_pattern or pattern.startswith("python/{wagon}/{feature}/tests/{layer}"), \
62
+ f"Pattern should be {expected_pattern}, got {pattern}"
63
+
64
+
65
+ @pytest.mark.tester
66
+ def test_red_creates_layer_directories():
67
+ """
68
+ SPEC-TESTER-CONV-0079: Test path generation creates layer directories automatically.
69
+
70
+ Given: red.convention.yaml with layer structure
71
+ When: Generating test paths
72
+ Then: Layer directories are created automatically
73
+ """
74
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
75
+
76
+ with open(RED_CONVENTION, 'r') as f:
77
+ convention = yaml.safe_load(f)
78
+
79
+ structure = convention.get('layer_structure') or convention.get('test_structure')
80
+ assert structure is not None, "Convention must define test structure"
81
+
82
+ python_config = structure.get('python', {})
83
+
84
+ # Check for auto-create configuration
85
+ assert 'auto_create_directories' in python_config or \
86
+ python_config.get('behavior', {}).get('create_layer_dirs') == True, \
87
+ "Convention must specify that layer directories are auto-created"
@@ -0,0 +1,90 @@
1
+ """
2
+ SPEC-TESTER-CONV-0081: RED convention defines 4-layer test structure for Supabase
3
+
4
+ Test that red.convention.yaml enforces 4-layer test structure for Supabase.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ RED_CONVENTION = REPO_ROOT / "atdd" / "tester" / "conventions" / "red.convention.yaml"
14
+
15
+
16
+ @pytest.mark.tester
17
+ def test_red_defines_supabase_layer_structure():
18
+ """
19
+ SPEC-TESTER-CONV-0081: RED convention defines 4-layer test structure for Supabase.
20
+
21
+ Given: Supabase structure is supabase/functions/{wagon}/{feature}/
22
+ When: Reading Supabase test structure configuration
23
+ Then: Convention specifies test structure supabase/functions/{wagon}/{feature}/tests/{layer}/
24
+ """
25
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
26
+
27
+ with open(RED_CONVENTION, 'r') as f:
28
+ convention = yaml.safe_load(f)
29
+
30
+ structure = convention.get('layer_structure') or convention.get('test_structure')
31
+ assert structure is not None, "Convention must define test structure"
32
+
33
+ # Check Supabase/TypeScript-specific configuration
34
+ assert 'supabase' in structure or 'typescript' in structure, \
35
+ "Convention must define Supabase/TypeScript test structure"
36
+
37
+ supabase_config = structure.get('supabase') or structure.get('typescript')
38
+
39
+ # Verify 4 layers are defined
40
+ assert 'layers' in supabase_config, "Supabase config must define layers"
41
+ layers = supabase_config['layers']
42
+
43
+ expected_layers = {'presentation', 'application', 'domain', 'integration'}
44
+ assert set(layers) == expected_layers, \
45
+ f"Layers must be {expected_layers}, got {set(layers)}"
46
+
47
+ # Verify test path pattern
48
+ assert 'test_path_pattern' in supabase_config, "Supabase config must define test_path_pattern"
49
+ pattern = supabase_config['test_path_pattern']
50
+
51
+ assert '{wagon}' in pattern, "Pattern must include {wagon} placeholder"
52
+ assert '{feature}' in pattern, "Pattern must include {feature} placeholder"
53
+ assert '{layer}' in pattern, "Pattern must include {layer} placeholder"
54
+ assert 'tests' in pattern, "Pattern must include 'tests' directory"
55
+ assert 'supabase/functions' in pattern or 'functions' in pattern, \
56
+ "Pattern must reference Supabase functions directory"
57
+
58
+ # Verify it matches expected pattern
59
+ expected_pattern = "supabase/functions/{wagon}/{feature}/tests/{layer}/"
60
+ assert pattern == expected_pattern or \
61
+ pattern.startswith("supabase/functions/{wagon}/{feature}/tests/{layer}"), \
62
+ f"Pattern should be {expected_pattern}, got {pattern}"
63
+
64
+
65
+ @pytest.mark.tester
66
+ def test_http_tests_in_presentation():
67
+ """
68
+ SPEC-TESTER-CONV-0081: HTTP handler tests go in presentation layer.
69
+
70
+ Given: Supabase layer structure
71
+ When: Determining layer for HTTP handler tests
72
+ Then: HTTP tests are placed in presentation layer
73
+ """
74
+ assert RED_CONVENTION.exists(), "red.convention.yaml must exist"
75
+
76
+ with open(RED_CONVENTION, 'r') as f:
77
+ convention = yaml.safe_load(f)
78
+
79
+ structure = convention.get('layer_structure') or convention.get('test_structure')
80
+ supabase_config = structure.get('supabase') or structure.get('typescript', {})
81
+
82
+ # Check for layer mapping rules
83
+ layer_mapping = supabase_config.get('layer_mapping') or supabase_config.get('test_type_layers')
84
+
85
+ if layer_mapping:
86
+ # HTTP/controller tests should map to presentation
87
+ assert layer_mapping.get('http') == 'presentation' or \
88
+ layer_mapping.get('controller') == 'presentation' or \
89
+ 'http' in layer_mapping.get('presentation', []), \
90
+ "HTTP handler tests must be in presentation layer"