feat: add demo report import workflow
This commit is contained in:
103
tests/apps/forms/test_report_uploads_api.py
Normal file
103
tests/apps/forms/test_report_uploads_api.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""Tests for persistent original report uploads and binary downloads."""
|
||||
|
||||
import hashlib
|
||||
import tempfile
|
||||
|
||||
from apps.core.models import ReportUpload, ReportUploadStatus
|
||||
from apps.core.report_uploads import ReportUploadService
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from tests.apps.user.factories import UserFactory
|
||||
|
||||
|
||||
class ReportUploadApiTest(APITestCase):
|
||||
def setUp(self):
|
||||
self.media_directory = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.media_directory.cleanup)
|
||||
self.override = override_settings(MEDIA_ROOT=self.media_directory.name)
|
||||
self.override.enable()
|
||||
self.addCleanup(self.override.disable)
|
||||
self.user = UserFactory.create_user()
|
||||
|
||||
def _create_upload(self, *, content=b"original excel bytes") -> ReportUpload:
|
||||
return ReportUploadService.create(
|
||||
uploaded_file=SimpleUploadedFile(
|
||||
"Ф-1 отчет.xlsx",
|
||||
content,
|
||||
content_type="application/octet-stream",
|
||||
),
|
||||
form="f1",
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
def test_service_persists_original_bytes_and_finalizes_batch(self):
|
||||
content = b"same bytes that arrived"
|
||||
report_upload = self._create_upload(content=content)
|
||||
|
||||
ReportUploadService.finalize(report_upload.id, {"batch_id": 42})
|
||||
report_upload.refresh_from_db()
|
||||
|
||||
self.assertEqual(report_upload.load_batch, 42)
|
||||
self.assertEqual(report_upload.status, ReportUploadStatus.SUCCESS)
|
||||
self.assertEqual(report_upload.file_hash, hashlib.sha256(content).hexdigest())
|
||||
self.assertEqual(report_upload.original_file.read(), content)
|
||||
self.assertEqual(report_upload.file_name, "Ф-1 отчет.xlsx")
|
||||
self.assertEqual(report_upload.uploaded_by, self.user)
|
||||
|
||||
def test_download_requires_authentication(self):
|
||||
report_upload = self._create_upload()
|
||||
ReportUploadService.finalize(report_upload.id, {"batch_id": 42})
|
||||
|
||||
response = self.client.get(
|
||||
reverse("api_v1:report-upload-download", args=["f1", 42])
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_download_streams_original_bytes_and_filename(self):
|
||||
content = b"unchanged original xlsx bytes"
|
||||
report_upload = self._create_upload(content=content)
|
||||
ReportUploadService.finalize(report_upload.id, {"batch_id": 42})
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("api_v1:report-upload-download", args=["f1", 42])
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(b"".join(response.streaming_content), content)
|
||||
self.assertEqual(
|
||||
response["Content-Type"],
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
)
|
||||
self.assertIn("attachment", response["Content-Disposition"])
|
||||
self.assertIn("utf-8", response["Content-Disposition"].lower())
|
||||
|
||||
def test_download_rejects_a_different_non_staff_user(self):
|
||||
report_upload = self._create_upload()
|
||||
ReportUploadService.finalize(report_upload.id, {"batch_id": 42})
|
||||
self.client.force_authenticate(UserFactory.create_user())
|
||||
|
||||
response = self.client.get(
|
||||
reverse("api_v1:report-upload-download", args=["f1", 42])
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(response.data["errors"][0]["code"], "upload_forbidden")
|
||||
|
||||
def test_download_returns_gone_when_storage_object_was_removed(self):
|
||||
report_upload = self._create_upload()
|
||||
ReportUploadService.finalize(report_upload.id, {"batch_id": 42})
|
||||
report_upload.original_file.storage.delete(report_upload.original_file.name)
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("api_v1:report-upload-download", args=["f1", 42])
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_410_GONE)
|
||||
self.assertEqual(response.data["errors"][0]["code"], "source_file_gone")
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from apps.core.models import ReportUpload, ReportUploadStatus
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import override_settings
|
||||
from rest_framework import status
|
||||
@@ -89,6 +91,11 @@ class FormUploadContractsApiTest(APITestCase):
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.media_directory = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.media_directory.cleanup)
|
||||
self.media_override = override_settings(MEDIA_ROOT=self.media_directory.name)
|
||||
self.media_override.enable()
|
||||
self.addCleanup(self.media_override.disable)
|
||||
self.user = UserFactory.create_user()
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
@@ -172,6 +179,23 @@ class FormUploadContractsApiTest(APITestCase):
|
||||
)
|
||||
parse_mock.assert_not_called()
|
||||
task_mock.delay.assert_called_once()
|
||||
task_args, task_kwargs = task_mock.delay.call_args
|
||||
self.assertIn(
|
||||
response.data["upload_id"],
|
||||
(*task_args, *task_kwargs.values()),
|
||||
)
|
||||
report_upload = ReportUpload.objects.get(
|
||||
pk=response.data["upload_id"]
|
||||
)
|
||||
self.assertEqual(report_upload.form, case["form"])
|
||||
self.assertEqual(
|
||||
report_upload.status, ReportUploadStatus.QUEUED
|
||||
)
|
||||
self.assertIsNone(report_upload.load_batch)
|
||||
self.assertEqual(
|
||||
report_upload.original_file.read(),
|
||||
b"0" * self.BACKGROUND_THRESHOLD_PLUS,
|
||||
)
|
||||
|
||||
def test_upload_sync_contract(self):
|
||||
for _, case in self.CASES.items():
|
||||
@@ -201,6 +225,13 @@ class FormUploadContractsApiTest(APITestCase):
|
||||
)
|
||||
parse_mock.assert_called_once()
|
||||
task_mock.delay.assert_not_called()
|
||||
report_upload = ReportUpload.objects.get(
|
||||
pk=response.data["upload_id"]
|
||||
)
|
||||
self.assertEqual(report_upload.form, case["form"])
|
||||
self.assertEqual(report_upload.load_batch, 101)
|
||||
self.assertEqual(report_upload.status, ReportUploadStatus.SUCCESS)
|
||||
self.assertEqual(report_upload.original_file.read(), b"0" * 256)
|
||||
|
||||
def test_upload_processing_error_contract(self):
|
||||
for _, case in self.CASES.items():
|
||||
@@ -219,3 +250,7 @@ class FormUploadContractsApiTest(APITestCase):
|
||||
self.assertEqual(response.data["error_message"], "parse failed")
|
||||
self.assertEqual(response.data["details"], [])
|
||||
parse_mock.assert_called_once()
|
||||
report_upload = ReportUpload.objects.filter(form=case["form"]).latest(
|
||||
"created_at"
|
||||
)
|
||||
self.assertEqual(report_upload.status, ReportUploadStatus.FAILED)
|
||||
|
||||
Reference in New Issue
Block a user