""" gemini_factory + package_exporter 单元测试 """ import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import json import tempfile import zipfile from pathlib import Path import pytest from app.services.ai_engine.gemini_factory import ( build_ai_clients, _extract_image_bytes, _extract_gemini_image ) from app.services.ai_engine.package_exporter import build_delivery_package # ── gemini_factory ───────────────────────────────────────── class TestGeminiFactory: def test_build_clients_returns_ai_clients(self, monkeypatch): monkeypatch.setenv("IMAGE_API_BASE", "https://api.example.com") clients = build_ai_clients(plain_key="test_key_1234") assert clients is not None assert clients._model == clients._model_text def test_model_property_uses_env(self, monkeypatch): monkeypatch.setenv("IMAGE_API_BASE", "https://api.example.com") monkeypatch.setenv("MODEL_TEXT", "gpt-5.5") clients = build_ai_clients(plain_key="test_key_1234") assert clients._model_text == "gpt-5.5" def test_extract_image_bytes_b64(self): import base64 fake_png = b"\x89PNG fake" b64 = base64.b64encode(fake_png).decode() result = _extract_image_bytes({"data": [{"b64_json": b64}]}) assert result == fake_png def test_extract_image_bytes_empty_raises(self): with pytest.raises(ValueError): _extract_image_bytes({"data": []}) def test_extract_gemini_image_success(self): import base64 fake_img = b"\xff\xd8\xff fake jpeg" b64 = base64.b64encode(fake_img).decode() resp = {"candidates": [{"content": {"parts": [{"inlineData": {"data": b64}}]}}]} result = _extract_gemini_image(resp) assert result == fake_img def test_extract_gemini_image_no_image_raises(self): resp = {"candidates": [{"content": {"parts": [{"text": "no image"}]}}]} with pytest.raises(ValueError): _extract_gemini_image(resp) def test_key_not_in_repr(self, monkeypatch): monkeypatch.setenv("IMAGE_API_BASE", "https://api.example.com") clients = build_ai_clients(plain_key="super_secret_key_xyz") # repr 不泄露 key assert "super_secret_key_xyz" not in repr(clients) # ── package_exporter ────────────────────────────────────── class TestPackageExporter: def _make_notes(self): return [{ "title": "早八素颜也能有气色", "content": "姐妹们实测下来,轻薄不厚重是关键。✅ 通勤必备。", "tags": ["#素颜霜", "#日常通勤"], "images": [ {"seq": 1, "role": "hook", "data": b"\xff\xd8\xff fake_jpg_1"}, {"seq": 2, "role": "proof", "data": b"\xff\xd8\xff fake_jpg_2"}, ], "banned_word_status": "pass", }] def test_creates_zip(self, tmp_path): zip_path = build_delivery_package( workspace_id=1, task_id=99, notes=self._make_notes(), base_path=str(tmp_path), ) assert Path(zip_path).exists() assert zip_path.endswith(".zip") def test_zip_contains_required_files(self, tmp_path): zip_path = build_delivery_package( workspace_id=1, task_id=99, notes=self._make_notes(), base_path=str(tmp_path), ) with zipfile.ZipFile(zip_path) as zf: names = zf.namelist() # 应含文案.txt、发布清单、合规说明 assert any("文案.txt" in n for n in names) assert any("发布清单" in n for n in names) assert any("合规说明" in n for n in names) def test_images_named_by_seq(self, tmp_path): zip_path = build_delivery_package( workspace_id=1, task_id=99, notes=self._make_notes(), base_path=str(tmp_path), ) with zipfile.ZipFile(zip_path) as zf: names = zf.namelist() # 图片按 seq 序号命名 assert any("01_hook.jpg" in n for n in names) assert any("02_proof.jpg" in n for n in names) def test_copy_txt_contains_title_and_tags(self, tmp_path): zip_path = build_delivery_package( workspace_id=1, task_id=99, notes=self._make_notes(), base_path=str(tmp_path), ) with zipfile.ZipFile(zip_path) as zf: copy_txt = zf.read("note_01/文案.txt").decode("utf-8") assert "早八素颜也能有气色" in copy_txt assert "#素颜霜" in copy_txt def test_compliance_note_shows_status(self, tmp_path): zip_path = build_delivery_package( workspace_id=1, task_id=99, notes=self._make_notes(), base_path=str(tmp_path), ) with zipfile.ZipFile(zip_path) as zf: compliance = zf.read("✅合规说明.txt").decode("utf-8") assert "✅通过" in compliance def test_no_images_still_creates_package(self, tmp_path): notes = [{**self._make_notes()[0], "images": []}] zip_path = build_delivery_package( workspace_id=1, task_id=100, notes=notes, base_path=str(tmp_path), ) assert Path(zip_path).exists()