feat: queue offline Week-to-Today pulls (Closes #1249)
This commit is contained in:
1 parent
317fc7371a
commit
7c4e798776
2 files changed
+129
-24
No files matched your search
@@ -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=[];
|
||||
|
||||
Reference in new issue
Block a user