Unify Iron Coast mission names and cinematic mission previews
This commit is contained in:
1 parent
668b3ff86d
commit
2d95e145a5
406 files changed
+134963
-245
No files matched your search
@@ -0,0 +1,179 @@
|
||||
"""Synthetic packing checks; no production copies or compiler required."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from assemble_runtime import default_data_source, select_data_files
|
||||
|
||||
|
||||
class RuntimeDataSelectionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temporary.cleanup)
|
||||
self.root = Path(self.temporary.name)
|
||||
self.package = self.root / "RustRaptor"
|
||||
self.data = self.package / "data"
|
||||
self.catalog(self.data)
|
||||
self.briefings(self.data)
|
||||
|
||||
def write(self, root, name, payload):
|
||||
path = root / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(payload)
|
||||
return path
|
||||
|
||||
def catalog(self, root):
|
||||
self.write(root, "INDEX", b"00001 4 sprites/a.bin\n")
|
||||
self.write(root, "sprites/a.bin", b"base")
|
||||
|
||||
def briefings(self, root):
|
||||
for sector in range(1, 4):
|
||||
for wave in range(1, 10):
|
||||
for mode in ("sd", "hd"):
|
||||
self.write(root, f"briefings/sector_{sector}_wave_{wave}_{mode}.png",
|
||||
f"briefing {sector}/{wave}/{mode}".encode())
|
||||
|
||||
def hd(self, root, name="frames/new.png", payload=b"new frame"):
|
||||
self.write(root, name, payload)
|
||||
self.write(root, "hd.json", json.dumps({"sequences": [
|
||||
{"frames": [{"file": name}]}]}).encode())
|
||||
|
||||
def test_bundled_catalog_wins_over_parent_legacy_catalog(self):
|
||||
external = self.root / "data"
|
||||
self.catalog(external)
|
||||
self.write(external, "legacy-only", b"obsolete")
|
||||
self.assertEqual(default_data_source(self.package), self.data)
|
||||
files, entries, count = select_data_files(self.package)
|
||||
self.assertNotIn("legacy-only", files)
|
||||
self.assertEqual((len(entries), count), (1, 0))
|
||||
(self.data / "INDEX").unlink()
|
||||
self.assertEqual(default_data_source(self.package), external)
|
||||
|
||||
def test_complete_tree_roundtrips_briefings_provenance_and_hd_jobs(self):
|
||||
self.hd(self.data / "hd")
|
||||
extras = {
|
||||
"briefings/provenance.json": b"briefing provenance",
|
||||
"hd/provenance.json": b"HD lineage",
|
||||
"hd/jobs/family/derived/accepted.png": b"derived payload",
|
||||
"hd/jobs/family/raw/source.png": b"raw lineage",
|
||||
"hd/jobs/family/recipe.json": b"recipe",
|
||||
"misc/runtime-extra": b"extra data",
|
||||
}
|
||||
for name, payload in extras.items():
|
||||
self.write(self.data, name, payload)
|
||||
files, _, count = select_data_files(self.package)
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(len([n for n in files if n.startswith("briefings/") and n.endswith(".png")]), 54)
|
||||
self.assertTrue(set(extras) <= set(files))
|
||||
relocated = self.root / "Portable Runtime Ω" / "data"
|
||||
for name, path in files.items():
|
||||
destination = relocated / name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(path, destination)
|
||||
self.assertEqual({p.relative_to(self.data).as_posix(): p.read_bytes()
|
||||
for p in self.data.rglob("*") if p.is_file()},
|
||||
{p.relative_to(relocated).as_posix(): p.read_bytes()
|
||||
for p in relocated.rglob("*") if p.is_file()})
|
||||
|
||||
def test_external_catalog_fallback_preserves_existing_briefing(self):
|
||||
external = self.root / "external"
|
||||
self.catalog(external)
|
||||
override = "briefings/sector_1_wave_1_sd.png"
|
||||
self.write(external, override, b"external briefing")
|
||||
self.write(external, "provenance.json", b"external metadata")
|
||||
self.write(self.data, "briefings/provenance.json", b"bundled briefing lineage")
|
||||
files, _, _ = select_data_files(self.package, external)
|
||||
self.assertEqual(files[override].read_bytes(), b"external briefing")
|
||||
self.assertEqual(files["briefings/sector_3_wave_9_hd.png"].read_bytes(), b"briefing 3/9/hd")
|
||||
self.assertEqual(files["provenance.json"].read_bytes(), b"external metadata")
|
||||
self.assertEqual(len([n for n in files if n.startswith("briefings/") and n.endswith(".png")]), 54)
|
||||
self.assertEqual(files["briefings/provenance.json"].read_bytes(), b"bundled briefing lineage")
|
||||
|
||||
def test_explicit_hd_replaces_entire_old_subtree(self):
|
||||
self.hd(self.data / "hd", "old.png", b"obsolete frame")
|
||||
self.write(self.data, "hd/jobs/old/receipt.json", b"obsolete metadata")
|
||||
replacement = self.root / "approved-HD"
|
||||
self.hd(replacement)
|
||||
self.write(replacement, "jobs/new/raw/source.png", b"new lineage")
|
||||
self.write(replacement, "provenance.json", b"new provenance")
|
||||
files, _, count = select_data_files(self.package, hd_source=replacement)
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual({n for n in files if n.startswith("hd/")},
|
||||
{"hd/hd.json", "hd/frames/new.png", "hd/jobs/new/raw/source.png", "hd/provenance.json"})
|
||||
self.assertEqual(files["hd/provenance.json"].read_bytes(), b"new provenance")
|
||||
|
||||
def test_missing_required_briefing_or_explicit_manifest_fails(self):
|
||||
(self.data / "briefings/sector_3_wave_9_hd.png").unlink()
|
||||
with self.assertRaisesRegex(ValueError, "required mission briefing"):
|
||||
select_data_files(self.package)
|
||||
self.briefings(self.data)
|
||||
empty = self.root / "empty-hd"
|
||||
empty.mkdir()
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
select_data_files(self.package, hd_source=empty)
|
||||
|
||||
def test_unlisted_file_and_directory_symlinks_cannot_escape_source(self):
|
||||
outside = self.write(self.root, "outside", b"private")
|
||||
link = self.data / "unlisted-link"
|
||||
link.symlink_to(outside)
|
||||
with self.assertRaises(ValueError):
|
||||
select_data_files(self.package)
|
||||
link.unlink()
|
||||
link.symlink_to(self.root, target_is_directory=True)
|
||||
with self.assertRaisesRegex(ValueError, "symlink directory"):
|
||||
select_data_files(self.package)
|
||||
|
||||
def test_catalog_and_hd_manifest_traversal_and_size_are_checked(self):
|
||||
for catalog in (b"00001 4 ../outside\n", b"00001 999 sprites/a.bin\n"):
|
||||
self.write(self.data, "INDEX", catalog)
|
||||
with self.subTest(catalog=catalog), self.assertRaises(ValueError):
|
||||
select_data_files(self.package)
|
||||
self.catalog(self.data)
|
||||
replacement = self.root / "unsafe-HD"
|
||||
self.hd(replacement, "../outside", b"private")
|
||||
with self.assertRaises(ValueError):
|
||||
select_data_files(self.package, hd_source=replacement)
|
||||
|
||||
def test_external_fallback_briefing_symlink_cannot_escape_bundled_data(self):
|
||||
external = self.root / "external"
|
||||
self.catalog(external)
|
||||
missing = self.data / "briefings/sector_1_wave_1_sd.png"
|
||||
missing.unlink()
|
||||
missing.symlink_to(self.write(self.root, "outside", b"private"))
|
||||
with self.assertRaises(ValueError):
|
||||
select_data_files(self.package, external)
|
||||
|
||||
|
||||
def test_external_fallback_briefing_root_symlink_is_rejected(self):
|
||||
external = self.root / "external"
|
||||
self.catalog(external)
|
||||
outside = self.root / "outside-private"
|
||||
self.briefings(outside)
|
||||
bundled_briefings = self.data / "briefings"
|
||||
shutil.rmtree(bundled_briefings)
|
||||
bundled_briefings.symlink_to(outside / "briefings", target_is_directory=True)
|
||||
with self.assertRaisesRegex(ValueError, "symlink directory in bundled mission briefings"):
|
||||
select_data_files(self.package, external)
|
||||
|
||||
def test_unreadable_unlisted_lineage_directory_fails_instead_of_being_omitted(self):
|
||||
proof = self.write(self.data, "hd/jobs/family/proofs/lineage.json", b"required lineage")
|
||||
denied = proof.parent
|
||||
real_scandir = os.scandir
|
||||
|
||||
def checked_scandir(path):
|
||||
if Path(path) == denied:
|
||||
raise PermissionError("synthetic unreadable lineage directory")
|
||||
return real_scandir(path)
|
||||
|
||||
with patch("assemble_runtime.os.scandir", side_effect=checked_scandir):
|
||||
with self.assertRaisesRegex(PermissionError, "synthetic unreadable lineage directory"):
|
||||
select_data_files(self.package)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user