@@ -5,6 +5,7 @@ import type { MockedFunction } from "vitest"
55import { fileExistsAtPath } from "../../../utils/fs"
66import { isPathOutsideWorkspace } from "../../../utils/pathUtils"
77import type { Task } from "../../task/Task"
8+ import { checkpointSave } from "../../checkpoints"
89import { ApplyPatchTool } from "../ApplyPatchTool"
910
1011vi . mock ( "fs/promises" , ( ) => ( {
@@ -22,6 +23,10 @@ vi.mock("../../../utils/pathUtils", () => ({
2223 isPathOutsideWorkspace : vi . fn ( ) . mockReturnValue ( false ) ,
2324} ) )
2425
26+ vi . mock ( "../../checkpoints" , ( ) => ( {
27+ checkpointSave : vi . fn ( ) . mockResolvedValue ( undefined ) ,
28+ } ) )
29+
2530describe ( "ApplyPatchTool.execute - delete file success path" , ( ) => {
2631 const mockedFileExistsAtPath = fileExistsAtPath as MockedFunction < typeof fileExistsAtPath >
2732 const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction < typeof isPathOutsideWorkspace >
@@ -38,6 +43,7 @@ describe("ApplyPatchTool.execute - delete file success path", () => {
3843 | "say"
3944 | "processQueuedMessages"
4045 | "didEditFile"
46+ | "providerRef"
4147 >
4248 let mockAskApproval : MockedFunction < ( ...args : unknown [ ] ) => Promise < boolean > >
4349 let mockHandleError : MockedFunction < ( ...args : unknown [ ] ) => Promise < void > >
@@ -52,6 +58,11 @@ describe("ApplyPatchTool.execute - delete file success path", () => {
5258 mockTask = {
5359 cwd : "/workspace/project" ,
5460 consecutiveMistakeCount : 0 ,
61+ providerRef : {
62+ deref : vi . fn ( ) . mockReturnValue ( {
63+ getState : vi . fn ( ) . mockResolvedValue ( { } ) ,
64+ } ) ,
65+ } as unknown as Task [ "providerRef" ] ,
5566 recordToolUsage : vi . fn ( ) ,
5667 recordToolError : vi . fn ( ) ,
5768 rooIgnoreController : {
@@ -93,4 +104,57 @@ describe("ApplyPatchTool.execute - delete file success path", () => {
93104 expect ( mockTask . recordToolUsage ) . not . toHaveBeenCalled ( )
94105 expect ( mockTask . recordToolError ) . not . toHaveBeenCalled ( )
95106 } )
107+
108+ describe ( "per-write checkpoints (B1)" , ( ) => {
109+ const deletePatch = `*** Begin Patch
110+ *** Delete File: src/obsolete.ts
111+ *** End Patch`
112+ const mockedCheckpointSave = checkpointSave as MockedFunction < typeof checkpointSave >
113+
114+ it ( "records one suppressed checkpoint for the whole patch (default-on)" , async ( ) => {
115+ await tool . execute ( { patch : deletePatch } , mockTask as Task , {
116+ askApproval : mockAskApproval ,
117+ handleError : mockHandleError ,
118+ pushToolResult : mockPushToolResult ,
119+ } )
120+
121+ expect ( mockPushToolResult ) . toHaveBeenCalledWith ( expect . stringContaining ( "Successfully deleted" ) )
122+ expect ( mockedCheckpointSave ) . toHaveBeenCalledOnce ( )
123+ expect ( mockedCheckpointSave ) . toHaveBeenCalledWith ( mockTask as Task , false , true )
124+ } )
125+
126+ it ( "does not record a checkpoint when perWriteCheckpoints is disabled" , async ( ) => {
127+ // Structural cast for the test double (matches the mock style used for the controllers above).
128+ const ref = ( mockTask [ "providerRef" ] as unknown as { deref : MockedFunction < ( ) => unknown > } ) . deref
129+ ref . mockReturnValue ( {
130+ getState : vi . fn ( ) . mockResolvedValue ( { perWriteCheckpoints : false } ) ,
131+ } )
132+
133+ await tool . execute ( { patch : deletePatch } , mockTask as Task , {
134+ askApproval : mockAskApproval ,
135+ handleError : mockHandleError ,
136+ pushToolResult : mockPushToolResult ,
137+ } )
138+
139+ expect ( mockPushToolResult ) . toHaveBeenCalledWith ( expect . stringContaining ( "Successfully deleted" ) )
140+ expect ( mockedCheckpointSave ) . not . toHaveBeenCalled ( )
141+ } )
142+
143+ it ( "does not record a checkpoint when patch processing fails" , async ( ) => {
144+ // A malformed patch fails at parse time, before the change loop and
145+ // the post-loop checkpoint hook.
146+ const badPatch = `*** Begin Patch
147+ *** This is not a valid hunk
148+ *** End Patch`
149+
150+ await tool . execute ( { patch : badPatch } , mockTask as Task , {
151+ askApproval : mockAskApproval ,
152+ handleError : mockHandleError ,
153+ pushToolResult : mockPushToolResult ,
154+ } )
155+
156+ expect ( mockTask . recordToolError ) . toHaveBeenCalledWith ( "apply_patch" )
157+ expect ( mockedCheckpointSave ) . not . toHaveBeenCalled ( )
158+ } )
159+ } )
96160} )
0 commit comments