Files
life-echo/api/tests/test_infra_regressions.py
Sully 53e0065e3e refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env)
统一错误契约
Auth 与事务边界
Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client
可观测性(OpenTelemetry + LGTM)
2026-05-22 13:44:50 +08:00

103 lines
3.1 KiB
Python

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:
client = object()
def fake_get_sync_redis(*, decode_responses: bool):
assert decode_responses is True
return client
monkeypatch.setattr(post_commit, "get_sync_redis", fake_get_sync_redis)
first = post_commit._get_redis()
second = post_commit._get_redis()
assert first is second
assert first is client
@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")