Files
paperless-ngx/src/documents/tests/search/test_barcode_search.py
T
jurassicparkicecreamandshamoon 62cc31fbdb Feature: store barcode contents, list and search them (#14276)
* Feature: store barcode contents, list and search them

New setting PAPERLESS_CONSUMER_STORE_BARCODE_VALUES (off by default)
stores all barcodes found during consumption with the document: page,
type and content. They are listed on the metadata tab with a copy
button, returned by the documents API and searchable with barcodes:
in the advanced search. Versions keep their own barcodes, reprocessing
reads them again. Refs #9898

* Tests: cover the remaining barcode branches

Covers unchanged and failing barcode reads on reprocessing, unsupported files and DocumentBarcode.__str__, and uses toHaveLength in the barcode list spec as suggested by SonarCloud.

* Address review: keep barcodes when they can't be read, format choices

- Reprocessing and new versions keep the stored barcodes when the file
  can't be scanned or the scan fails, and replace them atomically.
- Format is a TextChoices of the zxing-cpp formats, with a test.
- Shared scan code, latest_version helper, TypedDict, serializer reuse.
- Barcodes in the split manifest, export/import tests, pytest-style tests.

* Barcode tests: TIFF reprocess, format check both ways, fixtures

- Reprocessing a TIFF with TIFF support off keeps the stored barcodes.
- The format test also fails when zxing-cpp drops a format.
- Fixtures in place of the sample dir mixin, plugin-level disabled test.
- Format labels aren't translated, OpenAPI enum named BarcodeFormatEnum.

* Review: module-level zxing reader, shorter barcode docs

- read_barcodes_zxing is a module-level function used by scan_pdf.
- Drop the trivial __str__ test, mark it no cover.
- Shorten the barcode docs and remove the duplicate in configuration.md.

* Fix header

* Use utility class

* Return barcodes from the metadata endpoint only

Drop the barcodes field and its prefetches from the document serializer,
as agreed in the review. Also remove the now empty component stylesheet.

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-10-05 16:25:19 +00:00

96 lines
3.0 KiB
Python

"""Stored barcode contents in the search index.
Barcodes are a JSON field like notes and custom fields: barcodes: resolves to
barcodes.value:, and a plain query without the prefix does not look at them.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from documents.models import DocumentBarcode
from paperless_testing.factories import DocumentBarcodeFactory
from paperless_testing.factories import DocumentFactory
if TYPE_CHECKING:
from collections.abc import Callable
from documents.models import Document
from documents.search._backend import TantivyBackend
pytestmark = [pytest.mark.search, pytest.mark.django_db]
class TestBarcodeSearch:
@pytest.fixture
def with_barcodes(self, backend: TantivyBackend) -> Document:
document = DocumentFactory(title="Letter", content="x")
DocumentBarcodeFactory(
document=document,
value="WIFI:T:WPA;S:Guest-WLAN;P:crocodile123;;",
)
DocumentBarcodeFactory(
document=document,
page=2,
value="DE89370400440532013000",
format=DocumentBarcode.Format.CODE128,
)
backend.add_or_update(document)
return document
def test_bare_barcodes_prefix_searches_values(
self,
with_barcodes: Document,
index_document: Callable[..., Document],
matched_ids: Callable[[str], set[int]],
) -> None:
"""
GIVEN:
- A document with stored barcodes, and a decoy document whose
content (not a barcode) contains the same word
WHEN:
- A bare "barcodes:" prefix query is run
THEN:
- Only the document whose barcode matches is returned, also for a
part of a barcode between separators
"""
index_document(title="Decoy", content="crocodile123 in the text")
assert matched_ids("barcodes:crocodile123") == {with_barcodes.pk}
assert matched_ids("barcodes.value:DE89370400440532013000") == {
with_barcodes.pk,
}
def test_barcodes_format_subpath(
self,
with_barcodes: Document,
matched_ids: Callable[[str], set[int]],
) -> None:
"""
GIVEN:
- A document with a QR code and a Code 128 barcode
WHEN:
- The format subpath is queried
THEN:
- The document is found by its barcode formats
"""
assert matched_ids("barcodes.format:qrcode") == {with_barcodes.pk}
assert matched_ids("barcodes.format:aztec") == set()
def test_plain_query_ignores_barcodes(
self,
with_barcodes: Document,
matched_ids: Callable[[str], set[int]],
) -> None:
"""
GIVEN:
- A document with a barcode value not found in its text
WHEN:
- The value is searched without a field prefix
THEN:
- Nothing is found, as with notes and custom fields
"""
assert matched_ids("crocodile123") == set()