|
5 | 5 | import tempfile |
6 | 6 | import unittest |
7 | 7 | from pathlib import Path |
| 8 | +from unittest.mock import patch |
8 | 9 |
|
9 | 10 | import pandas as pd |
10 | 11 |
|
|
16 | 17 | build_storage_layout, |
17 | 18 | ensure_publish_preflight, |
18 | 19 | load_release_artifacts, |
| 20 | + upload_release_artifacts, |
19 | 21 | ) |
20 | 22 | from src.release_contract import validate_release_outputs |
21 | 23 |
|
@@ -302,6 +304,66 @@ def test_firestore_payload_preserves_exact_legacy_artifact_bytes(self) -> None: |
302 | 304 | self.assertEqual(handoff["encoding"], "utf-8") |
303 | 305 | self.assertEqual(handoff["utf8_text"].encode("utf-8"), exact_bytes) |
304 | 306 |
|
| 307 | + def test_upload_reuses_validated_legacy_snapshot_after_path_mutation(self) -> None: |
| 308 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 309 | + root = Path(tmp_dir) |
| 310 | + output_dir = self.build_runtime_identity_outputs(root) |
| 311 | + artifacts = load_release_artifacts(output_dir, "core_major") |
| 312 | + settings = PublishSettings( |
| 313 | + enabled=True, |
| 314 | + dry_run=False, |
| 315 | + mode="core_major", |
| 316 | + project_id="test-project", |
| 317 | + cloud_bucket="test-bucket", |
| 318 | + cloud_root_prefix="crypto-live-pool-pipelines", |
| 319 | + firestore_collection="strategy", |
| 320 | + firestore_document="CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL", |
| 321 | + source_project="crypto-live-pool-pipelines", |
| 322 | + upload_current_pointer=True, |
| 323 | + ) |
| 324 | + storage_layout = build_storage_layout(settings, artifacts) |
| 325 | + firestore_payload = build_firestore_payload( |
| 326 | + settings, |
| 327 | + artifacts, |
| 328 | + storage_layout, |
| 329 | + ) |
| 330 | + validated_bytes = firestore_payload["live_pool_legacy_exact_bytes"][ |
| 331 | + "utf8_text" |
| 332 | + ].encode("utf-8") |
| 333 | + artifacts.live_pool_legacy_path.write_bytes(b'{"mutated": true}\n') |
| 334 | + |
| 335 | + uploaded: dict[str, bytes] = {} |
| 336 | + |
| 337 | + class FakeStore: |
| 338 | + def write_bytes(self, uri: str, payload: bytes) -> None: |
| 339 | + uploaded[uri] = payload |
| 340 | + |
| 341 | + with patch( |
| 342 | + "quant_platform_kit.cloud.get_object_store", |
| 343 | + return_value=FakeStore(), |
| 344 | + ): |
| 345 | + upload_release_artifacts( |
| 346 | + settings, |
| 347 | + artifacts, |
| 348 | + storage_layout, |
| 349 | + live_pool_legacy_exact_bytes=validated_bytes, |
| 350 | + ) |
| 351 | + |
| 352 | + legacy_objects = storage_layout["objects"]["live_pool_legacy.json"] |
| 353 | + release_bytes = uploaded[legacy_objects["release_uri"]] |
| 354 | + current_bytes = uploaded[legacy_objects["current_uri"]] |
| 355 | + manifest_digest = artifacts.artifact_manifest["artifacts"][ |
| 356 | + "live_pool_legacy" |
| 357 | + ]["sha256"] |
| 358 | + identity_digest = artifacts.runtime_evidence_identity["artifacts"][ |
| 359 | + "live_pool_legacy" |
| 360 | + ]["sha256"] |
| 361 | + self.assertEqual(release_bytes, validated_bytes) |
| 362 | + self.assertEqual(current_bytes, validated_bytes) |
| 363 | + self.assertEqual(hashlib.sha256(validated_bytes).hexdigest(), manifest_digest) |
| 364 | + self.assertEqual(hashlib.sha256(release_bytes).hexdigest(), manifest_digest) |
| 365 | + self.assertEqual(identity_digest, manifest_digest) |
| 366 | + |
305 | 367 | def test_firestore_payload_rejects_mutated_legacy_bytes_with_unchanged_identity(self) -> None: |
306 | 368 | with tempfile.TemporaryDirectory() as tmp_dir: |
307 | 369 | root = Path(tmp_dir) |
|
0 commit comments