feat: expand platform APIs, sources, and test coverage
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
This commit is contained in:
@@ -4,7 +4,9 @@ import io
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
from apps.core.models import BackgroundJob
|
||||
from apps.parsers.models import FinancialReport, FinancialReportLine
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import override_settings
|
||||
@@ -41,7 +43,9 @@ class FNSUploadIntegrationTest(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = UserFactory.create_user()
|
||||
self.client.force_authenticate(self.user)
|
||||
self.admin = UserFactory.create_user(is_staff=True)
|
||||
self.other = UserFactory.create_user()
|
||||
self.client.force_authenticate(self.admin)
|
||||
self.upload_url = reverse("api_v1:fns:fns-upload")
|
||||
|
||||
def _dirs(self, base_dir: str) -> tuple[str, str, str]:
|
||||
@@ -136,6 +140,47 @@ class FNSUploadIntegrationTest(APITestCase):
|
||||
os.path.exists(os.path.join(watch_dir, f"{filename}.lock"))
|
||||
)
|
||||
|
||||
def test_upload_creates_owned_background_job(self):
|
||||
content = _build_fns_excel_bytes()
|
||||
external_id = _digits(5)
|
||||
ogrn = _digits(13)
|
||||
filename = f"fin_{external_id}_{ogrn}.xlsx"
|
||||
upload = SimpleUploadedFile(
|
||||
filename,
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
):
|
||||
response = self.client.post(
|
||||
self.upload_url, {"files": [upload]}, format="multipart"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
task_id = response.data["task_ids"][0]
|
||||
job = BackgroundJob.objects.get(task_id=task_id)
|
||||
self.assertEqual(job.user_id, self.admin.id)
|
||||
|
||||
jobs_response = self.client.get(reverse("api_v1:jobs:job-list"))
|
||||
self.assertEqual(jobs_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(jobs_response.data), 1)
|
||||
self.assertEqual(jobs_response.data[0]["task_id"], task_id)
|
||||
|
||||
other_client = self.client_class()
|
||||
other_client.force_authenticate(self.other)
|
||||
status_response = other_client.get(
|
||||
reverse("api_v1:jobs:job-status", kwargs={"task_id": task_id})
|
||||
)
|
||||
self.assertEqual(status_response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_upload_invalid_filename_rejected(self):
|
||||
content = _build_fns_excel_bytes()
|
||||
upload = SimpleUploadedFile(
|
||||
@@ -196,6 +241,29 @@ class FNSUploadIntegrationTest(APITestCase):
|
||||
self.assertEqual(response.data["queued"], 0)
|
||||
self.assertEqual(response.data["skipped"], 1)
|
||||
|
||||
def test_regular_user_cannot_upload(self):
|
||||
self.client.force_authenticate(self.user)
|
||||
upload = SimpleUploadedFile(
|
||||
f"fin_{_digits(5)}_{_digits(13)}.xlsx",
|
||||
_build_fns_excel_bytes(),
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
):
|
||||
response = self.client.post(
|
||||
self.upload_url, {"files": [upload]}, format="multipart"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_upload_skips_when_file_already_exists(self):
|
||||
content = _build_fns_excel_bytes()
|
||||
external_id = _digits(5)
|
||||
@@ -228,3 +296,133 @@ class FNSUploadIntegrationTest(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
self.assertEqual(response.data["queued"], 0)
|
||||
self.assertEqual(response.data["skipped"], 1)
|
||||
|
||||
def test_upload_removes_stale_lock_and_queues_file(self):
|
||||
content = _build_fns_excel_bytes()
|
||||
external_id = _digits(5)
|
||||
ogrn = _digits(13)
|
||||
filename = f"fin_{external_id}_{ogrn}.xlsx"
|
||||
upload = SimpleUploadedFile(
|
||||
filename,
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
watch_dir, processed_dir, failed_dir = self._dirs(tmpdir)
|
||||
os.makedirs(watch_dir, exist_ok=True)
|
||||
lock_path = os.path.join(watch_dir, f"{filename}.lock")
|
||||
with open(lock_path, "w") as handle:
|
||||
handle.write("lock")
|
||||
stale = time.time() - 7200
|
||||
os.utime(lock_path, (stale, stale))
|
||||
|
||||
with override_settings(
|
||||
FNS_WATCH_DIRECTORY=watch_dir,
|
||||
FNS_PROCESSED_DIRECTORY=processed_dir,
|
||||
FNS_FAILED_DIRECTORY=failed_dir,
|
||||
FNS_LOCK_TTL_SECONDS=1,
|
||||
):
|
||||
response = self.client.post(
|
||||
self.upload_url, {"files": [upload]}, format="multipart"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
self.assertEqual(response.data["queued"], 1)
|
||||
self.assertEqual(response.data["skipped"], 0)
|
||||
|
||||
def test_upload_skips_when_lock_creation_races(self):
|
||||
upload = SimpleUploadedFile(
|
||||
f"fin_{_digits(5)}_{_digits(13)}.xlsx",
|
||||
_build_fns_excel_bytes(),
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
), patch("apps.parsers.views.Path.touch", side_effect=FileExistsError):
|
||||
response = self.client.post(
|
||||
self.upload_url,
|
||||
{"files": [upload]},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
self.assertEqual(response.data["queued"], 0)
|
||||
self.assertEqual(response.data["skipped"], 1)
|
||||
|
||||
def test_upload_cleans_up_lock_when_file_write_fails(self):
|
||||
filename = f"fin_{_digits(5)}_{_digits(13)}.xlsx"
|
||||
upload = SimpleUploadedFile(
|
||||
filename,
|
||||
_build_fns_excel_bytes(),
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
), patch("apps.parsers.views.open", side_effect=OSError("disk full")):
|
||||
response = self.client.post(
|
||||
self.upload_url,
|
||||
{"files": [upload]},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
self.assertFalse(
|
||||
os.path.exists(os.path.join(watch_dir, f"{filename}.lock"))
|
||||
)
|
||||
|
||||
def test_upload_deletes_background_job_when_task_enqueue_fails(self):
|
||||
filename = f"fin_{_digits(5)}_{_digits(13)}.xlsx"
|
||||
upload = SimpleUploadedFile(
|
||||
filename,
|
||||
_build_fns_excel_bytes(),
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
), patch(
|
||||
"apps.parsers.views.uuid.uuid4", return_value="job-task-id"
|
||||
), patch(
|
||||
"apps.parsers.views.process_fns_file.apply_async",
|
||||
side_effect=RuntimeError("queue down"),
|
||||
):
|
||||
response = self.client.post(
|
||||
self.upload_url,
|
||||
{"files": [upload]},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
self.assertFalse(
|
||||
BackgroundJob.objects.filter(task_id="job-task-id").exists()
|
||||
)
|
||||
self.assertFalse(
|
||||
os.path.exists(os.path.join(watch_dir, f"{filename}.lock"))
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user