provozni zaloha
This commit is contained in:
@@ -167,11 +167,13 @@ def test_fires_sorted_across_types(conn):
|
||||
|
||||
|
||||
def test_format_empty_window():
|
||||
assert format_upcoming([]) == ["(nothing scheduled in this window)"]
|
||||
assert format_upcoming([], {}) == ["(nothing scheduled in this window)"]
|
||||
|
||||
|
||||
def test_format_line_shape():
|
||||
lines = format_upcoming([
|
||||
{"fire_time": datetime(2026, 6, 10, 9, 0), "id": 1, "text": "call mom", "schedule_type": "cron"},
|
||||
])
|
||||
assert lines == ["2026-06-10 09:00 #1 call mom (cron)"]
|
||||
def test_format_line_shape_uses_display_id():
|
||||
# Internal id 5 maps to display ID 2 — the line shows the display ID.
|
||||
lines = format_upcoming(
|
||||
[{"fire_time": datetime(2026, 6, 10, 9, 0), "id": 5, "text": "call mom", "schedule_type": "cron"}],
|
||||
{5: 2},
|
||||
)
|
||||
assert lines == ["2026-06-10 09:00 #2 call mom (cron)"]
|
||||
|
||||
@@ -94,3 +94,69 @@ def test_config_validated_before_date_filter():
|
||||
# Out-of-range date still surfaces a structural error rather than returning [].
|
||||
with pytest.raises(ValueError):
|
||||
compute_fire_times(date(2000, 1, 1), "x", cfg(times_per_day=50, until="1999-01-01"))
|
||||
|
||||
|
||||
# --- Weekly period -----------------------------------------------------------
|
||||
|
||||
# Week of Mon 2026-03-23 .. Sun 2026-03-29.
|
||||
WEEK = [date(2026, 3, d) for d in range(23, 30)]
|
||||
|
||||
|
||||
def weekly_cfg(**overrides) -> dict:
|
||||
base = {"times_per_day": 2, "window": WINDOW, "period": "week"}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
def _week_fires(text: str, cfg_dict: dict) -> list[datetime]:
|
||||
return [fire for day in WEEK for fire in compute_fire_times(day, text, cfg_dict)]
|
||||
|
||||
|
||||
def test_weekly_count_across_week():
|
||||
assert len(_week_fires("x", weekly_cfg(times_per_day=2))) == 2
|
||||
|
||||
|
||||
def test_weekly_distinct_days():
|
||||
fires = _week_fires("x", weekly_cfg(times_per_day=3))
|
||||
assert len({f.date() for f in fires}) == 3
|
||||
|
||||
|
||||
def test_weekly_deterministic_across_days():
|
||||
# Every day of the week must agree on the same plan, so summing per-day calls
|
||||
# over the week yields a stable set regardless of call order.
|
||||
assert _week_fires("walk", weekly_cfg()) == _week_fires("walk", weekly_cfg())
|
||||
|
||||
|
||||
def test_weekly_within_window():
|
||||
for fire in _week_fires("x", weekly_cfg(times_per_day=4)):
|
||||
assert WINDOW_START.time() <= fire.time() <= WINDOW_END.time()
|
||||
|
||||
|
||||
def test_weekly_days_filter_limits_eligible():
|
||||
fires = _week_fires("x", weekly_cfg(times_per_day=2, days="1-5"))
|
||||
assert all(f.weekday() < 5 for f in fires)
|
||||
|
||||
|
||||
def test_weekly_count_clamped_to_eligible_days():
|
||||
# Capacity (7 days) admits 3, but until clips this week to Mon+Tue -> 2 fires, no error.
|
||||
bounded = weekly_cfg(times_per_day=3, until="2026-03-24")
|
||||
fires = _week_fires("x", bounded)
|
||||
assert len(fires) == 2
|
||||
assert {f.date() for f in fires} == {date(2026, 3, 23), date(2026, 3, 24)}
|
||||
|
||||
|
||||
def test_weekly_from_until_clips_to_partial_week():
|
||||
bounded = weekly_cfg(times_per_day=2, **{"from": "2026-03-25", "until": "2026-03-27"})
|
||||
fires = _week_fires("x", bounded)
|
||||
assert all(date(2026, 3, 25) <= f.date() <= date(2026, 3, 27) for f in fires)
|
||||
assert len(fires) == 2
|
||||
|
||||
|
||||
def test_weekly_count_exceeds_capacity_raises():
|
||||
with pytest.raises(ValueError):
|
||||
compute_fire_times(WEEK[0], "x", weekly_cfg(times_per_day=8))
|
||||
|
||||
|
||||
def test_weekly_count_exceeds_filtered_capacity_raises():
|
||||
with pytest.raises(ValueError):
|
||||
compute_fire_times(WEEK[0], "x", weekly_cfg(times_per_day=3, days="1,2"))
|
||||
|
||||
@@ -217,6 +217,61 @@ def test_remove_by_id_disambiguates_duplicates(tmp_path, capsys):
|
||||
assert "drink water" in captured.out
|
||||
|
||||
|
||||
def test_display_id_renumbers_after_remove(tmp_path, capsys):
|
||||
db_path = tmp_path / "test.sqlite"
|
||||
init_db(db_path)
|
||||
|
||||
for text in ("first", "second", "third"):
|
||||
_run(db_path, ["add", "--text", text, "--cron", "0 9 * * *"])
|
||||
capsys.readouterr()
|
||||
|
||||
# Display IDs follow insertion order: #1 first, #2 second, #3 third.
|
||||
_run(db_path, ["remove", "--id", "1"]) # removes "first"
|
||||
capsys.readouterr()
|
||||
|
||||
ret = _run(db_path, ["list"])
|
||||
captured = capsys.readouterr()
|
||||
assert ret == 0
|
||||
assert "#1 second [enabled]" in captured.out
|
||||
assert "#2 third [enabled]" in captured.out
|
||||
assert "first" not in captured.out
|
||||
|
||||
# After renumbering, display #1 is now "second".
|
||||
ret = _run(db_path, ["remove", "--id", "1"])
|
||||
captured = capsys.readouterr()
|
||||
assert ret == 0
|
||||
assert json.loads(captured.out)["removed"]["text"] == "second"
|
||||
|
||||
|
||||
def test_id_out_of_range_reports_display_id(tmp_path, capsys):
|
||||
db_path = tmp_path / "test.sqlite"
|
||||
init_db(db_path)
|
||||
|
||||
_run(db_path, ["add", "--text", "only one", "--cron", "0 9 * * *"])
|
||||
capsys.readouterr()
|
||||
|
||||
ret = _run(db_path, ["remove", "--id", "5"])
|
||||
captured = capsys.readouterr()
|
||||
assert ret == 1
|
||||
assert json.loads(captured.err) == {"error": "no match", "display_id": 5}
|
||||
|
||||
|
||||
def test_ambiguous_keyword_returns_display_ids(tmp_path, capsys):
|
||||
db_path = tmp_path / "test.sqlite"
|
||||
init_db(db_path)
|
||||
|
||||
_run(db_path, ["add", "--text", "drink water", "--cron", "0 9 * * *"])
|
||||
_run(db_path, ["add", "--text", "drink water", "--cron", "0 10 * * *"])
|
||||
capsys.readouterr()
|
||||
|
||||
ret = _run(db_path, ["remove", "--keyword", "drink"])
|
||||
captured = capsys.readouterr()
|
||||
assert ret == 1
|
||||
err = json.loads(captured.err)
|
||||
assert err["error"] == "ambiguous"
|
||||
assert sorted(m["display_id"] for m in err["matches"]) == [1, 2]
|
||||
|
||||
|
||||
def test_resolve_requires_id_or_keyword(tmp_path, capsys):
|
||||
db_path = tmp_path / "test.sqlite"
|
||||
init_db(db_path)
|
||||
|
||||
Reference in New Issue
Block a user