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]