""" Тесты для FNS парсера бухгалтерской отчетности. """ from apps.parsers.clients.fns.parser import FNSExcelParser, FNSParserError from apps.parsers.clients.fns.schemas import ParsedReport, ReportLine from apps.parsers.models import FinancialReport from apps.parsers.services import FNSReportService from apps.registers.models import Organization from django.test import TestCase from tests.utils.fixtures import fake def _digits(length: int) -> str: return "".join(str(fake.random_int(0, 9)) for _ in range(length)) def _year() -> int: return fake.random_int(min=2020, max=2026) def _line_code() -> str: return _digits(4) def _form_code() -> str: return str(fake.random_int(min=1, max=6)) def _create_registry_organization(*, inn: str, ogrn: str) -> Organization: return Organization.objects.create( pn_name=fake.company(), mn_ogrn=int(ogrn), mn_inn=int(inn), in_kpp=int(_digits(9)), mn_okpo=_digits(8), ) class TestFNSExcelParserFilename(TestCase): """Тесты парсинга имени файла.""" def test_parse_valid_filename(self): """Корректное имя файла.""" external_id = _digits(7) ogrn = _digits(13) external_id_parsed, ogrn_parsed = FNSExcelParser.parse_filename( f"fin_{external_id}_{ogrn}.xlsx" ) self.assertEqual(external_id_parsed, external_id) self.assertEqual(ogrn_parsed, ogrn) def test_parse_filename_with_long_id(self): """Имя файла с длинным ID.""" external_id = _digits(8) ogrn = _digits(13) external_id_parsed, ogrn_parsed = FNSExcelParser.parse_filename( f"fin_{external_id}_{ogrn}.xlsx" ) self.assertEqual(external_id_parsed, external_id) self.assertEqual(ogrn_parsed, ogrn) def test_parse_filename_with_15_digit_ogrn(self): """Имя файла с 15-значным ОГРН (ИП).""" external_id = _digits(3) ogrn = _digits(15) external_id_parsed, ogrn_parsed = FNSExcelParser.parse_filename( f"fin_{external_id}_{ogrn}.xlsx" ) self.assertEqual(external_id_parsed, external_id) self.assertEqual(ogrn_parsed, ogrn) def test_parse_invalid_filename_no_fin_prefix(self): """Неверный формат - без префикса fin.""" with self.assertRaises(FNSParserError): FNSExcelParser.parse_filename( f"{fake.word()}_{_digits(3)}_{_digits(13)}.xlsx" ) def test_parse_invalid_filename_wrong_extension(self): """Неверный формат - неправильное расширение.""" with self.assertRaises(FNSParserError): FNSExcelParser.parse_filename(f"fin_{_digits(3)}_{_digits(13)}.xls") def test_parse_invalid_filename_short_ogrn(self): """Неверный формат - короткий ОГРН.""" with self.assertRaises(FNSParserError): FNSExcelParser.parse_filename(f"fin_{_digits(3)}_{_digits(12)}.xlsx") class TestReportLineSchema(TestCase): """Тесты схемы ReportLine.""" def test_create_report_line(self): """Создание строки отчета.""" form_code = _form_code() line_code = _line_code() line_name = fake.word() year = _year() period_start = fake.random_int(min=10, max=1000) period_end = fake.random_int(min=10, max=1000) line = ReportLine( form_code=form_code, line_code=line_code, line_name=line_name, year=year, period_start=period_start, period_end=period_end, ) self.assertEqual(line.form_code, form_code) self.assertEqual(line.line_code, line_code) self.assertEqual(line.year, year) self.assertEqual(line.period_start, period_start) self.assertEqual(line.period_end, period_end) def test_report_line_with_none_values(self): """Строка отчета с пустыми значениями.""" line = ReportLine( form_code=_form_code(), line_code=_line_code(), line_name=fake.word(), year=_year(), ) self.assertIsNone(line.period_start) self.assertIsNone(line.period_end) class TestParsedReportSchema(TestCase): """Тесты схемы ParsedReport.""" def test_create_parsed_report(self): """Создание отчета.""" external_id = _digits(3) ogrn = _digits(13) year_a = _year() year_b = year_a - 1 if year_a > 2020 else year_a + 1 year_c = year_a + 1 if year_a < 2026 else year_a - 1 report = ParsedReport( external_id=external_id, ogrn=ogrn, lines=[ ReportLine(_form_code(), _line_code(), fake.word(), year_a, 100, 200), ReportLine(_form_code(), _line_code(), fake.word(), year_b, 50, 100), ReportLine(_form_code(), _line_code(), fake.word(), year_c, 1000, 1500), ], ) self.assertEqual(report.external_id, external_id) self.assertEqual(report.ogrn, ogrn) self.assertEqual(len(report.lines), 3) def test_report_years(self): """Получение годов из отчета.""" year_a = _year() year_b = year_a - 1 if year_a > 2020 else year_a + 1 year_c = year_a + 1 if year_a < 2026 else year_a - 1 report = ParsedReport( external_id=_digits(3), ogrn=_digits(13), lines=[ ReportLine(_form_code(), _line_code(), fake.word(), year_a, 100, 200), ReportLine(_form_code(), _line_code(), fake.word(), year_b, 50, 100), ReportLine(_form_code(), _line_code(), fake.word(), year_c, 30, 50), ], ) self.assertEqual(report.years, {year_a, year_b, year_c}) def test_report_forms(self): """Получение форм из отчета.""" form_a = _form_code() form_b = _form_code() form_c = _form_code() report = ParsedReport( external_id=_digits(3), ogrn=_digits(13), lines=[ ReportLine(form_a, _line_code(), fake.word(), _year(), 100, 200), ReportLine(form_b, _line_code(), fake.word(), _year(), 1000, 1500), ReportLine(form_c, _line_code(), fake.word(), _year(), 500, 600), ], ) self.assertEqual(report.forms, {form_a, form_b, form_c}) def test_get_lines_by_form(self): """Фильтрация строк по форме.""" form_target = _form_code() form_other = _form_code() while form_other == form_target: form_other = _form_code() report = ParsedReport( external_id=_digits(3), ogrn=_digits(13), lines=[ ReportLine(form_target, _line_code(), fake.word(), _year(), 100, 200), ReportLine(form_other, _line_code(), fake.word(), _year(), 1000, 1500), ReportLine(form_target, _line_code(), fake.word(), _year(), 300, 400), ], ) form1_lines = report.get_lines_by_form(form_target) self.assertEqual(len(form1_lines), 2) self.assertTrue(all(line.form_code == form_target for line in form1_lines)) def test_get_lines_by_year(self): """Фильтрация строк по году.""" year_target = _year() year_other = year_target - 1 if year_target > 2020 else year_target + 1 report = ParsedReport( external_id=_digits(3), ogrn=_digits(13), lines=[ ReportLine( _form_code(), _line_code(), fake.word(), year_target, 100, 200 ), ReportLine( _form_code(), _line_code(), fake.word(), year_other, 50, 100 ), ReportLine( _form_code(), _line_code(), fake.word(), year_target, 150, 250 ), ], ) year_lines = report.get_lines_by_year(year_target) self.assertEqual(len(year_lines), 2) self.assertTrue(all(line.year == year_target for line in year_lines)) class TestFNSParserParseValue(TestCase): """Тесты метода _parse_value.""" def test_parse_integer(self): """Парсинг целого числа.""" value = fake.random_int(min=1, max=1000) self.assertEqual(FNSExcelParser._parse_value(value), value) def test_parse_float(self): """Парсинг числа с плавающей точкой.""" value = fake.random_int(min=1, max=1000) self.assertEqual(FNSExcelParser._parse_value(value + 0.5), value) def test_parse_string_integer(self): """Парсинг строкового числа.""" value = fake.random_int(min=1, max=1000) self.assertEqual(FNSExcelParser._parse_value(str(value)), value) def test_parse_string_with_comma(self): """Парсинг числа с запятой.""" value = fake.random_int(min=1, max=1000) self.assertEqual(FNSExcelParser._parse_value(f"{value},5"), value) def test_parse_string_with_spaces(self): """Парсинг числа с пробелами.""" value = fake.random_int(min=1000, max=999999) formatted = f"{value:,}".replace(",", " ") self.assertEqual(FNSExcelParser._parse_value(formatted), value) def test_parse_none(self): """Парсинг None.""" self.assertIsNone(FNSExcelParser._parse_value(None)) def test_parse_empty_string(self): """Парсинг пустой строки.""" self.assertIsNone(FNSExcelParser._parse_value("")) def test_parse_dash(self): """Парсинг прочерка.""" self.assertIsNone(FNSExcelParser._parse_value("-")) def test_parse_invalid_string(self): """Парсинг некорректной строки.""" self.assertIsNone(FNSExcelParser._parse_value(fake.word())) class TestFNSReportServiceIntegration(TestCase): """Интеграционные тесты сервиса FNSReportService.""" def test_save_report(self): """Сохранение отчета.""" external_id = _digits(6) ogrn = _digits(13) file_hash = fake.sha1(raw_output=False) lines_data = [ { "form_code": _form_code(), "line_code": _line_code(), "line_name": fake.word(), "year": _year(), "period_start": fake.random_int(min=10, max=1000), "period_end": fake.random_int(min=10, max=1000), }, { "form_code": _form_code(), "line_code": _line_code(), "line_name": fake.word(), "year": _year(), "period_start": fake.random_int(min=10, max=1000), "period_end": fake.random_int(min=10, max=1000), }, ] report = FNSReportService.save_report( external_id=external_id, ogrn=ogrn, file_name=f"fin_{external_id}_{ogrn}.xlsx", file_hash=file_hash, source=FinancialReport.SourceType.API, batch_id=1, lines_data=lines_data, ) self.assertIsNotNone(report.id) self.assertEqual(report.external_id, external_id) self.assertEqual(report.ogrn, ogrn) self.assertEqual(report.status, FinancialReport.Status.SUCCESS) self.assertEqual(report.lines.count(), 2) def test_save_report_links_registry_organization_when_exists(self): """Отчет ФНС должен связываться с организацией из registers по ОГРН.""" ogrn = _digits(13) organization = _create_registry_organization(inn=_digits(10), ogrn=ogrn) external_id = _digits(6) report = FNSReportService.save_report( external_id=external_id, ogrn=ogrn, file_name=f"fin_{external_id}_{ogrn}.xlsx", file_hash=fake.sha1(raw_output=False), source=FinancialReport.SourceType.API, batch_id=1, lines_data=[], ) self.assertEqual(report.registry_organization_id, organization.id) def test_exists_by_hash(self): """Проверка существования по хешу.""" unique_hash = fake.sha1(raw_output=False) FNSReportService.save_report( external_id=_digits(6), ogrn=_digits(13), file_name=fake.file_name(extension="xlsx"), file_hash=unique_hash, source=FinancialReport.SourceType.API, batch_id=1, lines_data=[], ) self.assertTrue(FNSReportService.exists_by_hash(unique_hash)) self.assertFalse(FNSReportService.exists_by_hash(fake.sha1(raw_output=False))) def test_find_by_ogrn(self): """Поиск по ОГРН.""" ogrn = _digits(13) FNSReportService.save_report( external_id=_digits(6), ogrn=ogrn, file_name=fake.file_name(extension="xlsx"), file_hash=fake.sha1(raw_output=False), source=FinancialReport.SourceType.FILE_WATCH, batch_id=1, lines_data=[], ) FNSReportService.save_report( external_id=_digits(6), ogrn=ogrn, file_name=fake.file_name(extension="xlsx"), file_hash=fake.sha1(raw_output=False), source=FinancialReport.SourceType.FILE_WATCH, batch_id=1, lines_data=[], ) reports = FNSReportService.find_by_ogrn(ogrn) self.assertEqual(reports.count(), 2)