Two milestones: a note can carry its own check (317), and a rule keeps what it used to say (323) #135

Merged
bvandeusen merged 23 commits from dev into main 2026-08-31 00:01:15 -04:00
Showing only changes of commit 4736a0a0ba - Show all commits
+15 -6
View File
@@ -1,13 +1,23 @@
"""Tests for task lifecycle timestamps and recurrence logic.""" """Tests for task lifecycle timestamps and recurrence logic.
The note stand-ins come from `fake_note`, not a bare MagicMock. These four
predated that helper and broke the moment `update_note` started reading a
column they did not set (milestone 317): a fresh MagicMock is truthy on every
attribute, so `verify_with` read as "this record carries a check" and the
guard refused the write. That is note 2109's lesson, and the reason fake_note
exists — a stand-in has to be able to say NO.
"""
from datetime import date, datetime, timezone from datetime import date, datetime, timezone
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from tests.helpers import fake_note, make_mock_session
# ── Timestamp side-effect tests ────────────────────────────────────────────── # ── Timestamp side-effect tests ──────────────────────────────────────────────
async def test_update_note_sets_started_at_on_in_progress(): async def test_update_note_sets_started_at_on_in_progress():
"""started_at is set when status transitions to in_progress.""" """started_at is set when status transitions to in_progress."""
mock_note = MagicMock() mock_note = fake_note()
mock_note.status = "in_progress" mock_note.status = "in_progress"
mock_note.started_at = None mock_note.started_at = None
mock_note.recurrence_rule = None mock_note.recurrence_rule = None
@@ -28,7 +38,7 @@ async def test_update_note_sets_started_at_on_in_progress():
async def test_update_note_sets_completed_at_on_done(): async def test_update_note_sets_completed_at_on_done():
"""completed_at is set when status transitions to done.""" """completed_at is set when status transitions to done."""
mock_note = MagicMock() mock_note = fake_note()
mock_note.status = "done" mock_note.status = "done"
mock_note.started_at = datetime(2026, 3, 1, tzinfo=timezone.utc) mock_note.started_at = datetime(2026, 3, 1, tzinfo=timezone.utc)
mock_note.completed_at = None mock_note.completed_at = None
@@ -50,7 +60,7 @@ async def test_update_note_sets_completed_at_on_done():
async def test_update_note_clears_timestamps_on_todo(): async def test_update_note_clears_timestamps_on_todo():
"""started_at and completed_at are cleared when status resets to todo.""" """started_at and completed_at are cleared when status resets to todo."""
mock_note = MagicMock() mock_note = fake_note()
mock_note.status = "todo" mock_note.status = "todo"
mock_note.started_at = datetime(2026, 3, 1, tzinfo=timezone.utc) mock_note.started_at = datetime(2026, 3, 1, tzinfo=timezone.utc)
mock_note.completed_at = datetime(2026, 3, 15, tzinfo=timezone.utc) mock_note.completed_at = datetime(2026, 3, 15, tzinfo=timezone.utc)
@@ -74,7 +84,7 @@ async def test_update_note_clears_timestamps_on_todo():
async def test_update_note_preserves_started_at_if_already_set(): async def test_update_note_preserves_started_at_if_already_set():
"""started_at is not overwritten on a second transition to in_progress.""" """started_at is not overwritten on a second transition to in_progress."""
original_start = datetime(2026, 3, 1, tzinfo=timezone.utc) original_start = datetime(2026, 3, 1, tzinfo=timezone.utc)
mock_note = MagicMock() mock_note = fake_note()
mock_note.status = "in_progress" mock_note.status = "in_progress"
mock_note.started_at = original_start mock_note.started_at = original_start
mock_note.recurrence_rule = None mock_note.recurrence_rule = None
@@ -96,7 +106,6 @@ async def test_update_note_preserves_started_at_if_already_set():
# ── Recurrence rule validation ──────────────────────────────────────────────── # ── Recurrence rule validation ────────────────────────────────────────────────
import pytest import pytest
from tests.helpers import make_mock_session
def test_validate_interval_rule_valid(): def test_validate_interval_rule_valid():