Files
mostovik-backend/tests/apps/parsers/test_fns_parser.py
Aleksandr Meshchriakov 052389d921 refactor(parsers): перенести тесты в ROOT_DIR/tests и синхронизировать контракты задач
- перенесены тесты parsers из src/apps/parsers/tests в tests/apps/parsers

- обновлены тесты задач под текущее поведение Celery (ошибки пробрасываются исключениями)

- убрана зависимость тестов от внешнего брокера через локальные eager-вызовы

- добавлены/уточнены фабрики и импорты для единой структуры тестов

- обновлены README и CHANGELOG с новым правилом размещения тестов и запуском
2026-03-04 15:35:50 +01:00

374 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
"""
Тесты для 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)