fix: retry unchanged reschedule intent after stale revision (Closes #1311)
CI / lint (pull_request) Successful in 3m47s
CI / build-release (pull_request) Successful in 8s
CI / browser-journey (pull_request) Successful in 6m58s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-23 15:43:30 +00:00
1 parent 544ffb4ae7
commit 75846cf045
2 files changed
+106 -5

No files matched your search

+67
View File
@@ -64,6 +64,73 @@ console.log(JSON.stringify({opened,confirmed,requests,adoptedToday,weekState}));
assert result["confirmed"]["today"]["revision"] == 5
def test_reschedule_controller_retries_one_stale_revision_when_active_intent_is_unchanged():
result = run_controller("""
const requests=[];
let attempts=0;
const weekState={revision:7,offline_snapshot:false,timezone:'UTC',days:[
{plan_date:'2026-08-25',ids:[],capacity_minutes:90,estimates:{}}
]};
const week={
load:async()=>weekState,
review:()=>({days:[{...weekState.days[0],label:'Tue',planned_minutes:0}]}),
adopt:value=>Object.assign(weekState,value),
};
const api=async(url,options)=>{
if(url==='api/v1/today')return {revision:5,ids:['active'],estimates:{active:45}};
if(url==='api/v1/week')return {revision:8,offline_snapshot:false,timezone:'UTC',days:[
{plan_date:'2026-08-25',ids:[],capacity_minutes:90,estimates:{}}
]};
const body=JSON.parse(options.body);requests.push(body);attempts++;
if(attempts===1){const error=new Error('stale revision');error.status=409;throw error;}
return {today:{revision:6,ids:[],estimates:{}},week:{revision:9,timezone:'UTC',days:[
{plan_date:'2026-08-25',ids:['active'],capacity_minutes:90,estimates:{active:45}}
]}};
};
const controller=createReschedule({week,api,getToday:()=>null,adoptToday:()=>{},operationId:()=> 'move-active'});
await controller.open('active');
let message='',confirmed=null;
try{confirmed=await controller.confirm('2026-08-25',45);}catch(error){message=error.message;}
console.log(JSON.stringify({message,confirmed,requests}));
""")
assert result["message"] == ""
assert result["confirmed"]["today"]["revision"] == 6
assert [request["today_revision"] for request in result["requests"]] == [5, 5]
assert [request["week_revision"] for request in result["requests"]] == [7, 8]
assert result["requests"][0]["operation_id"] == result["requests"][1]["operation_id"]
def test_reschedule_controller_rebases_admitted_move_once_after_stale_revision():
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)};
const requests=[];let attempts=0;
const initialWeek={revision:7,offline_snapshot:false,timezone:'UTC',days:[
{plan_date:'2026-08-25',ids:[],capacity_minutes:90,estimates:{}}
]};
const week={load:async()=>initialWeek,review:()=>({days:[{...initialWeek.days[0],label:'Tue',planned_minutes:0}]}),adopt:()=>{}};
const api=async(url,options)=>{
if(url==='api/v1/today')return {revision:5,ids:['active'],estimates:{active:45}};
if(url==='api/v1/week')return {revision:8,offline_snapshot:false,timezone:'UTC',days:initialWeek.days};
const body=JSON.parse(options.body);requests.push(body);attempts++;
if(attempts===1){const error=new Error('stale revision');error.status=409;throw error;}
return {today:{revision:6,ids:[],estimates:{}},week:{revision:9,timezone:'UTC',days:[]}};
};
const controller=createReschedule({week,api,adoptToday:()=>{},storage,getLogin:()=> 'timmy',operationId:()=> 'move-active'});
await controller.open('active');
let message='',confirmed=null;
try{confirmed=await controller.confirm('2026-08-25',45);}catch(error){message=error.message;}
console.log(JSON.stringify({message,confirmed,requests,remaining:values.size}));
""")
assert result["message"] == ""
assert result["confirmed"]["today"]["revision"] == 6
assert [request["today_revision"] for request in result["requests"]] == [5, 5]
assert [request["week_revision"] for request in result["requests"]] == [7, 8]
assert result["remaining"] == 0
def test_reschedule_controller_blocks_offline_full_and_overloaded_days_without_mutation():
result = run_controller("""
let calls=0;