atdd 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atdd/__init__.py +6 -0
- atdd/__main__.py +4 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.2.1.dist-info/METADATA +221 -0
- atdd-0.2.1.dist-info/RECORD +184 -0
- atdd-0.2.1.dist-info/WHEEL +5 -0
- atdd-0.2.1.dist-info/entry_points.txt +2 -0
- atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
- atdd-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test cross-language consistency between Python, Dart, and TypeScript.
|
|
3
|
+
|
|
4
|
+
Validates:
|
|
5
|
+
- Entities defined consistently across languages
|
|
6
|
+
- Enums match across languages
|
|
7
|
+
- Value object structures align
|
|
8
|
+
- API contracts are honored
|
|
9
|
+
|
|
10
|
+
Inspired by: .claude/utils/coder/ (multiple utilities)
|
|
11
|
+
But: Self-contained, no utility dependencies
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
import re
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict, List, Set
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Path constants
|
|
22
|
+
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
23
|
+
PYTHON_DIR = REPO_ROOT / "python"
|
|
24
|
+
LIB_DIR = REPO_ROOT / "lib"
|
|
25
|
+
SUPABASE_DIR = REPO_ROOT / "supabase"
|
|
26
|
+
CONTRACTS_DIR = REPO_ROOT / "contracts"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def extract_python_classes() -> Dict[str, Dict]:
|
|
30
|
+
"""
|
|
31
|
+
Extract class definitions from Python code.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dict mapping class name to metadata
|
|
35
|
+
"""
|
|
36
|
+
if not PYTHON_DIR.exists():
|
|
37
|
+
return {}
|
|
38
|
+
|
|
39
|
+
classes = {}
|
|
40
|
+
|
|
41
|
+
for py_file in PYTHON_DIR.rglob("*.py"):
|
|
42
|
+
if '/test/' in str(py_file):
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
with open(py_file, 'r', encoding='utf-8') as f:
|
|
47
|
+
content = f.read()
|
|
48
|
+
except Exception:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
# Find class definitions
|
|
52
|
+
class_pattern = r'class\s+([A-Z][a-zA-Z0-9_]*)\s*[:\(]'
|
|
53
|
+
for match in re.finditer(class_pattern, content):
|
|
54
|
+
class_name = match.group(1)
|
|
55
|
+
classes[class_name] = {
|
|
56
|
+
'file': str(py_file.relative_to(REPO_ROOT)),
|
|
57
|
+
'language': 'python'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return classes
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def extract_dart_classes() -> Dict[str, Dict]:
|
|
64
|
+
"""
|
|
65
|
+
Extract class definitions from Dart code.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict mapping class name to metadata
|
|
69
|
+
"""
|
|
70
|
+
if not LIB_DIR.exists():
|
|
71
|
+
return {}
|
|
72
|
+
|
|
73
|
+
classes = {}
|
|
74
|
+
|
|
75
|
+
for dart_file in LIB_DIR.rglob("*.dart"):
|
|
76
|
+
if dart_file.name.endswith('_test.dart'):
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
with open(dart_file, 'r', encoding='utf-8') as f:
|
|
81
|
+
content = f.read()
|
|
82
|
+
except Exception:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
# Find class definitions
|
|
86
|
+
class_pattern = r'class\s+([A-Z][a-zA-Z0-9_]*)\s*[{\s]'
|
|
87
|
+
for match in re.finditer(class_pattern, content):
|
|
88
|
+
class_name = match.group(1)
|
|
89
|
+
classes[class_name] = {
|
|
90
|
+
'file': str(dart_file.relative_to(REPO_ROOT)),
|
|
91
|
+
'language': 'dart'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return classes
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def extract_python_enums() -> Dict[str, Set[str]]:
|
|
98
|
+
"""
|
|
99
|
+
Extract enum definitions from Python code.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dict mapping enum name to set of values
|
|
103
|
+
"""
|
|
104
|
+
if not PYTHON_DIR.exists():
|
|
105
|
+
return {}
|
|
106
|
+
|
|
107
|
+
enums = {}
|
|
108
|
+
|
|
109
|
+
for py_file in PYTHON_DIR.rglob("*.py"):
|
|
110
|
+
if '/test/' in str(py_file):
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
with open(py_file, 'r', encoding='utf-8') as f:
|
|
115
|
+
content = f.read()
|
|
116
|
+
except Exception:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# Find enum definitions: class XxxEnum(Enum)
|
|
120
|
+
enum_pattern = r'class\s+([A-Z][a-zA-Z0-9_]*)\s*\(\s*Enum\s*\):'
|
|
121
|
+
for match in re.finditer(enum_pattern, content):
|
|
122
|
+
enum_name = match.group(1)
|
|
123
|
+
|
|
124
|
+
# Extract enum values (simplified)
|
|
125
|
+
# Pattern: NAME = value
|
|
126
|
+
value_pattern = r'^\s+([A-Z_]+)\s*='
|
|
127
|
+
values = set()
|
|
128
|
+
for line in content.split('\n'):
|
|
129
|
+
val_match = re.match(value_pattern, line)
|
|
130
|
+
if val_match:
|
|
131
|
+
values.add(val_match.group(1))
|
|
132
|
+
|
|
133
|
+
if values:
|
|
134
|
+
enums[enum_name] = values
|
|
135
|
+
|
|
136
|
+
return enums
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def extract_dart_enums() -> Dict[str, Set[str]]:
|
|
140
|
+
"""
|
|
141
|
+
Extract enum definitions from Dart code.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dict mapping enum name to set of values
|
|
145
|
+
"""
|
|
146
|
+
if not LIB_DIR.exists():
|
|
147
|
+
return {}
|
|
148
|
+
|
|
149
|
+
enums = {}
|
|
150
|
+
|
|
151
|
+
for dart_file in LIB_DIR.rglob("*.dart"):
|
|
152
|
+
if dart_file.name.endswith('_test.dart'):
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
with open(dart_file, 'r', encoding='utf-8') as f:
|
|
157
|
+
content = f.read()
|
|
158
|
+
except Exception:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# Find enum definitions: enum XxxEnum { ... }
|
|
162
|
+
enum_pattern = r'enum\s+([A-Z][a-zA-Z0-9_]*)\s*\{([^}]+)\}'
|
|
163
|
+
for match in re.finditer(enum_pattern, content):
|
|
164
|
+
enum_name = match.group(1)
|
|
165
|
+
enum_body = match.group(2)
|
|
166
|
+
|
|
167
|
+
# Extract values
|
|
168
|
+
values = set()
|
|
169
|
+
for value in enum_body.split(','):
|
|
170
|
+
val = value.strip()
|
|
171
|
+
if val and not val.startswith('//'):
|
|
172
|
+
# Remove any comments
|
|
173
|
+
val = val.split('//')[0].strip()
|
|
174
|
+
if val:
|
|
175
|
+
values.add(val)
|
|
176
|
+
|
|
177
|
+
if values:
|
|
178
|
+
enums[enum_name] = values
|
|
179
|
+
|
|
180
|
+
return enums
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def find_contract_entities() -> Dict[str, Set[str]]:
|
|
184
|
+
"""
|
|
185
|
+
Extract entities from contract schemas.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dict mapping entity name to set of fields
|
|
189
|
+
"""
|
|
190
|
+
if not CONTRACTS_DIR.exists():
|
|
191
|
+
return {}
|
|
192
|
+
|
|
193
|
+
entities = {}
|
|
194
|
+
|
|
195
|
+
for schema_file in CONTRACTS_DIR.rglob("*.schema.json"):
|
|
196
|
+
try:
|
|
197
|
+
with open(schema_file, 'r', encoding='utf-8') as f:
|
|
198
|
+
schema = json.load(f)
|
|
199
|
+
except Exception:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Extract entity name from $id
|
|
203
|
+
schema_id = schema.get('$id', '')
|
|
204
|
+
if ':' in schema_id:
|
|
205
|
+
entity_name = schema_id.split(':')[-1].replace('.', '_')
|
|
206
|
+
else:
|
|
207
|
+
entity_name = schema_file.stem
|
|
208
|
+
|
|
209
|
+
# Extract required fields
|
|
210
|
+
required = set(schema.get('required', []))
|
|
211
|
+
properties = set(schema.get('properties', {}).keys())
|
|
212
|
+
|
|
213
|
+
entities[entity_name] = required if required else properties
|
|
214
|
+
|
|
215
|
+
return entities
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@pytest.mark.coder
|
|
219
|
+
def test_entity_classes_exist_across_languages():
|
|
220
|
+
"""
|
|
221
|
+
SPEC-CODER-CONSISTENCY-0001: Core entities exist in all languages.
|
|
222
|
+
|
|
223
|
+
For polyglot codebases, core domain entities should exist in all languages.
|
|
224
|
+
|
|
225
|
+
Given: Entity classes in Python, Dart, contracts
|
|
226
|
+
When: Comparing entity names
|
|
227
|
+
Then: Core entities exist across languages
|
|
228
|
+
"""
|
|
229
|
+
python_classes = extract_python_classes()
|
|
230
|
+
dart_classes = extract_dart_classes()
|
|
231
|
+
contract_entities = find_contract_entities()
|
|
232
|
+
|
|
233
|
+
if not python_classes and not dart_classes:
|
|
234
|
+
pytest.skip("No classes found")
|
|
235
|
+
|
|
236
|
+
if not contract_entities:
|
|
237
|
+
pytest.skip("No contract entities found")
|
|
238
|
+
|
|
239
|
+
# Check if contract entities have implementations
|
|
240
|
+
missing_implementations = []
|
|
241
|
+
|
|
242
|
+
for entity_name, fields in contract_entities.items():
|
|
243
|
+
# Normalize name (PascalCase)
|
|
244
|
+
normalized = ''.join(word.capitalize() for word in entity_name.split('_'))
|
|
245
|
+
|
|
246
|
+
# Check Python
|
|
247
|
+
has_python = normalized in python_classes or entity_name in python_classes
|
|
248
|
+
# Check Dart
|
|
249
|
+
has_dart = normalized in dart_classes or entity_name in dart_classes
|
|
250
|
+
|
|
251
|
+
if not has_python and not has_dart:
|
|
252
|
+
missing_implementations.append(
|
|
253
|
+
f"Contract entity: {entity_name}\\n"
|
|
254
|
+
f" Fields: {', '.join(list(fields)[:5])}\\n"
|
|
255
|
+
f" Missing in: Python AND Dart"
|
|
256
|
+
)
|
|
257
|
+
elif not has_python:
|
|
258
|
+
missing_implementations.append(
|
|
259
|
+
f"Contract entity: {entity_name}\\n"
|
|
260
|
+
f" Missing in: Python"
|
|
261
|
+
)
|
|
262
|
+
elif not has_dart:
|
|
263
|
+
missing_implementations.append(
|
|
264
|
+
f"Contract entity: {entity_name}\\n"
|
|
265
|
+
f" Missing in: Dart"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if missing_implementations:
|
|
269
|
+
pytest.fail(
|
|
270
|
+
f"\\n\\nFound {len(missing_implementations)} contract entities without implementations:\\n\\n" +
|
|
271
|
+
"\\n\\n".join(missing_implementations[:10]) +
|
|
272
|
+
(f"\\n\\n... and {len(missing_implementations) - 10} more" if len(missing_implementations) > 10 else "")
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@pytest.mark.coder
|
|
277
|
+
def test_enums_match_across_languages():
|
|
278
|
+
"""
|
|
279
|
+
SPEC-CODER-CONSISTENCY-0002: Enums match across languages.
|
|
280
|
+
|
|
281
|
+
Enums should have same values in all languages.
|
|
282
|
+
|
|
283
|
+
Given: Enum definitions in Python and Dart
|
|
284
|
+
When: Comparing enum values
|
|
285
|
+
Then: Enums with same name have same values
|
|
286
|
+
"""
|
|
287
|
+
python_enums = extract_python_enums()
|
|
288
|
+
dart_enums = extract_dart_enums()
|
|
289
|
+
|
|
290
|
+
if not python_enums and not dart_enums:
|
|
291
|
+
pytest.skip("No enums found")
|
|
292
|
+
|
|
293
|
+
mismatches = []
|
|
294
|
+
|
|
295
|
+
# Find enums with same name
|
|
296
|
+
for enum_name in set(python_enums.keys()) & set(dart_enums.keys()):
|
|
297
|
+
python_values = python_enums[enum_name]
|
|
298
|
+
dart_values = dart_enums[enum_name]
|
|
299
|
+
|
|
300
|
+
# Compare (case-insensitive)
|
|
301
|
+
python_lower = {v.lower() for v in python_values}
|
|
302
|
+
dart_lower = {v.lower() for v in dart_values}
|
|
303
|
+
|
|
304
|
+
if python_lower != dart_lower:
|
|
305
|
+
only_python = python_lower - dart_lower
|
|
306
|
+
only_dart = dart_lower - python_lower
|
|
307
|
+
|
|
308
|
+
mismatches.append(
|
|
309
|
+
f"Enum: {enum_name}\\n"
|
|
310
|
+
f" Only in Python: {', '.join(only_python) if only_python else 'none'}\\n"
|
|
311
|
+
f" Only in Dart: {', '.join(only_dart) if only_dart else 'none'}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if mismatches:
|
|
315
|
+
pytest.fail(
|
|
316
|
+
f"\\n\\nFound {len(mismatches)} enum mismatches:\\n\\n" +
|
|
317
|
+
"\\n\\n".join(mismatches)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@pytest.mark.coder
|
|
322
|
+
def test_naming_conventions_consistent():
|
|
323
|
+
"""
|
|
324
|
+
SPEC-CODER-CONSISTENCY-0003: Naming conventions are consistent.
|
|
325
|
+
|
|
326
|
+
Similar concepts should use similar names across languages.
|
|
327
|
+
|
|
328
|
+
Given: Class names in all languages
|
|
329
|
+
When: Comparing patterns
|
|
330
|
+
Then: Consistent naming (e.g., XxxEntity vs Xxx)
|
|
331
|
+
"""
|
|
332
|
+
python_classes = extract_python_classes()
|
|
333
|
+
dart_classes = extract_dart_classes()
|
|
334
|
+
|
|
335
|
+
if not python_classes or not dart_classes:
|
|
336
|
+
pytest.skip("Need classes in multiple languages")
|
|
337
|
+
|
|
338
|
+
# Check for common suffixes
|
|
339
|
+
python_suffixes = {}
|
|
340
|
+
dart_suffixes = {}
|
|
341
|
+
|
|
342
|
+
for name in python_classes.keys():
|
|
343
|
+
for suffix in ['Entity', 'Model', 'DTO', 'Service', 'Repository']:
|
|
344
|
+
if name.endswith(suffix):
|
|
345
|
+
base = name[:-len(suffix)]
|
|
346
|
+
python_suffixes.setdefault(suffix, set()).add(base)
|
|
347
|
+
|
|
348
|
+
for name in dart_classes.keys():
|
|
349
|
+
for suffix in ['Entity', 'Model', 'DTO', 'Service', 'Repository']:
|
|
350
|
+
if name.endswith(suffix):
|
|
351
|
+
base = name[:-len(suffix)]
|
|
352
|
+
dart_suffixes.setdefault(suffix, set()).add(base)
|
|
353
|
+
|
|
354
|
+
# Find inconsistencies
|
|
355
|
+
inconsistencies = []
|
|
356
|
+
|
|
357
|
+
for suffix in set(python_suffixes.keys()) | set(dart_suffixes.keys()):
|
|
358
|
+
python_bases = python_suffixes.get(suffix, set())
|
|
359
|
+
dart_bases = dart_suffixes.get(suffix, set())
|
|
360
|
+
|
|
361
|
+
# Find classes that use different suffixes
|
|
362
|
+
common_bases = python_bases & dart_bases
|
|
363
|
+
|
|
364
|
+
for base in common_bases:
|
|
365
|
+
# If same base exists with this suffix in both, good
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
# Check if base exists with different suffix
|
|
369
|
+
for base in python_bases:
|
|
370
|
+
# Check if Dart has same base with different suffix
|
|
371
|
+
for dart_suffix in dart_suffixes.keys():
|
|
372
|
+
if suffix != dart_suffix and base in dart_suffixes[dart_suffix]:
|
|
373
|
+
inconsistencies.append(
|
|
374
|
+
f"Base class: {base}\\n"
|
|
375
|
+
f" Python uses: {suffix}\\n"
|
|
376
|
+
f" Dart uses: {dart_suffix}"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if inconsistencies:
|
|
380
|
+
pytest.fail(
|
|
381
|
+
f"\\n\\nFound {len(inconsistencies)} naming inconsistencies:\\n\\n" +
|
|
382
|
+
"\\n\\n".join(inconsistencies[:10]) +
|
|
383
|
+
(f"\\n\\n... and {len(inconsistencies) - 10} more" if len(inconsistencies) > 10 else "")
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@pytest.mark.coder
|
|
388
|
+
def test_api_contracts_honored_across_languages():
|
|
389
|
+
"""
|
|
390
|
+
SPEC-CODER-CONSISTENCY-0004: API contracts honored in all implementations.
|
|
391
|
+
|
|
392
|
+
Contract schemas define API structure.
|
|
393
|
+
All language implementations should follow them.
|
|
394
|
+
|
|
395
|
+
Given: Contract schemas
|
|
396
|
+
When: Checking for matching entities
|
|
397
|
+
Then: Each language has entity matching contract
|
|
398
|
+
"""
|
|
399
|
+
contract_entities = find_contract_entities()
|
|
400
|
+
python_classes = extract_python_classes()
|
|
401
|
+
dart_classes = extract_dart_classes()
|
|
402
|
+
|
|
403
|
+
if not contract_entities:
|
|
404
|
+
pytest.skip("No contracts found")
|
|
405
|
+
|
|
406
|
+
# For each contract, check if at least one language implements it
|
|
407
|
+
unimplemented = []
|
|
408
|
+
|
|
409
|
+
for entity_name, fields in contract_entities.items():
|
|
410
|
+
normalized = ''.join(word.capitalize() for word in entity_name.split('_'))
|
|
411
|
+
|
|
412
|
+
has_any_impl = (
|
|
413
|
+
normalized in python_classes or
|
|
414
|
+
entity_name in python_classes or
|
|
415
|
+
normalized in dart_classes or
|
|
416
|
+
entity_name in dart_classes
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
if not has_any_impl:
|
|
420
|
+
unimplemented.append(
|
|
421
|
+
f"Contract: {entity_name}\\n"
|
|
422
|
+
f" Fields: {', '.join(list(fields)[:5])}\\n"
|
|
423
|
+
f" No implementations found"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if unimplemented:
|
|
427
|
+
pytest.fail(
|
|
428
|
+
f"\\n\\nFound {len(unimplemented)} unimplemented contracts:\\n\\n" +
|
|
429
|
+
"\\n\\n".join(unimplemented[:10]) +
|
|
430
|
+
(f"\\n\\n... and {len(unimplemented) - 10} more" if len(unimplemented) > 10 else "")
|
|
431
|
+
)
|