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,448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test contract and telemetry traceability reconciliation.
|
|
3
|
+
|
|
4
|
+
SPEC: .claude/agents/coach/utils.spec.yaml::traceability
|
|
5
|
+
IDs: SPEC-COACH-UTILS-0283 through SPEC-COACH-UTILS-0291
|
|
6
|
+
|
|
7
|
+
Validates:
|
|
8
|
+
- Detection of missing contract/telemetry references
|
|
9
|
+
- URN matching strategies (exact, normalized, path-based)
|
|
10
|
+
- Pragmatic fixes with user approval
|
|
11
|
+
- Bidirectional traceability validation
|
|
12
|
+
- Batch reconciliation reporting
|
|
13
|
+
- Clean 4-layer architecture
|
|
14
|
+
|
|
15
|
+
Architecture:
|
|
16
|
+
- Tests orchestrate command layer
|
|
17
|
+
- Command imports from atdd/coach/commands/traceability.py
|
|
18
|
+
- Leverages existing entities from atdd/planner/test_wagon_contract_traceability.py
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ============================================================================
|
|
27
|
+
# Test Fixtures
|
|
28
|
+
# ============================================================================
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def sample_wagon_manifest():
|
|
33
|
+
"""Sample wagon manifest with produce items."""
|
|
34
|
+
return {
|
|
35
|
+
'wagon': 'generate-identifiers',
|
|
36
|
+
'produce': [
|
|
37
|
+
{
|
|
38
|
+
'name': 'identifiers:uuid',
|
|
39
|
+
'urn': 'contract:system:identifiers:uuid',
|
|
40
|
+
'to': 'internal',
|
|
41
|
+
'contract': None, # Missing reference
|
|
42
|
+
'telemetry': None
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
'name': 'identifiers:username',
|
|
46
|
+
'urn': 'contract:system:identifiers:username',
|
|
47
|
+
'to': 'internal',
|
|
48
|
+
'contract': None, # Missing reference
|
|
49
|
+
'telemetry': None
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def sample_contract_file():
|
|
57
|
+
"""Sample contract file metadata."""
|
|
58
|
+
from atdd.coach.commands.traceability import ContractFile
|
|
59
|
+
|
|
60
|
+
return ContractFile(
|
|
61
|
+
file_path='contracts/commons/identifiers/uuid.schema.json',
|
|
62
|
+
contract_id='system:identifiers.uuid',
|
|
63
|
+
domain='system',
|
|
64
|
+
resource='identifiers.uuid',
|
|
65
|
+
version='1.0.0',
|
|
66
|
+
producer='wagon:generate-identifiers',
|
|
67
|
+
consumers=['wagon:register-account']
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ============================================================================
|
|
72
|
+
# SPEC-COACH-UTILS-0283: Detect missing contract references
|
|
73
|
+
# ============================================================================
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.platform
|
|
77
|
+
def test_detect_missing_contract_references(sample_wagon_manifest, sample_contract_file):
|
|
78
|
+
"""
|
|
79
|
+
SPEC-COACH-UTILS-0283: Detect missing contract references in wagon manifests.
|
|
80
|
+
|
|
81
|
+
Given: Wagon manifest declares produce item with URN 'contract:system:identifiers:uuid'
|
|
82
|
+
And: Contract file 'contracts/commons/identifiers/uuid.schema.json' exists
|
|
83
|
+
And: Manifest has 'contract: null' for this produce item
|
|
84
|
+
When: Running traceability reconciliation
|
|
85
|
+
Then: Detects missing contract reference
|
|
86
|
+
And: Proposes fix with actual contract file path
|
|
87
|
+
And: Shows user the proposed change for approval
|
|
88
|
+
"""
|
|
89
|
+
from atdd.coach.commands.traceability import TraceabilityReconciler
|
|
90
|
+
|
|
91
|
+
# This will fail until implementation exists
|
|
92
|
+
reconciler = TraceabilityReconciler()
|
|
93
|
+
|
|
94
|
+
# Parse produce items
|
|
95
|
+
produce_items = reconciler.parse_produce_items(sample_wagon_manifest)
|
|
96
|
+
|
|
97
|
+
# Find contracts
|
|
98
|
+
contracts = [sample_contract_file]
|
|
99
|
+
|
|
100
|
+
# Detect missing references
|
|
101
|
+
missing_refs = reconciler.detect_missing_contract_refs(produce_items, contracts)
|
|
102
|
+
|
|
103
|
+
assert len(missing_refs) > 0
|
|
104
|
+
assert missing_refs[0]['urn'] == 'contract:system:identifiers:uuid'
|
|
105
|
+
assert missing_refs[0]['proposed_fix'] == 'contracts/commons/identifiers/uuid.schema.json'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ============================================================================
|
|
109
|
+
# SPEC-COACH-UTILS-0284: Reconcile contract URN variations
|
|
110
|
+
# ============================================================================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.mark.platform
|
|
114
|
+
def test_reconcile_contract_urn_variations():
|
|
115
|
+
"""
|
|
116
|
+
SPEC-COACH-UTILS-0284: Reconcile contract URN with actual file using multiple matching strategies.
|
|
117
|
+
|
|
118
|
+
Given: Wagon produce URN 'contract:system:identifiers:uuid'
|
|
119
|
+
And: Contract file with '$id' field 'system:identifiers.uuid' (dot notation)
|
|
120
|
+
And: Contract file path 'contracts/commons/identifiers/uuid.schema.json'
|
|
121
|
+
When: Matching URN to contract file
|
|
122
|
+
Then: Tries exact match on $id
|
|
123
|
+
And: Tries normalized match (colon vs dot variations)
|
|
124
|
+
And: Tries path-based match
|
|
125
|
+
And: Returns matching contract file
|
|
126
|
+
"""
|
|
127
|
+
from atdd.coach.commands.traceability import ContractMatcher
|
|
128
|
+
|
|
129
|
+
matcher = ContractMatcher()
|
|
130
|
+
|
|
131
|
+
urn = 'contract:system:identifiers:uuid'
|
|
132
|
+
contracts = [
|
|
133
|
+
{
|
|
134
|
+
'file_path': 'contracts/commons/identifiers/uuid.schema.json',
|
|
135
|
+
'contract_id': 'system:identifiers.uuid'
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
# Should match despite dot vs colon notation
|
|
140
|
+
matched = matcher.find_by_urn(urn, contracts)
|
|
141
|
+
|
|
142
|
+
assert matched is not None
|
|
143
|
+
assert matched['file_path'] == 'contracts/commons/identifiers/uuid.schema.json'
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ============================================================================
|
|
147
|
+
# SPEC-COACH-UTILS-0285: Propose and apply fix
|
|
148
|
+
# ============================================================================
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.platform
|
|
152
|
+
def test_propose_and_apply_contract_fix(tmp_path):
|
|
153
|
+
"""
|
|
154
|
+
SPEC-COACH-UTILS-0285: Propose fix for missing contract reference with user approval.
|
|
155
|
+
|
|
156
|
+
Given: Missing contract reference detected
|
|
157
|
+
And: Contract file path 'contracts/commons/identifiers/uuid.schema.json'
|
|
158
|
+
And: Wagon manifest at 'plan/generate_identifiers/_generate_identifiers.yaml'
|
|
159
|
+
When: User approves fix
|
|
160
|
+
Then: Updates wagon manifest YAML file
|
|
161
|
+
And: Changes 'contract: null' to 'contract: contracts/commons/identifiers/uuid.schema.json'
|
|
162
|
+
And: Preserves YAML formatting and structure
|
|
163
|
+
And: Logs change to facts/audit.log
|
|
164
|
+
"""
|
|
165
|
+
from atdd.coach.commands.traceability import TraceabilityFixer
|
|
166
|
+
|
|
167
|
+
# Create temporary manifest file
|
|
168
|
+
manifest_file = tmp_path / "test_manifest.yaml"
|
|
169
|
+
manifest_content = """wagon: generate-identifiers
|
|
170
|
+
produce:
|
|
171
|
+
- name: identifiers:uuid
|
|
172
|
+
urn: contract:system:identifiers:uuid
|
|
173
|
+
to: internal
|
|
174
|
+
contract: null
|
|
175
|
+
telemetry: null
|
|
176
|
+
"""
|
|
177
|
+
manifest_file.write_text(manifest_content)
|
|
178
|
+
|
|
179
|
+
fixer = TraceabilityFixer()
|
|
180
|
+
|
|
181
|
+
# Apply fix
|
|
182
|
+
fix_applied = fixer.apply_contract_fix(
|
|
183
|
+
manifest_path=str(manifest_file),
|
|
184
|
+
produce_name='identifiers:uuid',
|
|
185
|
+
contract_path='contracts/commons/identifiers/uuid.schema.json'
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
assert fix_applied is True
|
|
189
|
+
|
|
190
|
+
# Verify file was updated
|
|
191
|
+
updated_content = manifest_file.read_text()
|
|
192
|
+
assert 'contract: contracts/commons/identifiers/uuid.schema.json' in updated_content
|
|
193
|
+
assert 'contract: null' not in updated_content
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ============================================================================
|
|
197
|
+
# SPEC-COACH-UTILS-0286: Validate bidirectional traceability
|
|
198
|
+
# ============================================================================
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@pytest.mark.platform
|
|
202
|
+
def test_validate_bidirectional_traceability():
|
|
203
|
+
"""
|
|
204
|
+
SPEC-COACH-UTILS-0286: Validate bidirectional traceability between wagon and contract.
|
|
205
|
+
|
|
206
|
+
Given: Wagon 'generate-identifiers' declares producing 'contract:system:identifiers:uuid'
|
|
207
|
+
And: Contract file has 'x-artifact-metadata.producer: wagon:generate-identifiers'
|
|
208
|
+
When: Validating bidirectional references
|
|
209
|
+
Then: Confirms wagon->contract reference exists
|
|
210
|
+
And: Confirms contract->wagon reference matches
|
|
211
|
+
And: Reports as fully traced
|
|
212
|
+
"""
|
|
213
|
+
from atdd.coach.commands.traceability import TraceabilityValidator
|
|
214
|
+
|
|
215
|
+
validator = TraceabilityValidator()
|
|
216
|
+
|
|
217
|
+
produce_item = {
|
|
218
|
+
'wagon': 'generate-identifiers',
|
|
219
|
+
'urn': 'contract:system:identifiers:uuid'
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
contract = {
|
|
223
|
+
'file_path': 'contracts/commons/identifiers/uuid.schema.json',
|
|
224
|
+
'producer': 'wagon:generate-identifiers'
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
is_bidirectional = validator.validate_bidirectional(produce_item, contract)
|
|
228
|
+
|
|
229
|
+
assert is_bidirectional is True
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ============================================================================
|
|
233
|
+
# SPEC-COACH-UTILS-0287: Detect mismatched producer
|
|
234
|
+
# ============================================================================
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@pytest.mark.platform
|
|
238
|
+
def test_detect_mismatched_producer():
|
|
239
|
+
"""
|
|
240
|
+
SPEC-COACH-UTILS-0287: Detect mismatched producer in contract metadata.
|
|
241
|
+
|
|
242
|
+
Given: Wagon 'generate-identifiers' declares producing 'contract:system:identifiers:uuid'
|
|
243
|
+
And: Contract file has 'x-artifact-metadata.producer: wagon:other-wagon'
|
|
244
|
+
When: Validating producer consistency
|
|
245
|
+
Then: Detects mismatch between wagon declaration and contract metadata
|
|
246
|
+
And: Reports as traceability violation
|
|
247
|
+
And: Proposes correction to either wagon or contract
|
|
248
|
+
"""
|
|
249
|
+
from atdd.coach.commands.traceability import TraceabilityValidator
|
|
250
|
+
|
|
251
|
+
validator = TraceabilityValidator()
|
|
252
|
+
|
|
253
|
+
produce_item = {
|
|
254
|
+
'wagon': 'generate-identifiers',
|
|
255
|
+
'urn': 'contract:system:identifiers:uuid'
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
contract = {
|
|
259
|
+
'file_path': 'contracts/commons/identifiers/uuid.schema.json',
|
|
260
|
+
'producer': 'wagon:other-wagon' # Mismatch!
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
mismatch = validator.check_producer_match(produce_item, contract)
|
|
264
|
+
|
|
265
|
+
assert mismatch is not None
|
|
266
|
+
assert mismatch['expected'] == 'wagon:generate-identifiers'
|
|
267
|
+
assert mismatch['actual'] == 'wagon:other-wagon'
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ============================================================================
|
|
271
|
+
# SPEC-COACH-UTILS-0288: Batch reconciliation report
|
|
272
|
+
# ============================================================================
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@pytest.mark.platform
|
|
276
|
+
def test_batch_reconciliation_report():
|
|
277
|
+
"""
|
|
278
|
+
SPEC-COACH-UTILS-0288: Batch reconciliation report for all wagons.
|
|
279
|
+
|
|
280
|
+
Given: Multiple wagon manifests in plan/ directory
|
|
281
|
+
And: Multiple contract files in contracts/ directory
|
|
282
|
+
And: Some with missing references, some with mismatches
|
|
283
|
+
When: Running full repository reconciliation
|
|
284
|
+
Then: Scans all wagon manifests
|
|
285
|
+
And: Scans all contract files
|
|
286
|
+
And: Groups issues by wagon
|
|
287
|
+
And: Shows statistics (total issues, by type)
|
|
288
|
+
And: Provides prioritized fix list
|
|
289
|
+
"""
|
|
290
|
+
from atdd.coach.commands.traceability import TraceabilityReconciler
|
|
291
|
+
|
|
292
|
+
reconciler = TraceabilityReconciler()
|
|
293
|
+
|
|
294
|
+
# This will scan the actual repo
|
|
295
|
+
report = reconciler.reconcile_all()
|
|
296
|
+
|
|
297
|
+
# reconcile_all() returns ReconciliationResult object
|
|
298
|
+
assert hasattr(report, 'total_issues')
|
|
299
|
+
assert hasattr(report, 'missing_contract_refs')
|
|
300
|
+
assert hasattr(report, 'by_wagon')
|
|
301
|
+
assert isinstance(report.total_issues, int)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# ============================================================================
|
|
305
|
+
# SPEC-COACH-UTILS-0289: Support telemetry reconciliation
|
|
306
|
+
# ============================================================================
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@pytest.mark.platform
|
|
310
|
+
def test_detect_missing_telemetry_references():
|
|
311
|
+
"""
|
|
312
|
+
SPEC-COACH-UTILS-0289: Support telemetry reference reconciliation.
|
|
313
|
+
|
|
314
|
+
Given: Wagon manifest declares produce item with telemetry URN
|
|
315
|
+
And: Telemetry file exists in telemetry/ directory
|
|
316
|
+
And: Manifest has 'telemetry: null' for this produce item
|
|
317
|
+
When: Running traceability reconciliation
|
|
318
|
+
Then: Detects missing telemetry reference
|
|
319
|
+
And: Proposes fix with actual telemetry file path
|
|
320
|
+
And: Applies same reconciliation logic as contracts
|
|
321
|
+
"""
|
|
322
|
+
from atdd.coach.commands.traceability import TraceabilityReconciler
|
|
323
|
+
|
|
324
|
+
reconciler = TraceabilityReconciler()
|
|
325
|
+
|
|
326
|
+
wagon_manifest = {
|
|
327
|
+
'wagon': 'generate-identifiers',
|
|
328
|
+
'produce': [
|
|
329
|
+
{
|
|
330
|
+
'name': 'uuid-generation-metrics',
|
|
331
|
+
'urn': 'telemetry:metric:be:uuid:generation:duration',
|
|
332
|
+
'to': 'internal',
|
|
333
|
+
'contract': None,
|
|
334
|
+
'telemetry': None # Missing reference
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
from atdd.coach.commands.traceability import TelemetryFile
|
|
340
|
+
|
|
341
|
+
telemetry_files = [
|
|
342
|
+
TelemetryFile(
|
|
343
|
+
file_path='telemetry/metrics/be/uuid/generation_duration.yaml',
|
|
344
|
+
telemetry_id='telemetry:metric:be:uuid:generation:duration',
|
|
345
|
+
domain='metric',
|
|
346
|
+
resource='be.uuid.generation.duration',
|
|
347
|
+
producer='wagon:generate-identifiers'
|
|
348
|
+
)
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
produce_items = reconciler.parse_produce_items(wagon_manifest)
|
|
352
|
+
missing_refs = reconciler.detect_missing_telemetry_refs(produce_items, telemetry_files)
|
|
353
|
+
|
|
354
|
+
assert len(missing_refs) > 0
|
|
355
|
+
assert missing_refs[0]['proposed_fix'] == 'telemetry/metrics/be/uuid/generation_duration.yaml'
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# ============================================================================
|
|
359
|
+
# SPEC-COACH-UTILS-0290: Leverage existing test diagnostics
|
|
360
|
+
# ============================================================================
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@pytest.mark.platform
|
|
364
|
+
def test_leverage_existing_test_diagnostics():
|
|
365
|
+
"""
|
|
366
|
+
SPEC-COACH-UTILS-0290: Leverage existing test diagnostics from test_wagon_contract_traceability.
|
|
367
|
+
|
|
368
|
+
Given: Existing test file 'atdd/planner/test_wagon_contract_traceability.py'
|
|
369
|
+
And: Test provides detailed reconciliation entities and use cases
|
|
370
|
+
When: Running traceability command
|
|
371
|
+
Then: Imports and uses WagonManifest, ContractFile, ProduceItem entities
|
|
372
|
+
And: Imports and uses ManifestParser, ContractFinder use cases
|
|
373
|
+
And: Avoids code duplication
|
|
374
|
+
And: Reuses proven diagnostic logic
|
|
375
|
+
"""
|
|
376
|
+
# Verify we can import from existing test file
|
|
377
|
+
# This demonstrates code reuse
|
|
378
|
+
try:
|
|
379
|
+
# The implementation should import from this module
|
|
380
|
+
from atdd.coach.commands.traceability import TraceabilityReconciler
|
|
381
|
+
|
|
382
|
+
# Verify the reconciler uses shared entities
|
|
383
|
+
reconciler = TraceabilityReconciler()
|
|
384
|
+
|
|
385
|
+
# Check that it has methods that align with existing diagnostic logic
|
|
386
|
+
assert hasattr(reconciler, 'parse_produce_items')
|
|
387
|
+
assert hasattr(reconciler, 'detect_missing_contract_refs')
|
|
388
|
+
|
|
389
|
+
except ImportError:
|
|
390
|
+
pytest.fail("Implementation should import from test diagnostic module")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# ============================================================================
|
|
394
|
+
# SPEC-COACH-UTILS-0291: Clean 4-layer architecture
|
|
395
|
+
# ============================================================================
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@pytest.mark.platform
|
|
399
|
+
def test_clean_architecture_layers():
|
|
400
|
+
"""
|
|
401
|
+
SPEC-COACH-UTILS-0291: Clean 4-layer architecture with entities, use cases, adapters, commands.
|
|
402
|
+
|
|
403
|
+
Given: Traceability command implementation
|
|
404
|
+
When: Refactoring for clean architecture
|
|
405
|
+
Then: Layer 1 (Entities) - ProduceItem, ContractFile, ReconciliationResult
|
|
406
|
+
And: Layer 2 (Use Cases) - ManifestParser, ContractFinder, TraceabilityReconciler
|
|
407
|
+
And: Layer 3 (Adapters) - ReportFormatter, YAMLUpdater
|
|
408
|
+
And: Layer 4 (Command) - CLI entry point, user interaction, orchestration
|
|
409
|
+
And: Each layer depends only on inner layers
|
|
410
|
+
And: Business logic isolated from I/O
|
|
411
|
+
"""
|
|
412
|
+
from atdd.coach.commands.traceability import (
|
|
413
|
+
# Layer 1: Entities
|
|
414
|
+
ProduceItem,
|
|
415
|
+
ContractFile,
|
|
416
|
+
ReconciliationResult,
|
|
417
|
+
|
|
418
|
+
# Layer 2: Use Cases
|
|
419
|
+
ManifestParser,
|
|
420
|
+
ContractFinder,
|
|
421
|
+
TraceabilityReconciler,
|
|
422
|
+
|
|
423
|
+
# Layer 3: Adapters
|
|
424
|
+
ReportFormatter,
|
|
425
|
+
YAMLUpdater
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Verify entities are dataclasses (immutable domain models)
|
|
429
|
+
assert hasattr(ProduceItem, '__dataclass_fields__')
|
|
430
|
+
assert hasattr(ContractFile, '__dataclass_fields__')
|
|
431
|
+
assert hasattr(ReconciliationResult, '__dataclass_fields__')
|
|
432
|
+
|
|
433
|
+
# Verify use cases have business logic methods
|
|
434
|
+
parser = ManifestParser()
|
|
435
|
+
assert callable(getattr(parser, 'parse_manifest', None))
|
|
436
|
+
|
|
437
|
+
finder = ContractFinder()
|
|
438
|
+
assert callable(getattr(finder, 'find_by_urn', None))
|
|
439
|
+
|
|
440
|
+
reconciler = TraceabilityReconciler()
|
|
441
|
+
assert callable(getattr(reconciler, 'reconcile_all', None))
|
|
442
|
+
|
|
443
|
+
# Verify adapters have presentation/I/O methods
|
|
444
|
+
formatter = ReportFormatter()
|
|
445
|
+
assert callable(getattr(formatter, 'format_report', None))
|
|
446
|
+
|
|
447
|
+
updater = YAMLUpdater()
|
|
448
|
+
assert callable(getattr(updater, 'update_yaml_field', None))
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPEC-COACH-UTILS-0291: Add implementation paths array to feature manifest files
|
|
3
|
+
|
|
4
|
+
Tests updating feature manifests with implementation paths from filesystem.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
import yaml
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_update_feature_implementation_paths(tmp_path):
|
|
12
|
+
"""
|
|
13
|
+
SPEC-COACH-UTILS-0291: Add implementation paths array to feature manifest files
|
|
14
|
+
|
|
15
|
+
Given: Feature manifests exist at plan/{wagon_snake}/features/{feature_snake}.yaml
|
|
16
|
+
Features have URNs in format feature:wagon-slug.feature-slug
|
|
17
|
+
Implementation directories may exist in python/, lib/, supabase/functions/, packages/
|
|
18
|
+
Filesystem uses snake_case for directory names
|
|
19
|
+
When: Updating feature manifests with implementation paths
|
|
20
|
+
Then: Each feature manifest gets a paths array field
|
|
21
|
+
paths array contains only existing implementation directories
|
|
22
|
+
Path format is language_dir/wagon_snake/feature_snake/
|
|
23
|
+
URN kebab-case is converted to filesystem snake_case
|
|
24
|
+
Checks these locations python/, lib/, supabase/functions/, packages/
|
|
25
|
+
Empty array if no implementations exist
|
|
26
|
+
Feature manifests are updated in place
|
|
27
|
+
YAML structure and formatting preserved
|
|
28
|
+
"""
|
|
29
|
+
# Setup directory structure
|
|
30
|
+
plan_dir = tmp_path / "plan"
|
|
31
|
+
plan_dir.mkdir()
|
|
32
|
+
|
|
33
|
+
# Create wagon directory with features subdirectory
|
|
34
|
+
wagon_dir = plan_dir / "test_wagon"
|
|
35
|
+
wagon_dir.mkdir()
|
|
36
|
+
features_dir = wagon_dir / "features"
|
|
37
|
+
features_dir.mkdir()
|
|
38
|
+
|
|
39
|
+
# Create feature manifest
|
|
40
|
+
feature_manifest = features_dir / "test_feature.yaml"
|
|
41
|
+
feature_data = {
|
|
42
|
+
"feature": "test-feature",
|
|
43
|
+
"urn": "feature:test-wagon.test-feature",
|
|
44
|
+
"description": "Test feature for path updates",
|
|
45
|
+
"acceptance": []
|
|
46
|
+
}
|
|
47
|
+
with open(feature_manifest, 'w') as f:
|
|
48
|
+
yaml.dump(feature_data, f, default_flow_style=False, sort_keys=False)
|
|
49
|
+
|
|
50
|
+
# Create another feature manifest with no implementations
|
|
51
|
+
feature_no_impl = features_dir / "no_impl_feature.yaml"
|
|
52
|
+
feature_no_impl_data = {
|
|
53
|
+
"feature": "no-impl-feature",
|
|
54
|
+
"urn": "feature:test-wagon.no-impl-feature",
|
|
55
|
+
"description": "Feature with no implementations",
|
|
56
|
+
"acceptance": []
|
|
57
|
+
}
|
|
58
|
+
with open(feature_no_impl, 'w') as f:
|
|
59
|
+
yaml.dump(feature_no_impl_data, f, default_flow_style=False, sort_keys=False)
|
|
60
|
+
|
|
61
|
+
# Create some implementation directories (snake_case)
|
|
62
|
+
# Python implementation exists
|
|
63
|
+
python_dir = tmp_path / "python" / "test_wagon" / "test_feature"
|
|
64
|
+
python_dir.mkdir(parents=True)
|
|
65
|
+
|
|
66
|
+
# Dart lib implementation exists
|
|
67
|
+
lib_dir = tmp_path / "lib" / "test_wagon" / "test_feature"
|
|
68
|
+
lib_dir.mkdir(parents=True)
|
|
69
|
+
|
|
70
|
+
# Supabase functions implementation DOES NOT exist (intentionally)
|
|
71
|
+
# packages implementation DOES NOT exist (intentionally)
|
|
72
|
+
|
|
73
|
+
# Import and call the update function
|
|
74
|
+
from atdd.coach.commands.registry import RegistryBuilder
|
|
75
|
+
|
|
76
|
+
builder = RegistryBuilder(tmp_path)
|
|
77
|
+
builder.update_feature_implementation_paths()
|
|
78
|
+
|
|
79
|
+
# Load the updated feature manifest
|
|
80
|
+
with open(feature_manifest, 'r') as f:
|
|
81
|
+
updated_data = yaml.safe_load(f)
|
|
82
|
+
|
|
83
|
+
# Assertions for test_feature
|
|
84
|
+
assert "paths" in updated_data, "Feature manifest should have paths field"
|
|
85
|
+
assert isinstance(updated_data["paths"], list), "paths should be an array"
|
|
86
|
+
assert len(updated_data["paths"]) == 2, "Should have 2 implementation paths (python and lib)"
|
|
87
|
+
|
|
88
|
+
expected_python_path = "python/test_wagon/test_feature/"
|
|
89
|
+
expected_lib_path = "lib/test_wagon/test_feature/"
|
|
90
|
+
|
|
91
|
+
assert expected_python_path in updated_data["paths"], f"Should contain {expected_python_path}"
|
|
92
|
+
assert expected_lib_path in updated_data["paths"], f"Should contain {expected_lib_path}"
|
|
93
|
+
|
|
94
|
+
# Should NOT contain paths that don't exist
|
|
95
|
+
assert "supabase/functions/test_wagon/test_feature/" not in updated_data["paths"]
|
|
96
|
+
assert "packages/test_wagon/test_feature/" not in updated_data["paths"]
|
|
97
|
+
|
|
98
|
+
# Other fields should remain unchanged
|
|
99
|
+
assert updated_data["feature"] == "test-feature"
|
|
100
|
+
assert updated_data["urn"] == "feature:test-wagon.test-feature"
|
|
101
|
+
assert updated_data["description"] == "Test feature for path updates"
|
|
102
|
+
|
|
103
|
+
# Check feature with no implementations
|
|
104
|
+
with open(feature_no_impl, 'r') as f:
|
|
105
|
+
no_impl_data = yaml.safe_load(f)
|
|
106
|
+
|
|
107
|
+
assert "paths" in no_impl_data, "Feature with no impls should still have paths field"
|
|
108
|
+
assert no_impl_data["paths"] == [], "Should have empty paths array when no implementations exist"
|