feat: preserve Today before Week Ahead rollover (Closes #1182)
This commit is contained in:
1 parent
3edc4e36b3
commit
f0706e2e6f
5 files changed
+104
-8
No files matched your search
+50
-2
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user