feat(hero): v0.1.2 — scale calibration, façade detail, export preset refactor (#28)

Merged PR #28: v0.1.2 — scale calibration, façade detail, export preset

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
This commit was merged in pull request #28.
This commit is contained in:
zwitschi authored and zwitschi committed 2026-11-08 18:48:33 +01:00
1 parent 640bff9631
commit ba68dcfdbc
60 files changed
+1114 -291

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
"""Tests for Tools/facade_detail_concepts.py — the pure parts.
No network calls: the OpenRouter request and the image bytes are represented as
the small structures the API uses. Generation itself is manual and paid.
"""
import base64
import json
import os
import sys
import unittest
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
sys.path.insert(0, os.path.join(ROOT, "Tools"))
import facade_detail_concepts as fc # noqa: E402
class StudyTableTest(unittest.TestCase):
def test_every_study_has_a_reference_and_a_prompt(self):
self.assertTrue(fc.STUDIES)
for name, (ref, prompt) in fc.STUDIES.items():
self.assertTrue(ref, f"{name} has no reference")
self.assertTrue(prompt.strip(), f"{name} has no prompt")
# References are relative to Art/reference and never escape it.
self.assertFalse(os.path.isabs(ref), f"{name} reference is absolute")
self.assertNotIn("..", ref, f"{name} reference escapes the directory")
def test_prompt_always_carries_the_anchor_suffix(self):
for name in fc.STUDIES:
prompt = fc.build_prompt(name)
self.assertIn(fc.ANCHOR_SUFFIX, prompt)
self.assertIn(fc.STUDIES[name][1], prompt)
def test_unknown_study_raises(self):
with self.assertRaises(KeyError):
fc.build_prompt("no-such-study")
class NextFreePathTest(unittest.TestCase):
def test_uses_the_plain_name_when_free(self):
import tempfile
with tempfile.TemporaryDirectory() as tmp:
self.assertEqual(os.path.basename(fc.next_free_path("x", tmp)), "x.png")
def test_never_overwrites(self):
import tempfile
with tempfile.TemporaryDirectory() as tmp:
open(os.path.join(tmp, "x.png"), "w").close()
self.assertEqual(os.path.basename(fc.next_free_path("x", tmp)), "x-v2.png")
open(os.path.join(tmp, "x-v2.png"), "w").close()
self.assertEqual(os.path.basename(fc.next_free_path("x", tmp)), "x-v3.png")
class DecodeImageTest(unittest.TestCase):
PAYLOAD_BYTES = b"\x89PNG\r\n\x1a\nfake"
def _data_url(self):
return "data:image/png;base64," + base64.b64encode(self.PAYLOAD_BYTES).decode()
def test_reads_message_images(self):
payload = {"choices": [{"message": {
"images": [{"image_url": {"url": self._data_url()}}]}}]}
self.assertEqual(fc.decode_image(payload), self.PAYLOAD_BYTES)
def test_reads_a_data_url_embedded_in_content(self):
payload = {"choices": [{"message": {"content": f"here: {self._data_url()}"}}]}
self.assertEqual(fc.decode_image(payload), self.PAYLOAD_BYTES)
def test_refusal_raises_with_the_text(self):
payload = {"choices": [{"message": {"content": "I cannot do that."}}]}
with self.assertRaises(SystemExit):
fc.decode_image(payload)
class DataUrlTest(unittest.TestCase):
def test_encodes_a_jpeg(self):
import tempfile
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as handle:
handle.write(b"jpegbytes")
path = handle.name
try:
url = fc.data_url(path)
self.assertTrue(url.startswith("data:image/jpeg;base64,"))
finally:
os.unlink(path)
def test_rejects_an_unsupported_type(self):
import tempfile
with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as handle:
path = handle.name
try:
with self.assertRaises(SystemExit):
fc.data_url(path)
finally:
os.unlink(path)
class SidecarShapeTest(unittest.TestCase):
def test_studies_and_outputs_are_local_only(self):
# The output directory lives under the gitignored Art/reference tree, so
# neither the images nor the sidecars can be committed.
self.assertIn(os.path.join("Art", "reference"), fc.OUT_DIR)
self.assertIn("gitignored", fc.__doc__)
if __name__ == "__main__":
unittest.main()