Files
life-echo/api/tests/test_db_transactional.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

148 lines
3.9 KiB
Python

"""transactional / transactional_sync commit and rollback behavior."""
from __future__ import annotations
from contextlib import asynccontextmanager, contextmanager
from unittest.mock import AsyncMock, MagicMock
import pytest
from app.core.db import (
transactional,
transactional_nested,
transactional_nested_sync,
transactional_sync,
)
@pytest.mark.asyncio
async def test_transactional_commits_on_success() -> None:
session = MagicMock()
session.commit = AsyncMock()
session.rollback = AsyncMock()
async with transactional(session):
pass
session.commit.assert_awaited_once()
session.rollback.assert_not_awaited()
@pytest.mark.asyncio
async def test_transactional_rolls_back_on_error() -> None:
session = MagicMock()
session.commit = AsyncMock()
session.rollback = AsyncMock()
with pytest.raises(RuntimeError, match="boom"):
async with transactional(session):
raise RuntimeError("boom")
session.commit.assert_not_awaited()
session.rollback.assert_awaited_once()
def test_transactional_sync_commits_on_success() -> None:
session = MagicMock()
session.commit = MagicMock()
session.rollback = MagicMock()
with transactional_sync(session):
pass
session.commit.assert_called_once()
session.rollback.assert_not_called()
def test_transactional_sync_rolls_back_on_error() -> None:
session = MagicMock()
session.commit = MagicMock()
session.rollback = MagicMock()
with pytest.raises(RuntimeError, match="boom"):
with transactional_sync(session):
raise RuntimeError("boom")
session.commit.assert_not_called()
session.rollback.assert_called_once()
@pytest.mark.asyncio
async def test_transactional_nested_releases_savepoint_on_success() -> None:
session = MagicMock()
session.commit = AsyncMock()
session.rollback = AsyncMock()
@asynccontextmanager
async def fake_begin_nested():
yield session
session.begin_nested = MagicMock(return_value=fake_begin_nested())
async with transactional_nested(session):
pass
session.begin_nested.assert_called_once()
session.commit.assert_not_awaited()
session.rollback.assert_not_awaited()
@pytest.mark.asyncio
async def test_transactional_nested_rolls_back_savepoint_on_error() -> None:
session = MagicMock()
session.commit = AsyncMock()
session.rollback = AsyncMock()
@asynccontextmanager
async def fake_begin_nested():
yield session
session.begin_nested = MagicMock(return_value=fake_begin_nested())
with pytest.raises(RuntimeError, match="boom"):
async with transactional_nested(session):
raise RuntimeError("boom")
session.begin_nested.assert_called_once()
session.commit.assert_not_awaited()
session.rollback.assert_not_awaited()
def test_transactional_nested_sync_releases_savepoint_on_success() -> None:
session = MagicMock()
session.commit = MagicMock()
session.rollback = MagicMock()
@contextmanager
def fake_begin_nested():
yield session
session.begin_nested = MagicMock(return_value=fake_begin_nested())
with transactional_nested_sync(session):
pass
session.begin_nested.assert_called_once()
session.commit.assert_not_called()
session.rollback.assert_not_called()
def test_transactional_nested_sync_rolls_back_savepoint_on_error() -> None:
session = MagicMock()
session.commit = MagicMock()
session.rollback = MagicMock()
@contextmanager
def fake_begin_nested():
yield session
session.begin_nested = MagicMock(return_value=fake_begin_nested())
with pytest.raises(RuntimeError, match="boom"):
with transactional_nested_sync(session):
raise RuntimeError("boom")
session.begin_nested.assert_called_once()
session.commit.assert_not_called()
session.rollback.assert_not_called()