Skip to content

Commit 9cf3c58

Browse files
authored
test: add unit test that deletes abandoned workspace (coder#7990)
* test: add unit test that deletes abandoned workspace This is to ensure we do not break this functionality in future. This is important incase this edge case happens, an admin can clean up the abandoned resources.
1 parent c916a9e commit 9cf3c58

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

cli/delete_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli_test
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67
"testing"
78

@@ -10,8 +11,11 @@ import (
1011

1112
"github.com/coder/coder/cli/clitest"
1213
"github.com/coder/coder/coderd/coderdtest"
14+
"github.com/coder/coder/coderd/database"
15+
"github.com/coder/coder/coderd/database/dbauthz"
1316
"github.com/coder/coder/codersdk"
1417
"github.com/coder/coder/pty/ptytest"
18+
"github.com/coder/coder/testutil"
1519
)
1620

1721
func TestDelete(t *testing.T) {
@@ -68,6 +72,51 @@ func TestDelete(t *testing.T) {
6872
<-doneChan
6973
})
7074

75+
// Super orphaned, as the workspace doesn't even have a user.
76+
// This is not a scenario we should ever get into, as we do not allow users
77+
// to be deleted if they have workspaces. However issue #7872 shows that
78+
// it is possible to get into this state. An admin should be able to still
79+
// force a delete action on the workspace.
80+
t.Run("OrphanDeletedUser", func(t *testing.T) {
81+
t.Parallel()
82+
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
83+
user := coderdtest.CreateFirstUser(t, client)
84+
deleteMeClient, deleteMeUser := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
85+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
86+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
87+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
88+
89+
workspace := coderdtest.CreateWorkspace(t, deleteMeClient, user.OrganizationID, template.ID)
90+
coderdtest.AwaitWorkspaceBuildJob(t, deleteMeClient, workspace.LatestBuild.ID)
91+
92+
// The API checks if the user has any workspaces, so we cannot delete a user
93+
// this way.
94+
ctx := testutil.Context(t, testutil.WaitShort)
95+
// nolint:gocritic // Unit test
96+
err := api.Database.UpdateUserDeletedByID(dbauthz.AsSystemRestricted(ctx), database.UpdateUserDeletedByIDParams{
97+
ID: deleteMeUser.ID,
98+
Deleted: true,
99+
})
100+
require.NoError(t, err)
101+
102+
inv, root := clitest.New(t, "delete", fmt.Sprintf("%s/%s", deleteMeUser.ID, workspace.Name), "-y", "--orphan")
103+
104+
clitest.SetupConfig(t, client, root)
105+
doneChan := make(chan struct{})
106+
pty := ptytest.New(t).Attach(inv)
107+
inv.Stderr = pty.Output()
108+
go func() {
109+
defer close(doneChan)
110+
err := inv.Run()
111+
// When running with the race detector on, we sometimes get an EOF.
112+
if err != nil {
113+
assert.ErrorIs(t, err, io.EOF)
114+
}
115+
}()
116+
pty.ExpectMatch("workspace has been deleted")
117+
<-doneChan
118+
})
119+
71120
t.Run("DifferentUser", func(t *testing.T) {
72121
t.Parallel()
73122
adminClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})

coderd/database/dbfake/dbfake.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4646,11 +4646,19 @@ func (q *fakeQuerier) UpdateUserDeletedByID(_ context.Context, params database.U
46464646
u.Deleted = params.Deleted
46474647
q.users[i] = u
46484648
// NOTE: In the real world, this is done by a trigger.
4649-
for i, k := range q.apiKeys {
4649+
i := 0
4650+
for {
4651+
if i >= len(q.apiKeys) {
4652+
break
4653+
}
4654+
k := q.apiKeys[i]
46504655
if k.UserID == u.ID {
46514656
q.apiKeys[i] = q.apiKeys[len(q.apiKeys)-1]
46524657
q.apiKeys = q.apiKeys[:len(q.apiKeys)-1]
4658+
// We removed an element, so decrement
4659+
i--
46534660
}
4661+
i++
46544662
}
46554663
return nil
46564664
}

0 commit comments

Comments
 (0)