]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Backend tests
authorshamoon <4887959+shamoon@users.noreply.github.com>
Thu, 6 Nov 2025 04:02:19 +0000 (20:02 -0800)
committershamoon <4887959+shamoon@users.noreply.github.com>
Thu, 6 Nov 2025 04:02:19 +0000 (20:02 -0800)
src/documents/tests/test_api_share_link_bundles.py [new file with mode: 0644]

diff --git a/src/documents/tests/test_api_share_link_bundles.py b/src/documents/tests/test_api_share_link_bundles.py
new file mode 100644 (file)
index 0000000..a8b5a91
--- /dev/null
@@ -0,0 +1,182 @@
+from __future__ import annotations
+
+from datetime import timedelta
+from pathlib import Path
+from unittest import mock
+
+from django.contrib.auth.models import User
+from django.utils import timezone
+from rest_framework import status
+from rest_framework.test import APITestCase
+
+from documents.models import ShareLink
+from documents.models import ShareLinkBundle
+from documents.tasks import cleanup_expired_share_link_bundles
+from documents.tests.factories import DocumentFactory
+from documents.tests.utils import DirectoriesMixin
+
+
+class ShareLinkBundleAPITests(DirectoriesMixin, APITestCase):
+    ENDPOINT = "/api/share_link_bundles/"
+
+    def setUp(self):
+        super().setUp()
+        self.user = User.objects.create_superuser(username="bundle_admin")
+        self.client.force_authenticate(self.user)
+        self.document = DocumentFactory.create()
+
+    @mock.patch("documents.views.build_share_link_bundle.delay")
+    def test_create_bundle_triggers_build_job(self, delay_mock):
+        payload = {
+            "document_ids": [self.document.pk],
+            "file_version": ShareLink.FileVersion.ARCHIVE,
+            "expiration_days": 7,
+        }
+
+        response = self.client.post(self.ENDPOINT, payload, format="json")
+
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        bundle = ShareLinkBundle.objects.get(pk=response.data["id"])
+        self.assertEqual(bundle.documents.count(), 1)
+        self.assertEqual(bundle.status, ShareLinkBundle.Status.PENDING)
+        delay_mock.assert_called_once_with(bundle.pk)
+
+    def test_create_bundle_rejects_missing_documents(self):
+        payload = {
+            "document_ids": [9999],
+            "file_version": ShareLink.FileVersion.ARCHIVE,
+            "expiration_days": 7,
+        }
+
+        response = self.client.post(self.ENDPOINT, payload, format="json")
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertIn("document_ids", response.data)
+
+    @mock.patch("documents.views.build_share_link_bundle.delay")
+    def test_rebuild_bundle_resets_state(self, delay_mock):
+        bundle = ShareLinkBundle.objects.create(
+            slug="rebuild-slug",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.FAILED,
+        )
+        bundle.documents.set([self.document])
+        bundle.last_error = "Something went wrong"
+        bundle.size_bytes = 100
+        bundle.file_path = "path/to/file.zip"
+        bundle.save()
+
+        response = self.client.post(f"{self.ENDPOINT}{bundle.pk}/rebuild/")
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        bundle.refresh_from_db()
+        self.assertEqual(bundle.status, ShareLinkBundle.Status.PENDING)
+        self.assertEqual(bundle.last_error, "")
+        self.assertIsNone(bundle.size_bytes)
+        self.assertEqual(bundle.file_path, "")
+        delay_mock.assert_called_once_with(bundle.pk)
+
+    def test_rebuild_bundle_rejects_processing_status(self):
+        bundle = ShareLinkBundle.objects.create(
+            slug="processing-slug",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.PROCESSING,
+        )
+        bundle.documents.set([self.document])
+
+        response = self.client.post(f"{self.ENDPOINT}{bundle.pk}/rebuild/")
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertIn("detail", response.data)
+
+    def test_download_ready_bundle_streams_file(self):
+        bundle_file = Path(self.dirs.media_dir) / "bundles" / "ready.zip"
+        bundle_file.parent.mkdir(parents=True, exist_ok=True)
+        bundle_file.write_bytes(b"binary-zip-content")
+
+        bundle = ShareLinkBundle.objects.create(
+            slug="readyslug",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.READY,
+            file_path=str(bundle_file),
+        )
+        bundle.documents.set([self.document])
+
+        self.client.logout()
+        response = self.client.get(f"/share/{bundle.slug}/")
+        content = b"".join(response.streaming_content)
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response["Content-Type"], "application/zip")
+        self.assertEqual(content, b"binary-zip-content")
+        self.assertIn("attachment;", response["Content-Disposition"])
+
+    def test_download_pending_bundle_returns_202(self):
+        bundle = ShareLinkBundle.objects.create(
+            slug="pendingslug",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.PENDING,
+        )
+        bundle.documents.set([self.document])
+
+        self.client.logout()
+        response = self.client.get(f"/share/{bundle.slug}/")
+
+        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
+
+    @mock.patch("documents.views.build_share_link_bundle.delay")
+    def test_download_missing_file_triggers_rebuild(self, delay_mock):
+        bundle = ShareLinkBundle.objects.create(
+            slug="missingfileslug",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.READY,
+            file_path=str(Path(self.dirs.media_dir) / "does-not-exist.zip"),
+        )
+        bundle.documents.set([self.document])
+
+        self.client.logout()
+        response = self.client.get(f"/share/{bundle.slug}/")
+
+        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
+        bundle.refresh_from_db()
+        self.assertEqual(bundle.status, ShareLinkBundle.Status.PENDING)
+        delay_mock.assert_called_once_with(bundle.pk)
+
+
+class ShareLinkBundleTaskTests(DirectoriesMixin, APITestCase):
+    def setUp(self):
+        super().setUp()
+        self.document = DocumentFactory.create()
+
+    def test_cleanup_expired_share_link_bundles(self):
+        expired_path = Path(self.dirs.media_dir) / "expired.zip"
+        expired_path.parent.mkdir(parents=True, exist_ok=True)
+        expired_path.write_bytes(b"expired")
+
+        active_path = Path(self.dirs.media_dir) / "active.zip"
+        active_path.write_bytes(b"active")
+
+        expired_bundle = ShareLinkBundle.objects.create(
+            slug="expired-bundle",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.READY,
+            expiration=timezone.now() - timedelta(days=1),
+            file_path=str(expired_path),
+        )
+        expired_bundle.documents.set([self.document])
+
+        active_bundle = ShareLinkBundle.objects.create(
+            slug="active-bundle",
+            file_version=ShareLink.FileVersion.ARCHIVE,
+            status=ShareLinkBundle.Status.READY,
+            expiration=timezone.now() + timedelta(days=1),
+            file_path=str(active_path),
+        )
+        active_bundle.documents.set([self.document])
+
+        cleanup_expired_share_link_bundles()
+
+        self.assertFalse(ShareLinkBundle.objects.filter(pk=expired_bundle.pk).exists())
+        self.assertTrue(ShareLinkBundle.objects.filter(pk=active_bundle.pk).exists())
+        self.assertFalse(expired_path.exists())
+        self.assertTrue(active_path.exists())