"""Flattening of SDK records — the places where real data bit us. These use light stand-ins rather than SDK objects: the point is the flattening rules, and the suite must stay runnable without a token or a network. """ import enum from dataclasses import dataclass from datetime import UTC, datetime from decimal import Decimal from typing import Any, cast from fintracker.sources.tinvest.client import _as_dict, _kind_of, _money, _operation @dataclass class Money: currency: str units: int nano: int class Kind(enum.Enum): """Stands in for the SDK's OperationType, which is a real Enum.""" OPERATION_TYPE_BUY = 15 @dataclass class Item: """Only the fields `_operation` reads.""" id: str type: Kind date: datetime instrument_uid: str position_uid: str figi: str payment: Money price: Money commission: Money accrued_int: Money description: str quantity: int quantity_done: int def item(**over) -> Item: base = { "id": "42", "type": Kind.OPERATION_TYPE_BUY, "date": datetime(2026, 4, 16, 18, 11, tzinfo=UTC), "instrument_uid": "uid-1", "position_uid": "pos-1", "figi": "TCS00A10B7T7", "payment": Money("rub", -984, -780000000), "price": Money("rub", 984, 780000000), "commission": Money("rub", -2, -940000000), "accrued_int": Money("", 0, 0), "description": "Покупка 1 облигации", "quantity": 12, "quantity_done": 1, } return Item(**{**base, **over}) def test_money_is_exact_not_float(): """units+nano must land as an exact Decimal — money never goes through float.""" assert _money(Money("rub", -984, -780000000)) == Decimal("-984.78") assert _money(Money("rub", 0, 500000000)) == Decimal("0.5") def op_of(**over): """`_operation` on the stand-in; the cast is for pyright, the fields are what matter.""" return _operation("acc", cast("Any", item(**over))) def test_quantity_uses_what_executed_not_what_was_ordered(): """A partially filled order reports quantity=12, quantity_done=1 — the position grew by 1.""" assert op_of().quantity == Decimal(1) def test_quantity_falls_back_when_done_is_absent(): assert op_of(quantity_done=0, quantity=7).quantity == Decimal(7) def test_operation_keeps_position_uid(): """The identity that survives T-Invest handing one paper several instrument uids.""" assert op_of().position_uid == "pos-1" def test_payload_is_queryable_json_not_a_repr_string(): """The raw tier exists to be re-derived from; `repr()` prose would defeat that.""" payload = op_of().payload assert payload["id"] == "42" assert payload["type"] == "OPERATION_TYPE_BUY" assert payload["quantity_done"] == 1 # MoneyValue collapses to an exact decimal string plus its currency assert payload["payment"] == {"value": "-984.78", "currency": "rub"} assert payload["date"] == "2026-04-16T18:11:00+00:00" def test_as_dict_expands_the_top_level_record_by_field(): """The record itself becomes a dict of its fields; money nested inside it collapses.""" assert _as_dict(Money("rub", 1, 500000000)) == { "currency": "rub", "units": 1, "nano": 500000000, } def test_kind_prefers_what_the_api_states(): """Guessing from the record's shape filed the rouble position as a share.""" @dataclass class Rouble: instrument_type: str maturity_date: None = None assert _kind_of(Rouble("currency")) == "currency" def test_kind_falls_back_to_shape_when_unstated(): @dataclass class Bond: maturity_date: datetime assert _kind_of(Bond(datetime(2030, 1, 1, tzinfo=UTC))) == "bond"