Files
life-echo/api/tests/test_chapter_cover_enqueue.py
Kevin 8af37e5e8e 修复:CI 部署环境与 ref 错配、迁移碎片化、图片意图 source_span、章节物化脏版式、会话历史与本地语音不一致
新增:TTS 上传 COS 与分片、章节 reading_segments 物化与快照、markdown 清洗、会话消息 repository、语音 store 重构与相关测试
2026-03-20 16:43:02 +08:00

119 lines
4.6 KiB
Python

"""Tests for chapter cover Celery enqueue deduplication gate."""
from unittest.mock import MagicMock, patch
from app.tasks.chapter_cover_enqueue import (
_enqueue_dedup_key,
try_enqueue_generate_chapter_cover,
)
def _md_with_n_inline_images(n: int) -> str:
lines = [f"![c](asset://a{i})" for i in range(n)]
return "\n\n".join(lines) + "\n\n正文"
def _eligible_pipeline_chapter():
ch = MagicMock()
ch.cover_asset_id = None
# 章节封面:正文内 asset 插图需 >3 才入队
ch.canonical_markdown = _md_with_n_inline_images(4)
ch.images = []
return ch
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_false_when_chapter_missing(mock_load, mock_redis, mock_gen_task):
mock_load.return_value = None
assert try_enqueue_generate_chapter_cover("missing-id", "pipeline") is False
mock_gen_task.delay.assert_not_called()
mock_redis.assert_not_called()
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_false_when_cover_asset_exists(
mock_load, mock_redis, mock_gen_task
):
ch = _eligible_pipeline_chapter()
ch.cover_asset_id = "asset-1"
mock_load.return_value = ch
assert try_enqueue_generate_chapter_cover("ch-1", "pipeline") is False
mock_gen_task.delay.assert_not_called()
mock_redis.assert_not_called()
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_false_when_redis_nx_not_acquired(
mock_load, mock_redis, mock_gen_task
):
mock_load.return_value = _eligible_pipeline_chapter()
r = MagicMock()
r.set.return_value = False
mock_redis.return_value = r
assert try_enqueue_generate_chapter_cover("ch-1", "pipeline") is False
mock_gen_task.delay.assert_not_called()
r.set.assert_called_once()
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_true_when_eligible_and_nx_ok(mock_load, mock_redis, mock_gen_task):
mock_load.return_value = _eligible_pipeline_chapter()
r = MagicMock()
r.set.return_value = True
mock_redis.return_value = r
mock_gen_task.delay = MagicMock()
assert try_enqueue_generate_chapter_cover("ch-1", "pipeline") is True
mock_gen_task.delay.assert_called_once_with("ch-1")
r.set.assert_called_once()
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_false_when_inline_images_not_enough(
mock_load, mock_redis, mock_gen_task
):
ch = _eligible_pipeline_chapter()
ch.canonical_markdown = _md_with_n_inline_images(3)
mock_load.return_value = ch
assert try_enqueue_generate_chapter_cover("ch-1", "pipeline") is False
mock_gen_task.delay.assert_not_called()
mock_redis.assert_not_called()
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_deletes_dedup_key_when_delay_fails(
mock_load, mock_redis, mock_gen_task
):
mock_load.return_value = _eligible_pipeline_chapter()
r = MagicMock()
r.set.return_value = True
mock_redis.return_value = r
mock_gen_task.delay = MagicMock(side_effect=RuntimeError("broker down"))
assert try_enqueue_generate_chapter_cover("ch-1", "pipeline") is False
mock_gen_task.delay.assert_called_once_with("ch-1")
r.delete.assert_called_once_with(_enqueue_dedup_key("ch-1"))
@patch("app.tasks.chapter_cover_tasks.generate_chapter_cover")
@patch("app.tasks.chapter_cover_enqueue.redis.from_url")
@patch("app.tasks.chapter_cover_enqueue._load_chapter_for_enqueue_sync")
def test_try_enqueue_http_skips_empty_category(mock_load, mock_redis, mock_gen_task):
ch = _eligible_pipeline_chapter()
ch.category = None
ch.status = "completed"
mock_load.return_value = ch
assert try_enqueue_generate_chapter_cover("ch-1", "http") is False
mock_gen_task.delay.assert_not_called()
mock_redis.assert_not_called()