test(search): hoist deferred imports, add _index() helper in test_acceptance.py

User/DocumentType/StoragePath were imported inside individual test bodies
despite the module already importing documents.models at top level -
nothing here needed deferred import. Also add an _index() helper
(Document.objects.create + backend.add_or_update in one call) for the many
sites where nothing needs to happen between creating a document and
indexing it; the two-step ceremony was outweighing the fixture data at
every call site. Left as two explicit steps wherever a Note or
CustomFieldInstance genuinely has to be attached before indexing.
This commit is contained in:
Trenton Holmes
2026-08-18 13:50:43 -07:00
parent a9c4d505b7
commit 80fd47656e
+50 -51
View File
@@ -14,11 +14,14 @@ from typing import TYPE_CHECKING
import pytest
import time_machine
from django.contrib.auth.models import User
from documents.models import CustomField
from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentType
from documents.models import Note
from documents.models import StoragePath
from documents.search._query import parse_user_query
if TYPE_CHECKING:
@@ -33,37 +36,48 @@ def _matched_ids(backend: TantivyBackend, query: str) -> set[int]:
return set(backend.search_ids(query, user=None))
def _index(backend: TantivyBackend, **kwargs: object) -> Document:
"""Create a Document and index it in one step, for the common case
where nothing needs to happen between the two (no related Note/
CustomFieldInstance to attach first)."""
doc = Document.objects.create(**kwargs)
backend.add_or_update(doc)
return doc
@pytest.fixture
def indexed_documents(backend: TantivyBackend) -> dict[str, int]:
"""Index a small fixture set, return {label: doc_id} for corpus queries."""
docs = {
"invoice_2020": Document.objects.create(
"invoice_2020": _index(
backend,
title="Invoice 2020",
content="invoice total due",
checksum="acc-invoice-2020",
archive_serial_number=100,
),
"invoice_2021": Document.objects.create(
"invoice_2021": _index(
backend,
title="Invoice 2021",
content="invoice total due",
checksum="acc-invoice-2021",
archive_serial_number=101,
),
"invoice_2023": Document.objects.create(
"invoice_2023": _index(
backend,
title="Invoice 2023",
content="invoice total due",
checksum="acc-invoice-2023",
archive_serial_number=102,
),
"receipt_2022": Document.objects.create(
"receipt_2022": _index(
backend,
title="Receipt 2022",
content="receipt total due",
checksum="acc-receipt-2022",
archive_serial_number=103,
),
}
for doc in docs.values():
backend.add_or_update(doc)
return {label: doc.pk for label, doc in docs.items()}
@@ -99,18 +113,18 @@ class TestFieldBoosts:
self,
backend: TantivyBackend,
) -> None:
title_match = Document.objects.create(
title_match = _index(
backend,
title="urgent",
content="nothing else relevant",
checksum="acc-boost-title",
)
content_match = Document.objects.create(
_index(
backend,
title="nothing",
content="urgent matter here",
checksum="acc-boost-content",
)
backend.add_or_update(title_match)
backend.add_or_update(content_match)
query = parse_user_query(backend._index, "urgent", UTC)
searcher = backend._index.searcher()
results = searcher.search(query, limit=10)
@@ -125,8 +139,6 @@ class TestJsonSubpaths:
self,
backend: TantivyBackend,
) -> None:
from django.contrib.auth.models import User
alice = User.objects.create_user(username="alice")
doc_with_note = Document.objects.create(
title="Has note",
@@ -134,13 +146,8 @@ class TestJsonSubpaths:
checksum="acc-note-with",
)
Note.objects.create(document=doc_with_note, user=alice, note="reminder")
doc_without = Document.objects.create(
title="No note",
content="x",
checksum="acc-note-without",
)
backend.add_or_update(doc_with_note)
backend.add_or_update(doc_without)
_index(backend, title="No note", content="x", checksum="acc-note-without")
matched = _matched_ids(backend, "notes.user:alice")
assert matched == {doc_with_note.pk}
@@ -166,6 +173,7 @@ class TestJsonSubpaths:
field=field,
value_text="policy",
)
backend.add_or_update(matching)
non_matching = Document.objects.create(
title="Non-matching",
content="x",
@@ -176,7 +184,6 @@ class TestJsonSubpaths:
field=other_field,
value_text="policy",
)
backend.add_or_update(matching)
backend.add_or_update(non_matching)
matched = _matched_ids(
backend,
@@ -215,13 +222,13 @@ class TestFuzzyBlendSurvivesWhooshGrammar:
) -> None:
settings.ADVANCED_FUZZY_SEARCH_THRESHOLD = 0.5
with time_machine.travel(FROZEN_NOW, tick=False):
doc = Document.objects.create(
doc = _index(
backend,
title="Receipt March",
content="receipt total due",
checksum="fuzzy-blend-1",
archive_serial_number=900,
)
backend.add_or_update(doc)
# Sanity: the exact spelling matches through the exact clause.
assert doc.pk in _matched_ids(backend, "added:today receipt")
# The regression: the misspelling (one transposition) only
@@ -249,13 +256,13 @@ class TestFuzzyBlendSurvivesWhooshGrammar:
# it even for a naive implementation.)
settings.ADVANCED_FUZZY_SEARCH_THRESHOLD = 0.5
with time_machine.travel(FROZEN_NOW, tick=False):
receipt_only = Document.objects.create(
_index(
backend,
title="Receipt Archive",
content="receipt archived stack",
checksum="fuzzy-blend-2",
archive_serial_number=901,
)
backend.add_or_update(receipt_only)
assert _matched_ids(backend, "added:today total NOT receipt") == set()
@@ -269,22 +276,22 @@ class TestUnquotedDateKeywordPhrases:
@pytest.fixture
def period_documents(self, backend: TantivyBackend) -> dict[str, int]:
with time_machine.travel(FROZEN_NOW, tick=False):
in_may = Document.objects.create(
in_may = _index(
backend,
title="May Doc",
content="statement",
checksum="kw-may",
archive_serial_number=910,
added=datetime(2026, 5, 20, 12, 0, tzinfo=UTC),
)
in_june = Document.objects.create(
in_june = _index(
backend,
title="June Doc",
content="statement",
checksum="kw-june",
archive_serial_number=911,
added=datetime(2026, 6, 10, 12, 0, tzinfo=UTC),
)
for doc in (in_may, in_june):
backend.add_or_update(doc)
return {"in_may": in_may.pk, "in_june": in_june.pk}
@pytest.mark.parametrize(
@@ -337,13 +344,13 @@ class TestUnquotedDateKeywordPhrases:
# text, not a date phrase: a title actually containing the words
# matches, and the date-window documents do not.
with time_machine.travel(FROZEN_NOW, tick=False):
wordy = Document.objects.create(
wordy = _index(
backend,
title="Notes from the previous month",
content="meeting notes",
checksum="kw-text",
archive_serial_number=912,
)
backend.add_or_update(wordy)
assert _matched_ids(backend, "title:previous month") == {wordy.pk}
@@ -357,8 +364,6 @@ class TestBareJsonFieldPrefixes:
self,
backend: TantivyBackend,
) -> None:
from django.contrib.auth.models import User
alice = User.objects.create_user(username="alice")
with_note = Document.objects.create(
title="Has note",
@@ -366,15 +371,15 @@ class TestBareJsonFieldPrefixes:
checksum="bare-notes-with",
)
Note.objects.create(document=with_note, user=alice, note="crocodile")
backend.add_or_update(with_note)
# This document's CONTENT contains the words a demoted text search
# would match; it must NOT match once the prefix addresses notes.
decoy = Document.objects.create(
_index(
backend,
title="Notes about things",
content="notes crocodile mention",
checksum="bare-notes-decoy",
)
backend.add_or_update(with_note)
backend.add_or_update(decoy)
assert _matched_ids(backend, "notes:crocodile") == {with_note.pk}
def test_bare_custom_fields_prefix_searches_values(
@@ -395,21 +400,19 @@ class TestBareJsonFieldPrefixes:
field=field,
value_text="crocodile",
)
decoy = Document.objects.create(
backend.add_or_update(with_value)
_index(
backend,
title="Custom things",
content="custom fields crocodile",
checksum="bare-cf-decoy",
)
backend.add_or_update(with_value)
backend.add_or_update(decoy)
assert _matched_ids(backend, "custom_fields:crocodile") == {with_value.pk}
def test_subpath_spellings_are_untouched(
self,
backend: TantivyBackend,
) -> None:
from django.contrib.auth.models import User
bob = User.objects.create_user(username="bob")
doc = Document.objects.create(
title="Bob note",
@@ -431,8 +434,6 @@ class TestFieldAliases:
self,
backend: TantivyBackend,
) -> None:
from documents.models import DocumentType
invoice_type = DocumentType.objects.create(name="invoice")
# Discriminating shape: document_type is itself a default search
# field, so if alias resolution ever broke and "type:invoice"
@@ -441,19 +442,19 @@ class TestFieldAliases:
# word in content, so a demoted search matches BOTH documents and
# the exact-set assertions fail. (The title avoids stemming to
# "type": english stems Typed -> type.)
typed = Document.objects.create(
typed = _index(
backend,
title="First",
content="quarterly statement",
checksum="alias-type-1",
document_type=invoice_type,
)
decoy = Document.objects.create(
_index(
backend,
title="Second",
content="invoice mentioned in body",
checksum="alias-type-2",
)
backend.add_or_update(typed)
backend.add_or_update(decoy)
assert _matched_ids(backend, "type:invoice") == {typed.pk}
assert _matched_ids(backend, "document_type:invoice") == {typed.pk}
@@ -461,10 +462,9 @@ class TestFieldAliases:
self,
backend: TantivyBackend,
) -> None:
from documents.models import StoragePath
archive = StoragePath.objects.create(name="archive", path="archive/{title}")
stored = Document.objects.create(
stored = _index(
backend,
title="Stored",
content="quarterly statement",
checksum="alias-path-1",
@@ -473,12 +473,11 @@ class TestFieldAliases:
# storage_path is NOT a default search field today, so a demoted
# "path:archive" already matches nothing; the content decoy keeps
# this test discriminating even if it ever joins the defaults.
loose = Document.objects.create(
_index(
backend,
title="Loose",
content="archive mentioned in body",
checksum="alias-path-2",
)
backend.add_or_update(stored)
backend.add_or_update(loose)
assert _matched_ids(backend, "path:archive") == {stored.pk}
assert _matched_ids(backend, "storage_path:archive") == {stored.pk}