mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-11 02:27:13 +00:00
155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
"""Negation must survive the blended query.
|
|
|
|
parse_user_query ORs an exact clause with an optional fuzzy clause. That
|
|
clause is built from positive terms only, so unless the query's exclusions
|
|
are applied to the blend as a whole, a document the exact clause excluded
|
|
is re-admitted by it. CJK terms are widened inside the exact clause itself,
|
|
so the query's own structure constrains them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
from documents.models import Document
|
|
|
|
|
|
pytestmark = [pytest.mark.search, pytest.mark.django_db]
|
|
|
|
|
|
class TestNegationConstrainsEveryClause:
|
|
@pytest.mark.usefixtures("fuzzy_enabled")
|
|
def test_fuzzy_clause_does_not_readmit_an_excluded_document(
|
|
self,
|
|
index_document: Callable[..., Document],
|
|
matched_ids: Callable[[str], set[int]],
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Two documents both matching a positive term, one of which
|
|
also contains a word the query excludes, with the fuzzy
|
|
blend clause enabled
|
|
WHEN:
|
|
- A query combining the positive term with a NOT exclusion is
|
|
run
|
|
THEN:
|
|
- Only the document without the excluded word is returned;
|
|
the fuzzy clause (built from positive terms only) does not
|
|
readmit the document the exact clause excluded
|
|
"""
|
|
secret = index_document(
|
|
title="Invoice A",
|
|
content="invoice total secret",
|
|
)
|
|
public = index_document(
|
|
title="Invoice B",
|
|
content="invoice total public",
|
|
)
|
|
|
|
assert matched_ids("invoice") == {secret.pk, public.pk}
|
|
assert matched_ids("invoice NOT secret") == {public.pk}
|
|
|
|
def test_an_excluded_document_stays_out_of_a_cjk_match(
|
|
self,
|
|
index_document: Callable[..., Document],
|
|
matched_ids: Callable[[str], set[int]],
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Two documents both containing a CJK run, one of which also
|
|
contains a word the query excludes
|
|
WHEN:
|
|
- A query combining the CJK term with a NOT exclusion is run
|
|
THEN:
|
|
- Only the document without the excluded word is returned;
|
|
the CJK term's bigram match sits beside the NOT inside the
|
|
same query, so the exclusion applies to it
|
|
"""
|
|
secret = index_document(
|
|
title="Tokyo A",
|
|
content="東京都の秘密です secret",
|
|
)
|
|
public = index_document(
|
|
title="Tokyo B",
|
|
content="東京都の報告書です public",
|
|
)
|
|
|
|
assert matched_ids("東京") == {secret.pk, public.pk}
|
|
assert matched_ids("東京 NOT secret") == {public.pk}
|
|
|
|
@pytest.mark.usefixtures("fuzzy_enabled")
|
|
def test_disjunctive_negation_still_admits_the_other_branch(
|
|
self,
|
|
index_document: Callable[..., Document],
|
|
matched_ids: Callable[[str], set[int]],
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A document matching a positive term and also containing a
|
|
word a disjunctive NOT branch excludes, plus an unrelated
|
|
document
|
|
WHEN:
|
|
- A query of the shape "term OR NOT excluded_word" is run
|
|
THEN:
|
|
- Both documents are returned; "invoice OR NOT secret"
|
|
excludes nothing on its own, so a document matching the
|
|
left branch stays in even though it contains the excluded
|
|
word
|
|
"""
|
|
secret_invoice = index_document(
|
|
title="Invoice A",
|
|
content="invoice total secret",
|
|
)
|
|
unrelated = index_document(
|
|
title="Recipe",
|
|
content="flour and water",
|
|
)
|
|
|
|
assert matched_ids("invoice OR NOT secret") == {
|
|
secret_invoice.pk,
|
|
unrelated.pk,
|
|
}
|
|
|
|
def test_a_negation_under_or_constrains_its_own_cjk_term(
|
|
self,
|
|
index_document: Callable[..., Document],
|
|
matched_ids: Callable[[str], set[int]],
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Two CJK documents, one of which also contains a word an OR
|
|
branch's own NOT excludes, plus an unrelated latin document
|
|
WHEN:
|
|
- The exclusion is under a disjunctive OR branch, and in
|
|
conjunctive position
|
|
THEN:
|
|
- Either way the excluded document is left out. The CJK
|
|
term's bigram match is widened in place inside its own OR
|
|
branch, so that branch's NOT applies to it; the other
|
|
branch still admits the latin document
|
|
"""
|
|
secret = index_document(
|
|
title="Tokyo A",
|
|
content="東京都の秘密です secret",
|
|
)
|
|
public = index_document(
|
|
title="Tokyo B",
|
|
content="東京都の報告書です public",
|
|
)
|
|
bill = index_document(
|
|
title="Bill",
|
|
content="bill payment received",
|
|
)
|
|
|
|
assert matched_ids("(東京 AND NOT secret) OR bill") == {
|
|
bill.pk,
|
|
public.pk,
|
|
}
|
|
assert matched_ids("東京 AND NOT secret") == {public.pk}
|
|
assert secret.pk in matched_ids("東京")
|