feat: preserve Today before Week Ahead rollover (Closes #1182)
CI / lint (pull_request) Successful in 3m25s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m37s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-20 14:33:15 +00:00
1 parent 3edc4e36b3
commit f0706e2e6f
5 files changed
+104 -8

No files matched your search

+50 -2
View File
@@ -4,7 +4,7 @@ from datetime import datetime, timezone
import pytest
from src import main
from src.today_store import TodayStore, WeekPlanConflict
from src.today_store import TodayPromotionConflict, TodayStore, WeekPlanConflict
def sample_days():
@@ -57,7 +57,7 @@ def test_week_promotion_moves_only_due_date_to_today_exactly_once(tmp_path):
encryption_key=b"p" * 32,
clock=lambda: datetime(2026, 8, 22, 8, tzinfo=timezone.utc).timestamp(),
)
today = store.apply("timmy", "today", "add", "issue:r:1:")
today = store.get("timmy")
week = store.replace_week("timmy", base_revision=0, days=sample_days(), timezone="UTC")
promoted = store.promote_week(
@@ -76,6 +76,54 @@ def test_week_promotion_moves_only_due_date_to_today_exactly_once(tmp_path):
assert remaining["days"] == [sample_days()[1]]
def test_week_promotion_preserves_nonempty_today_and_the_due_week(tmp_path):
store = TodayStore(
tmp_path / "today.sqlite3",
encryption_key=b"p" * 32,
clock=lambda: datetime(2026, 8, 22, 8, tzinfo=timezone.utc).timestamp(),
)
today = store.apply("timmy", "active", "add", "issue:r:active:")
week = store.replace_week("timmy", base_revision=0, days=sample_days(), timezone="UTC")
with pytest.raises(TodayPromotionConflict) as blocked:
store.promote_week(
"timmy", promotion_id="week-2026-08-21-r1", week_revision=week["revision"],
plan_date="2026-08-21", today_revision=today["revision"],
)
assert blocked.value.today == today
assert blocked.value.week == week
assert store.get("timmy") == today
assert store.get_week("timmy") == week
@pytest.mark.anyio
async def test_week_promotion_api_returns_both_preserved_plans_for_review(monkeypatch, tmp_path):
async def user():
return {"login": "timmy"}
store = TodayStore(
tmp_path / "today.sqlite3",
encryption_key=b"p" * 32,
clock=lambda: datetime(2026, 8, 22, 8, tzinfo=timezone.utc).timestamp(),
)
today = store.apply("timmy", "active", "add", "issue:r:active:")
week = store.replace_week("timmy", base_revision=0, days=sample_days(), timezone="UTC")
monkeypatch.setattr(main, "current_user", user)
monkeypatch.setattr(main, "_today_store", lambda: store)
with pytest.raises(main.HTTPException) as blocked:
await main.promote_week_plan(main.WeekPromotion(
promotion_id="week-2026-08-21-r1", week_revision=week["revision"],
plan_date="2026-08-21", today_revision=today["revision"],
))
assert blocked.value.status_code == 409
assert blocked.value.detail == {
"code": "week_today_in_progress", "today": today, "week": week,
}
def test_existing_tomorrow_plan_migrates_into_week_without_losing_planning_data(tmp_path):
store = TodayStore(tmp_path / "today.sqlite3", encryption_key=b"m" * 32)
tomorrow = store.replace_tomorrow(
+28
View File
@@ -230,6 +230,34 @@ console.log(JSON.stringify({promoted,requests}));
}]
def test_week_workflow_routes_blocked_rollover_into_today_review():
result = run_controller("""
const createWorkflow=createWeekPlan.Workflow;
const elements={'#mobile-week-summary':{textContent:''},'#my-work-action-status':{textContent:''}};
let opened=0,replaced=0,refreshed=0;
const blocked=new Error('blocked');blocked.code='week_today_in_progress';
const workflow=createWorkflow({
controller:{load:async()=>({}),promote:async()=>{throw blocked;},summary:()=> '1 item across 1 day'},
qs:selector=>elements[selector],getLogin:()=> 'timmy',openPlanner:()=>{opened+=1;},
escapeHtml:value=>value,escapeAttribute:value=>value,
todayWork:{replace:()=>{replaced+=1;},replacePlanning:()=>{replaced+=1;}},
refresh:()=>{refreshed+=1;},warm:()=>{},
});
const promoted=await workflow.promote({revision:4,ids:['unfinished']});
const retried=await workflow.promote({revision:4,ids:['unfinished']});
console.log(JSON.stringify({promoted,retried,opened,replaced,refreshed,status:elements['#my-work-action-status'].textContent}));
""")
assert result == {
"promoted": False,
"retried": False,
"opened": 1,
"replaced": 0,
"refreshed": 0,
"status": "Review unfinished Today before starting the saved Week Ahead day.",
}
def test_week_controller_does_not_promote_a_pending_week():
result = run_controller("""
const values=new Map();