signal-bot/tests/test_personal_data_sources.py
Krzysztof kuhy Rudnicki 3107a9e55f
Some checks are pending
CI / check (push) Waiting to run
CI / web (push) Waiting to run
feat: no photo reaches the album from the chat until somebody approves it
!dodaj is unwired: register_add_command is no longer called, its aliases
are gone from aliases.json, and the word is an unknown one like any
other -- answered with "Nie znam !dodaj", storing nothing. The ingest
path itself (pack_add, pack_ingest, pack_store, the stripper) stays,
tested at 100%, because the approval queue that replaces the direct
write will reuse every piece of it: !dodaj lands in pending/, and the
tagger view gets approve / reject.

The eight e2e scenarios that proved the direct write, the media
generation step and prove_the_add_photo_invariants leave with it;
64852ae has them for the queue to bring back in their new shape. One
scenario takes their place and pins the disabled state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 09:47:42 +02:00

126 lines
4.5 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
"""Where the personal-data gate learns its forbidden words from.
Worth its own test because ``!add_photo`` made one of those sources
*writable from a group chat*, and the failure mode is silent and permanent:
a label that is also a word in this repository would put that word on the
forbidden list and refuse every commit from then on, fixable only by
hand-editing an album the committer cannot see.
Driven entirely through ``forbidden()``, which is what the gate itself calls.
"""
from __future__ import annotations
import importlib.util
import json
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any
import pytest
if TYPE_CHECKING:
from types import ModuleType
REPO = Path(__file__).resolve().parent.parent
@pytest.fixture
def sources() -> ModuleType:
"""Import scripts/personal_data_sources.py, which is not a package."""
path = REPO / "scripts" / "personal_data_sources.py"
spec = importlib.util.spec_from_file_location("personal_data_sources", path)
assert spec is not None
assert spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _album(
root: Path,
*,
triggers: list[str] | None = None,
custom: dict[str, Any] | None = None,
corrupt_custom: bool = False,
) -> Path:
root.mkdir(parents=True, exist_ok=True)
people = [{"key": "p1", "triggers": triggers or ["!zenon"]}]
(root / "people.json").write_text(
json.dumps({"groups": ["g"], "people": people}), encoding="utf-8"
)
if corrupt_custom:
(root / "custom.json").write_text("{ not json", encoding="utf-8")
elif custom is not None:
(root / "custom.json").write_text(json.dumps(custom), encoding="utf-8")
return root
def _words(sources: ModuleType, root: Path, monkeypatch: pytest.MonkeyPatch) -> set[str]:
monkeypatch.setenv("PHOTO_PACK_DIR", str(root))
words, _ = sources.forbidden(set())
return set(words)
def test_a_people_trigger_is_forbidden(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
assert "zenon" in _words(sources, _album(tmp_path / "pack"), monkeypatch)
def test_a_custom_label_is_forbidden_too(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A label can be a name -- "ciocia_hania" is one however it got there."""
root = _album(tmp_path / "pack", custom={"t1": ["ciocia_hania"]})
assert "ciocia_hania" in _words(sources, root, monkeypatch)
def test_a_label_that_is_a_command_word_is_not_forbidden(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The wedge: "!add_photo kot" must not outlaw "kot" in aliases.json."""
root = _album(tmp_path / "pack", custom={"t1": ["kot"], "t2": ["ciocia_hania"]})
words = _words(sources, root, monkeypatch)
assert "kot" not in words
assert "ciocia_hania" in words
@pytest.mark.parametrize("label", ["losowe", "papiez", "komendy", "staty"])
def test_no_command_word_can_be_outlawed_by_a_label(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, label: str
) -> None:
"""Generalised: every alias in the table, not just the one that bit."""
root = _album(tmp_path / "pack", custom={"t1": [label]})
assert label not in _words(sources, root, monkeypatch)
def test_a_people_trigger_is_never_excused_by_the_vocabulary(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Only custom labels get the subtraction.
A curated real name turning up in this repository is precisely the leak
the gate exists to fail on, so it stays forbidden even when it collides.
"""
root = _album(tmp_path / "pack", triggers=["!kot"])
assert "kot" in _words(sources, root, monkeypatch)
def test_an_album_with_no_custom_file_still_works(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Other sources (the database, .env) contribute too, so this is membership."""
words = _words(sources, _album(tmp_path / "pack"), monkeypatch)
assert "zenon" in words
def test_a_corrupt_custom_file_is_ignored_rather_than_fatal(
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Refusing every commit because an album file got truncated is backwards."""
root = _album(tmp_path / "pack", corrupt_custom=True)
assert "zenon" in _words(sources, root, monkeypatch)