44 "bytes"
55 "context"
66 "database/sql"
7+ "encoding/json"
78 "fmt"
89 "io"
910 "net/http"
@@ -17,6 +18,7 @@ import (
1718 "github.com/google/uuid"
1819 "github.com/stretchr/testify/require"
1920 "go.uber.org/mock/gomock"
21+ "golang.org/x/xerrors"
2022
2123 "cdr.dev/slog"
2224 "cdr.dev/slog/sloggers/slogtest"
@@ -319,3 +321,152 @@ func TestWatchAgentContainers(t *testing.T) {
319321 }
320322 })
321323}
324+
325+ func TestWorkspaceAgentDeleteDevcontainer (t * testing.T ) {
326+ t .Parallel ()
327+
328+ tests := []struct {
329+ name string
330+ agentConnected bool // Controls FirstConnectedAt/LastConnectedAt validity
331+ agentConnError error // Error returned by fakeAgentProvider.AgentConn (nil = success)
332+ deleteError error // Error returned by DeleteDevcontainer mock (nil = success)
333+ expectedStatusCode int
334+ }{
335+ {
336+ name : "OK" ,
337+ agentConnected : true ,
338+ agentConnError : nil ,
339+ deleteError : nil ,
340+ expectedStatusCode : http .StatusNoContent ,
341+ },
342+ {
343+ name : "AgentNotConnected" ,
344+ agentConnected : false ,
345+ expectedStatusCode : http .StatusBadRequest ,
346+ },
347+ {
348+ name : "DevcontainerNotFound" ,
349+ agentConnected : true ,
350+ deleteError : func () error {
351+ body , _ := json .Marshal (codersdk.Response {
352+ Message : "Devcontainer not found." ,
353+ })
354+ return codersdk .ReadBodyAsError (& http.Response {
355+ StatusCode : http .StatusNotFound ,
356+ Body : io .NopCloser (bytes .NewReader (body )),
357+ Request : & http.Request {URL : & url.URL {}},
358+ })
359+ }(),
360+ expectedStatusCode : http .StatusNotFound ,
361+ },
362+ {
363+ name : "AgentConnectionFailure" ,
364+ agentConnected : true ,
365+ agentConnError : xerrors .New ("connection failed" ),
366+ expectedStatusCode : http .StatusInternalServerError ,
367+ },
368+ {
369+ name : "InternalError" ,
370+ agentConnected : true ,
371+ deleteError : xerrors .New ("internal error" ),
372+ expectedStatusCode : http .StatusInternalServerError ,
373+ },
374+ }
375+
376+ for _ , tc := range tests {
377+ t .Run (tc .name , func (t * testing.T ) {
378+ t .Parallel ()
379+
380+ var (
381+ ctx = testutil .Context (t , testutil .WaitShort )
382+ logger = slogtest .Make (t , & slogtest.Options {IgnoreErrors : true }).Leveled (slog .LevelDebug ).Named ("coderd" )
383+
384+ mCtrl = gomock .NewController (t )
385+ mDB = dbmock .NewMockStore (mCtrl )
386+ mCoordinator = tailnettest .NewMockCoordinator (mCtrl )
387+
388+ agentID = uuid .New ()
389+ resourceID = uuid .New ()
390+ jobID = uuid .New ()
391+ buildID = uuid .New ()
392+ workspaceID = uuid .New ()
393+ devcontainerID = uuid .NewString ()
394+
395+ r = chi .NewMux ()
396+
397+ api = API {
398+ ctx : ctx ,
399+ Options : & Options {
400+ AgentInactiveDisconnectTimeout : testutil .WaitShort ,
401+ Database : mDB ,
402+ Logger : logger ,
403+ DeploymentValues : & codersdk.DeploymentValues {},
404+ TailnetCoordinator : tailnettest .NewFakeCoordinator (),
405+ },
406+ }
407+ )
408+
409+ var tailnetCoordinator tailnet.Coordinator = mCoordinator
410+ api .TailnetCoordinator .Store (& tailnetCoordinator )
411+
412+ // Setup agent provider based on test case.
413+ if tc .agentConnected && tc .agentConnError == nil {
414+ mAgentConn := agentconnmock .NewMockAgentConn (mCtrl )
415+ mAgentConn .EXPECT ().DeleteDevcontainer (gomock .Any (), devcontainerID ).Return (tc .deleteError )
416+ api .agentProvider = fakeAgentProvider {
417+ agentConn : func (_ context.Context , _ uuid.UUID ) (_ workspacesdk.AgentConn , release func (), _ error ) {
418+ return mAgentConn , func () {}, nil
419+ },
420+ }
421+ } else if tc .agentConnError != nil {
422+ api .agentProvider = fakeAgentProvider {
423+ agentConn : func (_ context.Context , _ uuid.UUID ) (_ workspacesdk.AgentConn , release func (), _ error ) {
424+ return nil , nil , tc .agentConnError
425+ },
426+ }
427+ }
428+
429+ // Setup database mocks for ExtractWorkspaceAgentParam middleware.
430+ mDB .EXPECT ().GetWorkspaceAgentByID (gomock .Any (), agentID ).Return (database.WorkspaceAgent {
431+ ID : agentID ,
432+ ResourceID : resourceID ,
433+ LifecycleState : database .WorkspaceAgentLifecycleStateReady ,
434+ FirstConnectedAt : sql.NullTime {Valid : tc .agentConnected , Time : dbtime .Now ()},
435+ LastConnectedAt : sql.NullTime {Valid : tc .agentConnected , Time : dbtime .Now ()},
436+ }, nil )
437+ mDB .EXPECT ().GetWorkspaceResourceByID (gomock .Any (), resourceID ).Return (database.WorkspaceResource {
438+ ID : resourceID ,
439+ JobID : jobID ,
440+ }, nil )
441+ mDB .EXPECT ().GetProvisionerJobByID (gomock .Any (), jobID ).Return (database.ProvisionerJob {
442+ ID : jobID ,
443+ Type : database .ProvisionerJobTypeWorkspaceBuild ,
444+ }, nil )
445+ mDB .EXPECT ().GetWorkspaceBuildByJobID (gomock .Any (), jobID ).Return (database.WorkspaceBuild {
446+ WorkspaceID : workspaceID ,
447+ ID : buildID ,
448+ }, nil )
449+
450+ // Allow db2sdk.WorkspaceAgent to complete.
451+ mCoordinator .EXPECT ().Node (gomock .Any ()).Return (nil )
452+
453+ // Mount the HTTP handler and create the test server.
454+ r .With (httpmw .ExtractWorkspaceAgentParam (mDB )).
455+ Delete ("/workspaceagents/{workspaceagent}/containers/devcontainers/{devcontainer}" , api .workspaceAgentDeleteDevcontainer )
456+
457+ srv := httptest .NewServer (r )
458+ defer srv .Close ()
459+
460+ // Send the DELETE request using the test server's client.
461+ req , err := http .NewRequestWithContext (ctx , http .MethodDelete ,
462+ fmt .Sprintf ("%s/workspaceagents/%s/containers/devcontainers/%s" , srv .URL , agentID , devcontainerID ), nil )
463+ require .NoError (t , err )
464+
465+ resp , err := srv .Client ().Do (req )
466+ require .NoError (t , err )
467+ defer resp .Body .Close ()
468+
469+ require .Equal (t , tc .expectedStatusCode , resp .StatusCode )
470+ })
471+ }
472+ }
0 commit comments