• Implemented robust model and scene reference validations in both the FastAPI backend and frontend to prevent empty, corrupt, or missing files from silently failing or producing duplicated background
   fallbacks.
   • Enhanced scenery prompt translation on the backend to dynamically map user-specified "Image" numbers e.g. Image 1, Image 2 to Qwen-specific "Picture" placeholders Picture 1, Picture 2, avoiding template
   mismatch desyncs.

   Changes
   • File & Path Validation: Added detailed validations to the /generate-scenery endpoint to ensure model_filename and scene_image paths exist on disk, are resolved without query parameters stripping ?t=..., are
   not directories, and are openable and verifiable as PIL images.
   • Identical Reference Checks: Introduced checks in both the backend and frontend submitGenerateScenery inside car.html to raise an HTTP 400 exception / display a toast warning if the model and scene references
   point to the same image.
   • Dynamic Prompt Mapping: Configured the backend to auto-translate occurrences of image 1/2/3 in custom scenery prompts to Picture 1/2/3, facilitating correct Qwen layout hint targeting.
   • Regression Unit Tests: Wrote comprehensive unit tests test_13b_scenery_validation_checks in test_regression_api.py verifying file checking, duplicate reference detection, and query parameter stripping.

   Verification
   • Executed test_regression_api.py simulated API testing with 100% success 19/19 tests passed.
   • Executed test_regression_qwen.py end-to-end model/hardware integration test suite successfully 2/2 tests passed.
This commit is contained in:
mike
2026-07-01 12:09:53 +02:00
parent ec4fa1aa72
commit 5a8acece09
4 changed files with 559 additions and 64 deletions

View File

@@ -799,6 +799,116 @@ class TestAPIRegression(unittest.TestCase):
except Exception:
pass
def test_13b_scenery_validation_checks(self):
from unittest.mock import patch
import edit_api
from fastapi import HTTPException
# Test 1: model image is completely missing -> raise 404
req_missing = edit_api.SceneryRequest(
model_filename="non_existent_model_file_999.png",
scene_image="some_scene.png",
prompt="replace person from image 1 with the person from Image 2"
)
with self.assertRaises(HTTPException) as ctx:
edit_api.generate_scenery(req_missing)
self.assertEqual(ctx.exception.status_code, 404)
self.assertIn("Model image not found", ctx.exception.detail)
# Write dummy model image on disk for subsequent tests
model_filename = "test_model_validation_123.png"
model_path = os.path.join(self.output_dir, model_filename)
Image.new("RGB", (10, 10), color="red").save(model_path)
try:
# Test 2: model and scene images are the same file -> raise 400
req_identical = edit_api.SceneryRequest(
model_filename=model_filename,
scene_image=model_filename,
prompt="replace person from image 1 with the person from Image 2"
)
with self.assertRaises(HTTPException) as ctx:
edit_api.generate_scenery(req_identical)
self.assertEqual(ctx.exception.status_code, 400)
self.assertIn("cannot be the same image", ctx.exception.detail)
# Test 3: prompt refinement "image 1/2" -> "Picture 1/2"
# We mock the thread start and save_db_prompt to verify the processed prompt
saved_prompt = []
def mock_save_db_prompt(ptype, prompt_text, meta):
saved_prompt.append(prompt_text)
dummy_scene_filename = "dummy_scene_validation_123.png"
dummy_scene_path = os.path.join(self.output_dir, dummy_scene_filename)
Image.new("RGB", (10, 10), color="blue").save(dummy_scene_path)
try:
req_refine = edit_api.SceneryRequest(
model_filename=model_filename,
scene_image=dummy_scene_filename,
prompt="replace person from image 1 with the person from Image 2"
)
with patch("edit_api._load_wireframe_dir", return_value=self.output_dir), \
patch("edit_api.database.save_db_prompt", side_effect=mock_save_db_prompt), \
patch("threading.Thread.start") as mock_thread_start:
edit_api.generate_scenery(req_refine)
self.assertEqual(len(saved_prompt), 1)
self.assertEqual(saved_prompt[0], "replace person from Picture 1 with the person from Picture 2")
finally:
if os.path.exists(dummy_scene_path):
os.remove(dummy_scene_path)
finally:
if os.path.exists(model_path):
os.remove(model_path)
def test_13c_scenery_generation_insufficient_references_checks(self):
import edit_api
from fastapi import HTTPException
from PIL import Image
# Write dummy model image on disk
model_filename = "test_scenery_insufficient_model.png"
model_path = os.path.join(self.output_dir, model_filename)
Image.new("RGB", (10, 10), color="red").save(model_path)
try:
# Test 1: both scene_image and scene_video are missing -> raise HTTPException 400
req_no_bg = edit_api.SceneryRequest(
model_filename=model_filename,
prompt="some prompt"
)
with self.assertRaises(HTTPException) as ctx:
edit_api.generate_scenery(req_no_bg)
self.assertEqual(ctx.exception.status_code, 400)
self.assertIn("requires a background scene reference", ctx.exception.detail)
# Test 2: inside _scenery_worker, if len(refs) < 2, it raises ValueError and sets status to error
edit_api.jobs["test_scenery_insufficient_job"] = {"status": "running"}
dummy_scene = Image.new("RGB", (10, 10), color="blue")
edit_api._scenery_worker(
job_id="test_scenery_insufficient_job",
model_filename=model_filename,
scene_pil=dummy_scene,
prompt="test scenery prompt",
seed=42,
extra_pils=[],
scene_video=None, # explicitly None to trigger the < 2 refs check
scene_image=None, # explicitly None to trigger the < 2 refs check
extra_filename=None
)
job = edit_api.jobs["test_scenery_insufficient_job"]
self.assertEqual(job["status"], "error")
self.assertIn("expected at least 2 source references", job["error"])
finally:
if os.path.exists(model_path):
os.remove(model_path)
def test_14_static_serialization_of_deep_metadata(self):
import edit_api
import database