mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-18 23:58:07 +02:00
* feat: bundle standalone taste distillation and application workflows * docs: fix imported taste skill markdown lint * docs: align Turkish agent catalog with taste skills * refactor: make ECC the canonical reusable video engine * fix: preserve video duration when applying image overlays * fix: preserve background colors in image compositing * fix: report best-effort duration targets and shortfalls * feat: ship verified Fusion presets with compatibility provenance * feat(tasteforge): preserve native edits in application bundles * feat(tasteforge): compile local preservation without hosted input * fix: update js-yaml to patched 4.3.2 * test: report bounded Stop wrapper failure diagnostics * fix(tasteforge): fail closed on unsafe output names, missing overlays and cadence - cli: default report and spec paths are derived from pack name and profile genre; require the manifest's name pattern before using either as a filename part so a traversal string cannot write outside cwd/out. - apply_local: a pack without cadence.json, or with no measured shots and no explicit mean_shot, raises instead of silently planning 1.0s shots and reporting a measured cadence. - legacy apply: a missing overlay aborts before any paid upload; forge() would have rejected it after every take was generated. - requirements-live: pin fal-client>=0.13.0, the first release whose subscribe() accepts client_timeout. Addresses the five P1 findings from the independent review of #3033. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015fxHRsydPqEcYngGbqkgt1 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
111 lines
4.4 KiB
Python
111 lines
4.4 KiB
Python
"""Mocked minting regressions; no optional renderer or provider is needed."""
|
|
|
|
import importlib.util
|
|
import io
|
|
import tempfile
|
|
import unittest
|
|
from contextlib import redirect_stdout
|
|
from pathlib import Path
|
|
from types import ModuleType, SimpleNamespace
|
|
from unittest.mock import Mock, patch
|
|
|
|
SCRIPT = (
|
|
Path(__file__).resolve().parents[1] / "skills/taste-application/scripts/mint3d.py"
|
|
)
|
|
|
|
|
|
class MintPreservationTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
self.props = self.root / "props"
|
|
self.props.mkdir()
|
|
self.fal = Mock()
|
|
self.fal.FalError = RuntimeError
|
|
self.fal.ENDPOINTS = dict.fromkeys(
|
|
("image_to_3d", "text_to_3d", "retopology", "part_split"), "model"
|
|
)
|
|
self.fal.is_dry_run.return_value = False
|
|
self.fal.text_to_3d.return_value = "https://v3.fal.media/fullpbr.glb"
|
|
self.fal.retopologize.return_value = "https://v3.fal.media/proxy.glb"
|
|
self.fal.download.side_effect = lambda url, dest: Path(dest).write_text(url)
|
|
pack = SimpleNamespace(
|
|
dir=self.root, spec_path=self.root / "spec.json", read_json=lambda _: {}
|
|
)
|
|
self.loader = Mock(return_value=pack)
|
|
self.renderer = Mock()
|
|
self.renderer.turntable.return_value = (["frame.png"], "mock")
|
|
fake = ModuleType("taste")
|
|
fake.falapi = self.fal
|
|
fake.pack = SimpleNamespace(load=self.loader)
|
|
fake.render3d = self.renderer
|
|
spec = importlib.util.spec_from_file_location("mint_preservation_test", SCRIPT)
|
|
self.module = importlib.util.module_from_spec(spec)
|
|
with patch.dict("sys.modules", {"taste": fake}):
|
|
spec.loader.exec_module(self.module)
|
|
quiet = redirect_stdout(io.StringIO())
|
|
quiet.__enter__()
|
|
self.addCleanup(quiet.__exit__, None, None, None)
|
|
|
|
def test_raw_retained_before_remesh_and_rendered(self):
|
|
def remesh(url, **kwargs):
|
|
self.assertEqual((self.props / "visor.glb").read_text(), url)
|
|
return "https://v3.fal.media/proxy.glb"
|
|
|
|
self.fal.retopologize.side_effect = remesh
|
|
record = self.module.mint3d(
|
|
"genre", prompt="chrome visor", name="visor", retopo=True
|
|
)
|
|
self.assertEqual(
|
|
(self.props / "visor.glb").read_text(), "https://v3.fal.media/fullpbr.glb"
|
|
)
|
|
self.assertEqual(
|
|
(self.props / "visor_retopo.glb").read_text(),
|
|
"https://v3.fal.media/proxy.glb",
|
|
)
|
|
self.assertEqual(record["mesh"], str(self.props / "visor.glb"))
|
|
self.assertEqual(record["retopo_mesh"], str(self.props / "visor_retopo.glb"))
|
|
self.assertEqual(
|
|
self.renderer.turntable.call_args.args[0], self.props / "visor.glb"
|
|
)
|
|
|
|
def test_existing_artifacts_refused_before_generation(self):
|
|
for relative in (
|
|
"props/visor.glb",
|
|
"props/visor_plate.png",
|
|
"props/visor_retopo.glb",
|
|
"props/visor.json",
|
|
"props/visor_part00.glb",
|
|
"turntables/visor.mp4",
|
|
"turntables/visor",
|
|
):
|
|
with self.subTest(relative=relative):
|
|
artifact = self.root / relative
|
|
artifact.parent.mkdir(parents=True, exist_ok=True)
|
|
artifact.write_bytes(b"original")
|
|
with self.assertRaises(FileExistsError):
|
|
self.module.mint3d(
|
|
"genre", prompt="chrome visor", name="visor", retopo=True
|
|
)
|
|
self.assertEqual(artifact.read_bytes(), b"original")
|
|
artifact.unlink()
|
|
self.fal.text_to_3d.assert_not_called()
|
|
self.fal.text_to_image.assert_not_called()
|
|
self.fal.image_to_3d.assert_not_called()
|
|
|
|
def test_remesh_failure_keeps_original_renderable(self):
|
|
self.fal.retopologize.side_effect = RuntimeError("mock failure")
|
|
record = self.module.mint3d("genre", prompt="visor", name="visor", retopo=True)
|
|
self.assertEqual(
|
|
(self.props / "visor.glb").read_text(), "https://v3.fal.media/fullpbr.glb"
|
|
)
|
|
self.assertNotIn("retopo_mesh", record)
|
|
self.assertEqual(
|
|
self.renderer.turntable.call_args.args[0], self.props / "visor.glb"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|