Files
group_fqcd_jr/tests/unit/service/test_api_transaction_service.py
T

73 lines
2.6 KiB
Python

"""`ApiTransactionService` 的输入契约与 `digest` 测试。
`Idempotency-Key` 是所有公共写接口的强制头,它的长度与字符集校验必须在**开启事务之前**
完成——否则非法请求也会占用一次事务和一次 `INSERT ... ON DUPLICATE KEY` 抢占,把本该
零成本的拒绝变成一次真实的库写入尝试。本文件用"创建会话即报错"的替身来证明这个顺序。
抢占、命中回放、哈希冲突的完整路径需要 `PlatformRepository.table()` 反射真实表,
那部分由集成测试覆盖(`tests/integration/`),这里不重复造表。
"""
from typing import Any
import pytest
from app.core.contracts import RequestContext
from app.core.errors import ValidationAgentError
from app.service.api_transaction_service import ApiTransactionService, digest
CONTEXT = RequestContext(user_id="9001", trace_id="trace-1")
async def never_called(_session: Any) -> dict[str, Any]: # pragma: no cover - 不应被调用
raise AssertionError("非法请求不应执行 action")
def exploding_factory() -> None:
raise AssertionError("非法请求不应创建数据库会话")
@pytest.mark.parametrize(
"key",
[
None,
"", # 空
"x" * 15, # 短于 16
"x" * 129, # 长于 128
"稳健型稳健型稳健型稳健型", # 长度够但不是 ASCII
],
)
async def test_invalid_key_is_rejected_before_any_database_work(
key: str | None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(
"app.service.api_transaction_service.SessionFactory", exploding_factory
)
with pytest.raises(ValidationAgentError):
await ApiTransactionService().execute(CONTEXT, "conversations", key, {}, never_called)
async def test_valid_key_passes_the_guard(monkeypatch: pytest.MonkeyPatch) -> None:
"""合法 key 不应被前置校验拦下:用一个立即失败的替身证明校验已通过、进入数据库阶段。"""
monkeypatch.setattr(
"app.service.api_transaction_service.SessionFactory", exploding_factory
)
with pytest.raises(AssertionError, match="不应创建数据库会话"):
await ApiTransactionService().execute(CONTEXT, "conversations", "a" * 16, {}, never_called)
def test_digest_is_order_independent() -> None:
assert digest({"a": 1, "b": 2}) == digest({"b": 2, "a": 1})
def test_digest_changes_with_content() -> None:
assert digest({"a": 1}) != digest({"a": 2})
def test_digest_is_stable_for_non_ascii() -> None:
value = {"text": "稳健型"}
assert digest(value) == digest({"text": "稳健型"})
assert len(digest(value)) == 64