mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-03 06:40:31 +00:00
The unittest side of the suite built its temp directory tree with tempfile.mkdtemp and a manually enabled override_settings, cleaned up only if tearDown ran. That is now gone. DirectoriesMixin lives alongside the layout it bridges and does nothing but hand the paperless_dirs fixture to TestCase subclasses as self.dirs, so both halves of the suite get the same twelve settings, the same directory shapes and cleanup owned by tmp_path. The mixin moves to paperless_testing.dirs rather than staying in the documents test utilities, because modules in paperless and paperless_mail import it across the app boundary. The thirty-eight consuming modules change only their import line; self.dirs.scratch_dir and its siblings keep working.
137 lines
4.9 KiB
Python
137 lines
4.9 KiB
Python
from datetime import timedelta
|
|
from types import SimpleNamespace
|
|
from typing import TYPE_CHECKING
|
|
from typing import cast
|
|
from unittest import mock
|
|
|
|
from django.db import connection
|
|
from django.test import TestCase
|
|
from django.test.utils import CaptureQueriesContext
|
|
from django.utils import timezone
|
|
|
|
from documents.conditionals import metadata_etag
|
|
from documents.conditionals import preview_etag
|
|
from documents.conditionals import thumbnail_etag
|
|
from documents.conditionals import thumbnail_last_modified
|
|
from documents.models import Document
|
|
from documents.versioning import resolve_effective_document_by_pk
|
|
from paperless_testing.dirs import DirectoriesMixin
|
|
|
|
if TYPE_CHECKING:
|
|
from rest_framework.request import Request
|
|
|
|
|
|
class TestConditionals(DirectoriesMixin, TestCase):
|
|
def test_metadata_etag_uses_latest_version_for_root_request(self) -> None:
|
|
root = Document.objects.create(
|
|
title="root",
|
|
checksum="root-checksum",
|
|
archive_checksum="root-archive",
|
|
mime_type="application/pdf",
|
|
)
|
|
latest = Document.objects.create(
|
|
title="v1",
|
|
checksum="version-checksum",
|
|
archive_checksum="version-archive",
|
|
mime_type="application/pdf",
|
|
root_document=root,
|
|
)
|
|
request = cast("Request", SimpleNamespace(query_params={}))
|
|
|
|
self.assertEqual(
|
|
metadata_etag(request, root.id),
|
|
f"{latest.checksum}:{latest.modified.isoformat()}",
|
|
)
|
|
# preview_etag/thumbnail_etag resolve the same (pk, request) and should
|
|
# reuse metadata_etag's cached resolution instead of re-querying.
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
self.assertEqual(preview_etag(request, root.id), latest.archive_checksum)
|
|
self.assertEqual(thumbnail_etag(request, root.id), latest.checksum)
|
|
self.assertEqual(len(ctx.captured_queries), 0)
|
|
|
|
def test_metadata_etag_changes_when_document_modified_changes(self) -> None:
|
|
doc = Document.objects.create(
|
|
title="doc",
|
|
checksum="same-checksum",
|
|
mime_type="application/pdf",
|
|
)
|
|
# Each call simulates a separate incoming HTTP request, so it gets its
|
|
# own request object -- the per-request resolution cache must not leak
|
|
# a stale result across genuinely different requests.
|
|
original_etag = metadata_etag(
|
|
cast("Request", SimpleNamespace(query_params={})),
|
|
doc.id,
|
|
)
|
|
new_modified = timezone.now() + timedelta(seconds=5)
|
|
Document.objects.filter(id=doc.id).update(modified=new_modified)
|
|
|
|
second_request = cast("Request", SimpleNamespace(query_params={}))
|
|
self.assertNotEqual(metadata_etag(second_request, doc.id), original_etag)
|
|
self.assertEqual(
|
|
metadata_etag(second_request, doc.id),
|
|
f"{doc.checksum}:{new_modified.isoformat()}",
|
|
)
|
|
|
|
def test_resolve_effective_doc_returns_none_for_invalid_or_unrelated_version(
|
|
self,
|
|
) -> None:
|
|
root = Document.objects.create(
|
|
title="root",
|
|
checksum="root",
|
|
mime_type="application/pdf",
|
|
)
|
|
other_root = Document.objects.create(
|
|
title="other",
|
|
checksum="other",
|
|
mime_type="application/pdf",
|
|
)
|
|
other_version = Document.objects.create(
|
|
title="other-v1",
|
|
checksum="other-v1",
|
|
mime_type="application/pdf",
|
|
root_document=other_root,
|
|
)
|
|
|
|
invalid_request = cast(
|
|
"Request",
|
|
SimpleNamespace(query_params={"version": "not-a-number"}),
|
|
)
|
|
unrelated_request = cast(
|
|
"Request",
|
|
SimpleNamespace(query_params={"version": str(other_version.id)}),
|
|
)
|
|
|
|
self.assertIsNone(
|
|
resolve_effective_document_by_pk(root.id, invalid_request).document,
|
|
)
|
|
self.assertIsNone(
|
|
resolve_effective_document_by_pk(root.id, unrelated_request).document,
|
|
)
|
|
|
|
def test_thumbnail_last_modified_uses_effective_document_for_cache_key(
|
|
self,
|
|
) -> None:
|
|
root = Document.objects.create(
|
|
title="root",
|
|
checksum="root",
|
|
mime_type="application/pdf",
|
|
)
|
|
latest = Document.objects.create(
|
|
title="v2",
|
|
checksum="v2",
|
|
mime_type="application/pdf",
|
|
root_document=root,
|
|
)
|
|
latest.thumbnail_path.parent.mkdir(parents=True, exist_ok=True)
|
|
latest.thumbnail_path.write_bytes(b"thumb")
|
|
|
|
request = cast("Request", SimpleNamespace(query_params={}))
|
|
with mock.patch(
|
|
"documents.conditionals.get_thumbnail_modified_key",
|
|
return_value="thumb-modified-key",
|
|
) as get_thumb_key:
|
|
result = thumbnail_last_modified(request, root.id)
|
|
|
|
self.assertIsNotNone(result)
|
|
get_thumb_key.assert_called_once_with(latest.id)
|