Files
life-echo/api/tests/test_image_prompt_policy.py
Sully 53e0065e3e refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env)
统一错误契约
Auth 与事务边界
Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client
可观测性(OpenTelemetry + LGTM)
2026-05-22 13:44:50 +08:00

166 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""MemoirImagePromptService 与 image_prompt_fallback_disabledstory 主图 / 章节封面)。"""
import json
import pytest
from app.features.memoir.memoir_images.prompting import MemoirImagePromptService
from app.features.memoir.memoir_images.settings import MemoirImageSettings
from app.features.memoir.constants import memoir
def _svc(llm=None) -> MemoirImagePromptService:
return MemoirImagePromptService(llm=llm, settings=MemoirImageSettings())
def test_story_primary_fallback_uses_placeholder_when_llm_disabled():
from app.features.memoir.memoir_images.image_placeholder_template import (
IMAGE_PLACEHOLDER_TEMPLATE,
)
out = _svc(llm=None).build_story_primary_prompt(
"My Story", "career", "rainy street", None
)
assert IMAGE_PLACEHOLDER_TEMPLATE in out["prompt"]
assert "rainy street" in out["prompt"]
def test_story_primary_fallback_disabled_requires_brief(monkeypatch):
monkeypatch.setattr(
"app.features.memoir.memoir_images.prompting.memoir.image_prompt_fallback_disabled",
True,
)
with pytest.raises(RuntimeError, match="prompt_brief"):
_svc(llm=None).build_story_primary_prompt("T", "career", "", None)
def test_story_primary_style_from_chapter_category_when_no_intent_style():
"""Story.stage 存章节 category 时,未指定 style_profile 应走 CATEGORY_STYLE_MAP。"""
out = _svc(llm=None).build_story_primary_prompt("T", "career_early", "brief", None)
assert out["style"] == "realistic"
def test_story_primary_style_from_chat_stage_when_no_intent_style():
out = _svc(llm=None).build_story_primary_prompt("T", "career", "brief", None)
assert out["style"] == "realistic"
def test_story_primary_fallback_disabled_requires_llm(monkeypatch):
monkeypatch.setattr(
"app.features.memoir.memoir_images.prompting.memoir.image_prompt_fallback_disabled",
True,
)
with pytest.raises(RuntimeError, match="requires LLM"):
_svc(llm=None).build_story_primary_prompt("T", "career", "brief", None)
def test_story_primary_llm_path(monkeypatch):
class _FakeLlm:
def invoke(self, *_a, **_kw): # noqa: ANN001
raise AssertionError("invoke should not be used")
captured: dict[str, str] = {}
def fake_invoke_json(llm, prompt_text, **_kw): # noqa: ANN001
captured["prompt_text"] = prompt_text
return json.dumps(
{
"prompt": "A hero scene at dawn",
"style": "watercolor",
"size": "1280x720",
}
)
monkeypatch.setattr(
"app.features.memoir.memoir_images.prompting.invoke_json_object",
fake_invoke_json,
)
out = _svc(llm=_FakeLlm()).build_story_primary_prompt(
"Title", "childhood", "memo", "realistic"
)
assert out["prompt"].lower().startswith("watercolor")
assert "dawn" in out["prompt"].lower()
assert out["style"] == "watercolor"
raw = captured.get("prompt_text", "")
assert "Title" in raw and "memo" in raw
def test_cover_fallback_uses_service_template_when_llm_disabled():
out = _svc(llm=None).build_cover_prompt(
chapter_title="Ch1",
chapter_category="family",
context_excerpt="mountain lake",
)
assert "mountain lake" in out["prompt"].lower()
assert (
"chapter cover" in out["prompt"].lower()
or "illustration" in out["prompt"].lower()
)
def test_cover_fallback_when_excerpt_empty(monkeypatch):
monkeypatch.setattr(
"app.features.memoir.memoir_images.prompting.memoir.image_prompt_fallback_disabled",
False,
)
out = _svc(llm=None).build_cover_prompt(
chapter_title="",
chapter_category="family",
context_excerpt="",
)
assert len(out["prompt"]) > 20
def test_cover_fallback_disabled_requires_excerpt(monkeypatch):
monkeypatch.setattr(
"app.features.memoir.memoir_images.prompting.memoir.image_prompt_fallback_disabled",
True,
)
with pytest.raises(RuntimeError, match="context_excerpt"):
_svc(llm=None).build_cover_prompt(
chapter_title="T",
chapter_category="family",
context_excerpt="",
)
def test_image_prompt_orchestrator_provider_failure_uses_fallback(monkeypatch):
from app.agents.image_prompt.orchestrator import get_image_prompt_orchestrator
class BoomGateway:
def langchain_llm_for(self, *_a, **_kw): # noqa: ANN001
raise RuntimeError("provider missing")
monkeypatch.setattr(
"app.agents.image_prompt.orchestrator.memoir.image_prompt_fallback_disabled",
False,
)
monkeypatch.setattr("app.core.llm_gateway.LlmGateway", lambda: BoomGateway())
orch = get_image_prompt_orchestrator()
out = orch.build_cover_prompt(
chapter_title="T",
chapter_category="family",
context_excerpt="mountain lake",
)
assert "mountain lake" in out["prompt"].lower()
def test_image_prompt_orchestrator_provider_failure_raises_when_disabled(
monkeypatch,
):
from app.agents.image_prompt.orchestrator import get_image_prompt_orchestrator
class BoomGateway:
def langchain_llm_for(self, *_a, **_kw): # noqa: ANN001
raise RuntimeError("provider missing")
monkeypatch.setattr(
"app.agents.image_prompt.orchestrator.memoir.image_prompt_fallback_disabled",
True,
)
monkeypatch.setattr("app.core.llm_gateway.LlmGateway", lambda: BoomGateway())
with pytest.raises(RuntimeError, match="provider missing"):
get_image_prompt_orchestrator()