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,276 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test migration criteria decision algorithm.
|
|
4
|
+
|
|
5
|
+
SPEC-TESTER-CONV-0034 through SPEC-TESTER-CONV-0043
|
|
6
|
+
|
|
7
|
+
Validates the contract_needs_migration() function correctly applies
|
|
8
|
+
all decision rules in the proper order.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import pytest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from tempfile import NamedTemporaryFile
|
|
15
|
+
|
|
16
|
+
# pytest pythonpath = . handles imports from repo root
|
|
17
|
+
|
|
18
|
+
from atdd.coach.commands.migration import contract_needs_migration
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_test_contract(properties: dict, metadata: dict = None, description: str = "", aspect: str = "test") -> Path:
|
|
22
|
+
"""Create a temporary contract file for testing."""
|
|
23
|
+
contract = {
|
|
24
|
+
"$id": f"urn:contract:test:domain:{aspect}",
|
|
25
|
+
"properties": properties,
|
|
26
|
+
"description": description
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if metadata:
|
|
30
|
+
contract["x-artifact-metadata"] = metadata
|
|
31
|
+
|
|
32
|
+
# Create temp file with proper naming: {aspect}.schema.json
|
|
33
|
+
temp = NamedTemporaryFile(mode='w', suffix=f'{aspect}.schema.json', delete=False)
|
|
34
|
+
json.dump(contract, temp, indent=2)
|
|
35
|
+
temp.close()
|
|
36
|
+
|
|
37
|
+
return Path(temp.name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# SPEC-TESTER-CONV-0034
|
|
41
|
+
def test_entity_with_id_requires_migration():
|
|
42
|
+
"""Entity with id field requires migration."""
|
|
43
|
+
contract_path = create_test_contract(
|
|
44
|
+
properties={
|
|
45
|
+
"id": {"type": "string", "pattern": "^[0-9a-f]{8}"},
|
|
46
|
+
"player_id": {"type": "string"},
|
|
47
|
+
"status": {"type": "string"}
|
|
48
|
+
},
|
|
49
|
+
aspect="session"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
assert contract_needs_migration(contract_path) == True
|
|
54
|
+
finally:
|
|
55
|
+
contract_path.unlink()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# SPEC-TESTER-CONV-0035
|
|
59
|
+
def test_event_signals_no_migration():
|
|
60
|
+
"""Event signals (past tense, no id) do not require migration."""
|
|
61
|
+
# Test multiple event patterns
|
|
62
|
+
event_aspects = ["exhausted", "succeeded", "failed", "registered", "terminated"]
|
|
63
|
+
|
|
64
|
+
for aspect in event_aspects:
|
|
65
|
+
contract_path = create_test_contract(
|
|
66
|
+
properties={
|
|
67
|
+
"timestamp": {"type": "string", "format": "date-time"},
|
|
68
|
+
"player_id": {"type": "string"}
|
|
69
|
+
},
|
|
70
|
+
aspect=aspect
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
assert contract_needs_migration(contract_path) == False, f"{aspect} should not require migration"
|
|
75
|
+
finally:
|
|
76
|
+
contract_path.unlink()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# SPEC-TESTER-CONV-0036
|
|
80
|
+
def test_empty_contracts_no_migration():
|
|
81
|
+
"""Empty contracts (pure signals) do not require migration."""
|
|
82
|
+
contract_path = create_test_contract(
|
|
83
|
+
properties={},
|
|
84
|
+
aspect="exhausted"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
assert contract_needs_migration(contract_path) == False
|
|
89
|
+
finally:
|
|
90
|
+
contract_path.unlink()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# SPEC-TESTER-CONV-0037
|
|
94
|
+
def test_value_objects_no_migration():
|
|
95
|
+
"""Value objects without id do not require migration."""
|
|
96
|
+
# Test uuid value object
|
|
97
|
+
contract_path = create_test_contract(
|
|
98
|
+
properties={
|
|
99
|
+
"value": {"type": "string", "pattern": "^[0-9a-f]{8}"}
|
|
100
|
+
},
|
|
101
|
+
aspect="uuid"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
assert contract_needs_migration(contract_path) == False
|
|
106
|
+
finally:
|
|
107
|
+
contract_path.unlink()
|
|
108
|
+
|
|
109
|
+
# Test evaluation-score value object
|
|
110
|
+
contract_path = create_test_contract(
|
|
111
|
+
properties={
|
|
112
|
+
"score": {"type": "number"},
|
|
113
|
+
"confidence": {"type": "number"}
|
|
114
|
+
},
|
|
115
|
+
aspect="evaluation-score"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
assert contract_needs_migration(contract_path) == False
|
|
120
|
+
finally:
|
|
121
|
+
contract_path.unlink()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# SPEC-TESTER-CONV-0038
|
|
125
|
+
def test_internal_contracts_no_migration():
|
|
126
|
+
"""Internal contracts (transient DTOs) do not require migration."""
|
|
127
|
+
contract_path = create_test_contract(
|
|
128
|
+
properties={
|
|
129
|
+
"player_id": {"type": "string"},
|
|
130
|
+
"data": {"type": "object"}
|
|
131
|
+
},
|
|
132
|
+
metadata={"to": "internal"},
|
|
133
|
+
aspect="dto"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
assert contract_needs_migration(contract_path) == False
|
|
138
|
+
finally:
|
|
139
|
+
contract_path.unlink()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# SPEC-TESTER-CONV-0039
|
|
143
|
+
def test_explicit_persistent_flag():
|
|
144
|
+
"""Explicit persistence.strategy: jsonb overrides all heuristics."""
|
|
145
|
+
# Event pattern BUT persistence.strategy: jsonb → needs migration
|
|
146
|
+
contract_path = create_test_contract(
|
|
147
|
+
properties={
|
|
148
|
+
"timestamp": {"type": "string"}
|
|
149
|
+
},
|
|
150
|
+
metadata={"persistence": {"strategy": "jsonb"}},
|
|
151
|
+
aspect="completed" # Event pattern
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
assert contract_needs_migration(contract_path) == True
|
|
156
|
+
finally:
|
|
157
|
+
contract_path.unlink()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# SPEC-TESTER-CONV-0040
|
|
161
|
+
def test_explicit_non_persistent_flag():
|
|
162
|
+
"""Explicit persistence.strategy: none prevents migration."""
|
|
163
|
+
# Has id BUT persistence.strategy: none → no migration
|
|
164
|
+
contract_path = create_test_contract(
|
|
165
|
+
properties={
|
|
166
|
+
"id": {"type": "string"},
|
|
167
|
+
"data": {"type": "object"}
|
|
168
|
+
},
|
|
169
|
+
metadata={"persistence": {"strategy": "none"}},
|
|
170
|
+
aspect="entity"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
assert contract_needs_migration(contract_path) == False
|
|
175
|
+
finally:
|
|
176
|
+
contract_path.unlink()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# SPEC-TESTER-CONV-0041
|
|
180
|
+
def test_id_overrides_event_pattern():
|
|
181
|
+
"""Entity with id overrides event naming pattern."""
|
|
182
|
+
# Aspect is "paused" (event pattern) BUT has id → needs migration
|
|
183
|
+
contract_path = create_test_contract(
|
|
184
|
+
properties={
|
|
185
|
+
"id": {"type": "string"},
|
|
186
|
+
"session_id": {"type": "string"},
|
|
187
|
+
"paused_at": {"type": "string", "format": "date-time"}
|
|
188
|
+
},
|
|
189
|
+
aspect="paused"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
assert contract_needs_migration(contract_path) == True
|
|
194
|
+
finally:
|
|
195
|
+
contract_path.unlink()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# SPEC-TESTER-CONV-0042
|
|
199
|
+
def test_computed_aggregates_no_migration():
|
|
200
|
+
"""Computed aggregates without id do not require migration."""
|
|
201
|
+
computed_keywords = ["computed", "calculated", "derived", "aggregated"]
|
|
202
|
+
|
|
203
|
+
for keyword in computed_keywords:
|
|
204
|
+
contract_path = create_test_contract(
|
|
205
|
+
properties={
|
|
206
|
+
"total": {"type": "number"},
|
|
207
|
+
"count": {"type": "integer"}
|
|
208
|
+
},
|
|
209
|
+
description=f"This is a {keyword} result",
|
|
210
|
+
aspect="stats"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
assert contract_needs_migration(contract_path) == False, f"{keyword} should not require migration"
|
|
215
|
+
finally:
|
|
216
|
+
contract_path.unlink()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# SPEC-TESTER-CONV-0043
|
|
220
|
+
def test_conservative_default_external():
|
|
221
|
+
"""Conservative default: external contracts with properties need migration."""
|
|
222
|
+
contract_path = create_test_contract(
|
|
223
|
+
properties={
|
|
224
|
+
"player_id": {"type": "string"},
|
|
225
|
+
"score": {"type": "number"}
|
|
226
|
+
},
|
|
227
|
+
metadata={"to": "external"},
|
|
228
|
+
aspect="profile"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
assert contract_needs_migration(contract_path) == True
|
|
233
|
+
finally:
|
|
234
|
+
contract_path.unlink()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_decision_algorithm_order():
|
|
238
|
+
"""Test that decision rules are evaluated in correct order."""
|
|
239
|
+
# Rule 1 (explicit persistence.strategy) beats Rule 5 (has id)
|
|
240
|
+
contract_path = create_test_contract(
|
|
241
|
+
properties={"id": {"type": "string"}},
|
|
242
|
+
metadata={"persistence": {"strategy": "none"}},
|
|
243
|
+
aspect="entity"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
assert contract_needs_migration(contract_path) == False
|
|
248
|
+
finally:
|
|
249
|
+
contract_path.unlink()
|
|
250
|
+
|
|
251
|
+
# Rule 5 (has id) beats Rule 3 (event pattern)
|
|
252
|
+
contract_path = create_test_contract(
|
|
253
|
+
properties={"id": {"type": "string"}},
|
|
254
|
+
aspect="paused"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
assert contract_needs_migration(contract_path) == True
|
|
259
|
+
finally:
|
|
260
|
+
contract_path.unlink()
|
|
261
|
+
|
|
262
|
+
# Rule 2 (empty) beats everything except Rule 1
|
|
263
|
+
contract_path = create_test_contract(
|
|
264
|
+
properties={},
|
|
265
|
+
metadata={"to": "external"},
|
|
266
|
+
aspect="signal"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
assert contract_needs_migration(contract_path) == False
|
|
271
|
+
finally:
|
|
272
|
+
contract_path.unlink()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
if __name__ == "__main__":
|
|
276
|
+
pytest.main([__file__, "-v"])
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Platform tests: Migration generation from contracts.
|
|
3
|
+
|
|
4
|
+
SPEC-TESTER-CONV-0030: Generate migration template from contract schema
|
|
5
|
+
SPEC-TESTER-CONV-0033: Map JSON Schema types to PostgreSQL types
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
import json
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Path constants
|
|
13
|
+
REPO_ROOT = Path(__file__).resolve().parents[4]
|
|
14
|
+
CONTRACTS_DIR = REPO_ROOT / "contracts"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.platform
|
|
18
|
+
def test_generate_migration_from_contract():
|
|
19
|
+
"""
|
|
20
|
+
SPEC-TESTER-CONV-0030: Generate migration template from contract schema
|
|
21
|
+
|
|
22
|
+
Given: Contract schema at contracts/match/dilemma/current.schema.json
|
|
23
|
+
When: Running migration generator
|
|
24
|
+
Then: Migration file created with correct structure
|
|
25
|
+
Contains CREATE TABLE statement
|
|
26
|
+
Includes TODO markers for review
|
|
27
|
+
"""
|
|
28
|
+
# This test will pass once the generator is implemented
|
|
29
|
+
# For now, it documents the expected behavior
|
|
30
|
+
pytest.skip("Migration generator not yet implemented - RED test placeholder")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.platform
|
|
34
|
+
def test_table_naming_convention():
|
|
35
|
+
"""
|
|
36
|
+
SPEC-TESTER-CONV-0030: Table naming follows convention
|
|
37
|
+
|
|
38
|
+
Given: Contract at contracts/{theme}/{domain}/{aspect}.schema.json
|
|
39
|
+
When: Generating migration
|
|
40
|
+
Then: Table name is {theme}_{domain}_{aspect}
|
|
41
|
+
Uses snake_case
|
|
42
|
+
Matches PostgreSQL naming rules
|
|
43
|
+
"""
|
|
44
|
+
# Example test data
|
|
45
|
+
test_cases = [
|
|
46
|
+
{
|
|
47
|
+
"path": "contracts/match/dilemma/current.schema.json",
|
|
48
|
+
"expected_table": "match_dilemma_current"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "contracts/commons/ux/foundations.schema.json",
|
|
52
|
+
"expected_table": "commons_ux_foundations"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"path": "contracts/mechanic/timebank/remaining.schema.json",
|
|
56
|
+
"expected_table": "mechanic_timebank_remaining"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
for case in test_cases:
|
|
61
|
+
path = Path(case["path"])
|
|
62
|
+
parts = path.parts
|
|
63
|
+
|
|
64
|
+
theme = parts[1] # contracts/{theme}/...
|
|
65
|
+
domain = parts[2] # contracts/{theme}/{domain}/...
|
|
66
|
+
aspect = path.stem.replace(".schema", "")
|
|
67
|
+
|
|
68
|
+
actual_table = f"{theme}_{domain}_{aspect}"
|
|
69
|
+
|
|
70
|
+
assert actual_table == case["expected_table"], \
|
|
71
|
+
f"Table name mismatch for {case['path']}: expected {case['expected_table']}, got {actual_table}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.platform
|
|
75
|
+
def test_json_to_postgres_type_mapping():
|
|
76
|
+
"""
|
|
77
|
+
SPEC-TESTER-CONV-0033: Map JSON Schema types to PostgreSQL types
|
|
78
|
+
|
|
79
|
+
Given: Contract properties with various JSON Schema types
|
|
80
|
+
When: Mapping to PostgreSQL types
|
|
81
|
+
Then: Correct type mapping applied
|
|
82
|
+
string → TEXT
|
|
83
|
+
string with UUID pattern → UUID
|
|
84
|
+
string with date-time format → TIMESTAMPTZ
|
|
85
|
+
integer → INTEGER
|
|
86
|
+
number → NUMERIC
|
|
87
|
+
boolean → BOOLEAN
|
|
88
|
+
object → JSONB (with TODO)
|
|
89
|
+
array → JSONB (with TODO)
|
|
90
|
+
"""
|
|
91
|
+
# This will be implemented by the migration generator utility
|
|
92
|
+
# Testing the type mapping logic
|
|
93
|
+
|
|
94
|
+
type_mappings = {
|
|
95
|
+
# Primitives
|
|
96
|
+
("string", None, None): "TEXT",
|
|
97
|
+
("string", "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", None): "UUID",
|
|
98
|
+
("string", None, "date-time"): "TIMESTAMPTZ",
|
|
99
|
+
("string", None, "date"): "DATE",
|
|
100
|
+
("string", None, "email"): "TEXT",
|
|
101
|
+
("string", None, "uri"): "TEXT",
|
|
102
|
+
("integer", None, None): "INTEGER",
|
|
103
|
+
("number", None, None): "NUMERIC",
|
|
104
|
+
("boolean", None, None): "BOOLEAN",
|
|
105
|
+
|
|
106
|
+
# Complex (require review)
|
|
107
|
+
("object", None, None): "JSONB",
|
|
108
|
+
("array", None, None): "JSONB",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Verify mapping exists for common types
|
|
112
|
+
for (json_type, pattern, format_type), expected_pg_type in type_mappings.items():
|
|
113
|
+
# This will call the actual mapper once implemented
|
|
114
|
+
# For now, just verify the expected mappings are defined
|
|
115
|
+
assert expected_pg_type in ["TEXT", "UUID", "TIMESTAMPTZ", "DATE", "INTEGER", "NUMERIC", "BOOLEAN", "JSONB"], \
|
|
116
|
+
f"Invalid PostgreSQL type: {expected_pg_type}"
|