@@ -251,6 +251,191 @@ func TestWorkerDeleteWorker(t *testing.T) {
251251 assert .Nil (t , deletedWorker , "Expected deleted worker to be nil" )
252252}
253253
254+ func TestDeleteStaleWorkers (t * testing.T ) {
255+ helper .SetTestDatabaseConfigEnvs (t , dbPort )
256+ dbConfig , err := helper .NewDatabaseConfiguration ()
257+ if err != nil {
258+ t .Fatalf ("failed to create database configuration: %v" , err )
259+ }
260+ database := helper .NewTestDatabase (dbConfig )
261+
262+ workerDbHandler , err := NewWorkerDBHandler (database , true )
263+ assert .NoError (t , err , "Expected NewWorkerDBHandler to not return an error" )
264+
265+ t .Run ("Delete stale workers with different statuses" , func (t * testing.T ) {
266+ // Create workers: STOPPED (stale), STOPPED (fresh), READY (stale), RUNNING (stale)
267+ testCases := []struct {
268+ status string
269+ makeStale bool
270+ shouldDelete bool
271+ }{
272+ {model .WorkerStatusStopped , true , true }, // Should be deleted
273+ {model .WorkerStatusStopped , false , false }, // Should not be deleted (fresh)
274+ {model .WorkerStatusReady , true , false }, // Should not be deleted (not STOPPED)
275+ {model .WorkerStatusRunning , true , false }, // Should not be deleted (not STOPPED)
276+ }
277+
278+ workers := make ([]* model.Worker , len (testCases ))
279+
280+ for i , tc := range testCases {
281+ testWorker , err := model .NewWorker (fmt .Sprintf ("worker-%d" , i ), 3 )
282+ require .NoError (t , err , "Expected to create test worker %d" , i )
283+
284+ insertedWorker , err := workerDbHandler .InsertWorker (testWorker )
285+ require .NoError (t , err , "Expected to insert worker %d" , i )
286+
287+ insertedWorker .Status = tc .status
288+ updatedWorker , err := workerDbHandler .UpdateWorker (insertedWorker )
289+ require .NoError (t , err , "Expected to update worker %d status" , i )
290+ workers [i ] = updatedWorker
291+ }
292+
293+ // Make some workers stale (older than 1 hour)
294+ staleTime := time .Now ().UTC ().Add (- 1 * time .Hour )
295+ for i , tc := range testCases {
296+ if tc .makeStale {
297+ _ , err = database .Instance .Exec (
298+ "UPDATE worker SET updated_at = $1 WHERE rid = $2" ,
299+ staleTime , workers [i ].RID ,
300+ )
301+ require .NoError (t , err , "Expected to make worker %d stale" , i )
302+ }
303+ }
304+
305+ // Test DeleteStaleWorkers - should delete only stale STOPPED workers
306+ deleteThreshold := 10 * time .Minute
307+ deletedCount , err := workerDbHandler .DeleteStaleWorkers (deleteThreshold )
308+ assert .NoError (t , err , "Expected DeleteStaleWorkers to complete successfully" )
309+ assert .Equal (t , 1 , deletedCount , "Expected 1 worker to be deleted (stale STOPPED)" )
310+
311+ // Verify only the stale STOPPED worker was deleted
312+ for i , tc := range testCases {
313+ worker , err := workerDbHandler .SelectWorker (workers [i ].RID )
314+
315+ if tc .shouldDelete {
316+ assert .Error (t , err , "Expected worker %d to be deleted" , i )
317+ assert .Nil (t , worker , "Expected deleted worker %d to be nil" , i )
318+ } else {
319+ assert .NoError (t , err , "Expected worker %d to still exist" , i )
320+ assert .NotNil (t , worker , "Expected worker %d to not be nil" , i )
321+ assert .Equal (t , tc .status , worker .Status , "Expected worker %d status to remain unchanged" , i )
322+ }
323+ }
324+
325+ // Clean up remaining workers
326+ for i , tc := range testCases {
327+ if ! tc .shouldDelete {
328+ err = workerDbHandler .DeleteWorker (workers [i ].RID )
329+ assert .NoError (t , err , "Expected to delete remaining worker %d" , i )
330+ }
331+ }
332+ })
333+
334+ t .Run ("No stale workers to delete" , func (t * testing.T ) {
335+ // Create fresh workers with different statuses
336+ statuses := []string {model .WorkerStatusReady , model .WorkerStatusRunning , model .WorkerStatusStopped }
337+ workers := make ([]* model.Worker , len (statuses ))
338+
339+ for i , status := range statuses {
340+ testWorker , err := model .NewWorker (fmt .Sprintf ("fresh-worker-%d" , i ), 3 )
341+ require .NoError (t , err , "Expected to create fresh worker %d" , i )
342+
343+ insertedWorker , err := workerDbHandler .InsertWorker (testWorker )
344+ require .NoError (t , err , "Expected to insert fresh worker %d" , i )
345+
346+ insertedWorker .Status = status
347+ updatedWorker , err := workerDbHandler .UpdateWorker (insertedWorker )
348+ require .NoError (t , err , "Expected to update fresh worker %d status" , i )
349+ workers [i ] = updatedWorker
350+ }
351+
352+ // Test with short threshold - no workers should be deleted
353+ deletedCount , err := workerDbHandler .DeleteStaleWorkers (10 * time .Second )
354+ assert .NoError (t , err , "Expected DeleteStaleWorkers to complete successfully" )
355+ assert .Equal (t , 0 , deletedCount , "Expected no workers to be deleted" )
356+
357+ // Verify all workers still exist
358+ for i , worker := range workers {
359+ existingWorker , err := workerDbHandler .SelectWorker (worker .RID )
360+ assert .NoError (t , err , "Expected fresh worker %d to still exist" , i )
361+ assert .NotNil (t , existingWorker , "Expected fresh worker %d to not be nil" , i )
362+ }
363+
364+ // Clean up
365+ for i , worker := range workers {
366+ err = workerDbHandler .DeleteWorker (worker .RID )
367+ assert .NoError (t , err , "Expected to delete fresh worker %d" , i )
368+ }
369+ })
370+
371+ t .Run ("Delete only STOPPED workers older than threshold" , func (t * testing.T ) {
372+ // Create multiple STOPPED workers with different timestamps
373+ testCases := []struct {
374+ name string
375+ ageMinutes int
376+ shouldDelete bool
377+ }{
378+ {"very-old-stopped" , 120 , true }, // 2 hours old, should be deleted
379+ {"old-stopped" , 30 , true }, // 30 minutes old, should be deleted
380+ {"recent-stopped" , 5 , false }, // 5 minutes old, should not be deleted
381+ {"fresh-stopped" , 1 , false }, // 1 minute old, should not be deleted
382+ }
383+
384+ workers := make ([]* model.Worker , len (testCases ))
385+
386+ for i , tc := range testCases {
387+ testWorker , err := model .NewWorker (tc .name , 3 )
388+ require .NoError (t , err , "Expected to create worker %s" , tc .name )
389+
390+ insertedWorker , err := workerDbHandler .InsertWorker (testWorker )
391+ require .NoError (t , err , "Expected to insert worker %s" , tc .name )
392+
393+ // Set status to STOPPED
394+ insertedWorker .Status = model .WorkerStatusStopped
395+ updatedWorker , err := workerDbHandler .UpdateWorker (insertedWorker )
396+ require .NoError (t , err , "Expected to update worker %s status" , tc .name )
397+
398+ // Set custom timestamp
399+ timestamp := time .Now ().UTC ().Add (- time .Duration (tc .ageMinutes ) * time .Minute )
400+ _ , err = database .Instance .Exec (
401+ "UPDATE worker SET updated_at = $1 WHERE rid = $2" ,
402+ timestamp , updatedWorker .RID ,
403+ )
404+ require .NoError (t , err , "Expected to set custom timestamp for worker %s" , tc .name )
405+
406+ workers [i ] = updatedWorker
407+ }
408+
409+ // Test with 15 minute threshold
410+ deleteThreshold := 15 * time .Minute
411+ deletedCount , err := workerDbHandler .DeleteStaleWorkers (deleteThreshold )
412+ assert .NoError (t , err , "Expected DeleteStaleWorkers to complete successfully" )
413+ assert .Equal (t , 2 , deletedCount , "Expected 2 workers to be deleted (older than 15 minutes)" )
414+
415+ // Verify results
416+ for i , tc := range testCases {
417+ worker , err := workerDbHandler .SelectWorker (workers [i ].RID )
418+
419+ if tc .shouldDelete {
420+ assert .Error (t , err , "Expected worker %s to be deleted" , tc .name )
421+ assert .Nil (t , worker , "Expected deleted worker %s to be nil" , tc .name )
422+ } else {
423+ assert .NoError (t , err , "Expected worker %s to still exist" , tc .name )
424+ assert .NotNil (t , worker , "Expected worker %s to not be nil" , tc .name )
425+ assert .Equal (t , model .WorkerStatusStopped , worker .Status , "Expected worker %s to remain STOPPED" , tc .name )
426+ }
427+ }
428+
429+ // Clean up remaining workers
430+ for i , tc := range testCases {
431+ if ! tc .shouldDelete {
432+ err = workerDbHandler .DeleteWorker (workers [i ].RID )
433+ assert .NoError (t , err , "Expected to delete remaining worker %s" , tc .name )
434+ }
435+ }
436+ })
437+ }
438+
254439func TestWorkerSelectWorker (t * testing.T ) {
255440 helper .SetTestDatabaseConfigEnvs (t , dbPort )
256441 dbConfig , err := helper .NewDatabaseConfiguration ()
0 commit comments