Files
agent-pet-companion/skills/agent-pet-studio/tests/test_write_petpack_source.py
T

208 lines
9.1 KiB
Python

import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
from unittest import mock
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "write_petpack_source.py"
SPEC = importlib.util.spec_from_file_location("agent_pet_studio_source_helper", SCRIPT_PATH)
assert SPEC is not None and SPEC.loader is not None
studio_helper = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(studio_helper)
class StudioTimingTests(unittest.TestCase):
def test_skill_explains_why_portable_high_is_not_a_studio_option(self) -> None:
skill = (SCRIPT_PATH.parents[1] / "SKILL.md").read_text(encoding="utf-8")
skill = " ".join(skill.split())
self.assertIn("portable package and App", skill)
self.assertIn("ChatGPT/Codex built-in `imagegen`", skill)
self.assertIn("Reject `high` before image generation", skill)
self.assertIn("image model to return the selected runtime dimensions exactly", skill)
self.assertIn("stable equal-size", skill)
self.assertIn("exact-tier runtime PNGs", skill)
def test_quality_contract_has_only_low_and_standard(self) -> None:
self.assertEqual(
studio_helper.RENDER_SIZES,
{"low": (192, 208), "standard": (384, 416)},
)
self.assertEqual(studio_helper.quality_from_form({}), "standard")
self.assertEqual(studio_helper.quality_from_form({"quality": None}), "standard")
self.assertEqual(studio_helper.quality_from_form({"quality": "low"}), "low")
def test_rejects_high_quality_instead_of_silently_falling_back(self) -> None:
with self.assertRaisesRegex(SystemExit, "unsupported quality 'high'"):
studio_helper.quality_from_form({"quality": "high"})
with self.assertRaisesRegex(SystemExit, "unsupported quality 'high'"):
studio_helper.build_source({"quality": "high"})
def test_rejects_every_other_unsupported_quality(self) -> None:
for quality in ("ultra", "original", "HIGH", "", 384):
with self.subTest(quality=quality), self.assertRaises(SystemExit):
studio_helper.quality_from_form({"quality": quality})
def test_defaults_use_the_v3_authored_timing_contract(self) -> None:
timings, counts = studio_helper.timing_from_form({})
self.assertEqual(timings, studio_helper.DEFAULT_STATE_TIMINGS)
self.assertEqual(counts["idle"], 4)
self.assertEqual(counts["waiting"], 6)
self.assertEqual(counts["thinking"], 4)
self.assertEqual(counts["acknowledge"], 4)
self.assertEqual(counts["drag_left"], 6)
self.assertEqual(counts["drag_right"], 6)
self.assertEqual(sum(counts.values()), 42)
def test_v3_timing_flows_to_all_artifacts(self) -> None:
expected_counts = {
state: len(timing["frame_durations_ms"])
for state, timing in studio_helper.DEFAULT_STATE_TIMINGS.items()
}
def write_fake_png(path, *_args, **_kwargs):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"test-png")
with tempfile.TemporaryDirectory(prefix="agent-pet-studio-timing-") as temporary:
output = Path(temporary) / "petpack-source"
with mock.patch.object(studio_helper, "OUTPUT_DIR", output), mock.patch.object(
studio_helper, "write_png", side_effect=write_fake_png
):
manifest = studio_helper.build_source(
{
"description": "A timing contract test pet",
"style": "storybook",
"quality": "standard",
"reference_images": [],
}
)
brief = json.loads((output / "brief.json").read_text(encoding="utf-8"))
source = json.loads(
(output / "source" / "source.json").read_text(encoding="utf-8")
)
validation = json.loads(
(output / "build" / "validation.json").read_text(encoding="utf-8")
)
self.assertEqual(manifest["schema_version"], "apc.petpack.v3")
self.assertEqual(manifest["quality"], "standard")
self.assertEqual(
manifest["render_size"], {"width": 384, "height": 416}
)
self.assertEqual(
{
state["name"]: state["frame_durations_ms"]
for state in manifest["states"]
},
{
state: timing["frame_durations_ms"]
for state, timing in studio_helper.DEFAULT_STATE_TIMINGS.items()
},
)
self.assertEqual(brief["runtime"]["state_frame_counts"], expected_counts)
self.assertEqual(
brief["runtime"]["states"],
manifest["states"],
)
self.assertEqual(source["states"], manifest["states"])
self.assertEqual(source["state_frame_counts"], expected_counts)
self.assertEqual(source["form"]["quality"], "standard")
self.assertEqual(validation["states"], manifest["states"])
self.assertEqual(validation["state_frame_counts"], expected_counts)
for state, expected in expected_counts.items():
self.assertEqual(
len(list((output / "assets" / "frames" / state).glob("*.png"))),
expected,
)
def test_low_quality_uses_exact_runtime_render_size(self) -> None:
def write_fake_png(path, *_args, **_kwargs):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"test-png")
with tempfile.TemporaryDirectory(prefix="agent-pet-studio-low-") as temporary:
output = Path(temporary) / "petpack-source"
with mock.patch.object(studio_helper, "OUTPUT_DIR", output), mock.patch.object(
studio_helper, "write_png", side_effect=write_fake_png
):
manifest = studio_helper.build_source(
{
"description": "A low quality contract test pet",
"quality": "low",
}
)
self.assertEqual(manifest["quality"], "low")
self.assertEqual(manifest["render_size"], {"width": 192, "height": 208})
brief = json.loads((output / "brief.json").read_text(encoding="utf-8"))
source = json.loads(
(output / "source" / "source.json").read_text(encoding="utf-8")
)
self.assertEqual(brief["quality"], "low")
self.assertEqual(brief["runtime"]["render_size"], manifest["render_size"])
self.assertEqual(source["form"]["quality"], "low")
def test_rejects_removed_v1_timing_fields(self) -> None:
with self.assertRaises(SystemExit):
studio_helper.timing_from_form({"native_fps": 10})
with self.assertRaises(SystemExit):
studio_helper.timing_from_form({"state_durations_ms": {}})
def test_cli_validation_result_keeps_manifest_timing_contract(self) -> None:
counts = {
state: len(timing["frame_durations_ms"])
for state, timing in studio_helper.DEFAULT_STATE_TIMINGS.items()
}
manifest_states = [
{
"name": state,
"frames_dir": f"assets/frames/{state}",
**studio_helper.DEFAULT_STATE_TIMINGS[state],
}
for state in studio_helper.STATES
]
cli_result = mock.Mock(
returncode=0,
stdout=json.dumps(
{
"ok": True,
"frame_count": 1,
"state_frame_counts": {state: 1 for state in studio_helper.STATES},
"warnings": [],
}
),
stderr="",
)
with tempfile.TemporaryDirectory(prefix="agent-pet-studio-validation-") as temporary:
output = Path(temporary) / "petpack-source"
output.mkdir(parents=True)
(output / "manifest.json").write_text(
json.dumps(
{
"schema_version": "apc.petpack.v3",
"states": manifest_states,
}
),
encoding="utf-8",
)
with mock.patch.object(studio_helper, "OUTPUT_DIR", output), mock.patch.dict(
studio_helper.os.environ,
{"APC_PETCORE_CLI": "/fake/petcore-cli"},
), mock.patch.object(studio_helper.subprocess, "run", return_value=cli_result):
validation = studio_helper.validate_source()
persisted = json.loads(
(output / "build" / "validation.json").read_text(encoding="utf-8")
)
self.assertEqual(validation["frame_count"], 42)
self.assertEqual(validation["states"], manifest_states)
self.assertEqual(validation["state_frame_counts"], counts)
self.assertEqual(persisted, validation)
if __name__ == "__main__":
unittest.main()