"""`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