# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """The operator's alias file: where it is, what it accepts, what it refuses.""" from __future__ import annotations import json from typing import TYPE_CHECKING import pytest from signal_bot.custom_aliases import ENV_VAR, FILE_NAME, default_path, load, normalise, store from signal_bot.groups import ConfigError from signal_bot.state_dir import state_dir if TYPE_CHECKING: from pathlib import Path def test_the_file_sits_in_the_state_directory_unless_named( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: monkeypatch.delenv(ENV_VAR, raising=False) assert default_path() == state_dir() / FILE_NAME monkeypatch.setenv(ENV_VAR, str(tmp_path / "elsewhere.json")) assert default_path() == tmp_path / "elsewhere.json" def test_the_state_directory_is_systemds_first_then_xdg_then_home( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: monkeypatch.setenv("STATE_DIRECTORY", str(tmp_path / "state")) assert state_dir() == tmp_path / "state" monkeypatch.delenv("STATE_DIRECTORY") monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "share")) assert state_dir() == tmp_path / "share" / "signal-bot" monkeypatch.delenv("XDG_DATA_HOME") monkeypatch.setenv("HOME", str(tmp_path / "home")) assert state_dir() == tmp_path / "home" / ".local" / "share" / "signal-bot" def test_normalise_trims_bangs_and_deduplicates_in_order() -> None: assert normalise([" kotek ", "!kotek", "", "!Kicia", "kicia"]) == ("!kotek", "!Kicia", "!kicia") def test_normalise_refuses_a_trigger_of_more_than_one_word() -> None: with pytest.raises(ConfigError, match="one word"): normalise(["!kot ładny"]) def test_an_absent_file_is_no_aliases(tmp_path: Path) -> None: assert load(tmp_path / FILE_NAME) == {} def test_store_and_load_round_trip_and_drop_emptied_commands(tmp_path: Path) -> None: path = tmp_path / "deep" / FILE_NAME store(path, {"cat": ("!kotek",), "dog": ()}) assert load(path) == {"cat": ("!kotek",)} assert path.stat().st_mode & 0o777 == 0o600 @pytest.mark.parametrize( ("text", "complaint"), [ ("{not json", "cannot be read"), ('["!kotek"]', "must be an object"), ('{"cat": "!kotek"}', "list of strings"), ('{"cat": [1]}', "list of strings"), ('{"cat": ["!kot ładny"]}', "one word"), ], ) def test_a_file_the_bot_could_not_honour_is_refused_whole( tmp_path: Path, text: str, complaint: str ) -> None: path = tmp_path / FILE_NAME path.write_text(text, encoding="utf-8") with pytest.raises(ConfigError, match=complaint): load(path) def test_a_command_left_with_no_words_is_not_an_alias(tmp_path: Path) -> None: path = tmp_path / FILE_NAME path.write_text(json.dumps({"cat": [" "], "dog": ["!piesek"]}), encoding="utf-8") assert load(path) == {"dog": ("!piesek",)}