Discord bot fáze B: tlačítka na EV zprávě → patch session + okamžitý replan
services/discord_bot.py: gateway klient jako lifespan task (spojení ven, žádný veřejný endpoint; bez DISCORD_BOT_TOKEN tiše spí). Tlačítka [za 2h][za 4h][ráno][do plna][nenabíjet] s custom_id ev:<site>:<charger>:<akce> (přežijí restart); whitelist DISCORD_ALLOWED_USER_IDS; akce = fn_ev_session_ apply_patch → run_rolling_replan → export_setpoints → edit zprávy novým plánem. services/ev_notify.py: sdílený builder souhrnu (vyčleněno z collectoru), send bot-first s webhook fallbackem. requirements: discord.py>=2.4. 7 testů helperů (parse, deadline akce vč. morning přes Prague TZ). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
backend/tests/test_discord_bot.py
Normal file
57
backend/tests/test_discord_bot.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Discord bot — čisté helpery (custom_id, patch akcí), bez sítě/discord lib."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from services.discord_bot import action_to_patch, parse_custom_id
|
||||
|
||||
_NOW = datetime(2026, 6, 12, 10, 0, tzinfo=timezone.utc) # 12:00 Prague
|
||||
|
||||
|
||||
class ParseCustomIdTests(unittest.TestCase):
|
||||
def test_valid(self) -> None:
|
||||
self.assertEqual(
|
||||
parse_custom_id("ev:2:ev-charger-1:h2"), (2, "ev-charger-1", "h2")
|
||||
)
|
||||
|
||||
def test_invalid(self) -> None:
|
||||
for bad in ("", "ev:2:x:jump", "foo:1:c:h2", "ev:abc:c:h2"):
|
||||
self.assertIsNone(parse_custom_id(bad))
|
||||
|
||||
|
||||
class ActionPatchTests(unittest.TestCase):
|
||||
def _patch(self, action: str, **kw):
|
||||
return action_to_patch(
|
||||
action,
|
||||
now=_NOW,
|
||||
soc_at_connect=kw.get("soc", 55.0),
|
||||
default_deadline_hour=kw.get("hour", 7),
|
||||
)
|
||||
|
||||
def test_h2_deadline(self) -> None:
|
||||
p = self._patch("h2")
|
||||
self.assertIn("2026-06-12T12:00", p["target_deadline"])
|
||||
|
||||
def test_morning_next_occurrence(self) -> None:
|
||||
p = self._patch("morning", hour=7)
|
||||
# 12:00 Prague > 7:00 → zítra 7:00 Prague
|
||||
self.assertIn("2026-06-13T07:00", p["target_deadline"])
|
||||
|
||||
def test_morning_today_if_before(self) -> None:
|
||||
early = datetime(2026, 6, 12, 2, 0, tzinfo=timezone.utc) # 4:00 Prague
|
||||
p = action_to_patch("morning", now=early, soc_at_connect=50, default_deadline_hour=7)
|
||||
self.assertIn("2026-06-12T07:00", p["target_deadline"])
|
||||
|
||||
def test_full(self) -> None:
|
||||
p = self._patch("full")
|
||||
self.assertEqual(p["target_soc_pct"], 100)
|
||||
|
||||
def test_stop_targets_connect_soc(self) -> None:
|
||||
p = self._patch("stop", soc=42.5)
|
||||
self.assertEqual(p["target_soc_pct"], 42.5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user