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.
- atdd/__init__.py +0 -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.1.0.dist-info/METADATA +191 -0
- atdd-0.1.0.dist-info/RECORD +183 -0
- atdd-0.1.0.dist-info/WHEEL +5 -0
- atdd-0.1.0.dist-info/entry_points.txt +2 -0
- atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
- atdd-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test commons structure consistency across Python and Frontend.
|
|
3
|
+
|
|
4
|
+
Validates both structural patterns:
|
|
5
|
+
- Python: Feature-first (complex features have internal layers, utilities are flat)
|
|
6
|
+
- Frontend: Layer-first (all code organized by architectural layer)
|
|
7
|
+
|
|
8
|
+
Both patterns enforce:
|
|
9
|
+
- Domain layer purity (no framework imports)
|
|
10
|
+
- Consistent naming (commons, not shared)
|
|
11
|
+
- Proper dependency direction
|
|
12
|
+
|
|
13
|
+
Convention: atdd/coder/conventions/commons.convention.yaml
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
import re
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
REPO_ROOT = Path(__file__).resolve().parents[4]
|
|
23
|
+
PYTHON_COMMONS = REPO_ROOT / "python" / "commons"
|
|
24
|
+
WEB_COMMONS = REPO_ROOT / "web" / "src" / "commons"
|
|
25
|
+
WEB_SRC = REPO_ROOT / "web" / "src"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# CROSS-STACK VALIDATION
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.coder
|
|
34
|
+
def test_commons_exists_in_both_stacks():
|
|
35
|
+
"""
|
|
36
|
+
SPEC-CODER-COMMONS-0001: Commons exists in Python and Frontend.
|
|
37
|
+
|
|
38
|
+
GIVEN: Project with polyglot codebase
|
|
39
|
+
WHEN: Checking for commons directories
|
|
40
|
+
THEN: Both python/commons and web/src/commons exist
|
|
41
|
+
|
|
42
|
+
Validates: Consistent naming across stacks
|
|
43
|
+
"""
|
|
44
|
+
missing = []
|
|
45
|
+
|
|
46
|
+
if not PYTHON_COMMONS.exists():
|
|
47
|
+
missing.append(f"python/commons/ (expected at {PYTHON_COMMONS})")
|
|
48
|
+
|
|
49
|
+
if not WEB_COMMONS.exists():
|
|
50
|
+
missing.append(f"web/src/commons/ (expected at {WEB_COMMONS})")
|
|
51
|
+
|
|
52
|
+
if missing:
|
|
53
|
+
pytest.fail(
|
|
54
|
+
f"\n\nMissing commons directories:\n" +
|
|
55
|
+
"\n".join(f" - {m}" for m in missing)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.coder
|
|
60
|
+
def test_no_shared_directory_exists():
|
|
61
|
+
"""
|
|
62
|
+
SPEC-CODER-COMMONS-0003: Old 'shared' directory should not exist.
|
|
63
|
+
|
|
64
|
+
GIVEN: Project migrated to commons convention
|
|
65
|
+
WHEN: Checking for legacy shared directory
|
|
66
|
+
THEN: web/src/shared should not exist
|
|
67
|
+
|
|
68
|
+
Validates: Migration from shared to commons complete
|
|
69
|
+
"""
|
|
70
|
+
old_shared = REPO_ROOT / "web" / "src" / "shared"
|
|
71
|
+
|
|
72
|
+
if old_shared.exists():
|
|
73
|
+
files = list(old_shared.rglob("*"))
|
|
74
|
+
pytest.fail(
|
|
75
|
+
f"\n\nLegacy 'shared' directory still exists at web/src/shared\n"
|
|
76
|
+
f"Contains {len(files)} files.\n"
|
|
77
|
+
f"Migrate to web/src/commons/ and delete."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ============================================================================
|
|
82
|
+
# FRONTEND STRUCTURE VALIDATION (Layer-First)
|
|
83
|
+
# ============================================================================
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.coder
|
|
87
|
+
def test_frontend_commons_has_layer_structure():
|
|
88
|
+
"""
|
|
89
|
+
SPEC-CODER-COMMONS-0002: Frontend commons has domain/application/integration layers.
|
|
90
|
+
|
|
91
|
+
GIVEN: web/src/commons directory
|
|
92
|
+
WHEN: Checking layer subdirectories
|
|
93
|
+
THEN: domain/, application/, integration/ exist
|
|
94
|
+
|
|
95
|
+
Validates: Layer-first structure for frontend
|
|
96
|
+
"""
|
|
97
|
+
if not WEB_COMMONS.exists():
|
|
98
|
+
pytest.skip("web/src/commons does not exist")
|
|
99
|
+
|
|
100
|
+
expected_layers = ["domain", "application", "integration"]
|
|
101
|
+
missing_layers = []
|
|
102
|
+
|
|
103
|
+
for layer in expected_layers:
|
|
104
|
+
layer_path = WEB_COMMONS / layer
|
|
105
|
+
if not layer_path.exists():
|
|
106
|
+
missing_layers.append(layer)
|
|
107
|
+
|
|
108
|
+
if missing_layers:
|
|
109
|
+
pytest.fail(
|
|
110
|
+
f"\n\nMissing layers in web/src/commons/:\n" +
|
|
111
|
+
"\n".join(f" - {layer}/" for layer in missing_layers) +
|
|
112
|
+
f"\n\nExpected structure (layer-first):\n" +
|
|
113
|
+
" web/src/commons/\n" +
|
|
114
|
+
" +-- domain/ # Framework-agnostic types\n" +
|
|
115
|
+
" +-- application/ # Hooks, context\n" +
|
|
116
|
+
" +-- integration/ # Clients, adapters"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.coder
|
|
121
|
+
def test_path_alias_uses_commons():
|
|
122
|
+
"""
|
|
123
|
+
SPEC-CODER-COMMONS-0004: Path aliases use @commons, not @shared.
|
|
124
|
+
|
|
125
|
+
GIVEN: tsconfig.json and vite.config.ts
|
|
126
|
+
WHEN: Checking path aliases
|
|
127
|
+
THEN: @commons is defined, @shared is not
|
|
128
|
+
|
|
129
|
+
Validates: Correct path alias configuration
|
|
130
|
+
"""
|
|
131
|
+
tsconfig_path = REPO_ROOT / "web" / "tsconfig.json"
|
|
132
|
+
vite_config_path = REPO_ROOT / "web" / "vite.config.ts"
|
|
133
|
+
|
|
134
|
+
issues = []
|
|
135
|
+
|
|
136
|
+
if tsconfig_path.exists():
|
|
137
|
+
content = tsconfig_path.read_text()
|
|
138
|
+
if "@shared" in content:
|
|
139
|
+
issues.append("tsconfig.json still contains @shared alias")
|
|
140
|
+
if "@commons" not in content:
|
|
141
|
+
issues.append("tsconfig.json missing @commons alias")
|
|
142
|
+
|
|
143
|
+
if vite_config_path.exists():
|
|
144
|
+
content = vite_config_path.read_text()
|
|
145
|
+
if "'@shared'" in content or '"@shared"' in content:
|
|
146
|
+
issues.append("vite.config.ts still contains @shared alias")
|
|
147
|
+
if "'@commons'" not in content and '"@commons"' not in content:
|
|
148
|
+
issues.append("vite.config.ts missing @commons alias")
|
|
149
|
+
|
|
150
|
+
if issues:
|
|
151
|
+
pytest.fail(
|
|
152
|
+
"\n\nPath alias configuration issues:\n" +
|
|
153
|
+
"\n".join(f" - {i}" for i in issues)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@pytest.mark.coder
|
|
158
|
+
def test_no_imports_from_shared():
|
|
159
|
+
"""
|
|
160
|
+
SPEC-CODER-COMMONS-0005: No imports from @shared or ./shared.
|
|
161
|
+
|
|
162
|
+
GIVEN: All TypeScript files in web/src
|
|
163
|
+
WHEN: Checking import statements
|
|
164
|
+
THEN: No imports reference @shared or relative shared paths
|
|
165
|
+
|
|
166
|
+
Validates: All imports migrated to @commons
|
|
167
|
+
"""
|
|
168
|
+
if not WEB_SRC.exists():
|
|
169
|
+
pytest.skip("web/src does not exist")
|
|
170
|
+
|
|
171
|
+
violations: List[str] = []
|
|
172
|
+
|
|
173
|
+
for ts_file in WEB_SRC.rglob("*.ts"):
|
|
174
|
+
_check_file_for_shared_imports(ts_file, violations)
|
|
175
|
+
|
|
176
|
+
for tsx_file in WEB_SRC.rglob("*.tsx"):
|
|
177
|
+
_check_file_for_shared_imports(tsx_file, violations)
|
|
178
|
+
|
|
179
|
+
if violations:
|
|
180
|
+
pytest.fail(
|
|
181
|
+
f"\n\nFound {len(violations)} files still importing from 'shared':\n\n" +
|
|
182
|
+
"\n".join(violations[:10]) +
|
|
183
|
+
(f"\n\n... and {len(violations) - 10} more" if len(violations) > 10 else "")
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _check_file_for_shared_imports(file_path: Path, violations: List[str]) -> None:
|
|
188
|
+
"""Check a file for shared imports."""
|
|
189
|
+
try:
|
|
190
|
+
content = file_path.read_text()
|
|
191
|
+
except Exception:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
patterns = [
|
|
195
|
+
r"from\s+['\"]@shared",
|
|
196
|
+
r"from\s+['\"]\.\.?/shared",
|
|
197
|
+
r"import\s+.*from\s+['\"]@shared",
|
|
198
|
+
r"import\s+.*from\s+['\"]\.\.?/shared",
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
for pattern in patterns:
|
|
202
|
+
if re.search(pattern, content):
|
|
203
|
+
rel_path = file_path.relative_to(REPO_ROOT)
|
|
204
|
+
violations.append(f" - {rel_path}")
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.mark.coder
|
|
209
|
+
def test_frontend_domain_no_framework_imports():
|
|
210
|
+
"""
|
|
211
|
+
SPEC-CODER-COMMONS-0006: Frontend domain layer has no framework imports.
|
|
212
|
+
|
|
213
|
+
GIVEN: Files in web/src/commons/domain/
|
|
214
|
+
WHEN: Checking import statements
|
|
215
|
+
THEN: No preact, react, or @tanstack imports
|
|
216
|
+
|
|
217
|
+
Validates: Domain layer purity (no preact, react, @tanstack)
|
|
218
|
+
"""
|
|
219
|
+
domain_dir = WEB_COMMONS / "domain"
|
|
220
|
+
|
|
221
|
+
if not domain_dir.exists():
|
|
222
|
+
pytest.skip("web/src/commons/domain does not exist")
|
|
223
|
+
|
|
224
|
+
forbidden = ["preact", "react", "@tanstack", "@maintain-ux"]
|
|
225
|
+
violations: List[str] = []
|
|
226
|
+
|
|
227
|
+
for ts_file in domain_dir.rglob("*.ts"):
|
|
228
|
+
try:
|
|
229
|
+
content = ts_file.read_text()
|
|
230
|
+
except Exception:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
for forbidden_import in forbidden:
|
|
234
|
+
if f"from '{forbidden_import}" in content or f'from "{forbidden_import}' in content:
|
|
235
|
+
rel_path = ts_file.relative_to(REPO_ROOT)
|
|
236
|
+
violations.append(f" - {rel_path}: imports {forbidden_import}")
|
|
237
|
+
|
|
238
|
+
if violations:
|
|
239
|
+
pytest.fail(
|
|
240
|
+
f"\n\nFrontend domain layer should be framework-agnostic:\n" +
|
|
241
|
+
"\n".join(violations)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@pytest.mark.coder
|
|
246
|
+
def test_frontend_commons_has_index_files():
|
|
247
|
+
"""
|
|
248
|
+
SPEC-CODER-COMMONS-0007: Frontend commons has proper barrel exports.
|
|
249
|
+
|
|
250
|
+
GIVEN: web/src/commons directory
|
|
251
|
+
WHEN: Checking for index.ts files
|
|
252
|
+
THEN: Root and each layer has index.ts
|
|
253
|
+
|
|
254
|
+
Validates: Public API structure
|
|
255
|
+
"""
|
|
256
|
+
if not WEB_COMMONS.exists():
|
|
257
|
+
pytest.skip("web/src/commons does not exist")
|
|
258
|
+
|
|
259
|
+
expected_index_files = [
|
|
260
|
+
WEB_COMMONS / "index.ts",
|
|
261
|
+
WEB_COMMONS / "domain" / "index.ts",
|
|
262
|
+
WEB_COMMONS / "application" / "index.ts",
|
|
263
|
+
WEB_COMMONS / "integration" / "index.ts",
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
missing = [f for f in expected_index_files if not f.exists()]
|
|
267
|
+
|
|
268
|
+
if missing:
|
|
269
|
+
pytest.fail(
|
|
270
|
+
f"\n\nMissing index.ts barrel exports:\n" +
|
|
271
|
+
"\n".join(f" - {f.relative_to(REPO_ROOT)}" for f in missing)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ============================================================================
|
|
276
|
+
# PYTHON STRUCTURE VALIDATION (Feature-First)
|
|
277
|
+
# ============================================================================
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@pytest.mark.coder
|
|
281
|
+
def test_python_commons_has_init_files():
|
|
282
|
+
"""
|
|
283
|
+
SPEC-CODER-COMMONS-0008: Python commons has __init__.py files.
|
|
284
|
+
|
|
285
|
+
GIVEN: python/commons directory
|
|
286
|
+
WHEN: Checking for __init__.py files
|
|
287
|
+
THEN: Root has __init__.py
|
|
288
|
+
|
|
289
|
+
Validates: Python package structure
|
|
290
|
+
"""
|
|
291
|
+
if not PYTHON_COMMONS.exists():
|
|
292
|
+
pytest.skip("python/commons does not exist")
|
|
293
|
+
|
|
294
|
+
init_file = PYTHON_COMMONS / "__init__.py"
|
|
295
|
+
|
|
296
|
+
if not init_file.exists():
|
|
297
|
+
pytest.fail(
|
|
298
|
+
f"\n\nMissing __init__.py in python/commons/\n"
|
|
299
|
+
f"Expected: {init_file.relative_to(REPO_ROOT)}"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@pytest.mark.coder
|
|
304
|
+
def test_python_events_has_internal_layer_structure():
|
|
305
|
+
"""
|
|
306
|
+
SPEC-CODER-COMMONS-0009: Python events feature has internal layer structure.
|
|
307
|
+
|
|
308
|
+
GIVEN: python/commons/events directory
|
|
309
|
+
WHEN: Checking for internal layers
|
|
310
|
+
THEN: events/src/application/ports/ and events/src/integration/ exist
|
|
311
|
+
|
|
312
|
+
Validates: Feature-first pattern for complex features
|
|
313
|
+
"""
|
|
314
|
+
events_dir = PYTHON_COMMONS / "events"
|
|
315
|
+
|
|
316
|
+
if not events_dir.exists():
|
|
317
|
+
pytest.skip("python/commons/events does not exist")
|
|
318
|
+
|
|
319
|
+
expected_paths = [
|
|
320
|
+
events_dir / "src" / "application" / "ports",
|
|
321
|
+
events_dir / "src" / "integration",
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
missing = [p for p in expected_paths if not p.exists()]
|
|
325
|
+
|
|
326
|
+
if missing:
|
|
327
|
+
pytest.fail(
|
|
328
|
+
f"\n\nPython events feature missing internal layer structure:\n" +
|
|
329
|
+
"\n".join(f" - {p.relative_to(REPO_ROOT)}" for p in missing) +
|
|
330
|
+
f"\n\nExpected structure (feature-first with internal layers):\n" +
|
|
331
|
+
" python/commons/events/\n" +
|
|
332
|
+
" +-- src/\n" +
|
|
333
|
+
" +-- application/ports/ # EventBusPort\n" +
|
|
334
|
+
" +-- integration/ # Queues, adapters"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@pytest.mark.coder
|
|
339
|
+
def test_python_resilience_is_flat():
|
|
340
|
+
"""
|
|
341
|
+
SPEC-CODER-COMMONS-0010: Python resilience is flat (no internal layers).
|
|
342
|
+
|
|
343
|
+
GIVEN: python/commons/resilience directory
|
|
344
|
+
WHEN: Checking structure
|
|
345
|
+
THEN: Contains .py files directly, no src/application/integration subdirs
|
|
346
|
+
|
|
347
|
+
Validates: Flat structure for simple utilities
|
|
348
|
+
"""
|
|
349
|
+
resilience_dir = PYTHON_COMMONS / "resilience"
|
|
350
|
+
|
|
351
|
+
if not resilience_dir.exists():
|
|
352
|
+
pytest.skip("python/commons/resilience does not exist")
|
|
353
|
+
|
|
354
|
+
# Check that utility files exist at root
|
|
355
|
+
expected_files = ["retry.py", "circuit_breaker.py"]
|
|
356
|
+
missing_files = [f for f in expected_files if not (resilience_dir / f).exists()]
|
|
357
|
+
|
|
358
|
+
# Check that no unnecessary layer subdirs exist
|
|
359
|
+
unnecessary_subdirs = []
|
|
360
|
+
for subdir in ["src", "application", "integration", "domain"]:
|
|
361
|
+
if (resilience_dir / subdir).exists():
|
|
362
|
+
unnecessary_subdirs.append(subdir)
|
|
363
|
+
|
|
364
|
+
issues = []
|
|
365
|
+
|
|
366
|
+
if missing_files:
|
|
367
|
+
issues.append(
|
|
368
|
+
f"Missing utility files: {', '.join(missing_files)}"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if unnecessary_subdirs:
|
|
372
|
+
issues.append(
|
|
373
|
+
f"Unnecessary layer subdirs (resilience should be flat): {', '.join(unnecessary_subdirs)}"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if issues:
|
|
377
|
+
pytest.fail(
|
|
378
|
+
f"\n\nPython resilience structure issues:\n" +
|
|
379
|
+
"\n".join(f" - {i}" for i in issues) +
|
|
380
|
+
f"\n\nExpected structure (flat for simple utilities):\n" +
|
|
381
|
+
" python/commons/resilience/\n" +
|
|
382
|
+
" +-- __init__.py\n" +
|
|
383
|
+
" +-- retry.py\n" +
|
|
384
|
+
" +-- circuit_breaker.py"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@pytest.mark.coder
|
|
389
|
+
def test_python_domain_no_framework_imports():
|
|
390
|
+
"""
|
|
391
|
+
SPEC-CODER-COMMONS-0011: Python domain layer has no framework imports.
|
|
392
|
+
|
|
393
|
+
GIVEN: Files in python/commons/domain/ and python/commons/validation.py
|
|
394
|
+
WHEN: Checking import statements
|
|
395
|
+
THEN: No flask, fastapi, or django imports
|
|
396
|
+
|
|
397
|
+
Validates: Domain layer purity (no flask, fastapi, django)
|
|
398
|
+
"""
|
|
399
|
+
if not PYTHON_COMMONS.exists():
|
|
400
|
+
pytest.skip("python/commons does not exist")
|
|
401
|
+
|
|
402
|
+
forbidden = ["flask", "fastapi", "django", "sqlalchemy", "requests"]
|
|
403
|
+
violations: List[str] = []
|
|
404
|
+
|
|
405
|
+
# Check domain directory
|
|
406
|
+
domain_dir = PYTHON_COMMONS / "domain"
|
|
407
|
+
if domain_dir.exists():
|
|
408
|
+
for py_file in domain_dir.rglob("*.py"):
|
|
409
|
+
_check_python_file_for_framework_imports(py_file, forbidden, violations)
|
|
410
|
+
|
|
411
|
+
# Check validation.py (also domain layer)
|
|
412
|
+
validation_file = PYTHON_COMMONS / "validation.py"
|
|
413
|
+
if validation_file.exists():
|
|
414
|
+
_check_python_file_for_framework_imports(validation_file, forbidden, violations)
|
|
415
|
+
|
|
416
|
+
if violations:
|
|
417
|
+
pytest.fail(
|
|
418
|
+
f"\n\nPython domain layer should be framework-agnostic:\n" +
|
|
419
|
+
"\n".join(violations)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _check_python_file_for_framework_imports(
|
|
424
|
+
file_path: Path, forbidden: List[str], violations: List[str]
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Check a Python file for framework imports."""
|
|
427
|
+
try:
|
|
428
|
+
content = file_path.read_text()
|
|
429
|
+
except Exception:
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
for forbidden_import in forbidden:
|
|
433
|
+
patterns = [
|
|
434
|
+
rf"^import\s+{forbidden_import}",
|
|
435
|
+
rf"^from\s+{forbidden_import}",
|
|
436
|
+
]
|
|
437
|
+
for pattern in patterns:
|
|
438
|
+
if re.search(pattern, content, re.MULTILINE):
|
|
439
|
+
rel_path = file_path.relative_to(REPO_ROOT)
|
|
440
|
+
violations.append(f" - {rel_path}: imports {forbidden_import}")
|
|
441
|
+
break
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# ============================================================================
|
|
445
|
+
# STRUCTURAL PATTERN DOCUMENTATION
|
|
446
|
+
# ============================================================================
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@pytest.mark.coder
|
|
450
|
+
def test_structural_patterns_documented():
|
|
451
|
+
"""
|
|
452
|
+
SPEC-CODER-COMMONS-0012: Structural patterns are documented in convention.
|
|
453
|
+
|
|
454
|
+
GIVEN: commons.convention.yaml
|
|
455
|
+
WHEN: Checking for structure_patterns section
|
|
456
|
+
THEN: Both python (feature-first) and frontend (layer-first) patterns documented
|
|
457
|
+
|
|
458
|
+
Validates: Convention documents intentional divergence
|
|
459
|
+
"""
|
|
460
|
+
convention_file = REPO_ROOT / "atdd" / "coder" / "conventions" / "commons.convention.yaml"
|
|
461
|
+
|
|
462
|
+
if not convention_file.exists():
|
|
463
|
+
pytest.fail(
|
|
464
|
+
f"\n\nMissing convention file:\n"
|
|
465
|
+
f" - {convention_file.relative_to(REPO_ROOT)}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
content = convention_file.read_text()
|
|
469
|
+
|
|
470
|
+
required_sections = [
|
|
471
|
+
"structure_patterns:",
|
|
472
|
+
"feature-first",
|
|
473
|
+
"layer-first",
|
|
474
|
+
"divergence_rationale:",
|
|
475
|
+
]
|
|
476
|
+
|
|
477
|
+
missing = [s for s in required_sections if s not in content]
|
|
478
|
+
|
|
479
|
+
if missing:
|
|
480
|
+
pytest.fail(
|
|
481
|
+
f"\n\nConvention file missing structural pattern documentation:\n" +
|
|
482
|
+
"\n".join(f" - {s}" for s in missing) +
|
|
483
|
+
f"\n\nThe intentional divergence between Python (feature-first) and\n"
|
|
484
|
+
f"Frontend (layer-first) should be documented in commons.convention.yaml"
|
|
485
|
+
)
|