@@ -14,6 +14,7 @@ import (
1414"golang.org/x/xerrors"
1515
1616"cdr.dev/slog"
17+ "cdr.dev/slog/sloggers/sloghuman"
1718"github.com/coder/quartz"
1819
1920"github.com/coder/coder/v2/codersdk"
@@ -57,6 +58,12 @@ func (m *fakeClient) createExternalWorkspace(ctx context.Context, req codersdk.C
5758},nil
5859}
5960
61+ func (m * fakeClient )deleteWorkspace (ctx context.Context ,workspaceID uuid.UUID )error {
62+ m .logger .Debug (ctx ,"called fake DeleteWorkspace" ,slog .F ("workspace_id" ,workspaceID .String ()))
63+ // Simulate successful deletion in tests
64+ return nil
65+ }
66+
6067// fakeAppStatusPatcher implements the appStatusPatcher interface for testing
6168type fakeAppStatusPatcher struct {
6269t * testing.T
@@ -480,3 +487,68 @@ func TestParseStatusMessage(t *testing.T) {
480487})
481488}
482489}
490+
491+ func TestRunner_Cleanup (t * testing.T ) {
492+ t .Parallel ()
493+
494+ ctx := testutil .Context (t ,testutil .WaitMedium )
495+
496+ fakeClient := & fakeClientWithCleanupTracking {
497+ fakeClient :newFakeClient (t ),
498+ deleteWorkspaceCalls :make ([]uuid.UUID ,0 ),
499+ }
500+ fakeClient .initialize (slog .Make (sloghuman .Sink (testutil .NewTestLogWriter (t ))).Leveled (slog .LevelDebug ))
501+
502+ cfg := Config {
503+ AppSlug :"test-app" ,
504+ TemplateID : uuid.UUID {5 ,6 ,7 ,8 },
505+ WorkspaceName :"test-workspace" ,
506+ MetricLabelValues : []string {"test" },
507+ Metrics :NewMetrics (prometheus .NewRegistry (),"test" ),
508+ ReportStatusPeriod :100 * time .Millisecond ,
509+ ReportStatusDuration :200 * time .Millisecond ,
510+ StartReporting :make (chan struct {}),
511+ ConnectedWaitGroup :& sync.WaitGroup {},
512+ }
513+
514+ runner := & Runner {
515+ client :fakeClient ,
516+ patcher :newFakeAppStatusPatcher (t ),
517+ cfg :cfg ,
518+ clock :quartz .NewMock (t ),
519+ }
520+
521+ logWriter := testutil .NewTestLogWriter (t )
522+
523+ // Case 1: No workspace created - Cleanup should do nothing
524+ err := runner .Cleanup (ctx ,"test-runner" ,logWriter )
525+ require .NoError (t ,err )
526+ require .Len (t ,fakeClient .deleteWorkspaceCalls ,0 ,"deleteWorkspace should not be called when no workspace was created" )
527+
528+ // Case 2: Workspace created - Cleanup should delete it
529+ runner .workspaceID = uuid.UUID {1 ,2 ,3 ,4 }
530+ err = runner .Cleanup (ctx ,"test-runner" ,logWriter )
531+ require .NoError (t ,err )
532+ require .Len (t ,fakeClient .deleteWorkspaceCalls ,1 ,"deleteWorkspace should be called once" )
533+ require .Equal (t ,runner .workspaceID ,fakeClient .deleteWorkspaceCalls [0 ],"deleteWorkspace should be called with correct workspace ID" )
534+
535+ // Case 3: Cleanup with error
536+ fakeClient .deleteError = xerrors .New ("delete failed" )
537+ runner .workspaceID = uuid.UUID {5 ,6 ,7 ,8 }
538+ err = runner .Cleanup (ctx ,"test-runner" ,logWriter )
539+ require .Error (t ,err )
540+ require .Contains (t ,err .Error (),"delete external workspace" )
541+ }
542+
543+ // fakeClientWithCleanupTracking extends fakeClient to track deleteWorkspace calls
544+ type fakeClientWithCleanupTracking struct {
545+ * fakeClient
546+ deleteWorkspaceCalls []uuid.UUID
547+ deleteError error
548+ }
549+
550+ func (c * fakeClientWithCleanupTracking )deleteWorkspace (ctx context.Context ,workspaceID uuid.UUID )error {
551+ c .deleteWorkspaceCalls = append (c .deleteWorkspaceCalls ,workspaceID )
552+ c .logger .Debug (ctx ,"called fake DeleteWorkspace with tracking" ,slog .F ("workspace_id" ,workspaceID .String ()))
553+ return c .deleteError
554+ }