arm-core/api/tests/test_api_goodwill_mutations.py
Andrey Epifancev a086c39a78 Гудвилл: доля до двух знаков, итоги по ГО и УК с бэка
participation() — единственное место формулы доли участия; totals с УК, group_totals
по ГО, GoodwillTableOut.groups; экспорт берёт «Всего» и «Итого по листу» из ответа таблицы;
доля 0–100 не больше двух знаков (DOC-259025 §5.2), ответ — два знака.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:22:26 +04:00

158 lines
7.5 KiB
Python

"""Проверка ввода строки до обращения к хранилищу."""
from decimal import Decimal
from http import HTTPStatus
import pytest
from sqlalchemy import func, select
from src.api.dependencies.audit import RequestAudit
from src.common.exceptions import ConflictError, DomainValidationError
from src.models.audit_ex import AuditEvent
from src.models.enums import AuditAction
from src.models.kfo import Goodwill, KfoResult
from src.models.osv import OsvEntry
from src.models.statements import FinancialStatementValue
from src.schemas.goodwill import GoodwillRowIn
from src.services.goodwill import validate_decision
from tests.test_api_consolidated_osv import KA_RF
from tests.test_api_goodwill import goodwill_world
@pytest.mark.parametrize(
"value", ["-1", "100.01", "100.0001", "80.123", "80,123", "12.3456", "1e2", "NaN", "Infinity", "abc", ""]
)
def test_invalid_share(value):
with pytest.raises(DomainValidationError) as error:
validate_decision(GoodwillRowIn(ownership_share=value, include_in_kfo=False))
assert error.value.code == "INVALID_SHARE"
@pytest.mark.parametrize("value", ["0", "100", "7.5", "80.12", "80,12"])
def test_valid_share(value):
result = validate_decision(GoodwillRowIn(ownership_share=value, include_in_kfo=True, comment=" Обоснование "))
assert str(result.ownership_share) == value.replace(",", ".")
assert result.comment == "Обоснование"
def test_required_fields():
assert validate_decision(GoodwillRowIn(ownership_share=None, include_in_kfo=False)).ownership_share is None
with pytest.raises(DomainValidationError) as error:
validate_decision(GoodwillRowIn(ownership_share=None, include_in_kfo=True, comment="Да"))
assert error.value.code == "INVALID_SHARE"
with pytest.raises(DomainValidationError) as error:
validate_decision(GoodwillRowIn(ownership_share="100", include_in_kfo=True, comment=" "))
assert error.value.code == "COMMENT_REQUIRED"
@pytest.mark.integration
@pytest.mark.fixtures
async def test_save_settings_and_disappearing_pair(db_session, auth_client, fixtures_dir):
w = await goodwill_world(db_session, fixtures_dir)
client = await auth_client(KA_RF)
base = f"/consolidations/versions/{w.version.id}"
path = base + f"/goodwill/{w.a.id}/{w.b.id}"
body = dict(ownership_share="80.12", include_in_kfo=True, comment="Подтверждено")
response = await client.put(path, json=body)
assert response.status_code == HTTPStatus.CONFLICT and response.json()["detail"]["code"] == "LOCK_REQUIRED"
assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED
other = await auth_client("rka@armka.local")
rejected = await other.put(path, json=body)
assert rejected.status_code == HTTPStatus.CONFLICT
assert rejected.json()["detail"]["code"] == "LOCKED_BY_OTHER"
response = await client.put(path, json=body)
assert response.status_code == HTTPStatus.OK, response.text
row = response.json()
assert row["ownership_share"] == "80.12"
assert row["participation_amount"] == "80120000.00"
assert row["goodwill_amount"] == "189877300.00"
again = await client.put(path, json=body)
assert again.status_code == HTTPStatus.OK
assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 1
on = await client.put(base + "/goodwill-settings", json=dict(goodwill_enabled=True))
assert on.status_code == HTTPStatus.OK
assert on.json()["totals"]["goodwill_in_kfo_amount"] == "189877300.00"
audit_count = await db_session.scalar(
select(func.count()).select_from(AuditEvent).where(AuditEvent.action == AuditAction.GOODWILL_EDIT)
)
assert (
await client.put(base + "/goodwill-settings", json=dict(goodwill_enabled=True))
).status_code == HTTPStatus.OK
assert audit_count == await db_session.scalar(
select(func.count()).select_from(AuditEvent).where(AuditEvent.action == AuditAction.GOODWILL_EDIT)
)
off = await client.put(base + "/goodwill-settings", json=dict(goodwill_enabled=False))
assert off.json()["totals"]["goodwill_in_kfo_amount"] == "0.00"
assert off.json()["rows"][0]["ownership_share"] == "80.12"
body["include_in_kfo"] = False
assert (await client.put(path, json=body)).status_code == HTTPStatus.OK
assert (await client.get(base + "/goodwill")).json()["totals"]["goodwill_amount"] == "0.00"
entries = list(
(
await db_session.scalars(
select(OsvEntry).where(OsvEntry.account_code == "58.01", OsvEntry.owner_legal_entity_id == w.a.id)
)
).all()
)
for entry in entries:
entry.closing_debit = 0
await db_session.flush()
row = (await client.get(base + "/goodwill")).json()["rows"][0]
assert not row["has_investments"] and row["goodwill_amount"] is None
assert row["comment"] == "Подтверждено"
assert (await client.put(path, json=body)).status_code == HTTPStatus.OK
body["include_in_kfo"] = True
response = await client.put(path, json=body)
assert response.status_code == HTTPStatus.OK
assert response.json()["financial_investment_amount"] is None
assert response.json()["goodwill_amount"] is None
assert await db_session.scalar(select(func.count()).select_from(KfoResult)) == 0
@pytest.mark.integration
@pytest.mark.fixtures
async def test_mutation_validation_and_rollback(db_session, auth_client, fixtures_dir, monkeypatch):
w = await goodwill_world(db_session, fixtures_dir)
client = await auth_client(KA_RF)
base = f"/consolidations/versions/{w.version.id}"
path = base + f"/goodwill/{w.a.id}/{w.b.id}"
assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED
body = dict(ownership_share="100", include_in_kfo=True, comment="")
response = await client.put(path, json=body)
assert (
response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
and response.json()["detail"]["code"] == "COMMENT_REQUIRED"
)
body.update(ownership_share="100.00001", comment="Да")
assert (await client.put(path, json=body)).json()["detail"]["code"] == "INVALID_SHARE"
assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0
async def reject_audit(*args, **kwargs):
raise ConflictError("TEST_AUDIT_FAILURE", "Ошибка тестового аудита")
monkeypatch.setattr(RequestAudit, "record", reject_audit)
body["ownership_share"] = "100"
response = await client.put(path, json=body)
assert response.status_code == HTTPStatus.CONFLICT
assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0
@pytest.mark.integration
@pytest.mark.fixtures
async def test_money_overflow_keeps_database_unchanged(db_session, auth_client, fixtures_dir):
w = await goodwill_world(db_session, fixtures_dir)
client = await auth_client(KA_RF)
base = f"/consolidations/versions/{w.version.id}"
assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED
value = (await db_session.scalars(select(FinancialStatementValue))).one()
value.amount = Decimal("9999999999999999.99")
await db_session.flush()
response = await client.put(
base + f"/goodwill/{w.a.id}/{w.b.id}", json=dict(ownership_share="100", include_in_kfo=True, comment="Проверка")
)
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
assert response.json()["detail"]["code"] == "MONEY_OUT_OF_RANGE"
assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0