# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """!zaproponuj: filing an idea from inside the group.""" from __future__ import annotations import json from typing import TYPE_CHECKING from signal_bot.animals import ALIAS_FILE, load_aliases from signal_bot.context import CommandContext from signal_bot.models import parse_envelope from signal_bot.registry import CommandRegistry from signal_bot.suggest_command import ( ALREADY, COMMAND_NAME, USAGE, register_suggest_command, ) from signal_bot.unknown import SUGGEST_PRIMARY from tests.conftest import WATCHED_GROUP, envelope if TYPE_CHECKING: from signal_bot.config import Config from signal_bot.store import Store from signal_bot.transport_fake import FakeTransport async def _noop(ctx: CommandContext) -> None: """A handler that does nothing.""" def test_the_pinned_spelling_is_the_one_aliases_json_actually_lists() -> None: """unknown.py quotes SUGGEST_PRIMARY without reading the file. That is the trade -- no file read on the inbound path -- and this is the check that stops the constant and the data drifting apart. """ assert load_aliases()[COMMAND_NAME][0] == SUGGEST_PRIMARY def test_the_command_is_never_scoped_to_a_group() -> None: """help_command.GROUPS names it, and render_help looks every name up. A scoped command in that table raises KeyError in every group it is not allowed in, which would take !pomoc down rather than hide one line. """ registry = CommandRegistry() register_suggest_command(registry) (command,) = [c for c in registry.commands if c.name == COMMAND_NAME] assert command.groups is None assert command.takes_args def test_every_alias_is_a_plausible_command_word_and_not_a_name() -> None: """_repo_vocabulary() subtracts these from the personal-data gate. Every trigger listed here is permanently exempt from that half of the check, so a name-shaped one would punch a hole in it. """ document = json.loads(ALIAS_FILE.read_text(encoding="utf-8")) for trigger in document[COMMAND_NAME]: assert trigger.startswith("!") assert trigger[1:].isalpha() async def _run( registry: CommandRegistry, store: Store, transport: FakeTransport, config: Config, text: str, ) -> str: """Run !zaproponuj on one message and return what it answered.""" message = parse_envelope(envelope(text=text, timestamp=77)) assert message is not None resolved = registry.resolve_with_args(text, WATCHED_GROUP) assert resolved is not None ctx = CommandContext( message=message, group_id=WATCHED_GROUP, transport=transport, store=store, config=config, command_name=resolved.command.name, now=0.0, day="2026-08-31", args=resolved.args, ) await resolved.command.handler(ctx) return transport.sent[-1].text or "" def _registry() -> CommandRegistry: """!zaproponuj, plus one existing command to collide with.""" registry = CommandRegistry() registry.register("cat", ["!kot"], _noop, "kot") register_suggest_command(registry) return registry async def test_no_arguments_gets_the_usage_line( store: Store, transport: FakeTransport, config: Config ) -> None: """Somebody who typed the trigger alone is asking how it works.""" assert await _run(_registry(), store, transport, config, SUGGEST_PRIMARY) == USAGE assert store.suggestions.filed() == [] async def test_a_name_without_a_description_gets_the_usage_line( store: Store, transport: FakeTransport, config: Config ) -> None: """A bare word is not an idea; there would be nothing to act on.""" text = f"{SUGGEST_PRIMARY} !pogoda" assert await _run(_registry(), store, transport, config, text) == USAGE assert store.suggestions.filed() == [] async def test_something_that_is_not_a_command_word_is_refused( store: Store, transport: FakeTransport, config: Config ) -> None: """And is refused without echoing it: it may well be somebody's name.""" text = f"{SUGGEST_PRIMARY} !! cokolwiek" answer = await _run(_registry(), store, transport, config, text) assert USAGE in answer assert store.suggestions.filed() == [] async def test_an_existing_command_is_not_filed_again( store: Store, transport: FakeTransport, config: Config ) -> None: """Asked globally, so a command scoped elsewhere is not reported free.""" text = f"{SUGGEST_PRIMARY} !kot cos innego" answer = await _run(_registry(), store, transport, config, text) assert answer == ALREADY.format(word="!kot") assert store.suggestions.filed() == [] async def test_a_new_idea_is_filed_and_acknowledged( store: Store, transport: FakeTransport, config: Config ) -> None: """The whole point: the row lands, keyed on the envelope's timestamp.""" text = f"{SUGGEST_PRIMARY} !pogoda pokaz pogode na jutro" answer = await _run(_registry(), store, transport, config, text) assert "!pogoda" in answer (row,) = store.suggestions.filed() assert row.command == "!pogoda" assert row.body == "pokaz pogode na jutro" assert row.sent_at_ms == 77 async def test_the_leading_bang_is_optional( store: Store, transport: FakeTransport, config: Config ) -> None: """Half the group will type the name without it.""" text = f"{SUGGEST_PRIMARY} pogoda pokaz pogode" await _run(_registry(), store, transport, config, text) (row,) = store.suggestions.filed() assert row.command == "!pogoda"