"""Плоский справочник счетов: все уровни и порядок кодов.""" import uuid from types import SimpleNamespace from unittest.mock import AsyncMock import pytest from src.api.endpoints.directories import accounts as accounts_endpoint from src.api.endpoints.directories.accounts import list_accounts from src.app import app from src.models.enums import AccountKind, AccountRowType from src.repositories.directories import list_accounts as query_accounts from src.schemas.directories import AccountFilter async def test_accounts_are_flat_and_naturally_sorted() -> None: root_id, sub_id = uuid.uuid4(), uuid.uuid4() rows = [ SimpleNamespace( id=sub_id, code="60.10", parent_id=root_id, name="Авансы", kind=AccountKind.A, row_type=AccountRowType.subaccount, is_off_balance=False, ), SimpleNamespace( id=root_id, code="60", parent_id=None, name="Расчёты", kind=AccountKind.AP, row_type=AccountRowType.account, is_off_balance=False, ), SimpleNamespace( id=uuid.uuid4(), code="60.2", parent_id=root_id, name="Поставщики", kind=AccountKind.P, row_type=AccountRowType.subaccount, is_off_balance=False, ), ] session = SimpleNamespace(scalars=AsyncMock(return_value=SimpleNamespace(all=lambda: rows))) assert [item.code for item in await query_accounts(session)] == ["60", "60.2", "60.10"] response = await list_accounts(session, AccountFilter()) assert [item.code for item in response] == ["60", "60.2", "60.10"] assert response[0].parent_id is None assert response[2].parent_id == root_id assert response[2].id == sub_id assert [item.model_dump(mode="json")["is_debit"] for item in response] == [True, False, True] assert [item.model_dump(mode="json")["is_credit"] for item in response] == [True, True, False] @pytest.mark.parametrize( ("filters", "expected_columns"), [ ({}, set()), ({"kind": [AccountKind.A]}, {"kind"}), ({"kind": [AccountKind.A, AccountKind.AP]}, {"kind"}), ({"row_type": AccountRowType.analytic}, {"row_type"}), ({"is_off_balance": False}, {"is_off_balance"}), ( {"kind": [AccountKind.AP, AccountKind.P], "row_type": AccountRowType.account, "is_off_balance": True}, {"kind", "row_type", "is_off_balance"}, ), ], ) async def test_account_filters_apply_only_when_supplied(filters: dict, expected_columns: set[str]) -> None: session = SimpleNamespace(scalars=AsyncMock(return_value=SimpleNamespace(all=list))) assert await query_accounts(session, **filters) == [] statement = session.scalars.await_args.args[0] where_clause = str(statement.whereclause) if statement.whereclause is not None else "" for column in ("kind", "row_type", "is_off_balance"): assert (f"chart_of_account.{column} " in where_clause) == (column in expected_columns) if "kind" in filters: assert "chart_of_account.kind IN" in where_clause assert statement.compile().params["kind_1"] == filters["kind"] async def test_account_endpoint_forwards_all_filters(monkeypatch: pytest.MonkeyPatch) -> None: query = AsyncMock(return_value=[]) monkeypatch.setattr(accounts_endpoint.repo, "list_accounts", query) session = SimpleNamespace() filters = AccountFilter(kind=[AccountKind.AP, AccountKind.P], row_type=AccountRowType.account, is_off_balance=False) assert await list_accounts(session, filters) == [] query.assert_awaited_once_with( session, kind=[AccountKind.AP, AccountKind.P], row_type=AccountRowType.account, is_off_balance=False ) def test_account_filters_are_optional_query_parameters() -> None: parameters = app.openapi()["paths"]["/api/v1/directories/accounts"]["get"]["parameters"] assert {item["name"] for item in parameters} == {"kind", "row_type", "is_off_balance"} assert all(item["in"] == "query" and item["required"] is False for item in parameters) kind = next(item for item in parameters if item["name"] == "kind") assert kind["schema"]["anyOf"][0]["type"] == "array"