atdd 0.6.0__py3-none-any.whl → 0.7.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/coach/schemas/config.schema.json +11 -0
- atdd/coach/templates/ATDD.md +1 -1
- atdd/coach/utils/locale_phase.py +97 -0
- atdd/coach/validators/shared_fixtures.py +49 -0
- atdd/coder/conventions/backend.convention.yaml +1 -1
- atdd/coder/conventions/boundaries.convention.yaml +9 -9
- atdd/coder/conventions/presentation.convention.yaml +8 -8
- atdd/coder/conventions/train.convention.yaml +15 -14
- atdd/coder/validators/test_i18n_runtime.py +171 -0
- atdd/coder/validators/test_presentation_convention.py +11 -11
- atdd/coder/validators/test_station_master_pattern.py +16 -14
- atdd/coder/validators/test_train_infrastructure.py +22 -14
- atdd/coder/validators/test_wagon_boundaries.py +2 -2
- atdd/tester/schemas/locale_manifest.schema.json +53 -0
- atdd/tester/validators/test_locale_coverage.py +451 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/METADATA +1 -1
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/RECORD +21 -17
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/WHEEL +0 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/entry_points.txt +0 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {atdd-0.6.0.dist-info → atdd-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Localization coverage validation (Localization Manifest Spec v1).
|
|
3
|
+
|
|
4
|
+
Validates locale files against manifest.json as single source of truth:
|
|
5
|
+
- LOCALE-TEST-1.1: Manifest schema compliance
|
|
6
|
+
- LOCALE-TEST-1.2: All locale/namespace files exist
|
|
7
|
+
- LOCALE-TEST-1.3: All files are valid JSON
|
|
8
|
+
- LOCALE-TEST-1.4: Keys match reference locale (deep comparison)
|
|
9
|
+
- LOCALE-TEST-1.5: Types match reference (object/array/primitive)
|
|
10
|
+
- LOCALE-TEST-1.6: Optional namespaces may be missing
|
|
11
|
+
- LOCALE-TEST-1.7: languageNames.<locale> exists in reference ui.json
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import pytest
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
18
|
+
|
|
19
|
+
import atdd
|
|
20
|
+
from atdd.coach.utils.locale_phase import (
|
|
21
|
+
LocalePhase,
|
|
22
|
+
should_enforce_locale,
|
|
23
|
+
emit_locale_warning,
|
|
24
|
+
)
|
|
25
|
+
from atdd.coach.utils.repo import find_repo_root
|
|
26
|
+
|
|
27
|
+
# Path constants
|
|
28
|
+
REPO_ROOT = find_repo_root()
|
|
29
|
+
ATDD_PKG_DIR = Path(atdd.__file__).resolve().parent
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_json_file(path: Path) -> Tuple[Optional[Dict], Optional[str]]:
|
|
33
|
+
"""Load JSON file, returning (data, error_message)."""
|
|
34
|
+
try:
|
|
35
|
+
with open(path) as f:
|
|
36
|
+
return json.load(f), None
|
|
37
|
+
except json.JSONDecodeError as e:
|
|
38
|
+
return None, f"Invalid JSON: {e}"
|
|
39
|
+
except Exception as e:
|
|
40
|
+
return None, str(e)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_all_keys(obj: Any, prefix: str = "") -> Set[str]:
|
|
44
|
+
"""
|
|
45
|
+
Extract all keys from nested object using dot notation.
|
|
46
|
+
Ignores keys starting with underscore (private/metadata keys).
|
|
47
|
+
"""
|
|
48
|
+
keys = set()
|
|
49
|
+
if isinstance(obj, dict):
|
|
50
|
+
for key, value in obj.items():
|
|
51
|
+
if key.startswith("_"):
|
|
52
|
+
continue
|
|
53
|
+
full_key = f"{prefix}.{key}" if prefix else key
|
|
54
|
+
keys.add(full_key)
|
|
55
|
+
keys.update(_get_all_keys(value, full_key))
|
|
56
|
+
return keys
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_type_signature(obj: Any) -> str:
|
|
60
|
+
"""Get type signature for comparison (object/array/primitive)."""
|
|
61
|
+
if isinstance(obj, dict):
|
|
62
|
+
return "object"
|
|
63
|
+
elif isinstance(obj, list):
|
|
64
|
+
return f"array[{len(obj)}]"
|
|
65
|
+
elif isinstance(obj, bool):
|
|
66
|
+
return "boolean"
|
|
67
|
+
elif isinstance(obj, int):
|
|
68
|
+
return "number"
|
|
69
|
+
elif isinstance(obj, float):
|
|
70
|
+
return "number"
|
|
71
|
+
elif isinstance(obj, str):
|
|
72
|
+
return "string"
|
|
73
|
+
elif obj is None:
|
|
74
|
+
return "null"
|
|
75
|
+
return "unknown"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _compare_types(ref_obj: Any, target_obj: Any, path: str = "") -> List[str]:
|
|
79
|
+
"""
|
|
80
|
+
Compare types between reference and target objects recursively.
|
|
81
|
+
Returns list of type mismatch descriptions.
|
|
82
|
+
"""
|
|
83
|
+
mismatches = []
|
|
84
|
+
ref_type = _get_type_signature(ref_obj)
|
|
85
|
+
target_type = _get_type_signature(target_obj)
|
|
86
|
+
|
|
87
|
+
if ref_type.startswith("array") and target_type.startswith("array"):
|
|
88
|
+
pass
|
|
89
|
+
elif ref_type != target_type:
|
|
90
|
+
key_display = path or "(root)"
|
|
91
|
+
mismatches.append(f"{key_display}: expected {ref_type}, got {target_type}")
|
|
92
|
+
|
|
93
|
+
if isinstance(ref_obj, dict) and isinstance(target_obj, dict):
|
|
94
|
+
for key in ref_obj:
|
|
95
|
+
if key.startswith("_"):
|
|
96
|
+
continue
|
|
97
|
+
if key in target_obj:
|
|
98
|
+
child_path = f"{path}.{key}" if path else key
|
|
99
|
+
mismatches.extend(_compare_types(ref_obj[key], target_obj[key], child_path))
|
|
100
|
+
|
|
101
|
+
return mismatches
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.locale
|
|
105
|
+
def test_locale_manifest_schema_compliance(locale_manifest, locale_manifest_path, load_schema):
|
|
106
|
+
"""
|
|
107
|
+
LOCALE-TEST-1.1: Manifest schema compliance
|
|
108
|
+
|
|
109
|
+
Given: localization.manifest configured in .atdd/config.yaml
|
|
110
|
+
When: Loading the manifest file
|
|
111
|
+
Then: File exists and validates against locale_manifest.schema.json
|
|
112
|
+
"""
|
|
113
|
+
if locale_manifest_path is None:
|
|
114
|
+
pytest.skip("Localization not configured (localization.manifest not in config)")
|
|
115
|
+
|
|
116
|
+
if not locale_manifest_path.exists():
|
|
117
|
+
msg = f"Manifest file not found: {locale_manifest_path.relative_to(REPO_ROOT)}"
|
|
118
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
119
|
+
pytest.fail(msg)
|
|
120
|
+
else:
|
|
121
|
+
emit_locale_warning("LOCALE-TEST-1.1", msg)
|
|
122
|
+
pytest.skip(msg)
|
|
123
|
+
|
|
124
|
+
schema = load_schema("tester", "locale_manifest.schema.json")
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
import jsonschema
|
|
128
|
+
jsonschema.validate(locale_manifest, schema)
|
|
129
|
+
except jsonschema.ValidationError as e:
|
|
130
|
+
msg = f"Manifest schema validation failed: {e.message}"
|
|
131
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
132
|
+
pytest.fail(msg)
|
|
133
|
+
else:
|
|
134
|
+
emit_locale_warning("LOCALE-TEST-1.1", msg)
|
|
135
|
+
pytest.skip(msg)
|
|
136
|
+
|
|
137
|
+
if locale_manifest["reference"] not in locale_manifest["locales"]:
|
|
138
|
+
msg = f"Reference locale '{locale_manifest['reference']}' not in locales list"
|
|
139
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
140
|
+
pytest.fail(msg)
|
|
141
|
+
else:
|
|
142
|
+
emit_locale_warning("LOCALE-TEST-1.1", msg)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.mark.locale
|
|
146
|
+
def test_locale_files_exist(locale_manifest, locales_dir):
|
|
147
|
+
"""
|
|
148
|
+
LOCALE-TEST-1.2: All <locale>/<namespace>.json files exist
|
|
149
|
+
|
|
150
|
+
Given: Manifest with locales and namespaces
|
|
151
|
+
When: Checking file system
|
|
152
|
+
Then: All required namespace files exist for all locales
|
|
153
|
+
"""
|
|
154
|
+
if locale_manifest is None:
|
|
155
|
+
pytest.skip("Localization not configured")
|
|
156
|
+
|
|
157
|
+
missing_files = []
|
|
158
|
+
locales = locale_manifest.get("locales", [])
|
|
159
|
+
namespaces = locale_manifest.get("namespaces", [])
|
|
160
|
+
|
|
161
|
+
for locale in locales:
|
|
162
|
+
locale_dir = locales_dir / locale
|
|
163
|
+
for namespace in namespaces:
|
|
164
|
+
file_path = locale_dir / f"{namespace}.json"
|
|
165
|
+
if not file_path.exists():
|
|
166
|
+
missing_files.append(f"{locale}/{namespace}.json")
|
|
167
|
+
|
|
168
|
+
if missing_files:
|
|
169
|
+
msg = f"Missing locale files ({len(missing_files)}):\n" + "\n".join(f" - {f}" for f in missing_files[:20])
|
|
170
|
+
if len(missing_files) > 20:
|
|
171
|
+
msg += f"\n ... and {len(missing_files) - 20} more"
|
|
172
|
+
|
|
173
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
174
|
+
pytest.fail(msg)
|
|
175
|
+
else:
|
|
176
|
+
emit_locale_warning("LOCALE-TEST-1.2", msg)
|
|
177
|
+
pytest.skip(msg)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@pytest.mark.locale
|
|
181
|
+
def test_locale_files_valid_json(locale_manifest, locales_dir):
|
|
182
|
+
"""
|
|
183
|
+
LOCALE-TEST-1.3: All locale files are valid JSON
|
|
184
|
+
|
|
185
|
+
Given: Locale namespace files
|
|
186
|
+
When: Parsing as JSON
|
|
187
|
+
Then: All files parse without errors
|
|
188
|
+
"""
|
|
189
|
+
if locale_manifest is None:
|
|
190
|
+
pytest.skip("Localization not configured")
|
|
191
|
+
|
|
192
|
+
invalid_files = []
|
|
193
|
+
locales = locale_manifest.get("locales", [])
|
|
194
|
+
namespaces = locale_manifest.get("namespaces", [])
|
|
195
|
+
optional_namespaces = locale_manifest.get("optional_namespaces", [])
|
|
196
|
+
|
|
197
|
+
all_namespaces = namespaces + optional_namespaces
|
|
198
|
+
|
|
199
|
+
for locale in locales:
|
|
200
|
+
locale_dir = locales_dir / locale
|
|
201
|
+
for namespace in all_namespaces:
|
|
202
|
+
file_path = locale_dir / f"{namespace}.json"
|
|
203
|
+
if file_path.exists():
|
|
204
|
+
_, error = _load_json_file(file_path)
|
|
205
|
+
if error:
|
|
206
|
+
invalid_files.append(f"{locale}/{namespace}.json: {error}")
|
|
207
|
+
|
|
208
|
+
if invalid_files:
|
|
209
|
+
msg = f"Invalid JSON files ({len(invalid_files)}):\n" + "\n".join(f" - {f}" for f in invalid_files[:10])
|
|
210
|
+
if len(invalid_files) > 10:
|
|
211
|
+
msg += f"\n ... and {len(invalid_files) - 10} more"
|
|
212
|
+
|
|
213
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
214
|
+
pytest.fail(msg)
|
|
215
|
+
else:
|
|
216
|
+
emit_locale_warning("LOCALE-TEST-1.3", msg)
|
|
217
|
+
pytest.skip(msg)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@pytest.mark.locale
|
|
221
|
+
def test_locale_keys_match_reference(locale_manifest, locales_dir):
|
|
222
|
+
"""
|
|
223
|
+
LOCALE-TEST-1.4: Keys match reference locale (deep comparison, ignore _ prefix)
|
|
224
|
+
|
|
225
|
+
Given: Reference locale and other locales
|
|
226
|
+
When: Comparing keys at all nesting levels
|
|
227
|
+
Then: All locales have same keys as reference (ignoring _ prefixed keys)
|
|
228
|
+
"""
|
|
229
|
+
if locale_manifest is None:
|
|
230
|
+
pytest.skip("Localization not configured")
|
|
231
|
+
|
|
232
|
+
reference = locale_manifest.get("reference")
|
|
233
|
+
locales = locale_manifest.get("locales", [])
|
|
234
|
+
namespaces = locale_manifest.get("namespaces", [])
|
|
235
|
+
|
|
236
|
+
key_mismatches = []
|
|
237
|
+
|
|
238
|
+
for namespace in namespaces:
|
|
239
|
+
ref_path = locales_dir / reference / f"{namespace}.json"
|
|
240
|
+
ref_data, ref_error = _load_json_file(ref_path)
|
|
241
|
+
|
|
242
|
+
if ref_error:
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
ref_keys = _get_all_keys(ref_data)
|
|
246
|
+
|
|
247
|
+
for locale in locales:
|
|
248
|
+
if locale == reference:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
locale_path = locales_dir / locale / f"{namespace}.json"
|
|
252
|
+
locale_data, locale_error = _load_json_file(locale_path)
|
|
253
|
+
|
|
254
|
+
if locale_error:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
locale_keys = _get_all_keys(locale_data)
|
|
258
|
+
|
|
259
|
+
missing_keys = ref_keys - locale_keys
|
|
260
|
+
extra_keys = locale_keys - ref_keys
|
|
261
|
+
|
|
262
|
+
if missing_keys:
|
|
263
|
+
key_mismatches.append(
|
|
264
|
+
f"{locale}/{namespace}.json missing keys: {', '.join(sorted(missing_keys)[:5])}"
|
|
265
|
+
+ (f" (+{len(missing_keys) - 5} more)" if len(missing_keys) > 5 else "")
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if extra_keys:
|
|
269
|
+
key_mismatches.append(
|
|
270
|
+
f"{locale}/{namespace}.json extra keys: {', '.join(sorted(extra_keys)[:5])}"
|
|
271
|
+
+ (f" (+{len(extra_keys) - 5} more)" if len(extra_keys) > 5 else "")
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if key_mismatches:
|
|
275
|
+
msg = f"Key mismatches ({len(key_mismatches)}):\n" + "\n".join(f" - {m}" for m in key_mismatches[:15])
|
|
276
|
+
if len(key_mismatches) > 15:
|
|
277
|
+
msg += f"\n ... and {len(key_mismatches) - 15} more"
|
|
278
|
+
|
|
279
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
280
|
+
pytest.fail(msg)
|
|
281
|
+
else:
|
|
282
|
+
emit_locale_warning("LOCALE-TEST-1.4", msg)
|
|
283
|
+
pytest.skip(msg)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@pytest.mark.locale
|
|
287
|
+
def test_locale_types_match_reference(locale_manifest, locales_dir):
|
|
288
|
+
"""
|
|
289
|
+
LOCALE-TEST-1.5: Types match reference (object->object, array->array, primitive->same type)
|
|
290
|
+
|
|
291
|
+
Given: Reference locale and other locales
|
|
292
|
+
When: Comparing value types at each key
|
|
293
|
+
Then: Types match between reference and all locales
|
|
294
|
+
"""
|
|
295
|
+
if locale_manifest is None:
|
|
296
|
+
pytest.skip("Localization not configured")
|
|
297
|
+
|
|
298
|
+
reference = locale_manifest.get("reference")
|
|
299
|
+
locales = locale_manifest.get("locales", [])
|
|
300
|
+
namespaces = locale_manifest.get("namespaces", [])
|
|
301
|
+
|
|
302
|
+
type_mismatches = []
|
|
303
|
+
|
|
304
|
+
for namespace in namespaces:
|
|
305
|
+
ref_path = locales_dir / reference / f"{namespace}.json"
|
|
306
|
+
ref_data, ref_error = _load_json_file(ref_path)
|
|
307
|
+
|
|
308
|
+
if ref_error:
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
for locale in locales:
|
|
312
|
+
if locale == reference:
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
locale_path = locales_dir / locale / f"{namespace}.json"
|
|
316
|
+
locale_data, locale_error = _load_json_file(locale_path)
|
|
317
|
+
|
|
318
|
+
if locale_error:
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
mismatches = _compare_types(ref_data, locale_data)
|
|
322
|
+
for mismatch in mismatches:
|
|
323
|
+
type_mismatches.append(f"{locale}/{namespace}.json: {mismatch}")
|
|
324
|
+
|
|
325
|
+
if type_mismatches:
|
|
326
|
+
msg = f"Type mismatches ({len(type_mismatches)}):\n" + "\n".join(f" - {m}" for m in type_mismatches[:10])
|
|
327
|
+
if len(type_mismatches) > 10:
|
|
328
|
+
msg += f"\n ... and {len(type_mismatches) - 10} more"
|
|
329
|
+
|
|
330
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
331
|
+
pytest.fail(msg)
|
|
332
|
+
else:
|
|
333
|
+
emit_locale_warning("LOCALE-TEST-1.5", msg)
|
|
334
|
+
pytest.skip(msg)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@pytest.mark.locale
|
|
338
|
+
def test_optional_namespaces_match_reference(locale_manifest, locales_dir):
|
|
339
|
+
"""
|
|
340
|
+
LOCALE-TEST-1.6: Optional namespaces may be missing; if present, must match reference
|
|
341
|
+
|
|
342
|
+
Given: Optional namespaces in manifest
|
|
343
|
+
When: Checking locale files
|
|
344
|
+
Then: Optional namespace files may not exist, but if they do, keys must match reference
|
|
345
|
+
"""
|
|
346
|
+
if locale_manifest is None:
|
|
347
|
+
pytest.skip("Localization not configured")
|
|
348
|
+
|
|
349
|
+
optional_namespaces = locale_manifest.get("optional_namespaces", [])
|
|
350
|
+
if not optional_namespaces:
|
|
351
|
+
pytest.skip("No optional namespaces defined")
|
|
352
|
+
|
|
353
|
+
reference = locale_manifest.get("reference")
|
|
354
|
+
locales = locale_manifest.get("locales", [])
|
|
355
|
+
|
|
356
|
+
key_mismatches = []
|
|
357
|
+
|
|
358
|
+
for namespace in optional_namespaces:
|
|
359
|
+
ref_path = locales_dir / reference / f"{namespace}.json"
|
|
360
|
+
ref_data, ref_error = _load_json_file(ref_path)
|
|
361
|
+
|
|
362
|
+
if ref_error or ref_data is None:
|
|
363
|
+
continue
|
|
364
|
+
|
|
365
|
+
ref_keys = _get_all_keys(ref_data)
|
|
366
|
+
|
|
367
|
+
for locale in locales:
|
|
368
|
+
if locale == reference:
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
locale_path = locales_dir / locale / f"{namespace}.json"
|
|
372
|
+
if not locale_path.exists():
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
locale_data, locale_error = _load_json_file(locale_path)
|
|
376
|
+
if locale_error:
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
locale_keys = _get_all_keys(locale_data)
|
|
380
|
+
|
|
381
|
+
missing_keys = ref_keys - locale_keys
|
|
382
|
+
extra_keys = locale_keys - ref_keys
|
|
383
|
+
|
|
384
|
+
if missing_keys:
|
|
385
|
+
key_mismatches.append(
|
|
386
|
+
f"{locale}/{namespace}.json (optional) missing keys: {', '.join(sorted(missing_keys)[:5])}"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if extra_keys:
|
|
390
|
+
key_mismatches.append(
|
|
391
|
+
f"{locale}/{namespace}.json (optional) extra keys: {', '.join(sorted(extra_keys)[:5])}"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if key_mismatches:
|
|
395
|
+
msg = f"Optional namespace key mismatches ({len(key_mismatches)}):\n" + "\n".join(f" - {m}" for m in key_mismatches[:10])
|
|
396
|
+
if len(key_mismatches) > 10:
|
|
397
|
+
msg += f"\n ... and {len(key_mismatches) - 10} more"
|
|
398
|
+
|
|
399
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
400
|
+
pytest.fail(msg)
|
|
401
|
+
else:
|
|
402
|
+
emit_locale_warning("LOCALE-TEST-1.6", msg)
|
|
403
|
+
pytest.skip(msg)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@pytest.mark.locale
|
|
407
|
+
def test_language_names_complete(locale_manifest, locales_dir):
|
|
408
|
+
"""
|
|
409
|
+
LOCALE-TEST-1.7: languageNames.<locale> exists in reference ui.json
|
|
410
|
+
|
|
411
|
+
Given: Manifest with locales list
|
|
412
|
+
When: Checking reference ui.json for languageNames
|
|
413
|
+
Then: Each locale in manifest has a corresponding languageNames entry
|
|
414
|
+
"""
|
|
415
|
+
if locale_manifest is None:
|
|
416
|
+
pytest.skip("Localization not configured")
|
|
417
|
+
|
|
418
|
+
reference = locale_manifest.get("reference")
|
|
419
|
+
locales = locale_manifest.get("locales", [])
|
|
420
|
+
namespaces = locale_manifest.get("namespaces", [])
|
|
421
|
+
|
|
422
|
+
if "ui" not in namespaces:
|
|
423
|
+
pytest.skip("No 'ui' namespace configured - skipping languageNames check")
|
|
424
|
+
|
|
425
|
+
ui_path = locales_dir / reference / "ui.json"
|
|
426
|
+
ui_data, ui_error = _load_json_file(ui_path)
|
|
427
|
+
|
|
428
|
+
if ui_error:
|
|
429
|
+
pytest.skip(f"Cannot read reference ui.json: {ui_error}")
|
|
430
|
+
|
|
431
|
+
language_names = ui_data.get("languageNames", {})
|
|
432
|
+
if not language_names:
|
|
433
|
+
msg = "Reference ui.json missing 'languageNames' object"
|
|
434
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
435
|
+
pytest.fail(msg)
|
|
436
|
+
else:
|
|
437
|
+
emit_locale_warning("LOCALE-TEST-1.7", msg)
|
|
438
|
+
pytest.skip(msg)
|
|
439
|
+
|
|
440
|
+
missing_names = []
|
|
441
|
+
for locale in locales:
|
|
442
|
+
if locale not in language_names:
|
|
443
|
+
missing_names.append(locale)
|
|
444
|
+
|
|
445
|
+
if missing_names:
|
|
446
|
+
msg = f"Missing languageNames entries for locales: {', '.join(missing_names)}"
|
|
447
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
448
|
+
pytest.fail(msg)
|
|
449
|
+
else:
|
|
450
|
+
emit_locale_warning("LOCALE-TEST-1.7", msg)
|
|
451
|
+
pytest.skip(msg)
|
|
@@ -25,19 +25,20 @@ atdd/coach/commands/tests/test_telemetry_array_validation.py,sha256=WK5ZXvR1avlz
|
|
|
25
25
|
atdd/coach/conventions/session.convention.yaml,sha256=1wCxQ_Y2Wb2080Xt2JZs0_WsV8_4SC0Tq87G_BCGdiE,26049
|
|
26
26
|
atdd/coach/overlays/__init__.py,sha256=2lMiMSgfLJ3YHLpbzNI5B88AdQxiMEwjIfsWWb8t3To,123
|
|
27
27
|
atdd/coach/overlays/claude.md,sha256=33mhpqhmsRhCtdWlU7cMXAJDsaVra9uBBK8URV8OtQA,101
|
|
28
|
-
atdd/coach/schemas/config.schema.json,sha256=
|
|
28
|
+
atdd/coach/schemas/config.schema.json,sha256=47cFGE5juBv9ewhtgrNir4b6I9imIIo8VjoD9yvASf4,4578
|
|
29
29
|
atdd/coach/schemas/manifest.schema.json,sha256=WO13-YF_FgH1awh96khCtk-112b6XSC24anlY3B7GjY,2885
|
|
30
|
-
atdd/coach/templates/ATDD.md,sha256=
|
|
30
|
+
atdd/coach/templates/ATDD.md,sha256=h_oPpKLX7nuafC0VAoKCnkM2-kGQeksR34QWlGyfMwU,13236
|
|
31
31
|
atdd/coach/templates/SESSION-TEMPLATE.md,sha256=cGT_0x5KLbPHOCiuM8evLGpWKIlR-aggqxiBtbjSJoo,9478
|
|
32
32
|
atdd/coach/utils/__init__.py,sha256=7Jbo-heJEKSAn6I0s35z_2S4R8qGZ48PL6a2IntcNYg,148
|
|
33
33
|
atdd/coach/utils/config.py,sha256=6XXaaeVfjTrJwdaR0IZ6Kf1-1ZHhaCVLO5pNx_A2el4,3320
|
|
34
34
|
atdd/coach/utils/coverage_phase.py,sha256=14CzGiTEeb-Z-CMYnJjx1-4dn3LbQVJUlFr_-1bKVMc,3250
|
|
35
|
+
atdd/coach/utils/locale_phase.py,sha256=S6eORhvj2N412BOY4QFQGcLA_twsr_D4vviy4X-KDNo,3236
|
|
35
36
|
atdd/coach/utils/repo.py,sha256=0kiF5WpVTen0nO14u5T0RflznZhgGco2i9CwKobOh38,3757
|
|
36
37
|
atdd/coach/utils/train_spec_phase.py,sha256=Mk8CiMoO6jb-VGttHgI20KIG26r9cjSz4gDfk01q1M0,3025
|
|
37
38
|
atdd/coach/utils/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
39
|
atdd/coach/utils/graph/urn.py,sha256=O2AHIB_CmmMUvXzyejc_oFReNW_rOcw7m4qaqSYcnNQ,33558
|
|
39
40
|
atdd/coach/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
atdd/coach/validators/shared_fixtures.py,sha256=
|
|
41
|
+
atdd/coach/validators/shared_fixtures.py,sha256=Ia3B2fUW-aKibwVPF6RnRemtu3R_Dfb-2MvPVNitgxg,20931
|
|
41
42
|
atdd/coach/validators/test_enrich_wagon_registry.py,sha256=WeTwYJqoNY6mEYc-QAvQo7YVagSOjaNKxB6Q6dpWqIM,6561
|
|
42
43
|
atdd/coach/validators/test_registry.py,sha256=ffN70yA_1xxL3R8gdpGbY2M8dQXyuajIZhBZ-ylNiNs,17845
|
|
43
44
|
atdd/coach/validators/test_release_versioning.py,sha256=B40DfbtrSGguPc537zXmjT75hhySfocWLzJWqOKZQcU,5678
|
|
@@ -48,8 +49,8 @@ atdd/coach/validators/test_update_feature_paths.py,sha256=zOKVDgEIpncSJwDh_shyyo
|
|
|
48
49
|
atdd/coach/validators/test_validate_contract_consumers.py,sha256=b01yam_GwAERF6YaFmUV6Bd7SNMWQkUKBfNVvplbEcU,12613
|
|
49
50
|
atdd/coder/__init__.py,sha256=Rmi5S7Pzx7qsRe5MC66GduNGmkssWWnElkVvvNHFDgU,45
|
|
50
51
|
atdd/coder/conventions/adapter.recipe.yaml,sha256=Ss9OJJ-FEP8TU_D-N6X_1nO-Karb-Cg5OopL9_gLjMI,3039
|
|
51
|
-
atdd/coder/conventions/backend.convention.yaml,sha256=
|
|
52
|
-
atdd/coder/conventions/boundaries.convention.yaml,sha256=
|
|
52
|
+
atdd/coder/conventions/backend.convention.yaml,sha256=OJkBZUPaGFfKmO8UPslucbA6RZtsctvoywfMLFw5irg,16599
|
|
53
|
+
atdd/coder/conventions/boundaries.convention.yaml,sha256=uMahpzwNru8KuGDllXrajD5VQ5f62GPsz0nMsfgaAQw,25504
|
|
53
54
|
atdd/coder/conventions/commons.convention.yaml,sha256=zwfO4dYNrFjy4MLZ9RLK3Yd7gotTWYdFfXMPp3EfU0A,17309
|
|
54
55
|
atdd/coder/conventions/complexity.recipe.yaml,sha256=fjVnsb0kGGstTDKTaeYO_1XJ3408wE0lheAxf02Hm3s,3636
|
|
55
56
|
atdd/coder/conventions/component-naming.convention.yaml,sha256=w1LKJM7lhmpFqBCpRAvx03Z_Ugujd3P6rFfmp5xbhg0,7444
|
|
@@ -59,11 +60,11 @@ atdd/coder/conventions/design.recipe.yaml,sha256=LP2S504JpfzwoL3jPdSmYKMmg-_Bzlo
|
|
|
59
60
|
atdd/coder/conventions/dto.convention.yaml,sha256=aawWJVwoXU5yV73-rpHDSYtYMfm0XSWRRMujGCQrtYY,23700
|
|
60
61
|
atdd/coder/conventions/frontend.convention.yaml,sha256=2qBtLbqo1SrM17mXMlbZg4xlNAlq0_Kj2n18XphsQl8,19748
|
|
61
62
|
atdd/coder/conventions/green.convention.yaml,sha256=jq-Y5yHizf8o6NSJMD-9BPGrAv-otXRRjHikSGqn3aI,39091
|
|
62
|
-
atdd/coder/conventions/presentation.convention.yaml,sha256=
|
|
63
|
+
atdd/coder/conventions/presentation.convention.yaml,sha256=P8_J6Bh58AkeoGfsG91KPBO7pXJOvaYeCxp-JleX2Tw,21885
|
|
63
64
|
atdd/coder/conventions/refactor.convention.yaml,sha256=kpETeWqZRlahV55c9bEf1bLOokL_DDR0jY4mffKfY_c,19292
|
|
64
65
|
atdd/coder/conventions/technology.convention.yaml,sha256=OU6_XArKTKtLoI9c67GzAM3tjWj7Qx1qrW7uQgIXvVY,13826
|
|
65
66
|
atdd/coder/conventions/thinness.recipe.yaml,sha256=4FZplEJQA93qsvpJ4QPCn91duWtTL_V_CsqSgIFfBSo,2664
|
|
66
|
-
atdd/coder/conventions/train.convention.yaml,sha256=
|
|
67
|
+
atdd/coder/conventions/train.convention.yaml,sha256=saHeqQVzKkIjD1XxjTXApv5tGsQ_CPXFtsU8qbsvaag,11829
|
|
67
68
|
atdd/coder/conventions/verification.protocol.yaml,sha256=tYOId2A8xKoTqjcrKN1RNUK20_f0_e9GeVm_GBKrXMQ,1523
|
|
68
69
|
atdd/coder/conventions/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
70
|
atdd/coder/conventions/tests/test_adapter_recipe.py,sha256=293CU9rj7z6bj_1Wh02VhVCslaHfK-KeMJFgKgK7cvM,9370
|
|
@@ -84,18 +85,19 @@ atdd/coder/validators/test_green_layer_dependencies.py,sha256=8TWuTD3RrDBqp9LUCS
|
|
|
84
85
|
atdd/coder/validators/test_green_python_layer_structure.py,sha256=WXo1VA5-WprgtAQgC8ekhuIRJR47b_qgTYTjPn7svms,4389
|
|
85
86
|
atdd/coder/validators/test_green_supabase_layer_structure.py,sha256=cLj85acLX6Knewk9AWbiJDwEzoaE-NBNollyJCvRUD0,4371
|
|
86
87
|
atdd/coder/validators/test_hierarchy_coverage.py,sha256=1JuaiO4k-v3_rL5x4-MPsKajAv-K5cTzevW5eS4cC-U,12240
|
|
88
|
+
atdd/coder/validators/test_i18n_runtime.py,sha256=pmx1nhvOS1nk2xa1E5Lm8yPgpylNhV6AjDECeIzN4Pg,5575
|
|
87
89
|
atdd/coder/validators/test_import_boundaries.py,sha256=3kzVMKIwZU9FcS0YLU8gsuHSDRlRr2rU3UPgoraV2FU,11811
|
|
88
90
|
atdd/coder/validators/test_init_file_urns.py,sha256=uDJ2MgfJNFcjzoIKItn73n9V08m3ZBkt1SAZgHWdXPs,17914
|
|
89
91
|
atdd/coder/validators/test_preact_layer_boundaries.py,sha256=rabXY9gif3b8QH9_Kz5Pu6FZSvDpfQKDm_SKjdpLD44,7766
|
|
90
|
-
atdd/coder/validators/test_presentation_convention.py,sha256=
|
|
92
|
+
atdd/coder/validators/test_presentation_convention.py,sha256=LKmiMStrR6uqcyYOyBr08EHyeGHHiN2UvBpO1EgUzZM,10040
|
|
91
93
|
atdd/coder/validators/test_python_architecture.py,sha256=USnSujKVu7_BC2ij-pTSHyiFi4iBMBemIPR7oXYQ3B4,25243
|
|
92
94
|
atdd/coder/validators/test_quality_metrics.py,sha256=vLWPfL0x9sfCvKXO4uECW61RnJTsjbmuEUjCsBcUmuA,12551
|
|
93
|
-
atdd/coder/validators/test_station_master_pattern.py,sha256=
|
|
94
|
-
atdd/coder/validators/test_train_infrastructure.py,sha256=
|
|
95
|
+
atdd/coder/validators/test_station_master_pattern.py,sha256=R0VajDIabKQtXKFCZPzWaY-euTcMLr0ECIim-s1_zX8,9374
|
|
96
|
+
atdd/coder/validators/test_train_infrastructure.py,sha256=_91k0d1Q7RkQZb-2N8fD_eRXe-oMMQGhQ_u_41ytHlU,24661
|
|
95
97
|
atdd/coder/validators/test_train_urns.py,sha256=TvSNHbvG8SAtAPaS2K8mMgzE8GbJtrTaF4iEgkJEIIk,10099
|
|
96
98
|
atdd/coder/validators/test_typescript_architecture.py,sha256=gi76IwlqX_MpWsr8CXWH7v16KAoud-1OY92iuN-0BpU,22176
|
|
97
99
|
atdd/coder/validators/test_usecase_structure.py,sha256=nAeqmLWRCstXT8fDuxqk6iVcH4TLHjTvlaGDpanD9sM,14077
|
|
98
|
-
atdd/coder/validators/test_wagon_boundaries.py,sha256=
|
|
100
|
+
atdd/coder/validators/test_wagon_boundaries.py,sha256=lifrhazbW2fJOwgU0vnNCjXZGfknUh6pa7err2TYwyY,20696
|
|
99
101
|
atdd/planner/__init__.py,sha256=ZXzKPNqP_JgEeFnwwUAKfIwz2e1i2gwrmz1SyC65sl4,47
|
|
100
102
|
atdd/planner/conventions/acceptance.convention.yaml,sha256=q_1hm-uJ2Sbh6VqGHQ543L9NTnOuS2UGWT-n9uAdfmM,21918
|
|
101
103
|
atdd/planner/conventions/appendix.convention.yaml,sha256=wuv9mnSZ-L1vUp2xx0OmyOsYurApitsmO_MilLVGcOc,9394
|
|
@@ -149,6 +151,7 @@ atdd/tester/schemas/event.tmpl.json,sha256=3-7cSaesuPcVZP5NENrg-GnDRQPbwkWB_1ISI
|
|
|
149
151
|
atdd/tester/schemas/http.tmpl.json,sha256=zrynEO18HmEXanQDVk3XZNHNOp1lUyVKXTR-f7_KAGI,818
|
|
150
152
|
atdd/tester/schemas/job.tmpl.json,sha256=7HtX-esAb9asB-9uNyuWDRKE-HWovlNnuYYllSzjJDg,926
|
|
151
153
|
atdd/tester/schemas/load.tmpl.json,sha256=wSPfGXTEVQrgsqVXihwiDIKWS_QErkXlh2WdVJwLqoc,971
|
|
154
|
+
atdd/tester/schemas/locale_manifest.schema.json,sha256=4-A25522C4Bdy7Y3I9USqsk4K97hkv3rTr60-dcVZAk,1736
|
|
152
155
|
atdd/tester/schemas/metric.tmpl.json,sha256=jHTQgAsnVXWPlQipjnu79eqZwvHcDV5giaoN9w1HZdY,949
|
|
153
156
|
atdd/tester/schemas/pack.schema.json,sha256=pQfbvaB5Kl1SFCewz60zaTRviuZm-gM_9QCYipJWa3w,4575
|
|
154
157
|
atdd/tester/schemas/realtime.tmpl.json,sha256=c9Sbkqk6vFUhzTWI2slfyTfEwJ0ltFhQrtjPabRLtmo,1141
|
|
@@ -183,6 +186,7 @@ atdd/tester/validators/test_dual_ac_reference.py,sha256=LDhIqXyVxgWVCgj7FneDTLt6
|
|
|
183
186
|
atdd/tester/validators/test_fixture_validity.py,sha256=Fp4AWwhvZlos1ik_d7NbP030Qq-klZLnCmc12ylptqs,12101
|
|
184
187
|
atdd/tester/validators/test_hierarchy_coverage.py,sha256=o2jd3dsqvqtqQ3I3RQa4FGoYIJE0vArUIISurDxYTgw,21060
|
|
185
188
|
atdd/tester/validators/test_isolation.py,sha256=NYrqJcVDZH0SDRWHlPdazG6THT4w3XEvz_xn4PBxU4E,16489
|
|
189
|
+
atdd/tester/validators/test_locale_coverage.py,sha256=amnZAx5wTvF6mGbclvu40n0XhYBKLl97SnBVkbLMv1E,15487
|
|
186
190
|
atdd/tester/validators/test_migration_coverage.py,sha256=LOx0L9KLH4gVisNHXhxKrzHLgCgj4PVZxeZ-2gg-SQk,7344
|
|
187
191
|
atdd/tester/validators/test_migration_criteria.py,sha256=YDGvWjkVSjUVVNv4RJWLdy4iLoG1EXzmm_ficD0Gt3Q,7896
|
|
188
192
|
atdd/tester/validators/test_migration_generation.py,sha256=wpTmuxvM13OfSgC-3SJBdtB2XaPjBYyD-jXVYWi7Z9Q,4064
|
|
@@ -196,9 +200,9 @@ atdd/tester/validators/test_train_frontend_e2e.py,sha256=fpfUwTbAWzuqxbVKoaFw-ab
|
|
|
196
200
|
atdd/tester/validators/test_train_frontend_python.py,sha256=KK2U3oNFWLyBK7YHC0fU7shR05k93gVcO762AI8Q3pw,9018
|
|
197
201
|
atdd/tester/validators/test_typescript_test_naming.py,sha256=E-TyGv_GVlTfsbyuxrtv9sOWSZS_QcpH6rrJFbWoeeU,11280
|
|
198
202
|
atdd/tester/validators/test_typescript_test_structure.py,sha256=eV89SD1RaKtchBZupqhnJmaruoROosf3LwB4Fwe4UJI,2612
|
|
199
|
-
atdd-0.
|
|
200
|
-
atdd-0.
|
|
201
|
-
atdd-0.
|
|
202
|
-
atdd-0.
|
|
203
|
-
atdd-0.
|
|
204
|
-
atdd-0.
|
|
203
|
+
atdd-0.7.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
204
|
+
atdd-0.7.0.dist-info/METADATA,sha256=EEjcJY5c8XfUKF8ebYBc1o3mjFrJqqJldicW2coK-iQ,8716
|
|
205
|
+
atdd-0.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
206
|
+
atdd-0.7.0.dist-info/entry_points.txt,sha256=-C3yrA1WQQfN3iuGmSzPapA5cKVBEYU5Q1HUffSJTbY,38
|
|
207
|
+
atdd-0.7.0.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
|
|
208
|
+
atdd-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|