mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 18:07:29 +01:00
One row per live command on a new Commands tab of the management page,
each with an on/off switch, an optional "off until" moment and a cooldown
that overrides the bot's default for that command alone. Every switch is
live: the bot keeps them in its own SQLite (command_settings, a permanent
table) and reads the row on every attempt, so nothing goes through
/_reload and nothing restarts.
A command that is off answers "BEEP BOOP !kot WYŁĄCZONE." (with "DO HH:MM"
for a timed switch) rather than going quiet, so nobody reports it broken;
the refusal is filed as refused_off, which the analytics count with the
other refusals, and it neither starts the cooldown nor counts as a use.
The bot grows GET /_commands and PUT /_commands/{name}; the management app
proxies them under /api/commands and passes the bot's 404/503 through
unwrapped. !cisza and !odcisz carry no cooldown and the page says so
instead of accepting a number the bot would ignore (test_wiring_ungated
keeps that set honest against the real table).
Proven end to end: scripts/e2e.sh now brackets a PUT with switch_off.yaml
and switch_on.yaml, the same actor refused and then served, with no reload
between them. Also bumps vitest and @vitest/coverage-v8 to 5.0.0 and
@types/react-dom to 19.2.7, which the freshness gate asked for.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
3.2 KiB
Python
77 lines
3.2 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
|
|
"""The switchboard routes: the live table in, a setting out, live at once."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import httpx
|
|
|
|
from signal_bot.mute_command import MUTE_NAME
|
|
from tests.conftest import WATCHED_GROUP, mock_client, signal_client
|
|
from tests.test_app import _connected, _offline, _serving
|
|
|
|
if TYPE_CHECKING:
|
|
import pytest
|
|
|
|
|
|
def test_the_list_is_refused_while_still_connecting(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
with _serving(monkeypatch, mock_client(_offline)) as client:
|
|
response = client.get("/_commands")
|
|
assert response.status_code == httpx.codes.SERVICE_UNAVAILABLE
|
|
assert response.json()["detail"]["status"] == "connecting"
|
|
|
|
|
|
def test_every_live_command_is_listed_on_with_its_default(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
with _serving(monkeypatch, signal_client(WATCHED_GROUP)) as client:
|
|
_connected(client)
|
|
rows = client.get("/_commands").json()["commands"]
|
|
names = [row["name"] for row in rows]
|
|
assert names == sorted(names)
|
|
assert "cat" in names
|
|
by_name = {row["name"]: row for row in rows}
|
|
cat = by_name["cat"]
|
|
assert cat["trigger"].startswith("!")
|
|
assert cat["description"]
|
|
assert cat["enabled"] is True
|
|
assert cat["disabled_until"] is None
|
|
assert cat["cooldown_seconds"] is None
|
|
assert cat["default_cooldown_seconds"] > 0
|
|
# No cooldown on the mute pair, and the page must not pretend otherwise.
|
|
assert by_name[MUTE_NAME]["default_cooldown_seconds"] is None
|
|
|
|
|
|
def test_a_put_is_stored_and_read_back_from_the_list(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
with _serving(monkeypatch, signal_client(WATCHED_GROUP)) as client:
|
|
_connected(client)
|
|
body = {"enabled": False, "disabled_until": 1_800_000_000.0, "cooldown_seconds": 5}
|
|
response = client.put("/_commands/cat", json=body)
|
|
assert response.status_code == httpx.codes.OK
|
|
row = response.json()
|
|
assert (row["enabled"], row["disabled_until"], row["cooldown_seconds"]) == (
|
|
False,
|
|
1_800_000_000.0,
|
|
5,
|
|
)
|
|
listed = {r["name"]: r for r in client.get("/_commands").json()["commands"]}["cat"]
|
|
assert listed == row
|
|
# And back to the defaults: the row is replaced, not merged.
|
|
assert client.put("/_commands/cat", json={}).json()["enabled"] is True
|
|
|
|
|
|
def test_a_name_the_table_does_not_hold_is_a_404(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""The page's dropdown cannot produce one; a typo in curl can."""
|
|
with _serving(monkeypatch, signal_client(WATCHED_GROUP)) as client:
|
|
_connected(client)
|
|
response = client.put("/_commands/nosuch", json={"enabled": False})
|
|
assert response.status_code == httpx.codes.NOT_FOUND
|
|
assert "nosuch" in response.json()["detail"]
|
|
|
|
|
|
def test_a_put_is_refused_while_still_connecting(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
with _serving(monkeypatch, mock_client(_offline)) as client:
|
|
response = client.put("/_commands/cat", json={"enabled": False})
|
|
assert response.status_code == httpx.codes.SERVICE_UNAVAILABLE
|