feat(ai): enforce direct image edit contract
This commit is contained in:
+47
-5
@@ -69,10 +69,10 @@ class AITests(TempDirMixin, unittest.TestCase):
|
||||
"name": "Image",
|
||||
"category": "image",
|
||||
"enabled": True,
|
||||
"url": "https://example.invalid/v1/chat/completions",
|
||||
"url": "https://example.invalid/v1",
|
||||
"model": "image-model",
|
||||
"api_key": "sk-image-secret",
|
||||
"api_type": "auto",
|
||||
"api_type": "images_edits",
|
||||
"connect_timeout_seconds": 1,
|
||||
"timeout_seconds": 1,
|
||||
"extra_body": {},
|
||||
@@ -378,9 +378,13 @@ class AITests(TempDirMixin, unittest.TestCase):
|
||||
b64_image = base64.b64encode(generated.getvalue()).decode("ascii")
|
||||
|
||||
def fake_urlopen(request, timeout=None):
|
||||
body = json.loads(request.data.decode("utf-8"))
|
||||
self.assertEqual("image-model", body["model"])
|
||||
self.assertIn("目标分辨率:512", body["messages"][0]["content"][0]["text"])
|
||||
body = request.data
|
||||
self.assertIn(b'name="model"', body)
|
||||
self.assertIn(b"image-model", body)
|
||||
self.assertIn(b'name="image[]"', body)
|
||||
self.assertIn(b'name="n"', body)
|
||||
self.assertIn(b"\r\n1\r\n", body)
|
||||
self.assertIn("multipart/form-data", request.headers["Content-type"])
|
||||
return _Response({"data": [{"b64_json": b64_image}]})
|
||||
|
||||
with mock.patch("app.ai.urllib.request.urlopen", side_effect=fake_urlopen):
|
||||
@@ -399,6 +403,44 @@ class AITests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_image_edit_body_keeps_repeated_image_fields_in_order(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
first = os.path.join(temp_dir, "primary.jpg")
|
||||
second = os.path.join(temp_dir, "reference.png")
|
||||
with open(first, "wb") as fh:
|
||||
fh.write(b"first-image")
|
||||
with open(second, "wb") as fh:
|
||||
fh.write(b"second-image")
|
||||
|
||||
body, content_type = ai._image_edit_body(
|
||||
{"model": "image-model", "extra_body": {"quality": "high"}},
|
||||
"生成商品图",
|
||||
[first, second],
|
||||
"1k",
|
||||
)
|
||||
|
||||
self.assertIn("multipart/form-data", content_type)
|
||||
self.assertEqual(2, body.count(b'name="image[]"'))
|
||||
self.assertLess(body.index(b"primary.jpg"), body.index(b"reference.png"))
|
||||
self.assertIn(b'name="n"', body)
|
||||
self.assertIn(b"\r\n1\r\n", body)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_direct_image_response_only_accepts_openai_data_fields(self):
|
||||
encoded = base64.b64encode(b"image-bytes").decode("ascii")
|
||||
|
||||
self.assertEqual(encoded, ai._find_image_ref({"data": [{"b64_json": encoded}]}))
|
||||
self.assertEqual(
|
||||
"https://images.example.com/new.png",
|
||||
ai._find_image_ref({"data": [{"url": "https://images.example.com/new.png"}]}),
|
||||
)
|
||||
self.assertIsNone(ai._find_image_ref({"choices": [{"image_url": "x"}]}))
|
||||
with self.assertRaisesRegex(ai.AIError, "OpenAI 图片编辑接口"):
|
||||
ai._extract_image_bytes({"message": {"url": "https://bad.example/x"}}, {}, {})
|
||||
with self.assertRaisesRegex(ai.AIError, "只允许 http/https"):
|
||||
ai._extract_image_bytes({"data": [{"url": "file:///tmp/image.png"}]}, {}, {})
|
||||
|
||||
def test_cmhub_gen_title_uses_alias_and_emits_metadata(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, key_path = self._cmhub_config(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user