feat: queue offline Week-to-Today pulls (Closes #1249)
CI / lint (pull_request) Failing after 33s
CI / build-release (pull_request) Has been skipped
CI / browser-journey (pull_request) Has been skipped
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-22 03:31:34 +00:00
1 parent 317fc7371a
commit 7c4e798776
2 files changed
+129 -24

No files matched your search

+74
View File
@@ -358,6 +358,80 @@ console.log(JSON.stringify({pending,resume,confirmed,requests,keys:[...values.ke
assert result["state"]["revision"] == 8
def test_week_controller_queues_two_offline_pulls_and_replays_them_fifo_after_reload():
result = run_controller("""
const values=new Map(),delivered=[];
const storage={getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
let offline=true;
let serverToday={revision:4,ids:['active'],capacity_minutes:180,estimates:{active:30}};
let serverWeek={revision:7,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['first','second','later'],capacity_minutes:150,estimates:{first:35,second:40,later:45}}]};
const fetchJson=async(url,options={})=>{
if(offline)throw new Error('connection lost');
const body=JSON.parse(options.body);delivered.push(body);
if(body.today_revision!==serverToday.revision||body.week_revision!==serverWeek.revision){const error=new Error('changed');error.status=409;throw error;}
serverToday={...serverToday,revision:serverToday.revision+1,ids:[...serverToday.ids,body.identity],
estimates:{...serverToday.estimates,[body.identity]:serverWeek.days[0].estimates[body.identity]}};
serverWeek={...serverWeek,revision:serverWeek.revision+1,days:serverWeek.days.map(day=>({...day,
ids:day.ids.filter(id=>id!==body.identity),estimates:Object.fromEntries(Object.entries(day.estimates).filter(([id])=>id!==body.identity))}))};
return {today:serverToday,week:serverWeek};
};
const options={storage,getLogin:()=> 'Timmy',fetchJson,localDate:()=> '2026-08-20',timeZone:()=> 'UTC'};
const first=createWeekPlan(options);first.adopt(serverWeek);
const queuedFirst=await first.pullItem('first',serverToday,'pull-first');
const queuedSecond=await first.pullItem('second',queuedFirst.today,'pull-second');
const restored=createWeekPlan(options),resumed=restored.resumePull();
offline=false;
const confirmed=await restored.flushPull();
console.log(JSON.stringify({queuedFirst,queuedSecond,resumed,confirmed,delivered,keys:[...values.keys()],state:restored.state()}));
""")
assert result["queuedFirst"]["today"]["ids"] == ["active", "first"]
assert result["queuedSecond"]["today"]["ids"] == ["active", "first", "second"]
assert result["resumed"]["today"]["ids"] == ["active", "first", "second"]
assert [request["operation_id"] for request in result["delivered"]] == ["pull-first", "pull-second"]
assert result["delivered"][1]["today_revision"] == 5
assert result["delivered"][1]["week_revision"] == 8
assert result["confirmed"]["today"]["ids"] == ["active", "first", "second"]
assert result["confirmed"]["week"]["days"][0]["ids"] == ["later"]
assert result["confirmed"]["sync_pending"] is False
assert result["keys"] == ["stackchain.week-confirmed.v1.timmy"]
assert result["state"]["revision"] == 9
def test_week_controller_resumes_server_truth_when_a_fifo_head_conflicts():
result = run_controller("""
const values=new Map();
const storage={getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
let offline=true,posts=0;
const remoteToday={revision:10,ids:['server-today'],capacity_minutes:180,estimates:{'server-today':30}};
const remoteWeek={revision:12,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['second','third','server-week'],capacity_minutes:180,estimates:{second:40,third:45,'server-week':25}}]};
const fetchJson=async(url,options={})=>{
if(offline)throw new Error('connection lost');
if(!options.method)return url.endsWith('/today')?remoteToday:remoteWeek;
posts+=1;
if(posts===1)return {today:{revision:5,ids:['active','first'],capacity_minutes:180,estimates:{active:30,first:35}},
week:{revision:8,timezone:'UTC',days:[{plan_date:'2026-08-21',ids:['second','third'],capacity_minutes:180,estimates:{second:40,third:45}}]}};
const error=new Error('changed');error.status=409;throw error;
};
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:['first','second','third'],capacity_minutes:180,estimates:{first:35,second:40,third:45}}]});
let today={revision:4,ids:['active'],capacity_minutes:180,estimates:{active:30}};
today=(await first.pullItem('first',today,'pull-first')).today;
today=(await first.pullItem('second',today,'pull-second')).today;
await first.pullItem('third',today,'pull-third');
offline=false;const conflicted=await first.flushPull();
const restored=createWeekPlan(options),resumed=restored.resumePull();
console.log(JSON.stringify({conflicted,resumed,state:restored.state(),pending:restored.pendingPull()}));
""")
assert result["conflicted"]["conflict"] is True
assert result["resumed"]["conflict"] is True
assert result["resumed"]["today"]["ids"] == ["server-today"]
assert result["state"]["revision"] == 12
assert result["state"]["days"][0]["ids"] == ["second", "third", "server-week"]
assert result["pending"]["body"]["operation_id"] == "pull-second"
def test_week_controller_preserves_a_conflicted_pull_intent_and_adopts_server_truth():
result = run_controller("""
const values=new Map(),requests=[];