ppt-web / tests /test_free_template_streaming.py
26fwyzpz6f-max
Clean deploy without binary files
6aecb2e
Raw
History Blame Contribute Delete
7.69 kB
from pathlib import Path
from types import SimpleNamespace
import pytest
from landppt.services.template.template_selection_service import TemplateSelectionService
ROOT = Path(__file__).resolve().parents[1]
def _read(relative_path: str) -> str:
return (ROOT / relative_path).read_text(encoding="utf-8")
class _ProjectManagerStub:
def __init__(self, project):
self.project = project
self.metadata_updates = []
async def get_project(self, project_id, user_id=None):
return self.project
async def update_project_metadata(self, project_id, metadata):
copied = dict(metadata)
self.metadata_updates.append(copied)
self.project.project_metadata = copied
class _GlobalTemplateServiceStub:
def __init__(self):
self.calls = []
async def generate_template_with_ai_stream(self, **kwargs):
self.calls.append(kwargs)
yield {
"type": "thinking",
"content": "前置说明```html\n<!DOCTYPE html><html><body><div class='preview'>partial",
}
yield {
"type": "complete",
"message": "模板生成完成!",
"template_name": "自由模板-proj1234",
"html_template": "<!DOCTYPE html><html><body><div class='final'>done</div></body></html>",
}
class _TemplateSelectionServiceStub:
def __init__(self, project):
self.project_manager = _ProjectManagerStub(project)
self.global_template_service = _GlobalTemplateServiceStub()
self._free_template_generation_locks = {}
self.cleared_projects = []
def clear_cached_style_genes(self, project_id=None):
self.cleared_projects.append(project_id)
class _JsonRequestStub:
def __init__(self, payload):
self._payload = payload
async def json(self):
return self._payload
@pytest.mark.asyncio
async def test_stream_free_template_generation_emits_preview_and_persists_final_html():
project = SimpleNamespace(
topic="流式模板",
outline={"slides": [{"title": "封面"}]},
confirmed_requirements={"style": "modern"},
requirements="请生成现代商务风格",
description="演示流式模板预览",
project_metadata={"template_mode": "free", "free_template_status": "pending"},
)
stub = _TemplateSelectionServiceStub(project)
service = TemplateSelectionService(stub)
events = [
event
async for event in service.stream_free_template_generation("proj12345678", user_id=99, force=False)
]
assert [event["type"] for event in events[:2]] == ["status", "status"]
assert any(event["type"] == "preview" for event in events)
assert events[-1]["type"] == "complete"
assert events[-1]["template"]["html_template"].startswith("<!DOCTYPE html>")
assert stub.project_manager.metadata_updates[0]["free_template_status"] == "generating"
final_metadata = stub.project_manager.metadata_updates[-1]
assert final_metadata["free_template_status"] == "ready"
assert final_metadata["free_template_name"] == "自由模板-proj1234"
assert final_metadata["free_template_html"] == "<!DOCTYPE html><html><body><div class='final'>done</div></body></html>"
assert final_metadata["free_template_prompt"]
assert stub.cleared_projects == ["proj12345678"]
assert stub.global_template_service.calls[0]["prompt_is_ready"] is True
@pytest.mark.asyncio
async def test_stream_free_template_generation_reuses_existing_template_without_regenerating():
project = SimpleNamespace(
topic="流式模板",
outline={},
confirmed_requirements={},
requirements="",
description="",
project_metadata={
"template_mode": "free",
"free_template_status": "ready",
"free_template_name": "已有模板",
"free_template_html": "<!DOCTYPE html><html><body>cached</body></html>",
},
)
stub = _TemplateSelectionServiceStub(project)
service = TemplateSelectionService(stub)
events = [
event
async for event in service.stream_free_template_generation("proj12345678", user_id=99, force=False)
]
assert [event["type"] for event in events] == ["status", "complete"]
assert events[-1]["template_name"] == "已有模板"
assert events[-1]["html_template"] == "<!DOCTYPE html><html><body>cached</body></html>"
assert stub.project_manager.metadata_updates == []
assert stub.global_template_service.calls == []
def test_extract_free_template_preview_html_supports_partial_html_chunks():
preview = TemplateSelectionService._extract_free_template_preview_html(
"说明文字```html\n<!DOCTYPE html><html><body><div class='preview'>partial"
)
assert preview == "<!DOCTYPE html><html><body><div class='preview'>partial"
@pytest.mark.asyncio
async def test_confirm_route_persists_current_frontend_html_before_generation(monkeypatch):
from landppt.web.route_modules import template_routes
project = SimpleNamespace(
topic="template-demo",
project_metadata={
"template_mode": "free",
"free_template_name": "first-version",
"free_template_html": "<!DOCTYPE html><html><body>first</body></html>",
"free_template_status": "ready",
},
)
stub = _TemplateSelectionServiceStub(project)
service = TemplateSelectionService(stub)
async def _get_selected_global_template(project_id, user_id=None):
return await service.get_selected_global_template(project_id, user_id=user_id)
stub.get_selected_global_template = _get_selected_global_template
monkeypatch.setattr(template_routes, "get_ppt_service_for_user", lambda user_id: stub)
confirm_result = await template_routes.confirm_project_free_template(
"proj-confirm",
_JsonRequestStub(
{
"save_to_library": False,
"template_name": "adjusted-version",
"html_template": "<!DOCTYPE html><html><body>adjusted</body></html>",
}
),
user=SimpleNamespace(id=7),
)
selected_result = await template_routes.get_selected_global_template(
"proj-confirm",
user=SimpleNamespace(id=7),
)
assert confirm_result["success"] is True
assert stub.project_manager.metadata_updates[-1]["free_template_html"] == "<!DOCTYPE html><html><body>adjusted</body></html>"
assert stub.project_manager.metadata_updates[-1]["free_template_name"] == "adjusted-version"
assert selected_result["template"]["html_template"] == "<!DOCTYPE html><html><body>adjusted</body></html>"
assert stub.cleared_projects == ["proj-confirm"]
def test_free_template_streaming_route_and_frontend_use_sse_preview_pipeline():
route_text = _read("src/landppt/web/route_modules/template_routes.py")
service_text = _read("src/landppt/services/template/template_selection_service.py")
script_text = _read("src/landppt/web/templates/components/project/todo_board_with_editor/script_1.html")
assert 'want_stream = True if stream_flag is None else bool(stream_flag)' in route_text
assert 'media_type="text/event-stream"' in route_text
assert 'stream_free_template_generation(' in route_text
assert '"type": "preview"' in service_text
assert "'Accept': 'text/event-stream'" in script_text
assert "consumeSseJsonStream(resp" in script_text
assert 'iframe.srcdoc = freeTemplateCurrentHtml;' in script_text
assert "html_template: getFreeTemplateHtmlForConfirm()" in script_text
assert 'submitted_html = data.get("html_template")' in route_text