core/tests/test_safe_display.py

108 lines
3.8 KiB
Python

"""Tests for the central safe-display sanitiser (ADR-0051).
These tests are doctrine guards: they encode the trust boundary so any
future refactor that weakens the sanitiser fails closed in CI before it
can be merged.
"""
from __future__ import annotations
import pytest
from core._safe_display import safe_display, safe_pack_id
class TestSafeDisplay:
def test_none_collapses_to_empty_mark(self) -> None:
assert safe_display(None) == "<empty>"
def test_empty_string_collapses_to_empty_mark(self) -> None:
assert safe_display("") == "<empty>"
def test_plain_ascii_is_passed_through(self) -> None:
assert safe_display("memory") == "memory"
def test_control_characters_are_replaced(self) -> None:
# \x1b is ESC — the prefix of every ANSI escape sequence.
assert safe_display("foo\x1b[31mbar") == "foo?[31mbar"
def test_newline_and_carriage_return_are_replaced(self) -> None:
# Newlines must never reach a log line uncleansed.
assert "\n" not in safe_display("foo\nbar")
assert "\r" not in safe_display("foo\r\nbar")
def test_null_byte_is_replaced(self) -> None:
assert "\x00" not in safe_display("foo\x00bar")
def test_c1_control_range_is_replaced(self) -> None:
# The C1 range (0x80..0x9F) is also control characters.
for code in (0x80, 0x90, 0x9F):
assert chr(code) not in safe_display(f"a{chr(code)}b")
def test_truncation_to_default_max_len(self) -> None:
out = safe_display("x" * 200)
assert len(out) <= 64
assert out.endswith("...")
def test_truncation_marker_only_when_needed(self) -> None:
assert safe_display("short") == "short"
assert "..." not in safe_display("short")
def test_custom_max_len_is_honoured(self) -> None:
out = safe_display("abcdefghij", max_len=8)
assert len(out) <= 8
assert out.endswith("...")
def test_zero_max_len_returns_empty_string(self) -> None:
assert safe_display("anything", max_len=0) == ""
def test_non_string_input_is_repr_coerced(self) -> None:
# Callers cannot smuggle a custom __str__ into a log line.
class Hostile:
def __str__(self) -> str: # pragma: no cover
return "\x1b[31mPWNED"
out = safe_display(Hostile(), max_len=128)
assert "\x1b" not in out
# repr() is what we coerce through, not str().
assert "Hostile" in out
def test_is_deterministic(self) -> None:
# Identical input → byte-identical output, no clock / env.
a = safe_display("test\x1btoken")
b = safe_display("test\x1btoken")
assert a == b
class TestSafePackId:
def test_simple_pack_id_passes_through(self) -> None:
assert safe_pack_id("en_core_cognition_v1") == "en_core_cognition_v1"
def test_path_separators_are_masked(self) -> None:
out = safe_pack_id("../etc/passwd")
assert "/" not in out
assert ".." in out # dots are allowed; slashes are masked
def test_whitespace_is_masked(self) -> None:
assert " " not in safe_pack_id("foo bar")
def test_unicode_is_masked(self) -> None:
out = safe_pack_id("café")
assert "é" not in out
def test_none_collapses_to_empty_mark(self) -> None:
assert safe_pack_id(None) == "<empty>"
def test_empty_collapses_to_empty_mark(self) -> None:
assert safe_pack_id("") == "<empty>"
def test_truncation_for_long_input(self) -> None:
out = safe_pack_id("a" * 200)
assert len(out) <= 48
assert out.endswith("...")
def test_module_exports_are_explicit() -> None:
"""``__all__`` lists exactly the public helpers (regression guard)."""
from core import _safe_display as mod
assert set(mod.__all__) == {"safe_display", "safe_pack_id"}