Фабрики ГК/ЮЛ/ГСК/членства/пользователя (flush, без commit), фикстуры seeded/app/auth_client в conftest (override сессии через begin_nested), test_me_and_dev_token переведён на общие фикстуры (сочетания флагов остались на локальном _build_app), test_factories.py проверяет фабрики. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
113 lines
5.4 KiB
Python
113 lines
5.4 KiB
Python
"""Фабрики справочников и фикстуры `app` / `auth_client` / `seeded` (задача 1.3): проверка до API-тестов."""
|
|
|
|
import pytest
|
|
from sqlalchemy import select
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.models.access import UserRole
|
|
from src.models.enums import EntityType, RelatedGroupKind
|
|
from tests.conftest import AuthClientFactory
|
|
from tests.factories import (
|
|
make_company_group,
|
|
make_legal_entity,
|
|
make_membership,
|
|
make_regional_office,
|
|
make_related_group,
|
|
make_user,
|
|
)
|
|
|
|
HTTP_OK = 200
|
|
INN_LENGTH = 10
|
|
|
|
|
|
@pytest.mark.integration
|
|
async def test_directory_factories_build_consistent_chain(db_session: AsyncSession) -> None:
|
|
office = await make_regional_office(db_session)
|
|
head = await make_regional_office(db_session, name="ГО Тест", is_head_office=True)
|
|
group = await make_company_group(db_session, office=office, crm_id="CRM-1")
|
|
entity = await make_legal_entity(db_session, group=group)
|
|
other = await make_legal_entity(db_session, name="ИП Тест", entity_type=EntityType.ИП, office=head)
|
|
gsk = await make_related_group(db_session, group=group)
|
|
gsz = await make_related_group(db_session, group=group, kind=RelatedGroupKind.gsz, name="ГСЗ Тест")
|
|
member = await make_membership(db_session, related_group=gsk, entity=entity)
|
|
# v5.14: одно ЮЛ одновременно в ГСК и ГСЗ
|
|
member_gsz = await make_membership(db_session, related_group=gsz, entity=entity)
|
|
|
|
assert head.is_head_office is True
|
|
assert office.is_head_office is False
|
|
assert group.code.startswith("GK-T")
|
|
assert group.base_regional_office_id == office.id
|
|
assert group.valid_from is not None
|
|
assert entity.regional_office_id == office.id # по умолчанию — филиал группы
|
|
assert entity.entity_type == EntityType.ЮЛ
|
|
assert len(entity.inn) == INN_LENGTH
|
|
assert entity.inn != other.inn
|
|
assert other.regional_office_id == head.id
|
|
assert other.company_group_id is None
|
|
assert gsk.code.startswith("GSK-T")
|
|
assert gsz.code.startswith("GSZ-T")
|
|
assert member.kind == RelatedGroupKind.gsk
|
|
assert member.is_current is True
|
|
assert member.valid_to is None
|
|
assert member_gsz.kind == RelatedGroupKind.gsz
|
|
|
|
|
|
@pytest.mark.integration
|
|
async def test_membership_not_current_is_closed(db_session: AsyncSession) -> None:
|
|
group = await make_company_group(db_session)
|
|
entity = await make_legal_entity(db_session, group=group)
|
|
related = await make_related_group(db_session, group=group)
|
|
closed = await make_membership(db_session, related_group=related, entity=entity, is_current=False)
|
|
again = await make_membership(db_session, related_group=related, entity=entity) # закрытая версия не мешает
|
|
assert closed.is_current is False
|
|
assert closed.valid_to is not None
|
|
assert again.is_current is True
|
|
|
|
|
|
@pytest.mark.integration
|
|
async def test_second_current_membership_in_same_kind_is_rejected(db_session: AsyncSession) -> None:
|
|
"""Фабрики не обходят ограничения БД: ЮЛ в двух ГСК одновременно — нарушение partial UNIQUE (v5.14)."""
|
|
group = await make_company_group(db_session)
|
|
entity = await make_legal_entity(db_session, group=group)
|
|
first = await make_related_group(db_session, group=group)
|
|
second = await make_related_group(db_session, group=group, name="ГСК-2")
|
|
await make_membership(db_session, related_group=first, entity=entity)
|
|
with pytest.raises(IntegrityError):
|
|
async with db_session.begin_nested():
|
|
await make_membership(db_session, related_group=second, entity=entity)
|
|
|
|
|
|
@pytest.mark.integration
|
|
async def test_make_user_assigns_seeded_roles(seeded: AsyncSession) -> None:
|
|
office = await make_regional_office(seeded)
|
|
user = await make_user(seeded, email="factory@armka.local", office=office, roles=["KA_RF", "EXPERT"])
|
|
plain = await make_user(seeded, email="plain@armka.local", office=office, is_active=False)
|
|
count = len((await seeded.execute(select(UserRole).where(UserRole.user_id == user.id))).scalars().all())
|
|
assert count == 2 # noqa: PLR2004
|
|
assert user.regional_office_id == office.id
|
|
assert plain.is_active is False
|
|
assert not (await seeded.execute(select(UserRole).where(UserRole.user_id == plain.id))).scalars().all()
|
|
|
|
|
|
@pytest.mark.integration
|
|
async def test_auth_client_gets_me_for_dev_user(auth_client: AuthClientFactory) -> None:
|
|
client = await auth_client("ka_rf@armka.local")
|
|
response = await client.get("/me")
|
|
assert response.status_code == HTTP_OK, response.text
|
|
assert response.json()["email"] == "ka_rf@armka.local"
|
|
assert response.json()["roles"] == ["KA_RF"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
async def test_auth_client_sees_data_created_by_factories(
|
|
db_session: AsyncSession, auth_client: AuthClientFactory
|
|
) -> None:
|
|
"""Пользователь, созданный фабрикой в той же сессии, получает токен и проходит `/me` (одна `db_session`)."""
|
|
office = await make_regional_office(db_session)
|
|
await make_user(db_session, email="made@armka.local", office=office, roles=["KA_RF"])
|
|
client = await auth_client("made@armka.local")
|
|
response = await client.get("/me")
|
|
assert response.status_code == HTTP_OK, response.text
|
|
assert response.json()["regional_office"]["id"] == str(office.id)
|