# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """Trigger resolution.""" from __future__ import annotations from typing import TYPE_CHECKING import pytest from signal_bot.context import CommandContext from signal_bot.models import parse_envelope from signal_bot.registry import CommandRegistry, DuplicateTriggerError from signal_bot.transport_fake import FakeTransport from tests.conftest import WATCHED_GROUP, envelope if TYPE_CHECKING: from signal_bot.config import Config from signal_bot.store import Store async def _noop(ctx: CommandContext) -> None: return None def test_resolves_a_registered_trigger() -> None: registry = CommandRegistry() registry.register("cat", ["!cat", "!kot"], _noop, "cat description") assert registry.resolve("!kot") is not None assert registry.resolve("!cat").name == "cat" def test_resolution_is_case_insensitive_and_trims() -> None: registry = CommandRegistry() registry.register("cat", ["!cat"], _noop, "cat description") assert registry.resolve(" !CAT ") is not None def test_unknown_and_prefixless_text_resolves_to_nothing() -> None: registry = CommandRegistry() registry.register("cat", ["!cat"], _noop, "cat description") assert registry.resolve("!nope") is None assert registry.resolve("cat") is None @pytest.mark.parametrize("text", [None, ""]) def test_empty_text_resolves_to_nothing(text: str | None) -> None: assert CommandRegistry().resolve(text) is None def test_duplicate_trigger_is_rejected() -> None: registry = CommandRegistry() registry.register("cat", ["!cat"], _noop, "cat description") with pytest.raises(DuplicateTriggerError, match="!cat"): registry.register("feline", ["!cat"], _noop, "feline description") def test_commands_lists_in_registration_order() -> None: registry = CommandRegistry() registry.register("cat", ["!cat"], _noop, "cat description") registry.register("dog", ["!dog"], _noop, "dog description") assert [c.name for c in registry.commands] == ["cat", "dog"] def _ctx(transport: FakeTransport, store: Store, config: Config) -> CommandContext: message = parse_envelope(envelope(text="!papiez")) assert message is not None return CommandContext( message=message, group_id=WATCHED_GROUP, transport=transport, store=store, config=config, command_name="pope", now=100.0, day="2026-08-30", ) async def test_reply_image_defaults_to_no_caption(store: Store, config: Config) -> None: transport = FakeTransport() await _ctx(transport, store, config).reply_image("Zm9v") assert transport.sent[0].caption == "" async def test_reply_image_passes_the_caption_through(store: Store, config: Config) -> None: """The caption rides in the same payload, so Signal shows one message.""" transport = FakeTransport() await _ctx(transport, store, config).reply_image("Zm9v", "8. Telesfor") sent = transport.sent[0] assert sent.caption == "8. Telesfor" assert sent.image_base64 == "Zm9v" assert sent.quote is not None async def _nothing(ctx: CommandContext) -> None: """A handler that does nothing, for tests about resolution only.""" def test_a_command_with_no_groups_exists_everywhere() -> None: registry = CommandRegistry() command = registry.register("open", ["!open"], _nothing, "everywhere") assert command.allowed_in("group-a==") assert registry.resolve_with_args("!open", "group-b==") is not None def test_a_scoped_command_does_not_exist_elsewhere() -> None: """Not refused -- absent. A refusal would announce that it exists.""" registry = CommandRegistry() registry.register("shy", ["!shy"], _nothing, "here only", groups=frozenset({"group-a=="})) assert registry.resolve_with_args("!shy", "group-a==") is not None assert registry.resolve_with_args("!shy", "group-b==") is None def test_words_after_a_trigger_are_arguments_only_where_they_are_wanted() -> None: """ "!cat blah" has always resolved to nothing, and still does.""" registry = CommandRegistry() registry.register("plain", ["!plain"], _nothing, "no arguments") registry.register("takes", ["!takes"], _nothing, "arguments", takes_args=True) assert registry.resolve_with_args("!plain blah", "g==") is None resolved = registry.resolve_with_args("!takes one two", "g==") assert resolved is not None assert resolved.args == ("one", "two") def test_a_bare_trigger_has_no_arguments() -> None: registry = CommandRegistry() registry.register("takes", ["!takes"], _nothing, "arguments", takes_args=True) resolved = registry.resolve_with_args("!takes", "g==") assert resolved is not None assert resolved.args == () def test_nothing_resolves_out_of_an_empty_message() -> None: registry = CommandRegistry() registry.register("takes", ["!takes"], _nothing, "arguments", takes_args=True) assert registry.resolve_with_args(None, "g==") is None assert registry.resolve_with_args(" ", "g==") is None assert registry.resolve_with_args("!nobody has this", "g==") is None