import asyncio import sys from types import ModuleType, SimpleNamespace import pytest from app.adapters.asr.tencent_asr import TencentASRProvider from app.core import chapter_pipeline_lock from app.features.story import post_commit def test_chapter_pipeline_lock_delegates_to_token_lock( monkeypatch: pytest.MonkeyPatch, ) -> None: handle = object() recorded: dict[str, object] = {} def fake_acquire(key: str, *, ttl_seconds: int) -> object: recorded["key"] = key recorded["ttl_seconds"] = ttl_seconds return handle def fake_release(arg: object) -> None: recorded["released"] = arg monkeypatch.setattr(chapter_pipeline_lock, "acquire_redis_lock", fake_acquire) monkeypatch.setattr(chapter_pipeline_lock, "release_redis_lock", fake_release) acquired = chapter_pipeline_lock.acquire_chapter_pipeline_lock( "user-1", "childhood", ttl_seconds=120 ) chapter_pipeline_lock.release_chapter_pipeline_lock(acquired) assert acquired is handle assert recorded == { "key": "lock:chapter:user-1:childhood", "ttl_seconds": 120, "released": handle, } def test_post_commit_reuses_singleton_redis_client( monkeypatch: pytest.MonkeyPatch, ) -> None: created: list[object] = [] class FakeRedis: pass def fake_from_url(url: str, *, decode_responses: bool) -> FakeRedis: client = FakeRedis() created.append(client) assert decode_responses is True assert url return client monkeypatch.setattr(post_commit, "_redis_client", None) monkeypatch.setattr(post_commit.redis, "from_url", fake_from_url) first = post_commit._get_redis() second = post_commit._get_redis() assert first is second assert created == [first] @pytest.mark.asyncio async def test_tencent_asr_transcribe_uses_to_thread( monkeypatch: pytest.MonkeyPatch, ) -> None: to_thread_calls: list[tuple[object, tuple[object, ...]]] = [] class FakeRequest: EngSerViceType: str | None = None SourceType: int | None = None VoiceFormat: str | None = None Data: str | None = None DataLen: int | None = None class FakeClient: def SentenceRecognition(self, req: FakeRequest) -> SimpleNamespace: return SimpleNamespace(Result=" 你好,世界 ") async def fake_to_thread(fn, *args): to_thread_calls.append((fn, args)) return fn(*args) models_module = ModuleType("tencentcloud.asr.v20190614.models") models_module.SentenceRecognitionRequest = FakeRequest package_module = ModuleType("tencentcloud.asr.v20190614") package_module.models = models_module monkeypatch.setitem(sys.modules, "tencentcloud.asr.v20190614", package_module) monkeypatch.setattr(asyncio, "to_thread", fake_to_thread) provider = TencentASRProvider("sid", "skey") client = FakeClient() monkeypatch.setattr(provider, "_get_client", lambda: client) text = await provider.transcribe(b"fake-audio", format="m4a") assert text == "你好,世界" assert len(to_thread_calls) == 1 fn, args = to_thread_calls[0] assert getattr(fn, "__self__", None) is client assert getattr(fn, "__name__", "") == "SentenceRecognition" request = args[0] assert request.VoiceFormat == "m4a" assert request.DataLen == len(b"fake-audio")