From ee7a0003040518deeb39cce4f8236cd1f2b1899c Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:56:38 -0800 Subject: [PATCH] Fix: place consumed files before running the post-consume script The consumer's storage phase opens its own file move scope, which nests under the consume task's scope. Moves requested while storing the document therefore stayed pending until the task ended, and the post-consume script could be handed a path from before the move. The consumer now flushes the pending file moves once after the storage block, outside the media lock and the transaction, and before the post-consume script runs. --- src/documents/consumer.py | 5 +++++ src/documents/tests/test_consumer.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/documents/consumer.py b/src/documents/consumer.py index bb4261e3cd..9d7769858c 100644 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -27,6 +27,7 @@ from documents.file_handling import generate_filename from documents.file_handling import generate_unique_filename from documents.file_handling import validate_path_in_root from documents.file_moves import defer_file_moves +from documents.file_moves import flush_pending_file_moves from documents.loggers import LoggingMixin from documents.media_lock import media_lock from documents.models import Correspondent @@ -810,6 +811,10 @@ class ConsumerPlugin( exception=e, ) + # The storage scope above nests under the consume task's scope, so its + # moves are still pending; place the file before the script reads its path. + flush_pending_file_moves() + self.run_post_consume_script(document) self.log.info(f"Document {document} consumption finished") diff --git a/src/documents/tests/test_consumer.py b/src/documents/tests/test_consumer.py index 131b9edb64..4c1679d619 100644 --- a/src/documents/tests/test_consumer.py +++ b/src/documents/tests/test_consumer.py @@ -250,6 +250,38 @@ class TestConsumer( assert document is not None self.assertIsFile(document.source_path) + @mock.patch("documents.consumer.load_classifier") + def test_consume_places_file_before_post_consume_script(self, m) -> None: + """ + GIVEN: + - A new document to consume + WHEN: + - It is consumed + THEN: + - Pending file moves are flushed before the post-consume script runs + """ + m.return_value = MagicMock() + order = MagicMock() + + with ( + mock.patch( + "documents.consumer.flush_pending_file_moves", + order.flush, + ), + mock.patch.object( + ConsumerPlugin, + "run_post_consume_script", + order.script, + ), + ): + with self.get_consumer(self.get_test_file()) as consumer: + consumer.run() + + self.assertEqual( + [c[0] for c in order.mock_calls], + ["flush", "script"], + ) + @mock.patch("documents.consumer.load_classifier") def test_consume_version_takes_media_lock_outside_transaction(self, m) -> None: """