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