feat: keep Week-to-Today pulls through reloads (Closes #1246)
This commit is contained in:
1 parent
a13a13d90c
commit
c7d38593dd
3 files changed
+179
-10
No files matched your search
@@ -326,6 +326,68 @@ console.log(JSON.stringify({pulled,requests,state:week.state()}));
|
||||
assert result["state"]["days"][0]["ids"] == ["sibling"]
|
||||
|
||||
|
||||
def test_week_controller_replays_a_lost_pull_receipt_with_the_same_operation_after_reload():
|
||||
result = run_controller("""
|
||||
const values=new Map(),requests=[];
|
||||
const storage={getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
|
||||
let loseResponse=true;
|
||||
const fetchJson=async(url,options={})=>{
|
||||
const body=JSON.parse(options.body);requests.push(body);
|
||||
if(loseResponse){loseResponse=false;throw new Error('connection lost');}
|
||||
return {today:{revision:5,ids:['active',body.identity],capacity_minutes:120,estimates:{active:30,[body.identity]:45}},
|
||||
week:{revision:8,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['sibling'],capacity_minutes:90,estimates:{sibling:30}}]}};
|
||||
};
|
||||
const options={storage,getLogin:()=> 'Timmy',fetchJson,localDate:()=> '2026-08-20',timeZone:()=> 'UTC'};
|
||||
const first=createWeekPlan(options);
|
||||
first.adopt({revision:7,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['pull','sibling'],capacity_minutes:90,estimates:{pull:45,sibling:30}}]});
|
||||
const pending=await first.pullItem('pull',{revision:4,ids:['active'],capacity_minutes:120,estimates:{active:30}},'stable-pull');
|
||||
const restored=createWeekPlan(options),resume=restored.resumePull();
|
||||
const confirmed=await restored.flushPull();
|
||||
console.log(JSON.stringify({pending,resume,confirmed,requests,keys:[...values.keys()],state:restored.state()}));
|
||||
""")
|
||||
|
||||
assert result["pending"]["sync_pending"] is True
|
||||
assert result["pending"]["today"]["ids"] == ["active", "pull"]
|
||||
assert result["pending"]["week"]["days"][0]["ids"] == ["sibling"]
|
||||
assert result["resume"]["sync_pending"] is True
|
||||
assert result["resume"]["today"] == result["pending"]["today"]
|
||||
assert [request["operation_id"] for request in result["requests"]] == ["stable-pull", "stable-pull"]
|
||||
assert result["confirmed"]["sync_pending"] is False
|
||||
assert result["confirmed"]["today"]["revision"] == 5
|
||||
assert result["keys"] == ["stackchain.week-confirmed.v1.timmy"]
|
||||
assert result["state"]["revision"] == 8
|
||||
|
||||
|
||||
def test_week_controller_preserves_a_conflicted_pull_intent_and_adopts_server_truth():
|
||||
result = run_controller("""
|
||||
const values=new Map(),requests=[];
|
||||
const storage={getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
|
||||
const remoteToday={revision:9,ids:['newer'],capacity_minutes:90,estimates:{newer:30}};
|
||||
const remoteWeek={revision:12,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['pull','server'],capacity_minutes:90,estimates:{pull:45,server:30}}]};
|
||||
const fetchJson=async(url,options={})=>{
|
||||
requests.push({url,method:options.method||'GET'});
|
||||
if(options.method){const error=new Error('changed');error.status=409;throw error;}
|
||||
return url.endsWith('/today')?remoteToday:remoteWeek;
|
||||
};
|
||||
const week=createWeekPlan({storage,getLogin:()=> 'timmy',fetchJson,localDate:()=> '2026-08-20',timeZone:()=> 'UTC'});
|
||||
week.adopt({revision:7,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['pull'],capacity_minutes:90,estimates:{pull:45}}]});
|
||||
const outcome=await week.pullItem('pull',{revision:4,ids:['active'],capacity_minutes:90,estimates:{active:30}},'conflicted-pull');
|
||||
console.log(JSON.stringify({outcome,state:week.state(),pending:week.pendingPull(),requests}));
|
||||
""")
|
||||
|
||||
assert result["outcome"]["conflict"] is True
|
||||
assert result["outcome"]["today"]["ids"] == ["newer"]
|
||||
assert result["state"]["revision"] == 12
|
||||
assert result["state"]["days"][0]["ids"] == ["pull", "server"]
|
||||
assert result["pending"]["body"]["operation_id"] == "conflicted-pull"
|
||||
assert result["pending"]["conflict"] is True
|
||||
assert result["requests"] == [
|
||||
{"url": "api/v1/week/pull-item", "method": "POST"},
|
||||
{"url": "api/v1/today", "method": "GET"},
|
||||
{"url": "api/v1/week", "method": "GET"},
|
||||
]
|
||||
|
||||
|
||||
def test_week_controller_retires_completed_work_from_every_day_in_one_durable_transition():
|
||||
result = run_controller("""
|
||||
const values=new Map();let writes=0;
|
||||
@@ -761,6 +823,20 @@ def test_dashboard_routes_week_overview_controls_through_existing_detail_flow_wi
|
||||
assert "width:100%" in pull_rule
|
||||
|
||||
|
||||
def test_dashboard_restores_and_retries_a_saved_week_to_today_pull_on_mobile_lifecycle():
|
||||
dashboard = (FRONTEND / "dashboard.js").read_text()
|
||||
week_plan = (FRONTEND / "week-plan.js").read_text()
|
||||
|
||||
recovery = dashboard.split(" function syncPendingTomorrow()", 1)[1].split("\n }", 1)[0]
|
||||
assert "weekFlow.resumePull()" in recovery
|
||||
assert "weekFlow.flushPull()" in recovery
|
||||
|
||||
lifecycle = dashboard.split("tomorrowPlan.startLifecycle({", 1)[1].split(" });", 1)[0]
|
||||
assert "syncPendingTomorrow()" in lifecycle
|
||||
assert dashboard.count("syncPendingTomorrow();") >= 2
|
||||
assert "result.sync_pending?String(item?.title||'Work')+' added to Today · sync pending.'" in week_plan
|
||||
|
||||
|
||||
def test_successful_issue_close_notifies_week_ahead_only_after_upstream_confirmation():
|
||||
issue_sheet = (FRONTEND / "issue-sheet.js").read_text()
|
||||
week_plan = (FRONTEND / "week-plan.js").read_text()
|
||||
|
||||
Reference in new issue
Block a user