@@ -2,6 +2,7 @@ package handlers
22
33import (
44 "bytes"
5+ "context"
56 "encoding/json"
67 "fmt"
78 "net/http"
@@ -18,6 +19,29 @@ import (
1819 "github.com/voidrunnerhq/voidrunner/pkg/logger"
1920)
2021
22+ // MockTaskExecutionService is a mock implementation of TaskExecutionServiceInterface
23+ type MockTaskExecutionService struct {
24+ mock.Mock
25+ }
26+
27+ func (m * MockTaskExecutionService ) CreateExecutionAndUpdateTaskStatus (ctx context.Context , taskID uuid.UUID , userID uuid.UUID ) (* models.TaskExecution , error ) {
28+ args := m .Called (ctx , taskID , userID )
29+ if args .Get (0 ) == nil {
30+ return nil , args .Error (1 )
31+ }
32+ return args .Get (0 ).(* models.TaskExecution ), args .Error (1 )
33+ }
34+
35+ func (m * MockTaskExecutionService ) CancelExecutionAndResetTaskStatus (ctx context.Context , executionID uuid.UUID , userID uuid.UUID ) error {
36+ args := m .Called (ctx , executionID , userID )
37+ return args .Error (0 )
38+ }
39+
40+ func (m * MockTaskExecutionService ) CompleteExecutionAndFinalizeTaskStatus (ctx context.Context , execution * models.TaskExecution , taskStatus models.TaskStatus , userID uuid.UUID ) error {
41+ args := m .Called (ctx , execution , taskStatus , userID )
42+ return args .Error (0 )
43+ }
44+
2145// HandlerIntegrationTest tests the interaction between handlers and middleware
2246func TestHandlerIntegration_TaskWithValidation (t * testing.T ) {
2347 gin .SetMode (gin .TestMode )
@@ -28,8 +52,9 @@ func TestHandlerIntegration_TaskWithValidation(t *testing.T) {
2852 logger := logger .New ("test" , "debug" )
2953
3054 // Setup handlers
55+ mockExecutionService := new (MockTaskExecutionService )
3156 taskHandler := NewTaskHandler (mockTaskRepo , logger .Logger )
32- executionHandler := NewTaskExecutionHandler (mockTaskRepo , mockExecutionRepo , logger .Logger )
57+ executionHandler := NewTaskExecutionHandler (mockTaskRepo , mockExecutionRepo , mockExecutionService , logger .Logger )
3358 validationMiddleware := middleware .TaskValidation (logger .Logger )
3459
3560 // Setup router with middleware
@@ -169,7 +194,8 @@ func TestTaskExecutionIntegration(t *testing.T) {
169194 logger := logger .New ("test" , "debug" )
170195
171196 // Setup handlers
172- executionHandler := NewTaskExecutionHandler (mockTaskRepo , mockExecutionRepo , logger .Logger )
197+ mockExecutionService := new (MockTaskExecutionService )
198+ executionHandler := NewTaskExecutionHandler (mockTaskRepo , mockExecutionRepo , mockExecutionService , logger .Logger )
173199
174200 // Setup router
175201 router := gin .New ()
@@ -207,9 +233,15 @@ func TestTaskExecutionIntegration(t *testing.T) {
207233 Status : models .TaskStatusPending ,
208234 }
209235
210- mockTaskRepo .On ("GetByID" , mock .Anything , taskID ).Return (task , nil ).Once ()
211- mockExecutionRepo .On ("Create" , mock .Anything , mock .AnythingOfType ("*models.TaskExecution" )).Return (nil ).Once ()
212- mockTaskRepo .On ("UpdateStatus" , mock .Anything , taskID , models .TaskStatusRunning ).Return (nil ).Once ()
236+ // Create expected execution object
237+ expectedExecution := & models.TaskExecution {
238+ ID : executionID ,
239+ TaskID : taskID ,
240+ Status : models .ExecutionStatusPending ,
241+ }
242+
243+ // Mock the service call instead of repository calls
244+ mockExecutionService .On ("CreateExecutionAndUpdateTaskStatus" , mock .Anything , taskID , userID ).Return (expectedExecution , nil ).Once ()
213245
214246 httpReq := httptest .NewRequest (http .MethodPost , fmt .Sprintf ("/tasks/%s/executions" , taskID ), nil )
215247 w := httptest .NewRecorder ()
@@ -244,10 +276,14 @@ func TestTaskExecutionIntegration(t *testing.T) {
244276 }
245277
246278 execution .Status = models .ExecutionStatusCompleted
279+ execution .ReturnCode = & returnCode
280+ execution .Stdout = & stdout
281+
282+ // For terminal updates, the Update handler only calls executionRepo.GetByID, then uses the service for atomic completion
283+ // The service itself handles task validation, so no direct taskRepo.GetByID call is made by the handler
247284 mockExecutionRepo .On ("GetByID" , mock .Anything , executionID ).Return (execution , nil ).Once ()
248- mockTaskRepo .On ("GetByID" , mock .Anything , taskID ).Return (task , nil ).Once ()
249- mockExecutionRepo .On ("Update" , mock .Anything , mock .AnythingOfType ("*models.TaskExecution" )).Return (nil ).Once ()
250- mockTaskRepo .On ("UpdateStatus" , mock .Anything , taskID , models .TaskStatusCompleted ).Return (nil ).Once ()
285+ // Mock the service call for atomic completion (handles all validation and updates internally)
286+ mockExecutionService .On ("CompleteExecutionAndFinalizeTaskStatus" , mock .Anything , mock .AnythingOfType ("*models.TaskExecution" ), models .TaskStatusCompleted , userID ).Return (nil ).Once ()
251287
252288 reqBody , _ := json .Marshal (updateReq )
253289 httpReq = httptest .NewRequest (http .MethodPut , fmt .Sprintf ("/executions/%s" , executionID ), bytes .NewBuffer (reqBody ))
@@ -257,20 +293,13 @@ func TestTaskExecutionIntegration(t *testing.T) {
257293
258294 assert .Equal (t , http .StatusOK , w .Code )
259295
260- mockTaskRepo .AssertExpectations (t )
261296 mockExecutionRepo .AssertExpectations (t )
297+ mockExecutionService .AssertExpectations (t )
262298 })
263299
264300 t .Run ("Cannot Start Execution on Running Task" , func (t * testing.T ) {
265- runningTask := & models.Task {
266- BaseModel : models.BaseModel {
267- ID : taskID ,
268- },
269- UserID : userID ,
270- Status : models .TaskStatusRunning , // Already running
271- }
272-
273- mockTaskRepo .On ("GetByID" , mock .Anything , taskID ).Return (runningTask , nil ).Once ()
301+ // Mock the service to return an error for already running task
302+ mockExecutionService .On ("CreateExecutionAndUpdateTaskStatus" , mock .Anything , taskID , userID ).Return (nil , fmt .Errorf ("task is already running" )).Once ()
274303
275304 httpReq := httptest .NewRequest (http .MethodPost , fmt .Sprintf ("/tasks/%s/executions" , taskID ), nil )
276305 w := httptest .NewRecorder ()
@@ -283,25 +312,12 @@ func TestTaskExecutionIntegration(t *testing.T) {
283312 require .NoError (t , err )
284313 assert .Contains (t , errorResponse ["error" ], "already running" )
285314
286- mockTaskRepo .AssertExpectations (t )
315+ mockExecutionService .AssertExpectations (t )
287316 })
288317
289318 t .Run ("Cannot Cancel Completed Execution" , func (t * testing.T ) {
290- completedExecution := & models.TaskExecution {
291- ID : executionID ,
292- TaskID : taskID ,
293- Status : models .ExecutionStatusCompleted , // Already completed
294- }
295-
296- task := & models.Task {
297- BaseModel : models.BaseModel {
298- ID : taskID ,
299- },
300- UserID : userID ,
301- }
302-
303- mockExecutionRepo .On ("GetByID" , mock .Anything , executionID ).Return (completedExecution , nil ).Once ()
304- mockTaskRepo .On ("GetByID" , mock .Anything , taskID ).Return (task , nil ).Once ()
319+ // Mock the service to return an error for completed execution
320+ mockExecutionService .On ("CancelExecutionAndResetTaskStatus" , mock .Anything , executionID , userID ).Return (fmt .Errorf ("cannot cancel execution with status: completed" )).Once ()
305321
306322 httpReq := httptest .NewRequest (http .MethodDelete , fmt .Sprintf ("/executions/%s" , executionID ), nil )
307323 w := httptest .NewRecorder ()
@@ -312,10 +328,9 @@ func TestTaskExecutionIntegration(t *testing.T) {
312328 var errorResponse map [string ]interface {}
313329 err := json .Unmarshal (w .Body .Bytes (), & errorResponse )
314330 require .NoError (t , err )
315- assert .Contains (t , errorResponse ["error" ], "Cannot cancel completed execution" )
331+ assert .Contains (t , errorResponse ["error" ], "cannot cancel execution with status: " )
316332
317- mockExecutionRepo .AssertExpectations (t )
318- mockTaskRepo .AssertExpectations (t )
333+ mockExecutionService .AssertExpectations (t )
319334 })
320335}
321336
0 commit comments