|
6 | 6 |
|
7 | 7 | from __future__ import annotations |
8 | 8 |
|
| 9 | +import hashlib |
9 | 10 | import json |
10 | 11 | import os |
11 | 12 | from collections.abc import Callable |
|
81 | 82 | _unit_dict_from_model, |
82 | 83 | ) |
83 | 84 | from codeclone.cache.integrity import as_str_dict as _as_str_dict |
84 | | -from codeclone.cache.integrity import sign_cache_payload |
| 85 | +from codeclone.cache.integrity import canonical_json, sign_cache_payload |
85 | 86 | from codeclone.cache.projection import ( |
86 | 87 | runtime_filepath_from_wire, |
87 | 88 | wire_filepath_from_runtime, |
@@ -1452,6 +1453,42 @@ def test_cache_signature_validation_ignores_json_whitespace(tmp_path: Path) -> N |
1452 | 1453 | assert loaded.get_file_entry("x.py") is not None |
1453 | 1454 |
|
1454 | 1455 |
|
| 1456 | +def test_cache_signature_matches_legacy_string_digest_for_unicode_payload() -> None: |
| 1457 | + cache = Cache(Path("cache.json")) |
| 1458 | + payload = _analysis_payload( |
| 1459 | + cache, |
| 1460 | + files={ |
| 1461 | + "unicodé.py": { |
| 1462 | + "st": [1, 10], |
| 1463 | + "rn": ["Ω", "é"], |
| 1464 | + } |
| 1465 | + }, |
| 1466 | + ) |
| 1467 | + legacy_digest = hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest() |
| 1468 | + |
| 1469 | + assert sign_cache_payload(payload) == legacy_digest |
| 1470 | + |
| 1471 | + |
| 1472 | +def test_cache_load_accepts_legacy_string_signed_unicode_payload( |
| 1473 | + tmp_path: Path, |
| 1474 | +) -> None: |
| 1475 | + cache_path = tmp_path / "cache.json" |
| 1476 | + cache = Cache(cache_path) |
| 1477 | + cache.put_file_entry("unicodé.py", {"mtime_ns": 1, "size": 10}, [], [], []) |
| 1478 | + cache.save() |
| 1479 | + |
| 1480 | + raw = json.loads(cache_path.read_text("utf-8")) |
| 1481 | + payload = cast(dict[str, object], raw["payload"]) |
| 1482 | + raw["sig"] = hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest() |
| 1483 | + cache_path.write_text(json.dumps(raw, ensure_ascii=False, indent=2), "utf-8") |
| 1484 | + |
| 1485 | + loaded = Cache(cache_path) |
| 1486 | + loaded.load() |
| 1487 | + |
| 1488 | + assert loaded.load_warning is None |
| 1489 | + assert loaded.get_file_entry("unicodé.py") is not None |
| 1490 | + |
| 1491 | + |
1455 | 1492 | def test_decode_wire_file_and_name_section_helpers_cover_valid_and_invalid() -> None: |
1456 | 1493 | encoded = _encode_wire_file_entry( |
1457 | 1494 | { |
|
0 commit comments