test(search): add result-level coverage for RFC3339 T/Z date-range queries

A prior commit deleted test_query.py's parametrized "doesn't raise" coverage
for this shape (created:[...T...Z TO ...] and comma-combined ranges), which
was also the only place PR #13010's T/Z backward-compat guarantee was
exercised. Nothing in paperless's suite proved the full parse_user_query() ->
tantivy Query -> matched-document pipeline still honors it after the
whoosh-compat grammar fix (commit f936143 in the whoosh-compat repo). Add
result-level acceptance cases: an in/out-of-range T/Z bracket range, PR
#13010's original comma-combined two-field shape, and an exact-boundary case
proving a Z-suffixed bound is absolute UTC, not shifted by the local search
timezone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RVj8NFy821G3YhNf68PF6X
This commit is contained in:
Trenton Holmes
2026-08-18 11:05:04 -07:00
co-authored by Claude Sonnet 5
parent 0bf06f89a7
commit 9f0e4c6d89
@@ -10,6 +10,7 @@ now-deleted test_date_grammar_parity.py.
from __future__ import annotations
from datetime import UTC
from datetime import date
from datetime import datetime
from typing import TYPE_CHECKING
@@ -247,6 +248,92 @@ class TestMultitokenInNestedOr:
assert matched == {doc_a.pk, doc_b.pk}
class TestRfc3339TZDateRange:
"""paperless-ngx#13010: created/added bracket ranges using RFC3339 T/Z
datetime separators (e.g. `[2026-01-01T00:00:00Z TO ...]`) must keep
working - this is v2/Whoosh saved-search backward compatibility, not a
generic ISO-format nicety. whoosh-compat's own grammar previously had no
support for `T`/`Z` at all (fixed upstream, whoosh-compat commit
f936143); this proves the fix holds end-to-end against real indexed
documents and real timezone-sensitive matching, not just that the
library's date_from() parses the text."""
def test_t_z_range_matches_only_documents_within_bounds(
self,
backend: TantivyBackend,
) -> None:
in_range = Document.objects.create(
title="In range",
content="x",
checksum="acc-rfc3339-in-range",
added=datetime(2026, 3, 15, 10, 0, tzinfo=UTC),
)
out_of_range = Document.objects.create(
title="Out of range",
content="x",
checksum="acc-rfc3339-out-of-range",
added=datetime(2026, 8, 1, 10, 0, tzinfo=UTC),
)
for doc in (in_range, out_of_range):
backend.add_or_update(doc)
matched = _matched_ids(
backend,
"added:[2026-01-01T00:00:00Z TO 2026-06-01T00:00:00Z]",
)
assert matched == {in_range.pk}
def test_comma_combined_t_z_ranges_across_two_fields(
self,
backend: TantivyBackend,
) -> None:
# The Whoosh v2 comma syntax: two field:[range] expressions joined
# by a comma must be ANDed together (not passed to Tantivy as a
# literal comma, which it cannot parse).
matching = Document.objects.create(
title="Matches both ranges",
content="x",
checksum="acc-rfc3339-comma-match",
created=date(2026, 3, 15),
added=datetime(2026, 5, 15, 10, 0, tzinfo=UTC),
)
wrong_added = Document.objects.create(
title="created in range, added out of range",
content="x",
checksum="acc-rfc3339-comma-wrong-added",
created=date(2026, 3, 15),
added=datetime(2026, 8, 1, 10, 0, tzinfo=UTC),
)
for doc in (matching, wrong_added):
backend.add_or_update(doc)
matched = _matched_ids(
backend,
"created:[2026-01-01T00:00:00Z TO 2026-06-01T00:00:00Z],"
"added:[2026-05-01T00:00:00Z TO 2026-06-01T00:00:00Z]",
)
assert matched == {matching.pk}
def test_z_suffixed_bound_is_absolute_utc_not_local_shifted(
self,
backend: TantivyBackend,
) -> None:
# A document timestamped exactly at a Z-suffixed range boundary must
# match under UTC - if Z were (incorrectly) reinterpreted as local
# wall-clock time and shifted again, this exact-boundary match would
# silently fail or succeed for the wrong reason.
at_boundary = Document.objects.create(
title="At Z boundary",
content="x",
checksum="acc-rfc3339-z-boundary",
added=datetime(2026, 1, 1, 0, 0, 0, tzinfo=UTC),
)
backend.add_or_update(at_boundary)
matched = _matched_ids(
backend,
"added:[2026-01-01T00:00:00Z TO 2026-01-01T00:00:01Z]",
)
assert matched == {at_boundary.pk}
class TestUnregisteredIdFieldFoldsToLiteralText:
"""tag_id, owner_id, etc. are intentionally excluded from the
FieldRegistry - whoosh-compat parity leniency folds them into a literal