feat(parsers): support FNS zip uploads in admin
Some checks failed
CI/CD Pipeline / Run Tests (push) Successful in 3m0s
CI/CD Pipeline / Code Quality Checks (push) Failing after 7m39s
CI/CD Pipeline / Telegram Notify Success (push) Has been skipped

This commit is contained in:
2026-03-20 13:43:11 +01:00
parent e470189f44
commit b8015d9cdd
8 changed files with 459 additions and 198 deletions

View File

@@ -4,9 +4,11 @@ import io
import os
import tempfile
import time
import zipfile
from unittest.mock import patch
from apps.core.models import BackgroundJob
from apps.parsers.fns_upload import FNSUploadService
from apps.parsers.models import FinancialReport, FinancialReportLine
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings
@@ -38,6 +40,14 @@ def _build_fns_excel_bytes() -> bytes:
return buf.getvalue()
def _build_fns_zip_bytes(file_map: dict[str, bytes]) -> bytes:
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for file_name, content in file_map.items():
archive.writestr(file_name, content)
return buffer.getvalue()
class FNSUploadIntegrationTest(APITestCase):
"""Tests real upload + processing of FNS files."""
@@ -348,7 +358,7 @@ class FNSUploadIntegrationTest(APITestCase):
FNS_WATCH_DIRECTORY=watch_dir,
FNS_PROCESSED_DIRECTORY=processed_dir,
FNS_FAILED_DIRECTORY=failed_dir,
), patch("apps.parsers.views.Path.touch", side_effect=FileExistsError):
), patch("apps.parsers.fns_upload.Path.touch", side_effect=FileExistsError):
response = self.client.post(
self.upload_url,
{"files": [upload]},
@@ -375,7 +385,10 @@ class FNSUploadIntegrationTest(APITestCase):
FNS_WATCH_DIRECTORY=watch_dir,
FNS_PROCESSED_DIRECTORY=processed_dir,
FNS_FAILED_DIRECTORY=failed_dir,
), patch("apps.parsers.views.open", side_effect=OSError("disk full")):
), patch(
"apps.parsers.fns_upload.Path.write_bytes",
side_effect=OSError("disk full"),
):
response = self.client.post(
self.upload_url,
{"files": [upload]},
@@ -406,9 +419,9 @@ class FNSUploadIntegrationTest(APITestCase):
FNS_PROCESSED_DIRECTORY=processed_dir,
FNS_FAILED_DIRECTORY=failed_dir,
), patch(
"apps.parsers.views.uuid.uuid4", return_value="job-task-id"
"apps.parsers.fns_upload.uuid.uuid4", return_value="job-task-id"
), patch(
"apps.parsers.views.process_fns_file.apply_async",
"apps.parsers.fns_upload.process_fns_file.apply_async",
side_effect=RuntimeError("queue down"),
):
response = self.client.post(
@@ -426,3 +439,54 @@ class FNSUploadIntegrationTest(APITestCase):
self.assertFalse(
os.path.exists(os.path.join(watch_dir, f"{filename}.lock"))
)
def test_queue_zip_archive_processes_valid_files_and_skips_invalid(self):
first_name = f"fin_{_digits(5)}_{_digits(13)}.xlsx"
second_name = f"fin_{_digits(5)}_{_digits(13)}.xlsx"
zip_content = _build_fns_zip_bytes(
{
first_name: _build_fns_excel_bytes(),
second_name: _build_fns_excel_bytes(),
"nested/fin_0000001_1234567890123.xlsx": _build_fns_excel_bytes(),
"readme.txt": b"invalid",
}
)
archive_upload = SimpleUploadedFile(
"fin_ropk.zip",
zip_content,
content_type="application/zip",
)
with tempfile.TemporaryDirectory() as tmpdir:
watch_dir, processed_dir, failed_dir = self._dirs(tmpdir)
with override_settings(
FNS_WATCH_DIRECTORY=watch_dir,
FNS_PROCESSED_DIRECTORY=processed_dir,
FNS_FAILED_DIRECTORY=failed_dir,
):
result = FNSUploadService.queue_zip_archive(
archive_file=archive_upload,
requested_by_id=self.admin.id,
)
self.assertEqual(result.queued, 2)
self.assertEqual(result.skipped, 0)
self.assertEqual(result.invalid, 2)
self.assertEqual(FinancialReport.objects.count(), 2)
self.assertEqual(FinancialReportLine.objects.count(), 2)
def test_queue_zip_archive_rejects_bad_zip(self):
archive_upload = SimpleUploadedFile(
"fin_ropk.zip",
b"not-a-zip",
content_type="application/zip",
)
with self.assertRaisesMessage(
ValueError,
"Загруженный файл не является корректным ZIP архивом",
):
FNSUploadService.queue_zip_archive(
archive_file=archive_upload,
requested_by_id=self.admin.id,
)