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,575 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test unified registry system.
|
|
3
|
+
|
|
4
|
+
Specs: SPEC-COACH-UTILS-0200 through SPEC-COACH-UTILS-0214
|
|
5
|
+
Location: .claude/agents/coach/utils.spec.yaml::unified_registry
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
10
|
+
import yaml
|
|
11
|
+
import json
|
|
12
|
+
import tempfile
|
|
13
|
+
import shutil
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Test fixtures
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def temp_repo():
|
|
19
|
+
"""Create temporary repository structure for testing."""
|
|
20
|
+
temp_dir = tempfile.mkdtemp()
|
|
21
|
+
repo_root = Path(temp_dir)
|
|
22
|
+
|
|
23
|
+
# Create directory structure
|
|
24
|
+
(repo_root / "plan").mkdir()
|
|
25
|
+
(repo_root / "contracts").mkdir()
|
|
26
|
+
(repo_root / "telemetry").mkdir()
|
|
27
|
+
(repo_root / "atdd" / "tester").mkdir(parents=True)
|
|
28
|
+
(repo_root / "python").mkdir()
|
|
29
|
+
(repo_root / "supabase" / "functions").mkdir(parents=True)
|
|
30
|
+
|
|
31
|
+
yield repo_root
|
|
32
|
+
|
|
33
|
+
# Cleanup
|
|
34
|
+
shutil.rmtree(temp_dir)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def sample_wagon_manifest(temp_repo):
|
|
39
|
+
"""Create sample wagon manifest."""
|
|
40
|
+
wagon_dir = temp_repo / "plan" / "test-wagon"
|
|
41
|
+
wagon_dir.mkdir()
|
|
42
|
+
|
|
43
|
+
manifest = {
|
|
44
|
+
"wagon": "test-wagon",
|
|
45
|
+
"description": "Test wagon for registry",
|
|
46
|
+
"theme": "testing",
|
|
47
|
+
"produce": [],
|
|
48
|
+
"consume": []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
manifest_path = wagon_dir / "_test-wagon.yaml"
|
|
52
|
+
with open(manifest_path, "w") as f:
|
|
53
|
+
yaml.dump(manifest, f)
|
|
54
|
+
|
|
55
|
+
return manifest_path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.fixture
|
|
59
|
+
def sample_wagons_registry(temp_repo):
|
|
60
|
+
"""Create sample plan/_wagons.yaml registry."""
|
|
61
|
+
registry_data = {
|
|
62
|
+
"wagons": [
|
|
63
|
+
{
|
|
64
|
+
"wagon": "existing-wagon",
|
|
65
|
+
"description": "Existing wagon",
|
|
66
|
+
"path": "plan/existing-wagon/",
|
|
67
|
+
"manifest": "plan/existing-wagon/_existing-wagon.yaml"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"wagon": "draft-wagon",
|
|
71
|
+
"description": "Draft wagon without manifest"
|
|
72
|
+
# No path/manifest = draft mode
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
registry_path = temp_repo / "plan" / "_wagons.yaml"
|
|
78
|
+
with open(registry_path, "w") as f:
|
|
79
|
+
yaml.dump(registry_data, f)
|
|
80
|
+
|
|
81
|
+
return registry_path
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# SPEC-COACH-UTILS-0200
|
|
85
|
+
def test_load_all_registries(temp_repo, sample_wagons_registry):
|
|
86
|
+
"""
|
|
87
|
+
Test that registry command loads all registries without distinction.
|
|
88
|
+
|
|
89
|
+
Given: User runs registry (no flags)
|
|
90
|
+
When: Registry loader executes
|
|
91
|
+
Then: Loads all registry manifests, returns unified view, read-only mode
|
|
92
|
+
"""
|
|
93
|
+
from atdd.coach.commands.registry import RegistryLoader
|
|
94
|
+
|
|
95
|
+
loader = RegistryLoader(temp_repo)
|
|
96
|
+
result = loader.load_all()
|
|
97
|
+
|
|
98
|
+
assert "plan" in result
|
|
99
|
+
assert "contracts" in result
|
|
100
|
+
assert "telemetry" in result
|
|
101
|
+
assert isinstance(result, dict)
|
|
102
|
+
|
|
103
|
+
# Verify read-only (no files modified)
|
|
104
|
+
assert sample_wagons_registry.exists()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# SPEC-COACH-UTILS-0201
|
|
108
|
+
def test_build_all_registries(temp_repo, sample_wagon_manifest):
|
|
109
|
+
"""
|
|
110
|
+
Test that registry --build updates all registries from source files.
|
|
111
|
+
|
|
112
|
+
Given: User runs registry --build
|
|
113
|
+
When: Registry builder executes
|
|
114
|
+
Then: Builds/updates manifests, shows preview, asks confirmation
|
|
115
|
+
"""
|
|
116
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
117
|
+
|
|
118
|
+
builder = RegistryBuilder(temp_repo)
|
|
119
|
+
|
|
120
|
+
# Mock user confirmation
|
|
121
|
+
with patch('builtins.input', return_value='yes'):
|
|
122
|
+
result = builder.build_all()
|
|
123
|
+
|
|
124
|
+
assert "plan" in result
|
|
125
|
+
assert result["plan"]["total_manifests"] >= 1
|
|
126
|
+
|
|
127
|
+
# Verify registry was created
|
|
128
|
+
wagons_registry = temp_repo / "plan" / "_wagons.yaml"
|
|
129
|
+
assert wagons_registry.exists()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# SPEC-COACH-UTILS-0202
|
|
133
|
+
def test_load_planner_registry(temp_repo, sample_wagons_registry):
|
|
134
|
+
"""
|
|
135
|
+
Test that registry --planner loads planner registry only.
|
|
136
|
+
|
|
137
|
+
Given: User runs registry --planner
|
|
138
|
+
When: Registry loader executes
|
|
139
|
+
Then: Loads only plan/_wagons.yaml, returns wagon entries
|
|
140
|
+
"""
|
|
141
|
+
from atdd.coach.commands.registry import RegistryLoader
|
|
142
|
+
|
|
143
|
+
loader = RegistryLoader(temp_repo)
|
|
144
|
+
result = loader.load_planner()
|
|
145
|
+
|
|
146
|
+
assert "wagons" in result
|
|
147
|
+
assert len(result["wagons"]) >= 1
|
|
148
|
+
assert result["wagons"][0]["wagon"] == "existing-wagon"
|
|
149
|
+
|
|
150
|
+
# Should not load other registries
|
|
151
|
+
assert "contracts" not in result
|
|
152
|
+
assert "telemetry" not in result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# SPEC-COACH-UTILS-0203
|
|
156
|
+
def test_build_planner_registry(temp_repo, sample_wagon_manifest, sample_wagons_registry):
|
|
157
|
+
"""
|
|
158
|
+
Test that registry --planner --build updates planner registry from wagon manifests.
|
|
159
|
+
|
|
160
|
+
Given: User runs registry --planner --build
|
|
161
|
+
When: Registry builder executes
|
|
162
|
+
Then: Scans wagon manifests, updates registry, preserves drafts
|
|
163
|
+
"""
|
|
164
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
165
|
+
|
|
166
|
+
builder = RegistryBuilder(temp_repo)
|
|
167
|
+
|
|
168
|
+
with patch('builtins.input', return_value='yes'):
|
|
169
|
+
result = builder.build_planner(preview_only=False)
|
|
170
|
+
|
|
171
|
+
assert result["total_manifests"] >= 1
|
|
172
|
+
assert result["new"] >= 0
|
|
173
|
+
assert result["updated"] >= 0
|
|
174
|
+
assert result["preserved_drafts"] >= 1 # draft-wagon should be preserved
|
|
175
|
+
|
|
176
|
+
# Verify draft wagon preserved
|
|
177
|
+
with open(sample_wagons_registry) as f:
|
|
178
|
+
registry = yaml.safe_load(f)
|
|
179
|
+
|
|
180
|
+
draft_wagons = [w for w in registry["wagons"] if w["wagon"] == "draft-wagon"]
|
|
181
|
+
assert len(draft_wagons) == 1
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# SPEC-COACH-UTILS-0204
|
|
185
|
+
def test_load_coder_registry(temp_repo):
|
|
186
|
+
"""
|
|
187
|
+
Test that registry --coder loads coder implementation registry.
|
|
188
|
+
|
|
189
|
+
Given: User runs registry --coder
|
|
190
|
+
When: Registry loader executes
|
|
191
|
+
Then: Loads python/_implementations.yaml with URNs and links
|
|
192
|
+
"""
|
|
193
|
+
from atdd.coach.commands.registry import RegistryLoader
|
|
194
|
+
|
|
195
|
+
# Create sample implementations registry
|
|
196
|
+
impl_registry = {
|
|
197
|
+
"implementations": [
|
|
198
|
+
{
|
|
199
|
+
"urn": "impl:test-wagon:domain:entity:python",
|
|
200
|
+
"file": "python/test_wagon/src/domain/entities/test.py",
|
|
201
|
+
"spec_urn": "spec:test-wagon:feature",
|
|
202
|
+
"test_urn": "test:test-wagon:test_entity.py::test_entity_creation",
|
|
203
|
+
"wagon": "test-wagon",
|
|
204
|
+
"layer": "domain",
|
|
205
|
+
"language": "python"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
impl_path = temp_repo / "python" / "_implementations.yaml"
|
|
211
|
+
with open(impl_path, "w") as f:
|
|
212
|
+
yaml.dump(impl_registry, f)
|
|
213
|
+
|
|
214
|
+
loader = RegistryLoader(temp_repo)
|
|
215
|
+
result = loader.load_coder()
|
|
216
|
+
|
|
217
|
+
assert "implementations" in result
|
|
218
|
+
assert len(result["implementations"]) == 1
|
|
219
|
+
assert result["implementations"][0]["urn"].startswith("impl:")
|
|
220
|
+
assert "spec_urn" in result["implementations"][0]
|
|
221
|
+
assert "test_urn" in result["implementations"][0]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# SPEC-COACH-UTILS-0205
|
|
225
|
+
def test_build_coder_registry(temp_repo):
|
|
226
|
+
"""
|
|
227
|
+
Test that registry --coder --build creates implementation registry from Python files.
|
|
228
|
+
|
|
229
|
+
Given: User runs registry --coder --build
|
|
230
|
+
When: Registry builder executes
|
|
231
|
+
Then: Scans Python files, extracts metadata, generates URNs, creates manifest
|
|
232
|
+
"""
|
|
233
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
234
|
+
|
|
235
|
+
# Create sample Python implementation
|
|
236
|
+
py_file = temp_repo / "python" / "test_wagon" / "src" / "domain" / "entities" / "test.py"
|
|
237
|
+
py_file.parent.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
|
|
239
|
+
py_content = '''"""
|
|
240
|
+
Test entity.
|
|
241
|
+
|
|
242
|
+
Spec: spec:test-wagon:feature
|
|
243
|
+
Test: test:test-wagon:test_entity.py::test_entity_creation
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
class TestEntity:
|
|
247
|
+
"""Domain entity for testing."""
|
|
248
|
+
pass
|
|
249
|
+
'''
|
|
250
|
+
|
|
251
|
+
py_file.write_text(py_content)
|
|
252
|
+
|
|
253
|
+
builder = RegistryBuilder(temp_repo)
|
|
254
|
+
|
|
255
|
+
with patch('builtins.input', return_value='yes'):
|
|
256
|
+
result = builder.build_coder(preview_only=False)
|
|
257
|
+
|
|
258
|
+
assert result["processed"] >= 1
|
|
259
|
+
|
|
260
|
+
# Verify registry created
|
|
261
|
+
impl_registry = temp_repo / "python" / "_implementations.yaml"
|
|
262
|
+
assert impl_registry.exists()
|
|
263
|
+
|
|
264
|
+
with open(impl_registry) as f:
|
|
265
|
+
data = yaml.safe_load(f)
|
|
266
|
+
|
|
267
|
+
assert "implementations" in data
|
|
268
|
+
assert len(data["implementations"]) >= 1
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# SPEC-COACH-UTILS-0206
|
|
272
|
+
def test_draft_mode_preserves_entries(temp_repo, sample_wagons_registry):
|
|
273
|
+
"""
|
|
274
|
+
Test that draft mode preserves registry entries without physical source files.
|
|
275
|
+
|
|
276
|
+
Given: Registry has entry without path/manifest field (draft)
|
|
277
|
+
When: registry --build executes
|
|
278
|
+
Then: Preserves draft entries, marks in change report
|
|
279
|
+
"""
|
|
280
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
281
|
+
|
|
282
|
+
builder = RegistryBuilder(temp_repo)
|
|
283
|
+
|
|
284
|
+
with patch('builtins.input', return_value='yes'):
|
|
285
|
+
result = builder.build_planner(preview_only=False)
|
|
286
|
+
|
|
287
|
+
# Verify draft wagon preserved
|
|
288
|
+
assert result["preserved_drafts"] >= 1
|
|
289
|
+
|
|
290
|
+
with open(sample_wagons_registry) as f:
|
|
291
|
+
registry = yaml.safe_load(f)
|
|
292
|
+
|
|
293
|
+
# draft-wagon should still exist
|
|
294
|
+
wagons = {w["wagon"]: w for w in registry["wagons"]}
|
|
295
|
+
assert "draft-wagon" in wagons
|
|
296
|
+
assert "manifest" not in wagons["draft-wagon"]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# SPEC-COACH-UTILS-0207
|
|
300
|
+
def test_spec_test_impl_traceability(temp_repo):
|
|
301
|
+
"""
|
|
302
|
+
Test that spec-test-impl traceability links artifacts across registries.
|
|
303
|
+
|
|
304
|
+
Given: Multiple registries with URN links
|
|
305
|
+
When: Building unified registry view
|
|
306
|
+
Then: Each impl links to spec_urn and test_urn, enables traceability queries
|
|
307
|
+
"""
|
|
308
|
+
from atdd.coach.commands.registry import RegistryLoader
|
|
309
|
+
|
|
310
|
+
# Create linked registries
|
|
311
|
+
spec_urn = "spec:test-wagon:feature"
|
|
312
|
+
test_urn = "test:test-wagon:test_feature.py::test_feature_works"
|
|
313
|
+
impl_urn = "impl:test-wagon:domain:entity:python"
|
|
314
|
+
|
|
315
|
+
# Implementation registry with links
|
|
316
|
+
impl_registry = {
|
|
317
|
+
"implementations": [{
|
|
318
|
+
"urn": impl_urn,
|
|
319
|
+
"spec_urn": spec_urn,
|
|
320
|
+
"test_urn": test_urn,
|
|
321
|
+
"wagon": "test-wagon"
|
|
322
|
+
}]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
impl_path = temp_repo / "python" / "_implementations.yaml"
|
|
326
|
+
with open(impl_path, "w") as f:
|
|
327
|
+
yaml.dump(impl_registry, f)
|
|
328
|
+
|
|
329
|
+
loader = RegistryLoader(temp_repo)
|
|
330
|
+
|
|
331
|
+
# Query traceability
|
|
332
|
+
impl_for_spec = loader.find_implementations_for_spec(spec_urn)
|
|
333
|
+
assert len(impl_for_spec) == 1
|
|
334
|
+
assert impl_for_spec[0]["urn"] == impl_urn
|
|
335
|
+
|
|
336
|
+
tests_for_impl = loader.find_tests_for_implementation(impl_urn)
|
|
337
|
+
assert tests_for_impl == test_urn
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# SPEC-COACH-UTILS-0208
|
|
341
|
+
def test_autogenerate_manifest_for_new_registry(temp_repo):
|
|
342
|
+
"""
|
|
343
|
+
Test that registry auto-generates manifest for new registry types.
|
|
344
|
+
|
|
345
|
+
Given: New registry type has no manifest (e.g., supabase/)
|
|
346
|
+
When: registry --build executes
|
|
347
|
+
Then: Detects missing manifest, scans sources, auto-generates manifest
|
|
348
|
+
"""
|
|
349
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
350
|
+
|
|
351
|
+
# Create supabase function without registry
|
|
352
|
+
func_file = temp_repo / "supabase" / "functions" / "test-func" / "index.ts"
|
|
353
|
+
func_file.parent.mkdir(parents=True, exist_ok=True)
|
|
354
|
+
func_file.write_text("export const handler = () => {}")
|
|
355
|
+
|
|
356
|
+
builder = RegistryBuilder(temp_repo)
|
|
357
|
+
|
|
358
|
+
with patch('builtins.input', return_value='yes'):
|
|
359
|
+
result = builder.build_supabase(preview_only=False)
|
|
360
|
+
|
|
361
|
+
# Verify manifest created
|
|
362
|
+
supabase_registry = temp_repo / "supabase" / "_functions.yaml"
|
|
363
|
+
assert supabase_registry.exists()
|
|
364
|
+
|
|
365
|
+
with open(supabase_registry) as f:
|
|
366
|
+
data = yaml.safe_load(f)
|
|
367
|
+
|
|
368
|
+
assert "functions" in data
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# SPEC-COACH-UTILS-0209
|
|
372
|
+
def test_detect_field_level_changes(temp_repo, sample_wagon_manifest, sample_wagons_registry):
|
|
373
|
+
"""
|
|
374
|
+
Test that registry detects and reports field-level changes.
|
|
375
|
+
|
|
376
|
+
Given: Existing registry with entries, source files have updated fields
|
|
377
|
+
When: registry --build executes
|
|
378
|
+
Then: Compares field-by-field, reports changed fields, shows detailed report
|
|
379
|
+
"""
|
|
380
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
381
|
+
|
|
382
|
+
# First, add test-wagon to existing registry
|
|
383
|
+
with open(sample_wagons_registry) as f:
|
|
384
|
+
registry = yaml.safe_load(f)
|
|
385
|
+
|
|
386
|
+
registry["wagons"].append({
|
|
387
|
+
"wagon": "test-wagon",
|
|
388
|
+
"description": "Original description",
|
|
389
|
+
"path": "plan/test-wagon/",
|
|
390
|
+
"manifest": str(sample_wagon_manifest.relative_to(temp_repo))
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
with open(sample_wagons_registry, "w") as f:
|
|
394
|
+
yaml.dump(registry, f)
|
|
395
|
+
|
|
396
|
+
# Update wagon manifest description
|
|
397
|
+
with open(sample_wagon_manifest) as f:
|
|
398
|
+
manifest = yaml.safe_load(f)
|
|
399
|
+
|
|
400
|
+
manifest["description"] = "Updated description for test wagon"
|
|
401
|
+
|
|
402
|
+
with open(sample_wagon_manifest, "w") as f:
|
|
403
|
+
yaml.dump(manifest, f)
|
|
404
|
+
|
|
405
|
+
builder = RegistryBuilder(temp_repo)
|
|
406
|
+
|
|
407
|
+
# Preview mode to see changes
|
|
408
|
+
result = builder.build_planner(preview_only=True)
|
|
409
|
+
|
|
410
|
+
assert "changes" in result
|
|
411
|
+
|
|
412
|
+
# Find change for test-wagon
|
|
413
|
+
test_wagon_changes = [c for c in result["changes"] if c.get("wagon") == "test-wagon"]
|
|
414
|
+
|
|
415
|
+
assert len(test_wagon_changes) > 0
|
|
416
|
+
assert "fields" in test_wagon_changes[0]
|
|
417
|
+
assert "description" in test_wagon_changes[0]["fields"]
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# SPEC-COACH-UTILS-0210
|
|
421
|
+
def test_registry_command_renamed():
|
|
422
|
+
"""
|
|
423
|
+
Test that registry command is renamed from registry_updater.
|
|
424
|
+
|
|
425
|
+
Given: Old command location atdd/coach/commands/registry_updater.py
|
|
426
|
+
When: Refactoring to unified registry system
|
|
427
|
+
Then: Command renamed to registry.py, maintains backward compatibility
|
|
428
|
+
"""
|
|
429
|
+
from atdd.coach.commands import registry
|
|
430
|
+
|
|
431
|
+
# Verify new module exists
|
|
432
|
+
assert hasattr(registry, 'RegistryLoader')
|
|
433
|
+
assert hasattr(registry, 'RegistryBuilder')
|
|
434
|
+
|
|
435
|
+
# Verify backward compatibility (should be able to import old names)
|
|
436
|
+
assert hasattr(registry, 'RegistryUpdater') or hasattr(registry, 'RegistryBuilder')
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# SPEC-COACH-UTILS-0211
|
|
440
|
+
def test_load_tester_registry(temp_repo):
|
|
441
|
+
"""
|
|
442
|
+
Test that registry --tester loads tester test registry.
|
|
443
|
+
|
|
444
|
+
Given: User runs registry --tester
|
|
445
|
+
When: Registry loader executes
|
|
446
|
+
Then: Loads atdd/tester/_tests.yaml with URNs and links
|
|
447
|
+
"""
|
|
448
|
+
from atdd.coach.commands.registry import RegistryLoader
|
|
449
|
+
|
|
450
|
+
# Create sample test registry
|
|
451
|
+
test_registry = {
|
|
452
|
+
"tests": [
|
|
453
|
+
{
|
|
454
|
+
"urn": "test:test-wagon:test_feature.py::test_feature_works",
|
|
455
|
+
"file": "atdd/tester/test_wagon/test_feature.py",
|
|
456
|
+
"spec_urn": "spec:test-wagon:feature",
|
|
457
|
+
"wagon": "test-wagon",
|
|
458
|
+
"acceptance_urn": "acceptance:test-wagon:EXEC001"
|
|
459
|
+
}
|
|
460
|
+
]
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
test_path = temp_repo / "atdd" / "tester" / "_tests.yaml"
|
|
464
|
+
with open(test_path, "w") as f:
|
|
465
|
+
yaml.dump(test_registry, f)
|
|
466
|
+
|
|
467
|
+
loader = RegistryLoader(temp_repo)
|
|
468
|
+
result = loader.load_tester()
|
|
469
|
+
|
|
470
|
+
assert "tests" in result
|
|
471
|
+
assert len(result["tests"]) == 1
|
|
472
|
+
assert result["tests"][0]["urn"].startswith("test:")
|
|
473
|
+
assert "spec_urn" in result["tests"][0]
|
|
474
|
+
assert "acceptance_urn" in result["tests"][0]
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# SPEC-COACH-UTILS-0212
|
|
478
|
+
def test_build_tester_registry(temp_repo):
|
|
479
|
+
"""
|
|
480
|
+
Test that registry --tester --build creates test registry from test files.
|
|
481
|
+
|
|
482
|
+
Given: User runs registry --tester --build
|
|
483
|
+
When: Registry builder executes
|
|
484
|
+
Then: Scans test files, extracts metadata, generates URNs, creates manifest
|
|
485
|
+
"""
|
|
486
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
487
|
+
|
|
488
|
+
# Create sample test file with URN marker
|
|
489
|
+
test_file = temp_repo / "atdd" / "tester" / "test_wagon" / "test_feature.py"
|
|
490
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
491
|
+
|
|
492
|
+
test_content = '''"""
|
|
493
|
+
Test feature.
|
|
494
|
+
|
|
495
|
+
URN: test:test-wagon:test_feature.py::test_feature_works
|
|
496
|
+
Spec: spec:test-wagon:feature
|
|
497
|
+
Acceptance: acceptance:test-wagon:EXEC001
|
|
498
|
+
"""
|
|
499
|
+
import pytest
|
|
500
|
+
|
|
501
|
+
def test_feature_works():
|
|
502
|
+
"""Test that feature works correctly."""
|
|
503
|
+
assert True
|
|
504
|
+
'''
|
|
505
|
+
|
|
506
|
+
test_file.write_text(test_content)
|
|
507
|
+
|
|
508
|
+
builder = RegistryBuilder(temp_repo)
|
|
509
|
+
|
|
510
|
+
with patch('builtins.input', return_value='yes'):
|
|
511
|
+
result = builder.build_tester(preview_only=False)
|
|
512
|
+
|
|
513
|
+
assert result["processed"] >= 1
|
|
514
|
+
|
|
515
|
+
# Verify registry created
|
|
516
|
+
test_registry = temp_repo / "atdd" / "tester" / "_tests.yaml"
|
|
517
|
+
assert test_registry.exists()
|
|
518
|
+
|
|
519
|
+
with open(test_registry) as f:
|
|
520
|
+
data = yaml.safe_load(f)
|
|
521
|
+
|
|
522
|
+
assert "tests" in data
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# SPEC-COACH-UTILS-0213
|
|
526
|
+
def test_registry_builder_dry_composition(temp_repo):
|
|
527
|
+
"""
|
|
528
|
+
Test that registry builder extracts common pattern for DRY composition.
|
|
529
|
+
|
|
530
|
+
Given: Existing registry_updater has similar update methods
|
|
531
|
+
When: Refactoring to unified registry system
|
|
532
|
+
Then: Extracts RegistryBuilder base class, shares common logic
|
|
533
|
+
"""
|
|
534
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
535
|
+
|
|
536
|
+
builder = RegistryBuilder(temp_repo)
|
|
537
|
+
|
|
538
|
+
# Verify builder has common methods
|
|
539
|
+
assert hasattr(builder, 'build_planner')
|
|
540
|
+
assert hasattr(builder, 'build_coder')
|
|
541
|
+
assert hasattr(builder, 'build_tester')
|
|
542
|
+
|
|
543
|
+
# Verify common preview/confirmation logic exists
|
|
544
|
+
assert hasattr(builder, '_print_change_report') or hasattr(builder, 'preview_changes')
|
|
545
|
+
assert hasattr(builder, '_detect_changes') or hasattr(builder, 'detect_changes')
|
|
546
|
+
|
|
547
|
+
# Each builder method should follow same pattern
|
|
548
|
+
# (scan, extract, build, preview, confirm, write)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# SPEC-COACH-UTILS-0214
|
|
552
|
+
def test_new_registries_follow_urn_conventions(temp_repo):
|
|
553
|
+
"""
|
|
554
|
+
Test that new registries follow existing URN conventions.
|
|
555
|
+
|
|
556
|
+
Given: Existing URN patterns in urn.py
|
|
557
|
+
When: Building new registries (tester, coder)
|
|
558
|
+
Then: Uses URNBuilder for URN generation and validation
|
|
559
|
+
"""
|
|
560
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
561
|
+
|
|
562
|
+
# Mock URNBuilder to verify it's used
|
|
563
|
+
with patch('atdd.coach.commands.registry.URNBuilder') as mock_urn:
|
|
564
|
+
mock_urn.test.return_value = "test:wagon:file::func"
|
|
565
|
+
mock_urn.impl.return_value = "impl:wagon:layer:comp:lang"
|
|
566
|
+
|
|
567
|
+
builder = RegistryBuilder(temp_repo)
|
|
568
|
+
|
|
569
|
+
# Should use URNBuilder for test URNs
|
|
570
|
+
test_urn = mock_urn.test("wagon", "file", "func")
|
|
571
|
+
assert test_urn.startswith("test:")
|
|
572
|
+
|
|
573
|
+
# Should use URNBuilder for impl URNs
|
|
574
|
+
impl_urn = mock_urn.impl("wagon", "layer", "comp", "lang")
|
|
575
|
+
assert impl_urn.startswith("impl:")
|