92 lines
3.4 KiB
Python
92 lines
3.4 KiB
Python
import json
|
|
import unittest
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
import sys
|
|
from types import SimpleNamespace
|
|
|
|
|
|
ROOT = Path(__file__).parents[1]
|
|
ADDON = ROOT / "spikes" / "rayforge-jcz-no-device-addon"
|
|
FIXTURE = (
|
|
ROOT / "tests" / "fixtures" / "synthetic-pirateray-recipe-metadata.json"
|
|
)
|
|
sys.path.insert(0, str(ADDON))
|
|
|
|
from pirate_ray_jcz.recipe_metadata import ( # noqa: E402
|
|
COMPATIBILITY_KEYS,
|
|
RecipeMetadataError,
|
|
validate_rayforge_recipe,
|
|
validate_recipe_metadata,
|
|
)
|
|
|
|
|
|
class RayforgeRecipeMetadataTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.metadata = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
self.context = dict(self.metadata["compatibility"])
|
|
|
|
def test_complete_synthetic_metadata_is_preserved_but_not_authorized(self):
|
|
result = validate_recipe_metadata(self.metadata, self.context)
|
|
self.assertEqual(1, result["schema_version"])
|
|
self.assertEqual("unverified", result["status"])
|
|
self.assertFalse(result["encoding_allowed"])
|
|
self.assertFalse(result["hardware_use_allowed"])
|
|
self.assertEqual(self.context, result["compatibility"])
|
|
|
|
def test_every_compatibility_axis_fails_closed_on_mismatch(self):
|
|
for key in COMPATIBILITY_KEYS:
|
|
with self.subTest(key=key):
|
|
context = dict(self.context)
|
|
context[key] = f"wrong-{key}"
|
|
with self.assertRaisesRegex(
|
|
RecipeMetadataError, f"recipe {key} does not match"
|
|
):
|
|
validate_recipe_metadata(self.metadata, context)
|
|
|
|
def test_missing_evidence_and_context_fail_closed(self):
|
|
cases = []
|
|
missing_provenance = deepcopy(self.metadata)
|
|
del missing_provenance["provenance"]
|
|
cases.append((missing_provenance, self.context))
|
|
missing_compatibility = deepcopy(self.metadata)
|
|
del missing_compatibility["compatibility"]["lens_profile_id"]
|
|
cases.append((missing_compatibility, self.context))
|
|
missing_context = dict(self.context)
|
|
del missing_context["correction_profile_sha256"]
|
|
cases.append((self.metadata, missing_context))
|
|
|
|
for metadata, context in cases:
|
|
with self.subTest(metadata=metadata, context=context):
|
|
with self.assertRaises(RecipeMetadataError):
|
|
validate_recipe_metadata(metadata, context)
|
|
|
|
def test_unreviewed_approval_label_is_rejected(self):
|
|
metadata = deepcopy(self.metadata)
|
|
metadata["status"] = "makerspace-approved"
|
|
with self.assertRaisesRegex(RecipeMetadataError, "approval policy"):
|
|
validate_recipe_metadata(metadata, self.context)
|
|
|
|
def test_rayforge_extra_is_the_only_accepted_seam(self):
|
|
recipe = SimpleNamespace(extra={"pirateray": self.metadata})
|
|
result = validate_rayforge_recipe(recipe, self.context)
|
|
self.assertEqual("synthetic-public", result["provenance"]["kind"])
|
|
|
|
with self.assertRaises(RecipeMetadataError):
|
|
validate_rayforge_recipe(SimpleNamespace(extra={}), self.context)
|
|
|
|
def test_module_has_no_hardware_transport(self):
|
|
source = (
|
|
ADDON / "pirate_ray_jcz" / "recipe_metadata.py"
|
|
).read_text(encoding="utf-8").lower()
|
|
for forbidden in (
|
|
"import usb",
|
|
"import serial",
|
|
"import socket",
|
|
"pyusb",
|
|
):
|
|
self.assertNotIn(forbidden, source)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|