Merge branch 'worktree-agent-a288972b643cdefcc' into dev
This commit is contained in:
@@ -1,27 +1,134 @@
|
||||
"""Discord bot — čisté helpery (custom_id, patch akcí), bez sítě/discord lib."""
|
||||
"""Discord bot — čisté helpery (custom_id, výběry → patch, deadline z voleb),
|
||||
bez sítě/discord lib."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from services.discord_bot import action_to_patch, parse_custom_id
|
||||
from services.discord_bot import (
|
||||
action_to_patch,
|
||||
choice_label,
|
||||
departure_choice_to_deadline,
|
||||
parse_custom_id,
|
||||
select_to_patch,
|
||||
)
|
||||
|
||||
_NOW = datetime(2026, 6, 12, 10, 0, tzinfo=timezone.utc) # 12:00 Prague
|
||||
_PRAGUE = ZoneInfo("Europe/Prague")
|
||||
# 2026-06-12 je pátek; 10:00 UTC = 12:00 Europe/Prague (CEST)
|
||||
_NOW = datetime(2026, 6, 12, 10, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def _prague(dt: datetime) -> str:
|
||||
return dt.astimezone(_PRAGUE).strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
|
||||
class ParseCustomIdTests(unittest.TestCase):
|
||||
def test_valid(self) -> None:
|
||||
def test_valid_selects(self) -> None:
|
||||
self.assertEqual(
|
||||
parse_custom_id("ev:2:ev-charger-1:dep"), (2, "ev-charger-1", "dep")
|
||||
)
|
||||
self.assertEqual(
|
||||
parse_custom_id("ev:2:ev-charger-1:tgt"), (2, "ev-charger-1", "tgt")
|
||||
)
|
||||
|
||||
def test_valid_legacy_buttons(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"):
|
||||
for bad in ("", "ev:2:x:jump", "foo:1:c:h2", "ev:abc:c:h2", "ev:1:c:dep:x"):
|
||||
self.assertIsNone(parse_custom_id(bad))
|
||||
|
||||
|
||||
class ActionPatchTests(unittest.TestCase):
|
||||
class DepartureChoiceTests(unittest.TestCase):
|
||||
"""Absolutní deadline z výběru „Kdy odjíždíš?" (Europe/Prague)."""
|
||||
|
||||
def test_h2(self) -> None:
|
||||
dl = departure_choice_to_deadline("h2", now=_NOW)
|
||||
self.assertEqual(_prague(dl), "2026-06-12 14:00")
|
||||
|
||||
def test_h4(self) -> None:
|
||||
dl = departure_choice_to_deadline("h4", now=_NOW)
|
||||
self.assertEqual(_prague(dl), "2026-06-12 16:00")
|
||||
|
||||
def test_today18_before_18(self) -> None:
|
||||
dl = departure_choice_to_deadline("today18", now=_NOW) # 12:00 Prague
|
||||
self.assertEqual(_prague(dl), "2026-06-12 18:00")
|
||||
|
||||
def test_today18_after_18_rolls_to_next_day(self) -> None:
|
||||
late = datetime(2026, 6, 12, 17, 30, tzinfo=timezone.utc) # 19:30 Prague
|
||||
dl = departure_choice_to_deadline("today18", now=late)
|
||||
self.assertEqual(_prague(dl), "2026-06-13 18:00")
|
||||
|
||||
def test_tomorrow7_crosses_midnight(self) -> None:
|
||||
# 23:30 Prague v pátek → zítra (sobota) 07:00, tj. +7,5 h
|
||||
late = datetime(2026, 6, 12, 21, 30, tzinfo=timezone.utc)
|
||||
dl = departure_choice_to_deadline("tomorrow7", now=late)
|
||||
self.assertEqual(_prague(dl), "2026-06-13 07:00")
|
||||
|
||||
def test_tomorrow12(self) -> None:
|
||||
dl = departure_choice_to_deadline("tomorrow12", now=_NOW)
|
||||
self.assertEqual(_prague(dl), "2026-06-13 12:00")
|
||||
|
||||
def test_monday7_from_friday_allows_over_48h(self) -> None:
|
||||
# explicitní volba smí přes 48h limit fn_ev_session_defaults
|
||||
dl = departure_choice_to_deadline("monday7", now=_NOW) # pátek 12:00
|
||||
self.assertEqual(_prague(dl), "2026-06-15 07:00")
|
||||
self.assertGreater((dl - _NOW).total_seconds(), 48 * 3600)
|
||||
|
||||
def test_monday7_on_monday_before_7_is_today(self) -> None:
|
||||
mon_early = datetime(2026, 6, 15, 3, 0, tzinfo=timezone.utc) # po 05:00
|
||||
dl = departure_choice_to_deadline("monday7", now=mon_early)
|
||||
self.assertEqual(_prague(dl), "2026-06-15 07:00")
|
||||
|
||||
def test_monday7_on_monday_after_7_is_next_week(self) -> None:
|
||||
mon_late = datetime(2026, 6, 15, 8, 0, tzinfo=timezone.utc) # po 10:00
|
||||
dl = departure_choice_to_deadline("monday7", now=mon_late)
|
||||
self.assertEqual(_prague(dl), "2026-06-22 07:00")
|
||||
|
||||
def test_unknown_choice_raises(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
departure_choice_to_deadline("never", now=_NOW)
|
||||
|
||||
|
||||
class SelectPatchTests(unittest.TestCase):
|
||||
"""Mapování výběrů na patch payload pro fn_ev_session_apply_patch."""
|
||||
|
||||
def test_dep_patches_only_deadline(self) -> None:
|
||||
p = select_to_patch("dep", "today18", now=_NOW, soc_at_connect=55.0)
|
||||
self.assertEqual(set(p), {"target_deadline"})
|
||||
self.assertIn("2026-06-12T18:00", p["target_deadline"])
|
||||
self.assertIn("+02:00", p["target_deadline"]) # Europe/Prague (CEST)
|
||||
|
||||
def test_tgt_patches_only_target(self) -> None:
|
||||
for value, expected in (("30", 30.0), ("50", 50.0), ("70", 70.0), ("100", 100.0)):
|
||||
p = select_to_patch("tgt", value, now=_NOW, soc_at_connect=55.0)
|
||||
self.assertEqual(p, {"target_soc_pct": expected})
|
||||
|
||||
def test_tgt_stop_targets_connect_soc(self) -> None:
|
||||
p = select_to_patch("tgt", "stop", now=_NOW, soc_at_connect=42.5)
|
||||
self.assertEqual(p, {"target_soc_pct": 42.5})
|
||||
|
||||
def test_tgt_stop_without_soc(self) -> None:
|
||||
p = select_to_patch("tgt", "stop", now=_NOW, soc_at_connect=None)
|
||||
self.assertEqual(p, {"target_soc_pct": 0.0})
|
||||
|
||||
def test_unknown_kind_raises(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
select_to_patch("foo", "30", now=_NOW, soc_at_connect=None)
|
||||
|
||||
def test_labels(self) -> None:
|
||||
self.assertEqual(choice_label("dep", "monday7"), "odjezd pondělí ráno 7:00")
|
||||
self.assertEqual(choice_label("tgt", "70"), "cíl 70 %")
|
||||
self.assertEqual(choice_label("tgt", "stop"), "nenabíjet")
|
||||
|
||||
|
||||
class LegacyActionPatchTests(unittest.TestCase):
|
||||
"""Legacy tlačítka starších zpráv (h2/h4/morning/full/stop)."""
|
||||
|
||||
def _patch(self, action: str, **kw):
|
||||
return action_to_patch(
|
||||
action,
|
||||
|
||||
Reference in New Issue
Block a user