197 lines
7.2 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Отображаемое значение числа по числовому формату ячейки (#42, R-2…R-6): примеры из реальных выгрузок 1С."""
from decimal import Decimal
import pytest
from src.parser.statements.number_format import FormatResult, display_amount, general_text
def shown(raw: str, fmt: str) -> tuple[str, Decimal | None]:
result = display_amount(Decimal(raw), fmt)
return result.kind, result.value
@pytest.mark.parametrize(
("raw", "fmt", "expected"),
[
("10741981390", "#,##0,", "10741981.00"),
("586260.26", "0,", "586.00"),
("898852.21795", "#,##0", "898852.00"),
("898852.5", "#,##0", "898853.00"),
("1234.5", "#,##0.00", "1234.50"),
("1234.567", "#,##0.00", "1234.57"),
("1500", "#,##0,", "2.00"),
("-1500", "#,##0,", "-2.00"),
("1499.99", "#,##0,", "1.00"),
("-1499.99", "#,##0,", "-1.00"),
("586260.26", "0.00,", "586.26"),
("5000000", "#,##0,,", "5.00"),
("1500", '#,##0,;(#,##0,);"-"', "2.00"),
("-1500", '#,##0,;(#,##0,);"-"', "-2.00"),
("1500", "#,##0;(#,##0)", "1500.00"),
("-1500", "#,##0;(#,##0)", "-1500.00"),
("-1500", "#,##0;#,##0", "-1500.00"),
("1500", "[Red]#,##0", "1500.00"),
("1500", "[$-419]#,##0", "1500.00"),
("1500", "#,##0_);[Red](#,##0)", "1500.00"),
("-1500", "#,##0_);[Red](#,##0)", "-1500.00"),
("1500.126", "0.000", "1500.13"),
("1500", '0 "тыс."', "1500.00"),
("-872206880.07", '[=-872206880.07]"(872 207)";#,##0,', "-872207.00"),
("-872206881", '[=-872206880.07]"(872 207)";#,##0,', "-872207.00"),
("-646532", '[=-646532]"(647)"', "-647.00"),
("12345678", '[=-872206880.07]"(872 207)";#,##0,', "12346.00"),
("1500499.9999999998", "#,##0,", "1501.00"),
("1500", "[$-419]#,##0,", "2.00"),
("1500", "\\(#,##0\\)", "1500.00"),
("5", "[=5]0;[=5]0,", "5.00"),
("-1500", "[$-419]#,##0,", "-2.00"),
],
)
def test_value(raw: str, fmt: str, expected: str) -> None:
kind, value = shown(raw, fmt)
assert kind == "value"
assert value is not None
assert str(value) == expected
@pytest.mark.parametrize(
("raw", "fmt"),
[
("0", '[=0]"-"'),
("0", '[=0]""'),
("0", '[=0]"-";General'),
("-0.79", '[=-0.79]"-"'),
("0", '#,##0,;(#,##0,);"-"'),
("0", ";;;"),
("7", ";;;"),
],
)
def test_no_value(raw: str, fmt: str) -> None:
assert shown(raw, fmt) == ("value", None)
@pytest.mark.parametrize(
("raw", "fmt"),
[
("5", '[=0]"-"'),
("5", "0%"),
("5", "0.00E+00"),
("5", "# ?/?"),
("5", "@"),
("5", "dd.mm.yyyy"),
("5", "h:mm AM/PM"),
("0", '[=0]"н/д"'),
("5", '"н/д"'),
("5", "0 руб"),
("5", "[=1E-5]0"),
("5", "[=1,5]0"),
("5", "0 ₽"),
("5", "0;0;0;@;0"),
],
)
def test_unparsed(raw: str, fmt: str) -> None:
assert shown(raw, fmt) == ("unparsed", None)
@pytest.mark.parametrize("fmt", ["General", "general", "", " "])
def test_raw(fmt: str) -> None:
assert shown("5.5", fmt) == ("raw", None)
def test_general_section_after_condition() -> None:
assert shown("5.5", '[=0]"-";General') == ("raw", None)
@pytest.mark.parametrize(
("raw", "fmt", "expected"),
[
("5", "[<10]0;[>=10]0,", "5.00"),
("20000", "[<10]0;[>=10]0,", "20.00"),
("5", "[<>5]0;0.0", "5.0"),
],
)
def test_comparison_conditions(raw: str, fmt: str, expected: str) -> None:
kind, value = shown(raw, fmt)
assert kind == "value"
assert value is not None
assert Decimal(expected) == value
def test_fourth_section_ignored() -> None:
assert shown("1500", "#,##0,;(#,##0,);0;@") == ("value", Decimal("2.00"))
def test_semicolon_in_quotes_does_not_split() -> None:
assert shown("0", '[=0]"a;b"') == ("unparsed", None)
def test_escaped_char_is_decoration() -> None:
assert shown("1500", "#,##0\\ \\р") == ("value", Decimal("1500.00"))
def test_huge_value_is_returned_without_range_check() -> None:
"""P4-4: диапазон numeric(18,2) проверяет пакет (VALUE_OUT_OF_RANGE), адаптер отдаёт показываемое число."""
assert shown("1" + "0" * 40, "#,##0") == ("value", Decimal("1" + "0" * 40 + ".00"))
def test_long_integers_keep_all_digits() -> None:
"""P4-4: длинные целые показываются без потери цифр, диапазон проверяет пакет."""
assert shown("999999999999999", "0") == ("value", Decimal("999999999999999.00"))
assert shown("9999999999999999", "0") == ("value", Decimal("9999999999999999.00"))
assert shown("99999999999999999", "0") == ("value", Decimal("99999999999999999.00"))
def test_scaled_huge_value_is_not_range_checked() -> None:
assert shown("99999999999999999000", "0,") == ("value", Decimal("99999999999999999.00"))
assert shown("999999999999999000", "0,") == ("value", Decimal("999999999999999.00"))
def test_condition_compares_with_excel_15_digit_precision() -> None:
"""Из xls значение приходит как -1486533582.3500001, условие записано `-1486533582.35`: Excel считает их равными."""
fmt = '[=-1486533582.35]"(1\u00a0486\u00a0534)"'
result = display_amount(Decimal("-1486533582.3500001"), fmt)
assert result == FormatResult("value", Decimal("-1486534"), "(1\u00a0486\u00a0534)")
def test_value_is_rounded_to_15_digits_half_up_before_scaling() -> None:
"""Артефакт float 1500499.9999999998 в Excel — 1500500, после деления на 1000 половина округляется вверх."""
assert shown("1500499.9999999998", "#,##0,") == ("value", Decimal("1501"))
def test_condition_operand_is_rounded_to_15_digits() -> None:
"""Литерал «-» — «нет значения»; «x» дал бы unparsed по R-5 независимо от условия, поэтому берём прочерк."""
result = display_amount(Decimal("0.1234567890123456"), '[=0.1234567890123456]"-"')
assert result == FormatResult("value", None, "-")
def test_condition_that_does_not_match_the_pattern_is_unparsed() -> None:
assert shown("0", '[=1E-5]"x"') == ("unparsed", None)
@pytest.mark.parametrize(
("raw", "fmt", "value", "text"),
[
("106311116.46", "#,##0,", "106311", "106 311"),
("-646532.4", "#,##0;(#,##0)", "-646532", "(646 532)"),
("-1500", "#,##0", "-1500", "-1 500"),
("-1500", "#,##0;#,##0", "-1500", "1 500"),
("1.5", "0.00", "1.50", "1,50"),
("0", '#,##0;-#,##0;"-"', None, "-"),
("12345678901234567", "0", "12345678901234567", "12345678901234567"),
],
)
def test_display_text(raw: str, fmt: str, value: str | None, text: str) -> None:
shown = display_amount(Decimal(raw), fmt)
expected = None if value is None else Decimal(value)
assert (shown.kind, shown.value, shown.text) == ("value", expected, text)
def test_general_text() -> None:
texts = [general_text(Decimal(x)) for x in ("7016006635.0", "106311116.460", "-0.5")]
assert texts == ["7016006635", "106311116,46", "-0,5"]