From 90d23bad9cbde7e338811639f283e0100ef126b6 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Tue, 22 Sep 2026 08:02:17 -0700 Subject: [PATCH] Chore: Give the search index directory one owner in the tests (#14217) Two fixtures created a temporary index directory and pointed INDEX_DIR at it, and paperless_dirs did the same, so a test that requested more than one got whichever assignment ran last. The search conftest no longer defines its own index_dir fixture, the tests that took it read paperless_dirs.index_dir instead, and _search_index is now a thin wrapper that requests paperless_dirs. The fixture that yields a Document is renamed from indexed_document to searchable_document so it no longer differs by one character from the index_document factory next to it. --- src/documents/tests/conftest.py | 25 +++-------- src/documents/tests/search/conftest.py | 9 ---- src/documents/tests/search/test_backend.py | 6 ++- src/documents/tests/search/test_schema.py | 41 ++++++++++--------- .../tests/search/test_schema_fingerprint.py | 32 ++++++++------- src/documents/tests/test_api_search_errors.py | 20 ++++----- .../tests/test_api_search_query_length.py | 16 ++++---- ...test_api_search_unterminated_date_range.py | 4 +- 8 files changed, 69 insertions(+), 84 deletions(-) diff --git a/src/documents/tests/conftest.py b/src/documents/tests/conftest.py index 860ac12ca..567342dcc 100644 --- a/src/documents/tests/conftest.py +++ b/src/documents/tests/conftest.py @@ -1,11 +1,9 @@ import shutil -from collections.abc import Generator from pathlib import Path from typing import TYPE_CHECKING import filelock import pytest -from pytest_django.fixtures import Settings from paperless_testing.factories import DocumentFactory @@ -52,28 +50,17 @@ def sample_doc( ) -@pytest.fixture() -def _search_index( - tmp_path: Path, - settings: Settings, -) -> Generator[None, None, None]: - """Create a temp index directory and point INDEX_DIR at it. +@pytest.fixture +def _search_index(paperless_dirs: "PaperlessDirs") -> None: + """Point the search backend at a fresh, empty index directory. - Resets the backend singleton before and after so each test gets a clean - index rather than reusing a stale singleton from another test. + paperless_dirs owns INDEX_DIR and resets the backend singleton on both + sides of the test, so requesting it is all that is needed. """ - from documents.search import reset_backend - - index_dir = tmp_path / "index" - index_dir.mkdir() - settings.INDEX_DIR = index_dir - reset_backend() - yield - reset_backend() @pytest.fixture -def indexed_document(_search_index: None) -> "Document": +def searchable_document(_search_index: None) -> "Document": """One searchable document, for tests about what the search endpoint returns rather than about what it finds. """ diff --git a/src/documents/tests/search/conftest.py b/src/documents/tests/search/conftest.py index abeb99d76..11dcc0575 100644 --- a/src/documents/tests/search/conftest.py +++ b/src/documents/tests/search/conftest.py @@ -14,7 +14,6 @@ from paperless_testing.factories import DocumentFactory if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Generator - from pathlib import Path from pytest_django.fixtures import Settings @@ -22,14 +21,6 @@ if TYPE_CHECKING: from paperless_testing.dirs import PaperlessDirs -@pytest.fixture -def index_dir(tmp_path: Path, settings: Settings) -> Path: - path = tmp_path / "index" - path.mkdir() - settings.INDEX_DIR = path - return path - - @pytest.fixture def backend(paperless_dirs: PaperlessDirs) -> Generator[TantivyBackend, None, None]: b = TantivyBackend(path=paperless_dirs.index_dir) diff --git a/src/documents/tests/search/test_backend.py b/src/documents/tests/search/test_backend.py index af12821f8..d215e3ca7 100644 --- a/src/documents/tests/search/test_backend.py +++ b/src/documents/tests/search/test_backend.py @@ -947,7 +947,8 @@ class TestSingleton: yield reset_backend() - def test_returns_same_instance_on_repeated_calls(self, index_dir) -> None: + @pytest.mark.usefixtures("paperless_dirs") + def test_returns_same_instance_on_repeated_calls(self) -> None: """Singleton pattern: repeated calls to get_backend() must return the same instance.""" assert get_backend() is get_backend() @@ -964,7 +965,8 @@ class TestSingleton: assert b1 is not b2 assert b2._path == tmp_path / "b" - def test_reset_forces_new_instance(self, index_dir) -> None: + @pytest.mark.usefixtures("paperless_dirs") + def test_reset_forces_new_instance(self) -> None: """reset_backend() must force creation of a new backend instance on next get_backend() call.""" b1 = get_backend() reset_backend() diff --git a/src/documents/tests/search/test_schema.py b/src/documents/tests/search/test_schema.py index 1d45d4445..81c081e83 100644 --- a/src/documents/tests/search/test_schema.py +++ b/src/documents/tests/search/test_schema.py @@ -13,11 +13,11 @@ from documents.search._schema import needs_rebuild from documents.search._schema import schema_fingerprint if TYPE_CHECKING: - from pathlib import Path - import tantivy from pytest_django.fixtures import Settings + from paperless_testing.dirs import PaperlessDirs + pytestmark = pytest.mark.search @@ -25,16 +25,19 @@ pytestmark = pytest.mark.search class TestNeedsRebuild: """needs_rebuild covers all sentinel-file states that require a full reindex.""" - def test_returns_true_when_settings_file_missing(self, index_dir: Path) -> None: - assert needs_rebuild(index_dir) is True + def test_returns_true_when_settings_file_missing( + self, + paperless_dirs: PaperlessDirs, + ) -> None: + assert needs_rebuild(paperless_dirs.index_dir) is True def test_returns_false_when_version_and_language_match( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: Settings, ) -> None: settings.SEARCH_LANGUAGE = "en" - (index_dir / ".index_settings.json").write_text( + (paperless_dirs.index_dir / ".index_settings.json").write_text( json.dumps( { "schema_version": SCHEMA_VERSION, @@ -43,51 +46,51 @@ class TestNeedsRebuild: }, ), ) - assert needs_rebuild(index_dir) is False + assert needs_rebuild(paperless_dirs.index_dir) is False def test_returns_true_on_schema_version_mismatch( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: Settings, ) -> None: settings.SEARCH_LANGUAGE = None - (index_dir / ".index_settings.json").write_text( + (paperless_dirs.index_dir / ".index_settings.json").write_text( json.dumps({"schema_version": SCHEMA_VERSION - 1, "language": None}), ) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def test_returns_true_when_version_is_not_an_integer( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: Settings, ) -> None: settings.SEARCH_LANGUAGE = None - (index_dir / ".index_settings.json").write_text( + (paperless_dirs.index_dir / ".index_settings.json").write_text( json.dumps({"schema_version": "not-a-number", "language": None}), ) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def test_returns_true_when_language_key_missing( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: Settings, ) -> None: settings.SEARCH_LANGUAGE = "en" - (index_dir / ".index_settings.json").write_text( + (paperless_dirs.index_dir / ".index_settings.json").write_text( json.dumps({"schema_version": SCHEMA_VERSION}), ) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def test_returns_true_when_language_differs( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: Settings, ) -> None: settings.SEARCH_LANGUAGE = "de" - (index_dir / ".index_settings.json").write_text( + (paperless_dirs.index_dir / ".index_settings.json").write_text( json.dumps({"schema_version": SCHEMA_VERSION, "language": "en"}), ) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def _schema_fields(schema: tantivy.Schema) -> dict[str, dict]: diff --git a/src/documents/tests/search/test_schema_fingerprint.py b/src/documents/tests/search/test_schema_fingerprint.py index 170448a96..21d3e765f 100644 --- a/src/documents/tests/search/test_schema_fingerprint.py +++ b/src/documents/tests/search/test_schema_fingerprint.py @@ -35,6 +35,8 @@ if TYPE_CHECKING: from pytest_django.fixtures import SettingsWrapper + from paperless_testing.dirs import PaperlessDirs + pytestmark = pytest.mark.search # The on-disk field layout of a v2 index, pinned as data. Any edit here is an @@ -469,7 +471,7 @@ def _fingerprint_of(descriptors: list[FieldDescriptor]) -> str: class TestNeedsRebuildOnFingerprint: def test_matching_fingerprint_does_not_rebuild( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: SettingsWrapper, ) -> None: """ @@ -482,13 +484,13 @@ class TestNeedsRebuildOnFingerprint: - It returns False """ settings.SEARCH_LANGUAGE = None - _sentinels(index_dir) + _sentinels(paperless_dirs.index_dir) - assert needs_rebuild(index_dir) is False + assert needs_rebuild(paperless_dirs.index_dir) is False def test_stale_fingerprint_rebuilds_despite_a_matching_version( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: SettingsWrapper, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -505,7 +507,7 @@ class TestNeedsRebuildOnFingerprint: every subsequent write would raise """ settings.SEARCH_LANGUAGE = None - _sentinels(index_dir) + _sentinels(paperless_dirs.index_dir) extended = [ *field_descriptors(), FieldDescriptor( @@ -519,11 +521,11 @@ class TestNeedsRebuildOnFingerprint: ] monkeypatch.setattr(_schema, "field_descriptors", lambda: extended) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def test_reordered_schema_rebuilds( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: SettingsWrapper, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -538,16 +540,16 @@ class TestNeedsRebuildOnFingerprint: - It returns True """ settings.SEARCH_LANGUAGE = None - _sentinels(index_dir) + _sentinels(paperless_dirs.index_dir) reordered = field_descriptors() reordered[1], reordered[2] = reordered[2], reordered[1] monkeypatch.setattr(_schema, "field_descriptors", lambda: reordered) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def test_missing_fingerprint_rebuilds( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: SettingsWrapper, ) -> None: """ @@ -561,15 +563,15 @@ class TestNeedsRebuildOnFingerprint: is rebuilt rather than trusted """ settings.SEARCH_LANGUAGE = None - (index_dir / ".index_settings.json").write_text( + (paperless_dirs.index_dir / ".index_settings.json").write_text( json.dumps({"schema_version": SCHEMA_VERSION, "language": None}), ) - assert needs_rebuild(index_dir) is True + assert needs_rebuild(paperless_dirs.index_dir) is True def test_written_sentinels_satisfy_the_check( self, - index_dir: Path, + paperless_dirs: PaperlessDirs, settings: SettingsWrapper, ) -> None: """ @@ -582,6 +584,6 @@ class TestNeedsRebuildOnFingerprint: - It returns False """ settings.SEARCH_LANGUAGE = "en" - _write_sentinels(index_dir) + _write_sentinels(paperless_dirs.index_dir) - assert needs_rebuild(index_dir) is False + assert needs_rebuild(paperless_dirs.index_dir) is False diff --git a/src/documents/tests/test_api_search_errors.py b/src/documents/tests/test_api_search_errors.py index c8a2849b9..ea33eedae 100644 --- a/src/documents/tests/test_api_search_errors.py +++ b/src/documents/tests/test_api_search_errors.py @@ -33,7 +33,7 @@ class TestSearchQueryErrorStillBecomesA400: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -68,7 +68,7 @@ class TestLibraryDefectsPropagate: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -98,7 +98,7 @@ class TestLibraryDefectsPropagate: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -141,7 +141,7 @@ class TestSelectionPathsAgreeWithSearch: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -181,7 +181,7 @@ class TestSelectionPathsAgreeWithSearch: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -221,7 +221,7 @@ class TestSelectionPathsAgreeWithSearch: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -259,7 +259,7 @@ class TestSelectionPathsAgreeWithSearch: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -287,7 +287,7 @@ class TestSelectionPathsAgreeWithSearch: { "documents": [], "all": True, - "filters": {"more_like_id": indexed_document.pk}, + "filters": {"more_like_id": searchable_document.pk}, }, format="json", ) @@ -298,7 +298,7 @@ class TestSelectionPathsAgreeWithSearch: self, admin_client: APIClient, monkeypatch: pytest.MonkeyPatch, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -328,7 +328,7 @@ class TestSelectionPathsAgreeWithSearch: { "documents": [], "all": True, - "filters": {"more_like_id": indexed_document.pk}, + "filters": {"more_like_id": searchable_document.pk}, }, format="json", ) diff --git a/src/documents/tests/test_api_search_query_length.py b/src/documents/tests/test_api_search_query_length.py index 2d52bc9ff..77dca7bdb 100644 --- a/src/documents/tests/test_api_search_query_length.py +++ b/src/documents/tests/test_api_search_query_length.py @@ -36,7 +36,7 @@ class TestGetSearchEndpointEnforcesTheCap: def test_query_one_over_the_cap_is_a_400( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -66,7 +66,7 @@ class TestGetSearchEndpointEnforcesTheCap: def test_query_at_exactly_the_cap_is_accepted( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -86,7 +86,7 @@ class TestGetSearchEndpointEnforcesTheCap: def test_an_ordinary_query_is_unaffected( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -112,7 +112,7 @@ class TestPostSelectionPathsEnforceTheCap: def test_bulk_edit_query_one_over_the_cap_is_a_400( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -154,7 +154,7 @@ class TestPostSelectionPathsEnforceTheCap: self, bulk_update_task_mock: mock.MagicMock, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -187,7 +187,7 @@ class TestPostSelectionPathsEnforceTheCap: def test_bulk_download_query_one_over_the_cap_is_a_400( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -236,7 +236,7 @@ class TestGlobalSearchEnforcesTheCapToo: def test_query_one_over_the_cap_is_a_400( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: @@ -260,7 +260,7 @@ class TestGlobalSearchEnforcesTheCapToo: def test_query_at_exactly_the_cap_is_accepted( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: diff --git a/src/documents/tests/test_api_search_unterminated_date_range.py b/src/documents/tests/test_api_search_unterminated_date_range.py index f8c26d743..18dca04ba 100644 --- a/src/documents/tests/test_api_search_unterminated_date_range.py +++ b/src/documents/tests/test_api_search_unterminated_date_range.py @@ -38,7 +38,7 @@ class TestUnterminatedBracketReturnsA400: def test_unterminated_bracket_is_a_400( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, query: str, ) -> None: """ @@ -59,7 +59,7 @@ class TestUnterminatedBracketReturnsA400: def test_properly_closed_bracket_still_searches_cleanly( self, admin_client: APIClient, - indexed_document: Document, + searchable_document: Document, ) -> None: """ GIVEN: