@@ -6,7 +6,7 @@ import * as os from "os"
66
77import type { HistoryItem } from "@roo-code/types"
88
9- import { TaskHistoryStore } from "../TaskHistoryStore"
9+ import { TaskHistoryStore , assertValidTransition } from "../TaskHistoryStore"
1010import { GlobalFileNames } from "../../../shared/globalFileNames"
1111
1212vi . mock ( "../../../utils/storage" , ( ) => ( {
@@ -641,5 +641,54 @@ describe("TaskHistoryStore", () => {
641641
642642 storeWithCallback . dispose ( )
643643 } )
644+
645+ it ( "propagates throw from first updater — neither record is written" , async ( ) => {
646+ await store . initialize ( )
647+
648+ const child = makeHistoryItem ( { id : "child-updater-throw" , status : "active" } )
649+ const parent = makeHistoryItem ( { id : "parent-updater-throw" , status : "delegated" } )
650+ await store . upsert ( child )
651+ await store . upsert ( parent )
652+
653+ await expect (
654+ store . atomicUpdatePair (
655+ "child-updater-throw" ,
656+ "parent-updater-throw" ,
657+ ( _c ) => {
658+ throw new Error ( "updater exploded" )
659+ } ,
660+ ( p ) => p ,
661+ ) ,
662+ ) . rejects . toThrow ( "updater exploded" )
663+
664+ // Neither record should have been modified
665+ expect ( store . get ( "child-updater-throw" ) ?. status ) . toBe ( "active" )
666+ expect ( store . get ( "parent-updater-throw" ) ?. status ) . toBe ( "delegated" )
667+ } )
668+
669+ it ( "transition guard in first updater: delegated → completed throws assertValidTransition" , async ( ) => {
670+ await store . initialize ( )
671+
672+ const child = makeHistoryItem ( { id : "child-guard-pair" , status : "delegated" } )
673+ const parent = makeHistoryItem ( { id : "parent-guard-pair" , status : "active" } )
674+ await store . upsert ( child )
675+ await store . upsert ( parent )
676+
677+ await expect (
678+ store . atomicUpdatePair (
679+ "child-guard-pair" ,
680+ "parent-guard-pair" ,
681+ ( c ) => {
682+ assertValidTransition ( c . status , "completed" )
683+ return { ...c , status : "completed" as const }
684+ } ,
685+ ( p ) => p ,
686+ ) ,
687+ ) . rejects . toThrow ( "Invalid task status transition: delegated → completed" )
688+
689+ // Original statuses preserved
690+ expect ( store . get ( "child-guard-pair" ) ?. status ) . toBe ( "delegated" )
691+ expect ( store . get ( "parent-guard-pair" ) ?. status ) . toBe ( "active" )
692+ } )
644693 } )
645694} )
0 commit comments