mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-11 20:28:01 +00:00
209 lines
7.1 KiB
Python
209 lines
7.1 KiB
Python
"""Tests for the document_sanity_checker management command.
|
|
|
|
Verifies Rich rendering (table, panel, summary) and end-to-end CLI behavior.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
from django.core.management import call_command
|
|
from rich.console import Console
|
|
|
|
from documents.management.commands.document_sanity_checker import Command
|
|
from documents.sanity_checker import SanityCheckMessages
|
|
from documents.tests.factories import DocumentFactory
|
|
|
|
if TYPE_CHECKING:
|
|
from documents.models import Document
|
|
from documents.tests.conftest import PaperlessDirs
|
|
|
|
|
|
def _render_to_string(messages: SanityCheckMessages) -> str:
|
|
"""Render command output to a plain string for assertion."""
|
|
buf = StringIO()
|
|
cmd = Command()
|
|
cmd.console = Console(file=buf, width=120, no_color=True)
|
|
cmd._render_results(messages)
|
|
return buf.getvalue()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Rich rendering
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRenderResultsNoIssues:
|
|
"""No DB access needed -- renders an empty SanityCheckMessages."""
|
|
|
|
def test_shows_panel(self) -> None:
|
|
output = _render_to_string(SanityCheckMessages())
|
|
assert "No issues detected" in output
|
|
assert "Sanity Check" in output
|
|
|
|
|
|
@pytest.mark.django_db
|
|
class TestRenderResultsWithIssues:
|
|
def test_error_row(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.error(sample_doc.pk, "Original missing")
|
|
output = _render_to_string(msgs)
|
|
assert "Sanity Check Results" in output
|
|
assert "ERROR" in output
|
|
assert "Original missing" in output
|
|
assert f"#{sample_doc.pk}" in output
|
|
assert sample_doc.title in output
|
|
|
|
def test_warning_row(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.warning(sample_doc.pk, "Suspicious file")
|
|
output = _render_to_string(msgs)
|
|
assert "WARN" in output
|
|
assert "Suspicious file" in output
|
|
|
|
def test_info_row(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.info(sample_doc.pk, "No OCR data")
|
|
output = _render_to_string(msgs)
|
|
assert "INFO" in output
|
|
assert "No OCR data" in output
|
|
|
|
@pytest.mark.usefixtures("_media_settings")
|
|
def test_global_message(self) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.warning(None, "Orphaned file: /tmp/stray.pdf")
|
|
output = _render_to_string(msgs)
|
|
assert "(global)" in output
|
|
assert "Orphaned file" in output
|
|
|
|
def test_multiple_messages_same_doc(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.error(sample_doc.pk, "Thumbnail missing")
|
|
msgs.error(sample_doc.pk, "Checksum mismatch")
|
|
output = _render_to_string(msgs)
|
|
assert "Thumbnail missing" in output
|
|
assert "Checksum mismatch" in output
|
|
|
|
@pytest.mark.usefixtures("_media_settings")
|
|
def test_unknown_doc_pk(self) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.error(99999, "Ghost document")
|
|
output = _render_to_string(msgs)
|
|
assert "#99999" in output
|
|
assert "Unknown" in output
|
|
|
|
|
|
@pytest.mark.django_db
|
|
class TestRenderResultsSummary:
|
|
def test_errors_only(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.error(sample_doc.pk, "broken")
|
|
output = _render_to_string(msgs)
|
|
assert "1 document(s) with" in output
|
|
assert "errors" in output
|
|
|
|
def test_warnings_only(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.warning(sample_doc.pk, "odd")
|
|
output = _render_to_string(msgs)
|
|
assert "1 document(s) with" in output
|
|
assert "warnings" in output
|
|
|
|
def test_infos_only(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.info(sample_doc.pk, "no OCR")
|
|
output = _render_to_string(msgs)
|
|
assert "1 document(s) with infos" in output
|
|
|
|
def test_empty_messages(self) -> None:
|
|
msgs = SanityCheckMessages()
|
|
output = _render_to_string(msgs)
|
|
assert "No issues detected." in output
|
|
|
|
def test_document_errors_and_global_warnings(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.error(sample_doc.pk, "broken")
|
|
msgs.warning(None, "orphan")
|
|
output = _render_to_string(msgs)
|
|
assert "1 document(s) with" in output
|
|
assert "errors" in output
|
|
assert "1 global warning(s)" in output
|
|
assert "2 document(s)" not in output
|
|
|
|
def test_global_warnings_only(self) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.warning(None, "extra file")
|
|
output = _render_to_string(msgs)
|
|
assert "1 global warning(s)" in output
|
|
assert "document(s) with" not in output
|
|
|
|
def test_all_levels_combined(self, sample_doc: Document) -> None:
|
|
msgs = SanityCheckMessages()
|
|
msgs.error(sample_doc.pk, "broken")
|
|
msgs.warning(sample_doc.pk, "odd")
|
|
msgs.info(sample_doc.pk, "fyi")
|
|
msgs.warning(None, "extra file")
|
|
output = _render_to_string(msgs)
|
|
assert "1 document(s) with errors" in output
|
|
assert "1 document(s) with warnings" in output
|
|
assert "1 document(s) with infos" in output
|
|
assert "1 global warning(s)" in output
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# End-to-end command execution
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.management
|
|
class TestDocumentSanityCheckerCommand:
|
|
def test_no_issues(self, sample_doc: Document) -> None:
|
|
out = StringIO()
|
|
call_command(
|
|
"document_sanity_checker",
|
|
"--no-progress-bar",
|
|
stdout=out,
|
|
skip_checks=True,
|
|
)
|
|
assert "No issues detected" in out.getvalue()
|
|
|
|
def test_missing_original(self, sample_doc: Document) -> None:
|
|
Path(sample_doc.source_path).unlink()
|
|
out = StringIO()
|
|
call_command(
|
|
"document_sanity_checker",
|
|
"--no-progress-bar",
|
|
stdout=out,
|
|
skip_checks=True,
|
|
)
|
|
output = out.getvalue()
|
|
assert "ERROR" in output
|
|
assert "Original of document does not exist" in output
|
|
|
|
@pytest.mark.usefixtures("_media_settings")
|
|
def test_checksum_mismatch(self, paperless_dirs: PaperlessDirs) -> None:
|
|
"""Lightweight document with zero-byte files triggers checksum mismatch."""
|
|
doc = DocumentFactory(
|
|
title="test",
|
|
content="test",
|
|
filename="test.pdf",
|
|
checksum="abc",
|
|
)
|
|
Path(doc.source_path).touch()
|
|
Path(doc.thumbnail_path).touch()
|
|
|
|
out = StringIO()
|
|
call_command(
|
|
"document_sanity_checker",
|
|
"--no-progress-bar",
|
|
stdout=out,
|
|
skip_checks=True,
|
|
)
|
|
output = out.getvalue()
|
|
assert "ERROR" in output
|
|
assert "Checksum mismatch. Stored: abc, actual:" in output
|