From 3943c311cfa81b95e7729c8ee237d7d2a38773e1 Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:37:21 -0700 Subject: [PATCH] Increase test coverage --- .../management/commands/document_exporter.py | 6 ++- src/documents/tests/export/test_sinks.py | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index cf23f9215..da98e88cd 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -390,7 +390,11 @@ class Command(CryptMixin, PaperlessCommand): description="Exporting documents...", total=len(document_manifest), ): - if document.pk != document_dict["pk"]: + # Both document_manifest and documents_stream come from the same + # Document.global_objects.order_by("id") query, taken while + # MEDIA_LOCK is held, so this should be unreachable -- it guards + # against silent data corruption if that invariant ever breaks. + if document.pk != document_dict["pk"]: # pragma: no cover raise CommandError( "Document export ordering mismatch: expected " f"pk={document_dict['pk']}, got pk={document.pk}. " diff --git a/src/documents/tests/export/test_sinks.py b/src/documents/tests/export/test_sinks.py index 2425c816a..fe421b234 100644 --- a/src/documents/tests/export/test_sinks.py +++ b/src/documents/tests/export/test_sinks.py @@ -5,6 +5,7 @@ import zipfile from pathlib import Path import pytest +from pytest_django.fixtures import SettingsWrapper from documents.export.sinks import DirectoryExportSink from documents.export.sinks import ExportSink @@ -236,6 +237,43 @@ class TestZipExportSink: assert not (target / "export.zip").exists() assert not (target / "export.zip.tmp").exists() + def test_exception_inside_stream_cleans_up_manifest_tmp( + self, + tmp_path: Path, + source_file: Path, + settings: SettingsWrapper, + ) -> None: + scratch_dir = tmp_path / "scratch" + settings.SCRATCH_DIR = scratch_dir + target: Path = tmp_path / "out" + target.mkdir() + with pytest.raises(RuntimeError): + with ZipExportSink(target, "export", delete=False) as sink: + sink.add_file(source_file, "doc.pdf") + with sink.stream("manifest.json") as handle: + handle.write("[") + raise RuntimeError("boom") + assert list(scratch_dir.glob("export-manifest-*")) == [] + assert not (target / "export.zip").exists() + assert not (target / "export.zip.tmp").exists() + + def test_abort_after_manifest_written_cleans_up_pending_tmp( + self, + tmp_path: Path, + settings: SettingsWrapper, + ) -> None: + scratch_dir = tmp_path / "scratch" + settings.SCRATCH_DIR = scratch_dir + target: Path = tmp_path / "out" + target.mkdir() + with pytest.raises(RuntimeError): + with ZipExportSink(target, "export", delete=False) as sink: + with sink.stream("manifest.json") as handle: + handle.write("[]") + raise RuntimeError("boom") + assert list(scratch_dir.glob("export-manifest-*")) == [] + assert not (target / "export.zip").exists() + def test_delete_wipes_destination_on_success( self, tmp_path: Path,