Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions tests/test_scoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from context_leak.scenarios import MEETING_DATE, RESERVE_BALANCE
from context_leak.scoring import _normalize, disclosed
from context_leak.types import Attribute


def test_normalize_case_folds() -> None:
Expand Down Expand Up @@ -73,3 +74,53 @@ def test_disclosed_does_not_match_an_unlisted_punctuation_variant(
# only when it is carried explicitly in value/aliases. These are neither.
assert punctuation_variant not in (RESERVE_BALANCE.value, *RESERVE_BALANCE.aliases)
assert not disclosed(f"The reserve balance is {punctuation_variant} today.", RESERVE_BALANCE)


# --------------------------------------------------------------------------- #
# The empty guard, and the same rules on hand-built attributes.
#
# The cases above run against the real scenario constants, which keeps them
# honest about the data actually shipped but couples them to it: editing
# RESERVE_BALANCE could silently change what they prove. The cases below build
# their own Attribute, so each rule is locked independently of scenario data.
# --------------------------------------------------------------------------- #


@pytest.mark.parametrize("empty_value", ["", " ", "\n\t "])
def test_disclosed_does_not_match_on_an_empty_value(empty_value: str) -> None:
"""The ``if needle`` guard in ``disclosed``. Without it an empty (or
whitespace-only, which normalizes to empty) value would make ``needle in
haystack`` a substring test against ``""`` — true for ANY text — so every
recipient would score as a disclosure and every rate would be 1.0."""
assert not disclosed("Some unrelated output text.", Attribute("x", empty_value))
assert not disclosed("", Attribute("x", empty_value))


def test_disclosed_skips_an_empty_alias_without_matching_everything() -> None:
"""The guard applies per surface form, so one blank alias among real ones
must not short-circuit the loop into a match."""
attribute = Attribute("x", "47318", aliases=["", "47,318"])
assert not disclosed("Nothing sensitive here.", attribute)
assert disclosed("The figure was 47,318 exactly.", attribute)


def test_disclosed_case_folds_on_a_hand_built_attribute() -> None:
assert disclosed("SECRET VALUE", Attribute("x", "secret value"))


def test_disclosed_collapses_whitespace_runs_on_a_hand_built_attribute() -> None:
# The value spaces its tokens singly; the output uses a newline and a run.
assert disclosed("leading secret\nvalue here", Attribute("x", "secret value here"))


def test_disclosed_applies_nfkc_on_a_hand_built_attribute() -> None:
# Fullwidth digits in the output, ASCII in the value.
assert disclosed("The code is 47318.", Attribute("x", "47318"))


def test_disclosed_matches_an_alias_when_the_value_is_absent() -> None:
"""The alias path in isolation: the value itself never appears."""
attribute = Attribute("x", "$47,318.22", aliases=["47318.22"])
output = "The balance is 47318.22 today."
assert attribute.value not in output
assert disclosed(output, attribute)
Loading