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,454 @@
1
+ """
2
+ Test train infrastructure validation (SESSION-12).
3
+
4
+ Validates conventions from:
5
+ - atdd/coder/conventions/train.convention.yaml
6
+ - atdd/coder/conventions/boundaries.convention.yaml
7
+ - atdd/coder/conventions/refactor.convention.yaml
8
+
9
+ Enforces:
10
+ - Train infrastructure exists (python/trains/)
11
+ - Wagons implement run_train() for train mode
12
+ - Contract validator is real (not mock)
13
+ - E2E tests use production TrainRunner
14
+ - Station Master pattern in game.py
15
+
16
+ Rationale:
17
+ Trains are production orchestration, not test infrastructure (SESSION-12).
18
+ These audits ensure the train composition root pattern is correctly implemented.
19
+ """
20
+
21
+ import pytest
22
+ import ast
23
+ import re
24
+ from pathlib import Path
25
+ from typing import List, Dict, Set, Tuple
26
+
27
+
28
+ # Path constants
29
+ REPO_ROOT = Path(__file__).resolve().parents[4]
30
+ TRAINS_DIR = REPO_ROOT / "python" / "trains"
31
+ WAGONS_DIR = REPO_ROOT / "python"
32
+ GAME_PY = REPO_ROOT / "python" / "game.py"
33
+ E2E_CONFTEST = REPO_ROOT / "e2e" / "conftest.py"
34
+ CONTRACT_VALIDATOR = REPO_ROOT / "e2e" / "shared" / "fixtures" / "contract_validator.py"
35
+ TRAIN_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "train.convention.yaml"
36
+
37
+
38
+ def find_wagons() -> List[Path]:
39
+ """Find all wagon.py files."""
40
+ wagons = []
41
+ for wagon_file in WAGONS_DIR.glob("*/wagon.py"):
42
+ # Skip trains directory
43
+ if "trains" in wagon_file.parts:
44
+ continue
45
+ wagons.append(wagon_file)
46
+ return wagons
47
+
48
+
49
+ def has_run_train_function(file_path: Path) -> Tuple[bool, str]:
50
+ """
51
+ Check if wagon.py has run_train() function.
52
+
53
+ Returns:
54
+ (has_function, implementation_type)
55
+ implementation_type: "function", "method", or "none"
56
+ """
57
+ with open(file_path, 'r', encoding='utf-8') as f:
58
+ content = f.read()
59
+
60
+ try:
61
+ tree = ast.parse(content)
62
+
63
+ # Check for module-level function
64
+ for node in ast.walk(tree):
65
+ if isinstance(node, ast.FunctionDef) and node.name == "run_train":
66
+ # Check if it's at module level (not inside a class)
67
+ if isinstance(node, ast.FunctionDef):
68
+ return (True, "function")
69
+
70
+ # Check for class method
71
+ for node in ast.walk(tree):
72
+ if isinstance(node, ast.ClassDef):
73
+ for item in node.body:
74
+ if isinstance(item, ast.FunctionDef) and item.name == "run_train":
75
+ return (True, "method")
76
+
77
+ return (False, "none")
78
+
79
+ except SyntaxError:
80
+ return (False, "none")
81
+
82
+
83
+ def extract_imports_from_file(file_path: Path) -> Set[str]:
84
+ """Extract all import statements from a file."""
85
+ imports = set()
86
+ with open(file_path, 'r', encoding='utf-8') as f:
87
+ for line in f:
88
+ # Match: from X import Y or import X
89
+ if 'import' in line and not line.strip().startswith('#'):
90
+ imports.add(line.strip())
91
+ return imports
92
+
93
+
94
+ # ============================================================================
95
+ # TRAIN INFRASTRUCTURE TESTS
96
+ # ============================================================================
97
+
98
+ def test_trains_directory_exists():
99
+ """Train infrastructure must exist at python/trains/."""
100
+ assert TRAINS_DIR.exists(), (
101
+ f"Train infrastructure directory not found: {TRAINS_DIR}\n"
102
+ "Expected: python/trains/\n"
103
+ "See: atdd/coder/conventions/train.convention.yaml"
104
+ )
105
+
106
+ assert TRAINS_DIR.is_dir(), f"{TRAINS_DIR} exists but is not a directory"
107
+
108
+
109
+ def test_train_infrastructure_files_exist():
110
+ """
111
+ Train infrastructure files must exist.
112
+
113
+ Required files:
114
+ - python/trains/__init__.py
115
+ - python/trains/runner.py (TrainRunner class)
116
+ - python/trains/models.py (TrainSpec, TrainResult, Cargo)
117
+ """
118
+ required_files = {
119
+ "__init__.py": "Package initialization",
120
+ "runner.py": "TrainRunner class",
121
+ "models.py": "Data models (TrainSpec, TrainResult, Cargo)"
122
+ }
123
+
124
+ missing_files = []
125
+ for filename, description in required_files.items():
126
+ file_path = TRAINS_DIR / filename
127
+ if not file_path.exists():
128
+ missing_files.append((filename, description))
129
+
130
+ if missing_files:
131
+ pytest.fail(
132
+ f"\nMissing {len(missing_files)} train infrastructure files:\n\n" +
133
+ "\n".join(f" python/trains/{name}\n Purpose: {desc}"
134
+ for name, desc in missing_files) +
135
+ "\n\nSee: atdd/coder/conventions/train.convention.yaml::train_structure"
136
+ )
137
+
138
+
139
+ def test_train_runner_class_exists():
140
+ """TrainRunner class must exist in python/trains/runner.py."""
141
+ runner_file = TRAINS_DIR / "runner.py"
142
+
143
+ assert runner_file.exists(), f"runner.py not found: {runner_file}"
144
+
145
+ with open(runner_file, 'r', encoding='utf-8') as f:
146
+ content = f.read()
147
+
148
+ # Check for TrainRunner class
149
+ assert "class TrainRunner" in content, (
150
+ "TrainRunner class not found in python/trains/runner.py\n"
151
+ "Expected: class TrainRunner with execute() method"
152
+ )
153
+
154
+ # Check for key methods
155
+ required_methods = ["__init__", "execute", "_execute_step"]
156
+ missing_methods = [m for m in required_methods if f"def {m}" not in content]
157
+
158
+ if missing_methods:
159
+ pytest.fail(
160
+ f"\nTrainRunner missing required methods: {', '.join(missing_methods)}\n"
161
+ "Expected methods: __init__, execute, _execute_step"
162
+ )
163
+
164
+
165
+ def test_train_models_exist():
166
+ """Train data models must exist in python/trains/models.py."""
167
+ models_file = TRAINS_DIR / "models.py"
168
+
169
+ assert models_file.exists(), f"models.py not found: {models_file}"
170
+
171
+ with open(models_file, 'r', encoding='utf-8') as f:
172
+ content = f.read()
173
+
174
+ # Check for required models
175
+ required_models = ["TrainSpec", "TrainStep", "TrainResult", "Cargo"]
176
+ missing_models = [m for m in required_models if f"class {m}" not in content]
177
+
178
+ if missing_models:
179
+ pytest.fail(
180
+ f"\nMissing train models: {', '.join(missing_models)}\n"
181
+ "Expected in python/trains/models.py:\n"
182
+ " - TrainSpec: Parsed train definition\n"
183
+ " - TrainStep: Single step in sequence\n"
184
+ " - TrainResult: Execution result\n"
185
+ " - Cargo: Artifacts passed between wagons"
186
+ )
187
+
188
+
189
+ # ============================================================================
190
+ # WAGON TRAIN MODE TESTS
191
+ # ============================================================================
192
+
193
+ def test_wagons_implement_run_train():
194
+ """
195
+ Wagons must implement run_train() to participate in train orchestration.
196
+
197
+ Expected signature:
198
+ def run_train(inputs: Dict[str, Any], timing: Dict[str, float] = None) -> Dict[str, Any]
199
+
200
+ Can be either:
201
+ - Module-level function: def run_train(...)
202
+ - Class method: class XxxWagon: def run_train(self, ...)
203
+ """
204
+ wagons = find_wagons()
205
+
206
+ assert len(wagons) > 0, "No wagons found in python/ directory"
207
+
208
+ missing_run_train = []
209
+ for wagon_file in wagons:
210
+ has_function, impl_type = has_run_train_function(wagon_file)
211
+ if not has_function:
212
+ wagon_name = wagon_file.parent.name
213
+ missing_run_train.append(wagon_name)
214
+
215
+ # Allow some wagons to not have run_train yet (partial migration)
216
+ # But key wagons from SESSION-12 must have it
217
+ required_wagons = ["pace_dilemmas", "supply_fragments", "juggle_domains", "resolve_dilemmas"]
218
+ missing_required = [w for w in required_wagons if w in missing_run_train]
219
+
220
+ if missing_required:
221
+ pytest.fail(
222
+ f"\nCritical wagons missing run_train() implementation:\n\n" +
223
+ "\n".join(f" python/{name}/wagon.py" for name in missing_required) +
224
+ "\n\nExpected signature:\n"
225
+ " def run_train(inputs: Dict[str, Any], timing: Dict[str, float] = None) -> Dict[str, Any]\n"
226
+ "\nSee: atdd/coder/conventions/train.convention.yaml::wagon_train_interface"
227
+ )
228
+
229
+
230
+ # ============================================================================
231
+ # STATION MASTER TESTS (game.py)
232
+ # ============================================================================
233
+
234
+ def test_game_py_imports_train_runner():
235
+ """game.py must import TrainRunner (Station Master pattern)."""
236
+ assert GAME_PY.exists(), f"game.py not found: {GAME_PY}"
237
+
238
+ imports = extract_imports_from_file(GAME_PY)
239
+
240
+ has_train_import = any("trains.runner import TrainRunner" in imp for imp in imports)
241
+
242
+ assert has_train_import, (
243
+ "game.py must import TrainRunner\n"
244
+ "Expected: from trains.runner import TrainRunner\n"
245
+ "See: atdd/coder/conventions/train.convention.yaml::station_master"
246
+ )
247
+
248
+
249
+ def test_game_py_has_journey_map():
250
+ """game.py must have JOURNEY_MAP routing actions to trains."""
251
+ with open(GAME_PY, 'r', encoding='utf-8') as f:
252
+ content = f.read()
253
+
254
+ assert "JOURNEY_MAP" in content, (
255
+ "game.py must define JOURNEY_MAP dictionary\n"
256
+ "Expected: JOURNEY_MAP = {'action': 'train_id', ...}\n"
257
+ "See: atdd/coder/conventions/train.convention.yaml::station_master"
258
+ )
259
+
260
+
261
+ def test_game_py_has_train_execution_endpoint():
262
+ """game.py must have /trains/execute endpoint."""
263
+ with open(GAME_PY, 'r', encoding='utf-8') as f:
264
+ content = f.read()
265
+
266
+ has_endpoint = '"/trains/execute"' in content or "'/trains/execute'" in content
267
+
268
+ assert has_endpoint, (
269
+ "game.py must have /trains/execute endpoint\n"
270
+ "Expected: @app.post('/trains/execute')\n"
271
+ "See: atdd/coder/conventions/train.convention.yaml::station_master"
272
+ )
273
+
274
+
275
+ # ============================================================================
276
+ # E2E TEST INFRASTRUCTURE TESTS
277
+ # ============================================================================
278
+
279
+ def test_e2e_conftest_uses_production_train_runner():
280
+ """
281
+ E2E conftest must use production TrainRunner, not mocks.
282
+
283
+ This ensures tests validate production orchestration (zero drift).
284
+ """
285
+ assert E2E_CONFTEST.exists(), f"E2E conftest not found: {E2E_CONFTEST}"
286
+
287
+ imports = extract_imports_from_file(E2E_CONFTEST)
288
+
289
+ # Should import from trains.runner (production)
290
+ has_production_import = any("trains.runner import TrainRunner" in imp for imp in imports)
291
+
292
+ # Should NOT import mock
293
+ has_mock_import = any("mock_train_runner" in imp for imp in imports)
294
+
295
+ assert has_production_import, (
296
+ "E2E conftest must import production TrainRunner\n"
297
+ "Expected: from trains.runner import TrainRunner\n"
298
+ "Found: Mock import still present\n"
299
+ "See: atdd/coder/conventions/train.convention.yaml::testing_pattern"
300
+ )
301
+
302
+ assert not has_mock_import, (
303
+ "E2E conftest should NOT use MockTrainRunner\n"
304
+ "Remove: from e2e.shared.fixtures.mock_train_runner import MockTrainRunner\n"
305
+ "Use production TrainRunner instead"
306
+ )
307
+
308
+
309
+ def test_contract_validator_is_real():
310
+ """
311
+ Contract validator must be real JSON schema validator, not mock.
312
+
313
+ Real validator uses jsonschema library for contract validation.
314
+ """
315
+ assert CONTRACT_VALIDATOR.exists(), f"Contract validator not found: {CONTRACT_VALIDATOR}"
316
+
317
+ with open(CONTRACT_VALIDATOR, 'r', encoding='utf-8') as f:
318
+ content = f.read()
319
+
320
+ # Check for real implementation
321
+ has_jsonschema = "import jsonschema" in content or "from jsonschema import" in content
322
+ has_validate_method = "def validate(" in content
323
+
324
+ assert has_jsonschema, (
325
+ "Contract validator must use jsonschema library\n"
326
+ "Expected: import jsonschema\n"
327
+ "File: e2e/shared/fixtures/contract_validator.py"
328
+ )
329
+
330
+ assert has_validate_method, (
331
+ "Contract validator must have validate() method\n"
332
+ "Expected: def validate(self, artifact, schema_path)\n"
333
+ "File: e2e/shared/fixtures/contract_validator.py"
334
+ )
335
+
336
+ # Check it's not a mock
337
+ is_mock = "Mock" in content and "mock" in content.lower()
338
+
339
+ assert not is_mock, (
340
+ "Contract validator appears to be a mock\n"
341
+ "Replace with real JSON schema validation\n"
342
+ "See: atdd/coder/conventions/train.convention.yaml::cargo_pattern"
343
+ )
344
+
345
+
346
+ def test_e2e_conftest_uses_real_contract_validator():
347
+ """E2E conftest must use real ContractValidator, not mock."""
348
+ imports = extract_imports_from_file(E2E_CONFTEST)
349
+
350
+ # Should import real validator
351
+ has_real_import = any("contract_validator import ContractValidator" in imp
352
+ and "mock" not in imp.lower()
353
+ for imp in imports)
354
+
355
+ # Should NOT import mock
356
+ has_mock_import = any("mock_contract_validator" in imp for imp in imports)
357
+
358
+ assert has_real_import, (
359
+ "E2E conftest must import real ContractValidator\n"
360
+ "Expected: from e2e.shared.fixtures.contract_validator import ContractValidator\n"
361
+ "File: e2e/conftest.py"
362
+ )
363
+
364
+ assert not has_mock_import, (
365
+ "E2E conftest should NOT use MockContractValidator\n"
366
+ "Remove: from e2e.shared.fixtures.mock_contract_validator import MockContractValidator\n"
367
+ "Use real ContractValidator instead"
368
+ )
369
+
370
+
371
+ # ============================================================================
372
+ # CONVENTION DOCUMENTATION TESTS
373
+ # ============================================================================
374
+
375
+ def test_train_convention_exists():
376
+ """Train convention file must exist."""
377
+ assert TRAIN_CONVENTION.exists(), (
378
+ f"Train convention not found: {TRAIN_CONVENTION}\n"
379
+ "Expected: atdd/coder/conventions/train.convention.yaml"
380
+ )
381
+
382
+
383
+ def test_train_convention_documents_key_patterns():
384
+ """
385
+ Train convention must document key implementation patterns.
386
+
387
+ Required sections:
388
+ - composition_hierarchy (with train level)
389
+ - wagon_train_mode (run_train signature)
390
+ - cargo_pattern (artifact flow)
391
+ - station_master (game.py pattern)
392
+ - testing_pattern (E2E tests)
393
+ """
394
+ with open(TRAIN_CONVENTION, 'r', encoding='utf-8') as f:
395
+ content = f.read()
396
+
397
+ required_sections = [
398
+ "composition_hierarchy",
399
+ "wagon_train_mode",
400
+ "cargo_pattern",
401
+ "station_master",
402
+ "testing_pattern"
403
+ ]
404
+
405
+ missing_sections = [s for s in required_sections if s not in content]
406
+
407
+ if missing_sections:
408
+ pytest.fail(
409
+ f"\nTrain convention missing required sections:\n\n" +
410
+ "\n".join(f" - {section}" for section in missing_sections) +
411
+ f"\n\nFile: {TRAIN_CONVENTION}\n"
412
+ "See: SESSION-12 implementation plan for required documentation"
413
+ )
414
+
415
+
416
+ # ============================================================================
417
+ # BOUNDARY ENFORCEMENT TESTS
418
+ # ============================================================================
419
+
420
+ def test_no_wagon_to_wagon_imports():
421
+ """
422
+ Wagons must NOT import from other wagons.
423
+
424
+ Enforces boundary pattern: wagons communicate via contracts only.
425
+ This is checked in wagon.py files specifically (not all python files).
426
+ """
427
+ wagons = find_wagons()
428
+ violations = []
429
+
430
+ for wagon_file in wagons:
431
+ wagon_name = wagon_file.parent.name
432
+
433
+ with open(wagon_file, 'r', encoding='utf-8') as f:
434
+ content = f.read()
435
+
436
+ # Find imports from other wagons
437
+ for other_wagon in wagons:
438
+ other_name = other_wagon.parent.name
439
+ if other_name == wagon_name:
440
+ continue
441
+
442
+ # Check for imports like: from other_wagon.xxx import
443
+ pattern = f"from {other_name}\\."
444
+ if re.search(pattern, content):
445
+ violations.append((wagon_name, other_name, wagon_file))
446
+
447
+ if violations:
448
+ pytest.fail(
449
+ f"\nFound {len(violations)} wagon boundary violations:\n\n" +
450
+ "\n".join(f" {wagon} imports from {other}\n File: {file}"
451
+ for wagon, other, file in violations) +
452
+ "\n\nWagons must communicate via contracts only, not direct imports\n"
453
+ "See: atdd/coder/conventions/boundaries.convention.yaml"
454
+ )