Функция не мержит, а заворачивает документы в единый конверт публикации: одиночный документ идёт тем же путём и даёт files[] из одного элемента. Потребителю не нужно знать, сколько документов распознал процессор. Старые имена вводили в заблуждение на самом частом случае. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JvCSGmwgL8juhZdXr32qXM
130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
import json
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from src.core.result_payload import build_result
|
||
|
||
DOC_A = {
|
||
'file_uid': 'uid-a',
|
||
'error': {'code': '', 'message': ''},
|
||
'data_ocr': {'label': {'form_name': 'Бухгалтерский баланс'}, 'table_structure': []},
|
||
}
|
||
DOC_B = {
|
||
'file_uid': 'uid-b',
|
||
'error': {'code': '', 'message': ''},
|
||
'data_ocr': {'label': {'form_name': 'Отчёт о финансовых результатах'}, 'table_structure': []},
|
||
}
|
||
|
||
|
||
def test_single_document_keeps_the_same_envelope():
|
||
# одиночный документ проходит через ту же функцию ради единого формата
|
||
result = build_result([DOC_A], file_id='file-1', legal_entity_id='le-1')
|
||
|
||
assert result == {
|
||
'file_id': 'file-1',
|
||
'legal_entity_id': 'le-1',
|
||
'files': [DOC_A],
|
||
}
|
||
|
||
|
||
def test_several_documents_go_into_one_envelope():
|
||
result = build_result([DOC_A, DOC_B], file_id='file-1', legal_entity_id='le-1')
|
||
|
||
assert result == {
|
||
'file_id': 'file-1',
|
||
'legal_entity_id': 'le-1',
|
||
'files': [DOC_A, DOC_B],
|
||
}
|
||
|
||
|
||
def test_envelope_shape_does_not_depend_on_count():
|
||
one = build_result([DOC_A], file_id='f', legal_entity_id='l')
|
||
many = build_result([DOC_A, DOC_B], file_id='f', legal_entity_id='l')
|
||
|
||
assert list(one) == list(many)
|
||
assert isinstance(one['files'], list)
|
||
|
||
|
||
def test_does_no_io_at_all(monkeypatch):
|
||
# функция не должна ничего открывать — чтение целиком на вызывающем
|
||
def explode(*args, **kwargs):
|
||
raise AssertionError('build_result не должна трогать файловую систему')
|
||
|
||
monkeypatch.setattr('builtins.open', explode)
|
||
|
||
result = build_result([DOC_A], file_id='f', legal_entity_id='l')
|
||
|
||
assert result['files'] == [DOC_A]
|
||
|
||
|
||
def test_keeps_document_content_untouched():
|
||
# содержимое кладётся как есть: незнакомые поля и вложенность не теряются
|
||
weird = {'file_uid': 'uid-c', 'unexpected': [1, {'deep': None}], 'кириллица': 'значение'}
|
||
|
||
result = build_result([weird], file_id='file-1', legal_entity_id='le-1')
|
||
|
||
assert result['files'] == [weird]
|
||
|
||
|
||
def test_preserves_given_order():
|
||
result = build_result([DOC_B, DOC_A], file_id='f', legal_entity_id='l')
|
||
|
||
assert [doc['file_uid'] for doc in result['files']] == ['uid-b', 'uid-a']
|
||
|
||
|
||
def test_accepts_any_iterable():
|
||
# вызывающий может отдавать генератор, например потоковое чтение с шары
|
||
result = build_result((d for d in (DOC_A, DOC_B)), file_id='f', legal_entity_id='l')
|
||
|
||
assert len(result['files']) == 2
|
||
|
||
|
||
def test_rejects_empty_input():
|
||
# ноль документов — процессор ничего не распознал; это ошибка, а не пустой успех
|
||
with pytest.raises(ValueError):
|
||
build_result([], file_id='f', legal_entity_id='l')
|
||
|
||
|
||
def test_cli_reads_local_files_and_writes_result(tmp_path):
|
||
# CLI — один из вызывающих: локальное чтение живёт здесь, а не в build_result
|
||
a = tmp_path / 'doc_1.json'
|
||
a.write_text(json.dumps(DOC_A, ensure_ascii=False), encoding='utf-8')
|
||
b = tmp_path / 'doc_2.json'
|
||
b.write_text(json.dumps(DOC_B, ensure_ascii=False), encoding='utf-8')
|
||
out = tmp_path / 'result.json'
|
||
|
||
completed = subprocess.run(
|
||
[sys.executable, '-m', 'src.core.result_payload', str(a), str(b),
|
||
'--file-id', 'file-1', '--legal-entity-id', 'le-1', '-o', str(out)],
|
||
cwd=Path(__file__).resolve().parent.parent,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
|
||
assert completed.returncode == 0, completed.stderr
|
||
assert json.loads(out.read_text(encoding='utf-8')) == {
|
||
'file_id': 'file-1',
|
||
'legal_entity_id': 'le-1',
|
||
'files': [DOC_A, DOC_B],
|
||
}
|
||
|
||
|
||
def test_cli_handles_single_document(tmp_path):
|
||
a = tmp_path / 'doc_1.json'
|
||
a.write_text(json.dumps(DOC_A, ensure_ascii=False), encoding='utf-8')
|
||
out = tmp_path / 'result.json'
|
||
|
||
completed = subprocess.run(
|
||
[sys.executable, '-m', 'src.core.result_payload', str(a),
|
||
'--file-id', 'file-1', '--legal-entity-id', 'le-1', '-o', str(out)],
|
||
cwd=Path(__file__).resolve().parent.parent,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
|
||
assert completed.returncode == 0, completed.stderr
|
||
assert json.loads(out.read_text(encoding='utf-8'))['files'] == [DOC_A]
|