修复:限定幂等键精确业务域

This commit is contained in:
焦龙言
2026-07-27 18:54:50 +08:00
parent 4a321098f8
commit b6f3b1e1f0
18 changed files with 730 additions and 135 deletions
@@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from fastapi import FastAPI, Request
from fastapi.routing import APIRoute
from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import IntegrityError, MultipleResultsFound
from sqlalchemy.ext.asyncio import AsyncSession
from common.aspect.interface_auth import CheckUserInterfaceAuth
@@ -1407,6 +1407,65 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
self.db.rollback.assert_awaited_once()
self.assertTrue(result.is_success)
async def test_same_key_retry_uses_exact_warehouse_when_user_can_access_duplicate_keys(self) -> None:
target = make_inbound(
status='CONFIRMED',
idempotency_key='confirm-key',
request_hash=self.request_hash,
)
other_domain = make_inbound(
inbound_id=12,
warehouse_id=8,
status='CONFIRMED',
idempotency_key='confirm-key',
request_hash=InboundService._build_confirm_request_hash(12),
)
async def read_key(
_db: AsyncSession,
_key: str,
*,
warehouse_ids: list[int] | None = None,
) -> BizInboundOrder | None:
matches = [
inbound
for inbound in (target, other_domain)
if warehouse_ids is None or inbound.warehouse_id in warehouse_ids
]
if len(matches) > 1:
raise MultipleResultsFound('广域幂等键查询返回多条入库单')
return matches[0] if matches else None
get_detail = AsyncMock(return_value=target)
key_read = AsyncMock(side_effect=read_key)
with (
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[7, 8])),
patch.object(OperationDao, 'get_inbound_by_id', new=get_detail),
patch.object(OperationDao, 'get_inbound_by_confirm_idempotency_key', new=key_read),
patch.object(OperationDao, 'get_inbound_for_update', new=AsyncMock(return_value=target)),
patch.object(
MasterDao,
'get_warehouse',
new=AsyncMock(return_value=SimpleNamespace(warehouse_id=7, status='0')),
),
patch.object(AccessService, 'assert_warehouse_access', new=AsyncMock()),
):
result = await InboundService.confirm_inbound_services(
self.db,
11,
self.command,
self.user,
)
self.assertTrue(result.is_success)
get_detail.assert_awaited_once_with(self.db, 11, warehouse_ids=[7, 8])
key_read.assert_awaited_once_with(
self.db,
'confirm-key',
warehouse_ids=[7],
)
self.db.rollback.assert_awaited_once()
async def test_same_key_retry_hides_document_when_current_mapping_is_revoked(self) -> None:
confirmed = make_inbound(
status='CONFIRMED',
@@ -1569,6 +1628,76 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
self.assertEqual(2, self.db.rollback.await_count)
self.assertTrue(result.is_success)
async def test_integrity_recovery_uses_exact_warehouse_with_duplicate_visible_keys(self) -> None:
initial = make_inbound(status='DRAFT')
recovered = make_inbound(
status='CONFIRMED',
idempotency_key='confirm-key',
request_hash=self.request_hash,
)
other_domain = make_inbound(
inbound_id=12,
warehouse_id=8,
status='CONFIRMED',
idempotency_key='confirm-key',
request_hash=InboundService._build_confirm_request_hash(12),
)
read_count = 0
async def read_key(
_db: AsyncSession,
_key: str,
*,
warehouse_ids: list[int] | None = None,
) -> BizInboundOrder | None:
nonlocal read_count
read_count += 1
if read_count == 1:
return None
matches = [
inbound
for inbound in (recovered, other_domain)
if warehouse_ids is None or inbound.warehouse_id in warehouse_ids
]
if len(matches) > 1:
raise MultipleResultsFound('广域幂等键恢复查询返回多条入库单')
return matches[0] if matches else None
warehouse_scope = AsyncMock(side_effect=[[7, 8], [7, 8]])
key_read = AsyncMock(side_effect=read_key)
parent_lock = AsyncMock(side_effect=[integrity_error(), recovered])
with (
patch.object(AccessDao, 'list_user_warehouse_ids', new=warehouse_scope),
patch.object(OperationDao, 'get_inbound_by_id', new=AsyncMock(return_value=initial)),
patch.object(OperationDao, 'get_inbound_by_confirm_idempotency_key', new=key_read),
patch.object(OperationDao, 'get_inbound_for_update', new=parent_lock),
patch.object(
MasterDao,
'get_warehouse',
new=AsyncMock(return_value=SimpleNamespace(warehouse_id=7, status='0')),
),
patch.object(AccessService, 'assert_warehouse_access', new=AsyncMock()),
):
result = await InboundService.confirm_inbound_services(
self.db,
11,
self.command,
self.user,
)
self.assertTrue(result.is_success)
self.assertEqual(2, warehouse_scope.await_count)
self.assertEqual(2, parent_lock.await_count)
self.assertEqual(
[
unittest.mock.call(self.db, 'confirm-key', warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', warehouse_ids=[7]),
],
key_read.await_args_list,
)
self.assertEqual(2, self.db.rollback.await_count)
self.db.commit.assert_not_awaited()
async def test_integrity_recovery_locks_parent_and_rechecks_revoked_current_mapping(self) -> None:
inbound = make_inbound(pallet_ids=(2,))
recovered = make_inbound(