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,399 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPEC-COACH-UTILS-0294: Generate complete contract metadata from wagon and feature interfaces
|
|
3
|
+
SPEC-COACH-UTILS-0295: Validate and update existing contract metadata completeness
|
|
4
|
+
SPEC-COACH-UTILS-0296: Create placeholder test files for scaffolded contracts
|
|
5
|
+
|
|
6
|
+
Tests the contract scaffold generation automation that reads wagon/feature interfaces
|
|
7
|
+
and auto-generates contract metadata following artifact-naming.convention.yaml.
|
|
8
|
+
"""
|
|
9
|
+
import pytest
|
|
10
|
+
import yaml
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.platform
|
|
16
|
+
def test_scaffold_contract_metadata_from_wagon_and_feature_interfaces(tmp_path):
|
|
17
|
+
"""
|
|
18
|
+
SPEC-COACH-UTILS-0294: Generate complete contract metadata from wagon and feature interfaces
|
|
19
|
+
|
|
20
|
+
Given: A contract artifact URN following artifact-naming.convention.yaml
|
|
21
|
+
Wagon manifests exist at plan/*/_*.yaml with produce[] and consume[] arrays
|
|
22
|
+
Feature manifests exist at plan/{wagon}/features/*.yaml with produces[] arrays
|
|
23
|
+
Artifact URN uses colon for hierarchy and dot for variant
|
|
24
|
+
Contract may or may not exist at derived file path from artifact URN
|
|
25
|
+
When: Scaffolding contract metadata from wagon and feature interfaces
|
|
26
|
+
Then: Parse artifact URN according to convention pattern theme(category)*aspect(.variant)?
|
|
27
|
+
Split URN by colons and dots to extract theme hierarchy aspect and variant
|
|
28
|
+
Convert artifact URN to contract file path using convention mapping
|
|
29
|
+
Scan all wagon manifests produce[] to find producer wagon
|
|
30
|
+
Cross-check producer wagon features[] produces[] arrays match wagon produce[]
|
|
31
|
+
Scan all wagon manifests consume[] to find consumer wagons
|
|
32
|
+
Extract dependencies from producer wagon consume[] array
|
|
33
|
+
Infer API method from aspect and variant patterns
|
|
34
|
+
Generate API path by joining theme hierarchy aspect with slashes
|
|
35
|
+
Extract traceability wagon_ref from producer wagon file path
|
|
36
|
+
Extract traceability feature_refs from producer wagon features[] URNs
|
|
37
|
+
Generate testing directory path and placeholder test file name
|
|
38
|
+
Create full contract scaffold with x-artifact-metadata
|
|
39
|
+
"""
|
|
40
|
+
# Setup test directories
|
|
41
|
+
plan_dir = tmp_path / "plan"
|
|
42
|
+
plan_dir.mkdir()
|
|
43
|
+
contracts_dir = tmp_path / "contracts"
|
|
44
|
+
contracts_dir.mkdir()
|
|
45
|
+
convention_dir = tmp_path / ".claude" / "conventions" / "planner"
|
|
46
|
+
convention_dir.mkdir(parents=True)
|
|
47
|
+
|
|
48
|
+
# Create artifact naming convention file
|
|
49
|
+
convention_data = {
|
|
50
|
+
"version": "2.1",
|
|
51
|
+
"name": "Artifact Naming Convention",
|
|
52
|
+
"naming_pattern": {
|
|
53
|
+
"full_pattern": "{theme}(:{category})*:{aspect}(.{variant})?"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
with open(convention_dir / "artifact-naming.convention.yaml", 'w') as f:
|
|
57
|
+
yaml.dump(convention_data, f)
|
|
58
|
+
|
|
59
|
+
# Create producer wagon that produces mechanic:timebank.exhausted
|
|
60
|
+
burn_timebank_dir = plan_dir / "burn_timebank"
|
|
61
|
+
burn_timebank_dir.mkdir()
|
|
62
|
+
wagon_manifest = burn_timebank_dir / "_burn_timebank.yaml"
|
|
63
|
+
wagon_data = {
|
|
64
|
+
"wagon": "burn-timebank",
|
|
65
|
+
"theme": "mechanic",
|
|
66
|
+
"produce": [
|
|
67
|
+
{"name": "mechanic:timebank.exhausted", "contract": "contract:mechanic:timebank.exhausted"}
|
|
68
|
+
],
|
|
69
|
+
"consume": [
|
|
70
|
+
{"name": "contract:match:state.committed"}
|
|
71
|
+
],
|
|
72
|
+
"features": [
|
|
73
|
+
{"name": "feature:burn-timebank:exhaust-timer"}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
with open(wagon_manifest, 'w') as f:
|
|
77
|
+
yaml.dump(wagon_data, f, default_flow_style=False, sort_keys=False)
|
|
78
|
+
|
|
79
|
+
# Create feature manifest that matches wagon produce
|
|
80
|
+
features_dir = burn_timebank_dir / "features"
|
|
81
|
+
features_dir.mkdir()
|
|
82
|
+
feature_manifest = features_dir / "exhaust_timer.yaml"
|
|
83
|
+
feature_data = {
|
|
84
|
+
"urn": "feature:burn-timebank:exhaust-timer",
|
|
85
|
+
"feature": "exhaust-timer",
|
|
86
|
+
"description": "Exhaust timebank timer",
|
|
87
|
+
"produces": [
|
|
88
|
+
{"name": "mechanic:timebank.exhausted", "contract": "contract:mechanic:timebank.exhausted"}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
with open(feature_manifest, 'w') as f:
|
|
92
|
+
yaml.dump(feature_data, f, default_flow_style=False, sort_keys=False)
|
|
93
|
+
|
|
94
|
+
# Create consumer wagon
|
|
95
|
+
reveal_status_dir = plan_dir / "reveal_status"
|
|
96
|
+
reveal_status_dir.mkdir()
|
|
97
|
+
consumer_manifest = reveal_status_dir / "_reveal_status.yaml"
|
|
98
|
+
consumer_data = {
|
|
99
|
+
"wagon": "reveal-status",
|
|
100
|
+
"theme": "sensory",
|
|
101
|
+
"consume": [
|
|
102
|
+
{"name": "contract:mechanic:timebank.exhausted"}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
with open(consumer_manifest, 'w') as f:
|
|
106
|
+
yaml.dump(consumer_data, f, default_flow_style=False, sort_keys=False)
|
|
107
|
+
|
|
108
|
+
# Import the scaffold function
|
|
109
|
+
from atdd.coach.commands.interface import scaffold_contract_metadata
|
|
110
|
+
|
|
111
|
+
# Execute scaffold generation
|
|
112
|
+
artifact_urn = "mechanic:timebank.exhausted"
|
|
113
|
+
result = scaffold_contract_metadata(
|
|
114
|
+
artifact_urn=artifact_urn,
|
|
115
|
+
plan_dir=plan_dir,
|
|
116
|
+
contracts_dir=contracts_dir,
|
|
117
|
+
convention_path=convention_dir / "artifact-naming.convention.yaml"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Verify contract was created at correct path
|
|
121
|
+
expected_path = contracts_dir / "mechanic" / "timebank" / "exhausted.schema.json"
|
|
122
|
+
assert expected_path.exists(), f"Contract not created at {expected_path}"
|
|
123
|
+
|
|
124
|
+
# Read and verify contract content
|
|
125
|
+
with open(expected_path) as f:
|
|
126
|
+
contract = json.load(f)
|
|
127
|
+
|
|
128
|
+
# Verify x-artifact-metadata structure
|
|
129
|
+
metadata = contract.get("x-artifact-metadata", {})
|
|
130
|
+
|
|
131
|
+
# Verify domain and resource parsed from URN
|
|
132
|
+
assert metadata["domain"] == "timebank", "Domain should be 'timebank'"
|
|
133
|
+
assert metadata["resource"] == "timebank.exhausted", "Resource should be 'timebank.exhausted'"
|
|
134
|
+
|
|
135
|
+
# Verify version
|
|
136
|
+
assert metadata["version"] == "1.0.0", "Version should default to 1.0.0"
|
|
137
|
+
|
|
138
|
+
# Verify producer from wagon
|
|
139
|
+
assert metadata["producer"] == "wagon:burn-timebank", "Producer should be burn-timebank wagon"
|
|
140
|
+
|
|
141
|
+
# Verify consumers scanned from all wagons
|
|
142
|
+
assert "wagon:reveal-status" in metadata["consumers"], "Should find reveal-status as consumer"
|
|
143
|
+
|
|
144
|
+
# Verify dependencies from producer wagon consume[]
|
|
145
|
+
assert "contract:match:state.committed" in metadata["dependencies"], "Should extract dependencies"
|
|
146
|
+
|
|
147
|
+
# Verify API inferred from resource pattern (exhausted = event = POST)
|
|
148
|
+
api = metadata.get("api", {})
|
|
149
|
+
operations = api.get("operations", [])
|
|
150
|
+
assert len(operations) > 0, "Should have API operations"
|
|
151
|
+
assert operations[0]["method"] == "POST", "Exhausted event should be POST"
|
|
152
|
+
assert operations[0]["path"] == "/mechanic/timebank/exhausted", "Path should be /mechanic/timebank/exhausted"
|
|
153
|
+
|
|
154
|
+
# Verify traceability
|
|
155
|
+
traceability = metadata.get("traceability", {})
|
|
156
|
+
assert traceability["wagon_ref"] == "plan/burn_timebank/_burn_timebank.yaml", "Wagon ref should match"
|
|
157
|
+
assert "feature:burn-timebank:exhaust-timer" in traceability["feature_refs"], "Feature ref should match"
|
|
158
|
+
|
|
159
|
+
# Verify testing paths
|
|
160
|
+
testing = metadata.get("testing", {})
|
|
161
|
+
assert testing["directory"] == "contracts/mechanic/timebank/tests/", "Test directory should be correct"
|
|
162
|
+
assert "exhausted_schema_test.json" in testing["schema_tests"], "Test file should be exhausted_schema_test.json"
|
|
163
|
+
|
|
164
|
+
# Verify result summary
|
|
165
|
+
assert result["created"] == True, "Contract should be marked as created"
|
|
166
|
+
assert result["path"] == str(expected_path), "Result should contain path"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.mark.platform
|
|
170
|
+
def test_validate_and_update_existing_contract_metadata(tmp_path):
|
|
171
|
+
"""
|
|
172
|
+
SPEC-COACH-UTILS-0295: Validate and update existing contract metadata completeness
|
|
173
|
+
|
|
174
|
+
Given: Contract schema exists with x-artifact-metadata section but may be incomplete
|
|
175
|
+
Wagon manifests have changed since contract was created
|
|
176
|
+
New consumers or dependencies may have been added
|
|
177
|
+
When: Validating and updating existing contract metadata
|
|
178
|
+
Then: Read existing contract x-artifact-metadata section
|
|
179
|
+
Re-scan wagon manifests to build complete metadata from current state
|
|
180
|
+
Compare existing metadata with generated metadata
|
|
181
|
+
Detect missing or outdated fields
|
|
182
|
+
Update only missing or outdated fields preserve user customizations
|
|
183
|
+
Do not overwrite manually edited fields
|
|
184
|
+
Validate updated contract against JSON Schema spec
|
|
185
|
+
Report what was updated and why
|
|
186
|
+
"""
|
|
187
|
+
# Setup test directories
|
|
188
|
+
plan_dir = tmp_path / "plan"
|
|
189
|
+
plan_dir.mkdir()
|
|
190
|
+
contracts_dir = tmp_path / "contracts"
|
|
191
|
+
contracts_dir.mkdir()
|
|
192
|
+
|
|
193
|
+
# Create producer wagon
|
|
194
|
+
pace_dilemmas_dir = plan_dir / "pace_dilemmas"
|
|
195
|
+
pace_dilemmas_dir.mkdir()
|
|
196
|
+
wagon_manifest = pace_dilemmas_dir / "_pace_dilemmas.yaml"
|
|
197
|
+
wagon_data = {
|
|
198
|
+
"wagon": "pace-dilemmas",
|
|
199
|
+
"theme": "match",
|
|
200
|
+
"produce": [
|
|
201
|
+
{"name": "match:dilemma.current", "contract": "contract:match:dilemma.current"}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
with open(wagon_manifest, 'w') as f:
|
|
205
|
+
yaml.dump(wagon_data, f, default_flow_style=False, sort_keys=False)
|
|
206
|
+
|
|
207
|
+
# Create NEW consumer wagon (added after contract was created)
|
|
208
|
+
resolve_dilemmas_dir = plan_dir / "resolve_dilemmas"
|
|
209
|
+
resolve_dilemmas_dir.mkdir()
|
|
210
|
+
consumer_manifest = resolve_dilemmas_dir / "_resolve_dilemmas.yaml"
|
|
211
|
+
consumer_data = {
|
|
212
|
+
"wagon": "resolve-dilemmas",
|
|
213
|
+
"theme": "mechanic",
|
|
214
|
+
"consume": [
|
|
215
|
+
{"name": "contract:match:dilemma.current"}
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
with open(consumer_manifest, 'w') as f:
|
|
219
|
+
yaml.dump(consumer_data, f, default_flow_style=False, sort_keys=False)
|
|
220
|
+
|
|
221
|
+
# Create EXISTING contract with INCOMPLETE metadata (missing new consumer)
|
|
222
|
+
dilemma_dir = contracts_dir / "match" / "dilemma"
|
|
223
|
+
dilemma_dir.mkdir(parents=True)
|
|
224
|
+
existing_contract = dilemma_dir / "current.schema.json"
|
|
225
|
+
contract_data = {
|
|
226
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
227
|
+
"$id": "match:dilemma.current",
|
|
228
|
+
"version": "1.0.0",
|
|
229
|
+
"title": "Current Dilemma Contract",
|
|
230
|
+
"description": "CUSTOM DESCRIPTION - should not be overwritten",
|
|
231
|
+
"type": "object",
|
|
232
|
+
"properties": {
|
|
233
|
+
"id": {"type": "string"}
|
|
234
|
+
},
|
|
235
|
+
"x-artifact-metadata": {
|
|
236
|
+
"domain": "dilemma",
|
|
237
|
+
"resource": "dilemma.current",
|
|
238
|
+
"version": "1.0.0",
|
|
239
|
+
"producer": "wagon:pace-dilemmas",
|
|
240
|
+
"consumers": [], # INCOMPLETE - missing new consumer
|
|
241
|
+
"dependencies": [],
|
|
242
|
+
"api": {
|
|
243
|
+
"operations": [{
|
|
244
|
+
"method": "GET",
|
|
245
|
+
"path": "/match/dilemma/current",
|
|
246
|
+
"description": "CUSTOM API DESCRIPTION - should not be overwritten"
|
|
247
|
+
}]
|
|
248
|
+
},
|
|
249
|
+
"traceability": {
|
|
250
|
+
"wagon_ref": "plan/pace_dilemmas/_pace_dilemmas.yaml"
|
|
251
|
+
# Missing feature_refs
|
|
252
|
+
},
|
|
253
|
+
"testing": {
|
|
254
|
+
"directory": "contracts/match/dilemma/tests/"
|
|
255
|
+
# Missing schema_tests array
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
with open(existing_contract, 'w') as f:
|
|
260
|
+
json.dump(contract_data, f, indent=2)
|
|
261
|
+
|
|
262
|
+
# Import validation function
|
|
263
|
+
from atdd.coach.commands.interface import validate_and_update_contract_metadata
|
|
264
|
+
|
|
265
|
+
# Execute validation and update
|
|
266
|
+
result = validate_and_update_contract_metadata(
|
|
267
|
+
contract_path=existing_contract,
|
|
268
|
+
plan_dir=plan_dir,
|
|
269
|
+
contracts_dir=contracts_dir
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Read updated contract
|
|
273
|
+
with open(existing_contract) as f:
|
|
274
|
+
updated_contract = json.load(f)
|
|
275
|
+
|
|
276
|
+
metadata = updated_contract["x-artifact-metadata"]
|
|
277
|
+
|
|
278
|
+
# Verify new consumer was added
|
|
279
|
+
assert "wagon:resolve-dilemmas" in metadata["consumers"], "Should add new consumer"
|
|
280
|
+
|
|
281
|
+
# Verify custom description was preserved
|
|
282
|
+
assert updated_contract["description"] == "CUSTOM DESCRIPTION - should not be overwritten", \
|
|
283
|
+
"Should preserve custom description"
|
|
284
|
+
|
|
285
|
+
# Verify custom API description was preserved
|
|
286
|
+
api_desc = metadata["api"]["operations"][0].get("description", "")
|
|
287
|
+
assert "CUSTOM API DESCRIPTION" in api_desc, "Should preserve custom API description"
|
|
288
|
+
|
|
289
|
+
# Verify missing feature_refs was added
|
|
290
|
+
assert "feature_refs" in metadata["traceability"], "Should add missing feature_refs"
|
|
291
|
+
|
|
292
|
+
# Verify missing schema_tests was added
|
|
293
|
+
assert "schema_tests" in metadata["testing"], "Should add missing schema_tests array"
|
|
294
|
+
assert len(metadata["testing"]["schema_tests"]) > 0, "Should populate schema_tests"
|
|
295
|
+
|
|
296
|
+
# Verify result report
|
|
297
|
+
assert "consumers" in result["updates"], "Should report consumers update"
|
|
298
|
+
assert "traceability.feature_refs" in result["updates"], "Should report feature_refs added"
|
|
299
|
+
assert "testing.schema_tests" in result["updates"], "Should report schema_tests added"
|
|
300
|
+
assert result["preserved_customizations"] > 0, "Should count preserved customizations"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@pytest.mark.platform
|
|
304
|
+
def test_create_placeholder_contract_tests(tmp_path):
|
|
305
|
+
"""
|
|
306
|
+
SPEC-COACH-UTILS-0296: Create placeholder test files for scaffolded contracts
|
|
307
|
+
|
|
308
|
+
Given: Contract has been scaffolded with x-artifact-metadata.testing section
|
|
309
|
+
Testing directory path specified in x-artifact-metadata.testing.directory
|
|
310
|
+
Test file names specified in x-artifact-metadata.testing.schema_tests array
|
|
311
|
+
Test directory may or may not exist
|
|
312
|
+
Test files may or may not exist
|
|
313
|
+
When: Creating placeholder test files for new contracts
|
|
314
|
+
Then: Create testing directory if it does not exist
|
|
315
|
+
For each test file in schema_tests array check if file exists
|
|
316
|
+
If test file does not exist create placeholder test JSON file
|
|
317
|
+
Placeholder contains minimal valid test structure with contract reference
|
|
318
|
+
Placeholder includes TODO comment indicating it needs implementation
|
|
319
|
+
Existing test files are not overwritten or modified
|
|
320
|
+
Report which test files were created vs already existed
|
|
321
|
+
"""
|
|
322
|
+
# Setup test directories
|
|
323
|
+
contracts_dir = tmp_path / "contracts"
|
|
324
|
+
contracts_dir.mkdir()
|
|
325
|
+
|
|
326
|
+
# Create contract with testing metadata
|
|
327
|
+
mechanic_dir = contracts_dir / "mechanic" / "timebank"
|
|
328
|
+
mechanic_dir.mkdir(parents=True)
|
|
329
|
+
contract_path = mechanic_dir / "exhausted.schema.json"
|
|
330
|
+
|
|
331
|
+
contract_data = {
|
|
332
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
333
|
+
"$id": "mechanic:timebank.exhausted",
|
|
334
|
+
"version": "1.0.0",
|
|
335
|
+
"title": "Timebank Exhausted",
|
|
336
|
+
"type": "object",
|
|
337
|
+
"x-artifact-metadata": {
|
|
338
|
+
"domain": "timebank",
|
|
339
|
+
"resource": "timebank.exhausted",
|
|
340
|
+
"testing": {
|
|
341
|
+
"directory": "contracts/mechanic/timebank/tests/",
|
|
342
|
+
"schema_tests": [
|
|
343
|
+
"exhausted_schema_test.json",
|
|
344
|
+
"exhausted_validation_test.json"
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
with open(contract_path, 'w') as f:
|
|
350
|
+
json.dump(contract_data, f, indent=2)
|
|
351
|
+
|
|
352
|
+
# Create ONE existing test file to verify it's not overwritten
|
|
353
|
+
tests_dir = mechanic_dir / "tests"
|
|
354
|
+
tests_dir.mkdir()
|
|
355
|
+
existing_test = tests_dir / "exhausted_schema_test.json"
|
|
356
|
+
existing_test_data = {
|
|
357
|
+
"description": "EXISTING TEST - should not be overwritten",
|
|
358
|
+
"contract": "mechanic:timebank.exhausted"
|
|
359
|
+
}
|
|
360
|
+
with open(existing_test, 'w') as f:
|
|
361
|
+
json.dump(existing_test_data, f, indent=2)
|
|
362
|
+
|
|
363
|
+
# Import function
|
|
364
|
+
from atdd.coach.commands.interface import create_placeholder_test_files
|
|
365
|
+
|
|
366
|
+
# Execute placeholder generation
|
|
367
|
+
result = create_placeholder_test_files(
|
|
368
|
+
contract_path=contract_path,
|
|
369
|
+
contracts_dir=contracts_dir
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Verify test directory exists
|
|
373
|
+
assert tests_dir.exists(), "Test directory should exist"
|
|
374
|
+
|
|
375
|
+
# Verify existing test was NOT modified
|
|
376
|
+
with open(existing_test) as f:
|
|
377
|
+
preserved_test = json.load(f)
|
|
378
|
+
assert preserved_test["description"] == "EXISTING TEST - should not be overwritten", \
|
|
379
|
+
"Should not overwrite existing test"
|
|
380
|
+
|
|
381
|
+
# Verify new test was created
|
|
382
|
+
new_test = tests_dir / "exhausted_validation_test.json"
|
|
383
|
+
assert new_test.exists(), "Should create new placeholder test"
|
|
384
|
+
|
|
385
|
+
# Verify placeholder structure
|
|
386
|
+
with open(new_test) as f:
|
|
387
|
+
placeholder = json.load(f)
|
|
388
|
+
|
|
389
|
+
assert "TODO" in placeholder.get("description", ""), "Should include TODO comment"
|
|
390
|
+
assert placeholder.get("contract") == "mechanic:timebank.exhausted", "Should reference contract"
|
|
391
|
+
assert "test_cases" in placeholder, "Should have test_cases structure"
|
|
392
|
+
|
|
393
|
+
# Verify result report
|
|
394
|
+
assert result["created"] == 1, "Should report 1 file created"
|
|
395
|
+
assert result["skipped"] == 1, "Should report 1 file skipped (existing)"
|
|
396
|
+
assert "exhausted_validation_test.json" in result["created_files"], \
|
|
397
|
+
"Should list created file"
|
|
398
|
+
assert "exhausted_schema_test.json" in result["skipped_files"], \
|
|
399
|
+
"Should list skipped file"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test runner for ATDD meta-tests.
|
|
4
|
+
|
|
5
|
+
Replaces run_all_tests.sh with a more flexible Python-based test runner.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestRunner:
|
|
15
|
+
"""Run ATDD meta-tests with various configurations."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, repo_root: Path = None):
|
|
18
|
+
self.repo_root = repo_root or Path(__file__).parent.parent.parent.parent
|
|
19
|
+
self.atdd_dir = self.repo_root / "atdd"
|
|
20
|
+
|
|
21
|
+
def run_tests(
|
|
22
|
+
self,
|
|
23
|
+
phase: Optional[str] = None,
|
|
24
|
+
verbose: bool = False,
|
|
25
|
+
coverage: bool = False,
|
|
26
|
+
html_report: bool = False,
|
|
27
|
+
markers: Optional[List[str]] = None,
|
|
28
|
+
parallel: bool = True,
|
|
29
|
+
) -> int:
|
|
30
|
+
"""
|
|
31
|
+
Run ATDD tests with specified options.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
phase: Test phase to run (planner, tester, coder, all, None=all)
|
|
35
|
+
verbose: Enable verbose output
|
|
36
|
+
coverage: Generate coverage report
|
|
37
|
+
html_report: Generate HTML report
|
|
38
|
+
markers: Additional pytest markers to filter
|
|
39
|
+
parallel: Run tests in parallel (uses pytest-xdist)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Exit code from pytest
|
|
43
|
+
"""
|
|
44
|
+
# Build pytest command
|
|
45
|
+
cmd = ["pytest"]
|
|
46
|
+
|
|
47
|
+
# Determine test path
|
|
48
|
+
if phase and phase != "all":
|
|
49
|
+
test_path = self.atdd_dir / phase
|
|
50
|
+
if not test_path.exists():
|
|
51
|
+
print(f"โ Error: Test phase '{phase}' not found at {test_path}")
|
|
52
|
+
return 1
|
|
53
|
+
cmd.append(str(test_path))
|
|
54
|
+
else:
|
|
55
|
+
# Run all atdd tests
|
|
56
|
+
cmd.append(str(self.atdd_dir))
|
|
57
|
+
|
|
58
|
+
# Add verbosity
|
|
59
|
+
if verbose:
|
|
60
|
+
cmd.append("-v")
|
|
61
|
+
else:
|
|
62
|
+
cmd.append("-q")
|
|
63
|
+
|
|
64
|
+
# Add markers
|
|
65
|
+
if markers:
|
|
66
|
+
for marker in markers:
|
|
67
|
+
cmd.extend(["-m", marker])
|
|
68
|
+
|
|
69
|
+
# Add coverage
|
|
70
|
+
if coverage:
|
|
71
|
+
cmd.extend([
|
|
72
|
+
"--cov=atdd",
|
|
73
|
+
"--cov-report=term-missing",
|
|
74
|
+
"--cov-report=html:atdd/htmlcov"
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
# Add HTML report
|
|
78
|
+
if html_report:
|
|
79
|
+
cmd.extend([
|
|
80
|
+
"--html=atdd/test_report.html",
|
|
81
|
+
"--self-contained-html"
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
# Add parallel execution
|
|
85
|
+
if parallel:
|
|
86
|
+
cmd.extend(["-n", "auto"])
|
|
87
|
+
|
|
88
|
+
# Show collected tests summary
|
|
89
|
+
cmd.append("--tb=short")
|
|
90
|
+
|
|
91
|
+
# Run pytest
|
|
92
|
+
print(f"๐งช Running: {' '.join(cmd)}")
|
|
93
|
+
print("=" * 60)
|
|
94
|
+
|
|
95
|
+
result = subprocess.run(cmd, cwd=self.repo_root)
|
|
96
|
+
return result.returncode
|
|
97
|
+
|
|
98
|
+
def run_phase(self, phase: str, **kwargs) -> int:
|
|
99
|
+
"""Run tests for a specific phase."""
|
|
100
|
+
return self.run_tests(phase=phase, **kwargs)
|
|
101
|
+
|
|
102
|
+
def run_all(self, **kwargs) -> int:
|
|
103
|
+
"""Run all ATDD meta-tests."""
|
|
104
|
+
return self.run_tests(phase="all", **kwargs)
|
|
105
|
+
|
|
106
|
+
def quick_check(self) -> int:
|
|
107
|
+
"""Quick smoke test - run without parallelization."""
|
|
108
|
+
print("๐ Running quick check (no parallel)...")
|
|
109
|
+
return self.run_tests(
|
|
110
|
+
phase="all",
|
|
111
|
+
verbose=False,
|
|
112
|
+
parallel=False,
|
|
113
|
+
html_report=False
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def full_suite(self) -> int:
|
|
117
|
+
"""Full test suite with coverage and HTML report."""
|
|
118
|
+
print("๐ฏ Running full test suite...")
|
|
119
|
+
return self.run_tests(
|
|
120
|
+
phase="all",
|
|
121
|
+
verbose=True,
|
|
122
|
+
coverage=True,
|
|
123
|
+
html_report=True,
|
|
124
|
+
parallel=True
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
"""CLI entry point for test runner."""
|
|
130
|
+
runner = TestRunner()
|
|
131
|
+
|
|
132
|
+
# Simple usage for now - can be enhanced with argparse
|
|
133
|
+
if len(sys.argv) > 1:
|
|
134
|
+
phase = sys.argv[1]
|
|
135
|
+
return runner.run_phase(phase, verbose=True, html_report=True)
|
|
136
|
+
else:
|
|
137
|
+
return runner.run_all(verbose=True, html_report=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Test module for coach commands
|