From c40849e9bb212363b5d4dee2a49aea3c27f016d2 Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Mon, 12 Oct 2026 10:40:58 -0700 Subject: [PATCH] Chore: make the backend test suite pass on MariaDB Make the reference predicates for the string custom field filters follow the column collation, which is case insensitive on MariaDB, and quote identifiers in the effective content query matchers with the backend's own quoting. --- .../tests/test_api_filter_by_custom_fields.py | 18 +++++++++++++++--- ...cument_list_effective_content_annotation.py | 11 +++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/documents/tests/test_api_filter_by_custom_fields.py b/src/documents/tests/test_api_filter_by_custom_fields.py index b9637aa3e..67641fe3f 100644 --- a/src/documents/tests/test_api_filter_by_custom_fields.py +++ b/src/documents/tests/test_api_filter_by_custom_fields.py @@ -6,6 +6,7 @@ from unittest.mock import patch from urllib.parse import quote import time_machine +from django.db import connection from django.test import override_settings from django.utils import timezone from rest_framework.test import APITestCase @@ -20,6 +21,13 @@ from paperless_testing.dirs import DirectoriesMixin from paperless_testing.factories import UserFactory +def _exact_equal(value: str | None, expected: str) -> bool: + """`exact` follows the column collation, which is case insensitive on MariaDB.""" + if connection.vendor == "mysql" and value is not None: + return value.casefold() == expected.casefold() + return value == expected + + class DocumentWrapper: """ Allows Pythonic access to the custom fields associated with the wrapped document. @@ -287,7 +295,8 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase): self._assert_query_match_predicate( ["string_field", "exact", "paperless"], lambda document: ( - "string_field" in document and document["string_field"] == "paperless" + "string_field" in document + and _exact_equal(document["string_field"], "paperless") ), ) @@ -296,7 +305,10 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase): ["string_field", "in", ["paperless", "Paperless"]], lambda document: ( "string_field" in document - and document["string_field"] in ("paperless", "Paperless") + and any( + _exact_equal(document["string_field"], expected) + for expected in ("paperless", "Paperless") + ) ), ) @@ -761,7 +773,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase): lambda document: ( not ( "string_field" in document - and document["string_field"] == "paperless" + and _exact_equal(document["string_field"], "paperless") ) ), ) diff --git a/src/documents/tests/test_document_list_effective_content_annotation.py b/src/documents/tests/test_document_list_effective_content_annotation.py index 9a1b0978d..10d73dc29 100644 --- a/src/documents/tests/test_document_list_effective_content_annotation.py +++ b/src/documents/tests/test_document_list_effective_content_annotation.py @@ -242,9 +242,7 @@ class TestDocumentListEffectiveContentAnnotation: # Only the list query itself reads a content column: no extra query # for the skipped prefetch, none for a per-instance fallback content_queries = [ - query - for query in ctx.captured_queries - if '"documents_document"."content"' in query["sql"] + query for query in ctx.captured_queries if _content_column() in query["sql"] ] assert len(content_queries) == 1 @@ -365,6 +363,11 @@ def _get_document_queries( return [q for q in ctx.captured_queries if '"django_content_type"' not in q["sql"]] +def _content_column() -> str: + quote = connection.ops.quote_name + return f"{quote('documents_document')}.{quote('content')}" + + def _get_effective_content_fallback_queries( ctx: CaptureQueriesContext, ) -> list[dict[str, str]]: @@ -379,7 +382,7 @@ def _get_effective_content_fallback_queries( return [ q for q in ctx.captured_queries - if q["sql"].startswith('SELECT "documents_document"."content" FROM') + if q["sql"].startswith(f"SELECT {_content_column()} FROM") ]