Route SCHEMA_FIELD_MISSING into an error, not an HTTP 400

This commit is contained in:
stumpylog
2026-09-13 15:28:41 -07:00
committed by GitHub
parent baed2b4a9d
commit b9f2eea469
3 changed files with 48 additions and 26 deletions
@@ -82,7 +82,7 @@ class TestEmitErrorRouting:
_map_emit_error(error)
assert excinfo.value is error
def test_misconfigured_cause_is_logged_and_becomes_a_400(
def test_misconfigured_cause_is_logged_and_reraised(
self,
caplog: pytest.LogCaptureFixture,
) -> None:
@@ -92,15 +92,21 @@ class TestEmitErrorRouting:
WHEN:
- _map_emit_error processes it
THEN:
- It becomes a SearchQueryError, and exactly one ERROR log
record is emitted naming the field and the diagnostic kind
- Exactly one ERROR log record is emitted naming the field and
the diagnostic kind, and the original QueryError propagates
unchanged: a registry/schema disagreement is transient (the
exact same query succeeds once the index is rebuilt), so it
surfaces as a 500 an operator can see rather than a 400
telling the client their query is permanently invalid
"""
kind = DiagnosticKind.SCHEMA_FIELD_MISSING
with caplog.at_level(logging.ERROR, logger="paperless.search"):
error = _map_emit_error(
QueryError(_diagnostic(kind, field=FieldRef("asn"))),
)
assert isinstance(error, SearchQueryError)
error = QueryError(_diagnostic(kind, field=FieldRef("asn")))
with (
caplog.at_level(logging.ERROR, logger="paperless.search"),
pytest.raises(QueryError) as excinfo,
):
_map_emit_error(error)
assert excinfo.value is error
errors = [r for r in caplog.records if r.levelno == logging.ERROR]
assert len(errors) == 1
assert "asn" in errors[0].getMessage()
@@ -144,7 +150,6 @@ class TestEmitErrorRouting:
DiagnosticKind.TEXT_RANGE,
DiagnosticKind.PATTERN_TOO_COMPLEX,
DiagnosticKind.EXISTS_REQUIRES_FAST,
DiagnosticKind.SCHEMA_FIELD_MISSING,
],
)
def test_user_facing_message_never_echoes_library_prose(
@@ -154,7 +159,10 @@ class TestEmitErrorRouting:
"""
GIVEN:
- A QueryError carrying whoosh-compat's own developer-facing
message text
message text (SCHEMA_FIELD_MISSING excluded: it is now
re-raised rather than converted, so it never produces a
user-facing message at all, see
test_misconfigured_cause_is_logged_and_reraised)
WHEN:
- _map_emit_error processes it
THEN:
@@ -170,7 +178,6 @@ class TestEmitErrorRouting:
DiagnosticKind.TEXT_RANGE,
DiagnosticKind.PATTERN_TOO_COMPLEX,
DiagnosticKind.EXISTS_REQUIRES_FAST,
DiagnosticKind.SCHEMA_FIELD_MISSING,
],
)
def test_user_facing_message_names_the_field(
@@ -9,7 +9,10 @@ action can clear the condition. Any authenticated user could otherwise emit
ERROR lines in a loop by repeating ``notes:*``.
SCHEMA_FIELD_MISSING, the other MISCONFIGURED kind, does compare the registry
against the live schema, so it stays an ERROR.
against the live schema, so it stays an ERROR log. But it is not a 400
either: the exact same query would succeed once the index is rebuilt, so it
is a transient server-side condition, not a permanently bad request, and is
re-raised the same way an INTERNAL cause is.
"""
from __future__ import annotations
@@ -81,7 +84,7 @@ class TestJsonExistsIsUserError:
class TestGenuineMisconfigurationStillLogs:
def test_schema_field_missing_is_an_error_log(
def test_schema_field_missing_is_an_error_log_and_reraised(
self,
caplog: pytest.LogCaptureFixture,
) -> None:
@@ -92,9 +95,13 @@ class TestGenuineMisconfigurationStillLogs:
WHEN:
- _map_emit_error processes it
THEN:
- It becomes a SearchQueryError and logs exactly one ERROR
record naming the diagnostic kind, since this is a real
mismatch an operator can fix and keeps the alert
- It logs exactly one ERROR record naming the diagnostic kind
(a real mismatch an operator can fix, so it keeps the
alert), and the original QueryError propagates unchanged
rather than becoming a SearchQueryError: the exact same
query would succeed once the index is rebuilt, so this is a
transient server-side condition, not a permanently bad
request, and surfaces as a 500 rather than a 400
"""
kind = DiagnosticKind.SCHEMA_FIELD_MISSING
error = QueryError(
@@ -106,9 +113,12 @@ class TestGenuineMisconfigurationStillLogs:
field_kind=FieldKind.U64,
),
)
with caplog.at_level(logging.ERROR, logger="paperless.search"):
mapped = _map_emit_error(error)
assert isinstance(mapped, SearchQueryError)
with (
caplog.at_level(logging.ERROR, logger="paperless.search"),
pytest.raises(QueryError) as excinfo,
):
_map_emit_error(error)
assert excinfo.value is error
records = [r for r in caplog.records if r.levelno == logging.ERROR]
assert len(records) == 1
assert kind.name in records[0].getMessage()