"""单张编辑的新参数必须进入同一次生图任务,且主图顺序与尺寸准确。""" from __future__ import annotations import sys import unittest from pathlib import Path from types import SimpleNamespace from unittest.mock import AsyncMock, patch from fastapi import HTTPException SERVER_DIR = Path(__file__).resolve().parents[1] if str(SERVER_DIR) not in sys.path: sys.path.insert(0, str(SERVER_DIR)) from api.suite import suite_image_edit, _with_dimensions from schemas.suite import ImageEditSingleRequest, ProductDimensions from services.generator import _image_size from services.prompts.common import build_context from services.prompts.single_edit import build_single_edit_prompt class SingleEditRequestTests(unittest.IsolatedAsyncioTestCase): async def test_main_and_two_auxiliary_images_keep_order_in_one_item(self) -> None: request = ImageEditSingleRequest( main_image_url="main", reference_image_urls=["detail", "scene"], prompt="替换背景", model="gpt-image-2@pcore", translate_text=True, target_language="ar", background="reference", ratio="4:5", ) job = AsyncMock(return_value=SimpleNamespace(id="task-id")) with patch("api.suite.create_image_job", job): response = await suite_image_edit(request) self.assertEqual(response.suite_id, "task-id") payload = job.await_args.kwargs self.assertEqual(payload["ratio"], "4:5") self.assertEqual(payload["lang"], "ar") self.assertEqual(payload["provider"], "pcore") self.assertEqual(payload["model"], "gpt-image-2") self.assertEqual(len(payload["item_payloads"]), 1) self.assertEqual([image["url"] for image in payload["request_payload"]["ref_images"]], ["main", "detail", "scene"]) self.assertTrue(payload["request_payload"]["single_edit"]["translate_text"]) async def test_structured_edit_rejects_other_provider(self) -> None: with self.assertRaises(HTTPException) as raised: await suite_image_edit(ImageEditSingleRequest( main_image_url="main", model="nano-banana-2", )) self.assertEqual(raised.exception.status_code, 400) class SingleEditPromptTests(unittest.TestCase): def test_translation_background_and_partial_dimensions_share_one_prompt(self) -> None: context = build_context(_with_dimensions( {"title": "AI 精修"}, ProductDimensions(width=8, unit="cm"), required=True, )) prompt = build_single_edit_prompt( requirements="保留商品颜色", config={"translate_text": True, "target_language": "ar", "background": "scene", "background_description": "warm kitchen"}, context=context, style_set=2, style_prompt=None, ratio="3:4", is_size_chart=True, reference_count=2, ) self.assertIn("Image 1 is the fixed main product image", prompt) self.assertIn("Images 2 through 3 are auxiliary", prompt) self.assertIn("Arabic", prompt) self.assertIn("right-to-left", prompt) self.assertIn("warm kitchen", prompt) self.assertIn("width: 8.0 cm", prompt) self.assertIn("Do not draw, label, or show an arrow for: length, height", prompt) def test_four_to_five_size_does_not_fall_back_to_square(self) -> None: for provider, is_wan, model in ( ("pcore", True, "gpt-image-2"), ("rightapi", True, "gpt-image-2"), ("doubao", True, "seedream"), ("tongyi", True, "wan2.6-image"), ("tongyi", False, "qwen-image-3.0-pro"), ): with self.subTest(provider=provider, model=model): width, height = map(int, _image_size(provider, "4:5", is_wan, model).replace("*", "x").split("x")) self.assertEqual(width * 5, height * 4) if __name__ == "__main__": unittest.main()