168 lines
5.2 KiB
Python
168 lines
5.2 KiB
Python
from datetime import date
|
||
from unittest.mock import AsyncMock
|
||
|
||
import pytest
|
||
from pydantic import ValidationError
|
||
|
||
from src.domain.schemas import MembershipRowCreate, MembershipRowResponse, MembershipRowUpdate
|
||
from src.services.membership_service import MembershipService
|
||
|
||
|
||
def _membership_row_dict(
|
||
membership_id: int = 1,
|
||
inn: str = "7701234567",
|
||
group_id: str = "GK001",
|
||
crm_group_id: str = "CRM001",
|
||
group_name: str = "ГК Тест",
|
||
organization_name: str = "ООО Тест",
|
||
is_gsk: bool = True,
|
||
is_gsz: bool = False,
|
||
org_type: str | None = "ЮЛ",
|
||
membership_from: date | None = None,
|
||
membership_to: date | None = None,
|
||
) -> dict:
|
||
return {
|
||
"membership_id": membership_id,
|
||
"inn": inn,
|
||
"group_id": group_id,
|
||
"crm_group_id": crm_group_id,
|
||
"group_name": group_name,
|
||
"organization_name": organization_name,
|
||
"is_gsk": is_gsk,
|
||
"is_gsz": is_gsz,
|
||
"org_type": org_type,
|
||
"membership_from": membership_from,
|
||
"membership_to": membership_to,
|
||
}
|
||
|
||
|
||
@pytest.fixture
|
||
def service() -> MembershipService:
|
||
svc = MembershipService(AsyncMock())
|
||
svc.repository = AsyncMock()
|
||
return svc
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_returns_none_when_row_not_found(service: MembershipService):
|
||
service.repository.get.return_value = None
|
||
|
||
result = await service.get(123)
|
||
|
||
service.repository.get.assert_awaited_once_with(123)
|
||
assert result is None
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_maps_repository_dict_to_response_schema(service: MembershipService):
|
||
expected_row = _membership_row_dict(membership_id=10, membership_from=date(2026, 1, 1))
|
||
service.repository.get.return_value = expected_row
|
||
|
||
result = await service.get(10)
|
||
|
||
assert isinstance(result, MembershipRowResponse)
|
||
assert result.membership_id == 10
|
||
assert result.inn == "7701234567"
|
||
assert result.crm_group_id == "CRM001"
|
||
assert result.membership_from == date(2026, 1, 1)
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_raises_validation_error_on_invalid_repository_payload(service: MembershipService):
|
||
# Реальный защитный кейс: сервис не должен молча пропускать некорректную структуру данных.
|
||
service.repository.get.return_value = {"membership_id": 10}
|
||
|
||
with pytest.raises(ValidationError):
|
||
await service.get(10)
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_list_passes_filters_and_maps_rows(service: MembershipService):
|
||
service.repository.list.return_value = [
|
||
_membership_row_dict(membership_id=1),
|
||
_membership_row_dict(membership_id=2, inn="7700000001", is_gsz=True),
|
||
]
|
||
|
||
result = await service.list(skip=5, limit=25, group_id="GK001", inn="7701234567", active_only=False)
|
||
|
||
service.repository.list.assert_awaited_once_with(
|
||
skip=5,
|
||
limit=25,
|
||
group_id="GK001",
|
||
inn="7701234567",
|
||
active_only=False,
|
||
)
|
||
assert [row.membership_id for row in result] == [1, 2]
|
||
assert result[1].is_gsz is True
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_create_sends_model_dump_to_repository_and_returns_response(service: MembershipService):
|
||
payload = MembershipRowCreate(
|
||
group_id="GK001",
|
||
org_id=77,
|
||
is_gsk=True,
|
||
is_gsz=False,
|
||
control_type="CTRL",
|
||
comment="Создание связи",
|
||
)
|
||
created_row = _membership_row_dict(membership_id=5)
|
||
service.repository.create.return_value = created_row
|
||
|
||
result = await service.create(payload)
|
||
|
||
service.repository.create.assert_awaited_once_with(payload.model_dump())
|
||
assert isinstance(result, MembershipRowResponse)
|
||
assert result.membership_id == 5
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_create_propagates_repository_error(service: MembershipService):
|
||
payload = MembershipRowCreate(group_id="GK001", org_id=77, is_gsk=False, is_gsz=False)
|
||
service.repository.create.side_effect = RuntimeError("db error")
|
||
|
||
with pytest.raises(RuntimeError, match="db error"):
|
||
await service.create(payload)
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_update_uses_exclude_none_payload_and_returns_none_for_missing_entity(
|
||
service: MembershipService,
|
||
):
|
||
payload = MembershipRowUpdate(is_gsz=True, comment=None, control_type=None)
|
||
service.repository.update.return_value = None
|
||
|
||
result = await service.update(99, payload)
|
||
|
||
service.repository.update.assert_awaited_once_with(
|
||
99,
|
||
{"is_gsz": True},
|
||
)
|
||
assert result is None
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_update_maps_result_when_row_exists(service: MembershipService):
|
||
payload = MembershipRowUpdate(is_gsz=True, comment="Обновлено")
|
||
service.repository.update.return_value = _membership_row_dict(
|
||
membership_id=7,
|
||
is_gsz=True,
|
||
membership_to=None,
|
||
)
|
||
|
||
result = await service.update(7, payload)
|
||
|
||
assert isinstance(result, MembershipRowResponse)
|
||
assert result.membership_id == 7
|
||
assert result.is_gsz is True
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_deactivate_returns_repository_flag(service: MembershipService):
|
||
service.repository.deactivate.return_value = True
|
||
|
||
result = await service.deactivate(15)
|
||
|
||
service.repository.deactivate.assert_awaited_once_with(15)
|
||
assert result is True
|