В strategy_indent: счёт, чей код продолжает код соседа того же отступа (68.01 → 68.01.1), — ребёнок; безымянная строка с оборотами счёта сразу за ним — его строка закрытия; аналитика на одном отступе вкладывается по суммам только внутри родителя с нарушением и только если он после этого сходится. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.4 KiB
Python
75 lines
3.4 KiB
Python
"""Генератор синтетики: детерминированность, состав набора и читаемость xlsb-писателя (не требует каталога fixtures)."""
|
||
|
||
import re
|
||
import time
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
|
||
from src.parser.readers import read_book
|
||
from tests.synthetic.generate import FILE_NAMES, generate_all
|
||
from tests.synthetic.xlsb_writer import write_xlsb
|
||
|
||
# Сколько ждать между двумя генерациями: у записей zip разрешение метки времени — 2 с
|
||
ZIP_TIME_RESOLUTION_SECONDS = 2.1
|
||
LONG_TEXT_LENGTH = 400
|
||
BIG_ROW = 70000
|
||
|
||
|
||
def test_generation_is_reproducible_byte_for_byte(tmp_path: Path) -> None:
|
||
"""Повторный запуск (позже, чем разрешение меток времени) даёт те же байты для каждого файла набора."""
|
||
first = generate_all(tmp_path / "first")
|
||
time.sleep(ZIP_TIME_RESOLUTION_SECONDS)
|
||
second = generate_all(tmp_path / "second")
|
||
|
||
assert [path.name for path in first] == [path.name for path in second]
|
||
for left, right in zip(first, second, strict=True):
|
||
assert left.read_bytes() == right.read_bytes(), left.name
|
||
|
||
|
||
def test_generated_set_is_s01_to_s36_and_s40_to_s47_with_one_xlsb(tmp_path: Path) -> None:
|
||
names = [path.name for path in generate_all(tmp_path)]
|
||
|
||
assert names == list(FILE_NAMES)
|
||
assert [re.match(r"s(\d\d)_", name) is not None for name in names] == [True] * len(names)
|
||
# s33…s36 — строки-показатели (AURORA-1613, #58), плоская форма ОСВ 1С (#59) — s40…s44
|
||
numbers = [*range(1, 37), *range(40, 48)]
|
||
assert [name[1:3] for name in names] == [f"{number:02d}" for number in numbers]
|
||
assert [name for name in names if name.endswith(".xlsb")] == ["s13_no_outline_indent.xlsb"]
|
||
assert sorted(path.name for path in tmp_path.iterdir()) == sorted(names)
|
||
|
||
|
||
def test_xlsb_writer_round_trips_through_the_reader() -> None:
|
||
"""Тексты (с ведущими пробелами и кириллицей), числа и несколько листов читаются `src.parser.readers`."""
|
||
long_text = "щ" * LONG_TEXT_LENGTH # запись длиннее 127 байт: длина кодируется двумя байтами
|
||
data = write_xlsb(
|
||
{
|
||
"Лист_1": {
|
||
(1, 1): "Привет",
|
||
(2, 3): Decimal("1234567.89"),
|
||
(3, 1): " отступ",
|
||
(4, 2): long_text,
|
||
(BIG_ROW, 4): 7,
|
||
},
|
||
"Второй": {(1, 1): "Привет"},
|
||
}
|
||
)
|
||
|
||
sheets, warnings = read_book(data, "XLSB")
|
||
|
||
assert [sheet.name for sheet in sheets] == ["Лист_1", "Второй"]
|
||
first = sheets[0]
|
||
assert first.text_at(1, 1) == "Привет"
|
||
assert first.cell(2, 3).value == Decimal("1234567.89")
|
||
assert (first.text_at(3, 1), first.cell(3, 1).indent) == ("отступ", 4)
|
||
assert first.text_at(4, 2) == long_text
|
||
assert first.cell(BIG_ROW, 4).value == Decimal(7)
|
||
assert first.n_rows == BIG_ROW
|
||
assert sheets[1].text_at(1, 1) == "Привет"
|
||
assert warnings == ["xlsb: outline level недоступен"]
|
||
|
||
|
||
def test_xlsb_writer_is_deterministic() -> None:
|
||
cells = {"Лист_1": {(1, 1): "а", (2, 2): Decimal("1.50")}}
|
||
|
||
assert write_xlsb(cells) == write_xlsb(cells)
|