修复:CI 部署环境与 ref 错配、迁移碎片化、图片意图 source_span、章节物化脏版式、会话历史与本地语音不一致

新增:TTS 上传 COS 与分片、章节 reading_segments 物化与快照、markdown 清洗、会话消息 repository、语音 store 重构与相关测试
This commit is contained in:
Kevin
2026-03-20 16:36:42 +08:00
parent 7317bf10cd
commit 8af37e5e8e
65 changed files with 1704 additions and 504 deletions

View File

@@ -0,0 +1,89 @@
"""reading_segments正文已含与主插图相同 asset 时不重复 cover_image。"""
import unittest
from app.features.memoir.helpers import build_reading_segments
from app.features.memoir.reading_segment_materialize import (
build_reading_segments_snapshot,
resolve_reading_segments_for_chapter_detail,
)
class _Intent:
def __init__(self, asset_id: str):
self.intent_role = "primary"
self.asset_id = asset_id
self.caption = "cap"
self.prompt_brief = None
self.style_profile = None
self.error = None
self.status = "completed"
self.created_at = None
self.updated_at = None
class _Story:
def __init__(self, sid: str, body: str, asset_id: str):
self.id = sid
self.title = "T"
self.canonical_markdown = body
self.image_intents = [_Intent(asset_id)]
class _Link:
def __init__(self, order: int, story: _Story):
self.order_index = order
self.story = story
class _Ch:
def __init__(self, story: _Story):
self.story_links = [_Link(0, story)]
class ReadingSegmentsDedupeTest(unittest.TestCase):
def test_omits_cover_when_primary_asset_in_body(self):
aid = "c67d11c5-23b7-4ab9-91ed-c05b5a27a12b"
body = f"正文\n\n![cap](asset://{aid})\n\n更多"
st = _Story("s1", body, aid)
ch = _Ch(st)
asset_url_map = {aid: "https://cos.example/img.png"}
segs = build_reading_segments(ch, asset_url_map=asset_url_map)
self.assertEqual(len(segs), 1)
self.assertIn("https://cos.example", segs[0]["body_markdown"])
self.assertIsNone(segs[0]["cover_image"])
def test_keeps_cover_when_primary_not_in_body(self):
aid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
body = "仅文字无图"
st = _Story("s1", body, aid)
ch = _Ch(st)
asset_url_map = {aid: "https://cos.example/cover.png"}
segs = build_reading_segments(ch, asset_url_map=asset_url_map)
self.assertEqual(len(segs), 1)
self.assertIsNotNone(segs[0]["cover_image"])
self.assertTrue(segs[0]["cover_image"].get("url"))
def test_persisted_snapshot_hydrate_matches_live_materialize(self):
aid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
body = "仅文字无图"
st = _Story("s1", body, aid)
ch = _Ch(st)
ch.reading_segments_json = build_reading_segments_snapshot(ch)
ch.markdown_compose_dirty = False
asset_url_map = {aid: "https://cos.example/cover.png"}
live = build_reading_segments(ch, asset_url_map=asset_url_map)
via_snapshot = resolve_reading_segments_for_chapter_detail(
ch, asset_url_map=asset_url_map
)
self.assertEqual(len(live), len(via_snapshot))
self.assertEqual(live[0]["story_id"], via_snapshot[0]["story_id"])
self.assertEqual(live[0]["body_markdown"], via_snapshot[0]["body_markdown"])
self.assertEqual(
(live[0]["cover_image"] or {}).get("url"),
(via_snapshot[0]["cover_image"] or {}).get("url"),
)
if __name__ == "__main__":
unittest.main()