"""T3.5 大纲注入上下文组装单测(确定性纯函数;ARCH §5.4)。 断言:四源(设定/伏笔/人物/世界观)都进文本、排序确定(同输入同输出)、空源不炸。 """ from __future__ import annotations from ww_api.services.outline_context import build_outline_context from ww_core.domain import ForeshadowLedgerView from ww_core.domain.project_repo import ProjectView from ww_core.domain.repositories import CharacterView, WorldEntityView def _project() -> ProjectView: import uuid return ProjectView( id=uuid.UUID(int=1), title="逆天剑帝", genre="玄幻", logline="少年逆袭", selling_points=["爽点密集", "升级流"], ) def test_build_context_includes_all_four_sources() -> None: text = build_outline_context( project=_project(), foreshadow=[ ForeshadowLedgerView( code="F1", title="神秘信物", status="OPEN", planted_at=3, expected_close_to=10 ) ], characters=[CharacterView(name="主角", role="protagonist", motive="复仇")], world_entities=[WorldEntityView(type="location", name="剑冢")], ) assert "逆天剑帝" in text assert "F1" in text and "神秘信物" in text assert "回收窗口 ?-10" in text assert "主角" in text assert "剑冢" in text def test_build_context_is_deterministic_and_sorted() -> None: project = _project() fs = [ ForeshadowLedgerView(code="F2", title="b", status="OPEN"), ForeshadowLedgerView(code="F1", title="a", status="OPEN"), ] a = build_outline_context(project=project, foreshadow=fs, characters=[], world_entities=[]) b = build_outline_context( project=project, foreshadow=list(reversed(fs)), characters=[], world_entities=[] ) assert a == b # F1 排在 F2 前(按 code 升序)。 assert a.index("[F1]") < a.index("[F2]") def test_build_context_handles_empty_sources() -> None: text = build_outline_context( project=_project(), foreshadow=[], characters=[], world_entities=[] ) assert "(无)" in text