Supports duration parsing (5m, 1h30m, 2d12h), short hex IDs for tracking, list/cancel subcommands, and repeating intervals via `!remind every <duration> <text>`. Includes 23 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
105 lines
2.7 KiB
Python
105 lines
2.7 KiB
Python
"""Tests for the remind plugin."""
|
|
|
|
import importlib.util
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# plugins/ is not a Python package -- load the module from file path
|
|
_spec = importlib.util.spec_from_file_location(
|
|
"plugins.remind", Path(__file__).resolve().parent.parent / "plugins" / "remind.py",
|
|
)
|
|
_mod = importlib.util.module_from_spec(_spec)
|
|
sys.modules[_spec.name] = _mod
|
|
_spec.loader.exec_module(_mod)
|
|
|
|
from plugins.remind import ( # noqa: E402
|
|
_format_duration,
|
|
_make_id,
|
|
_parse_duration,
|
|
)
|
|
|
|
|
|
class TestParseDurationRawSeconds:
|
|
def test_positive_integer(self):
|
|
assert _parse_duration("60") == 60
|
|
|
|
def test_zero_returns_none(self):
|
|
assert _parse_duration("0") is None
|
|
|
|
def test_negative_returns_none(self):
|
|
assert _parse_duration("-5") is None
|
|
|
|
def test_large_value(self):
|
|
assert _parse_duration("86400") == 86400
|
|
|
|
|
|
class TestParseDurationSpecs:
|
|
def test_minutes(self):
|
|
assert _parse_duration("5m") == 300
|
|
|
|
def test_hours_and_minutes(self):
|
|
assert _parse_duration("1h30m") == 5400
|
|
|
|
def test_days(self):
|
|
assert _parse_duration("2d") == 172800
|
|
|
|
def test_seconds_suffix(self):
|
|
assert _parse_duration("90s") == 90
|
|
|
|
def test_full_combo(self):
|
|
assert _parse_duration("1d12h30m15s") == 131415
|
|
|
|
|
|
class TestParseDurationInvalid:
|
|
def test_empty_string(self):
|
|
assert _parse_duration("") is None
|
|
|
|
def test_letters_only(self):
|
|
assert _parse_duration("abc") is None
|
|
|
|
def test_all_zeros(self):
|
|
assert _parse_duration("0m0s") is None
|
|
|
|
|
|
class TestParseDurationEdgeCases:
|
|
def test_uppercase_works(self):
|
|
assert _parse_duration("5M") == 300
|
|
|
|
def test_hours_with_zero_minutes(self):
|
|
assert _parse_duration("1h0m") == 3600
|
|
|
|
def test_all_zeros_except_one_second(self):
|
|
assert _parse_duration("0d0h0m1s") == 1
|
|
|
|
|
|
class TestFormatDuration:
|
|
def test_minutes_and_seconds(self):
|
|
assert _format_duration(90) == "1m30s"
|
|
|
|
def test_exact_hour(self):
|
|
assert _format_duration(3600) == "1h"
|
|
|
|
def test_exact_day(self):
|
|
assert _format_duration(86400) == "1d"
|
|
|
|
def test_zero(self):
|
|
assert _format_duration(0) == "0s"
|
|
|
|
def test_full_combo(self):
|
|
assert _format_duration(90061) == "1d1h1m1s"
|
|
|
|
def test_seconds_only(self):
|
|
assert _format_duration(45) == "45s"
|
|
|
|
|
|
class TestMakeId:
|
|
def test_returns_six_char_hex(self):
|
|
rid = _make_id("user", "check oven")
|
|
assert len(rid) == 6
|
|
assert all(c in "0123456789abcdef" for c in rid)
|
|
|
|
def test_different_inputs_differ(self):
|
|
rid1 = _make_id("alice", "task one")
|
|
rid2 = _make_id("bob", "task two")
|
|
assert rid1 != rid2
|