Skip to content

Commit 281cf6e

Browse files
committed
fix(coderd/database): ensure task name uniqueness
This change ensures task names are unique per user the same way we do for workspaces. This ensures we don't create tasks that are impossible to start due to another task being named the same creating a workspace name conflict. Updates coder/internal#948 Supersedes #20212
1 parent a05d8dc commit 281cf6e

5 files changed

Lines changed: 90 additions & 0 deletions

File tree

coderd/database/dump.sql

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP INDEX IF EXISTS tasks_owner_id_name_unique_idx;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CREATE UNIQUE INDEX IF NOT EXISTS tasks_owner_id_name_unique_idx ON tasks (owner_id, LOWER(name)) WHERE deleted_at IS NULL;
2+
COMMENT ON INDEX tasks_owner_id_name_unique_idx IS 'Index to ensure uniqueness for task owner/name';

coderd/database/querier_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7105,6 +7105,88 @@ func TestGetTaskByWorkspaceID(t *testing.T) {
71057105
}
71067106
}
71077107

7108+
func TestTaskNameUniqueness(t *testing.T) {
7109+
t.Parallel()
7110+
7111+
db, _ := dbtestutil.NewDB(t)
7112+
7113+
org := dbgen.Organization(t, db, database.Organization{})
7114+
user1 := dbgen.User(t, db, database.User{})
7115+
user2 := dbgen.User(t, db, database.User{})
7116+
template := dbgen.Template(t, db, database.Template{
7117+
OrganizationID: org.ID,
7118+
CreatedBy: user1.ID,
7119+
})
7120+
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
7121+
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
7122+
OrganizationID: org.ID,
7123+
CreatedBy: user1.ID,
7124+
})
7125+
7126+
taskName := "my-task"
7127+
7128+
// Create initial task for user1.
7129+
task1 := dbgen.Task(t, db, database.TaskTable{
7130+
OrganizationID: org.ID,
7131+
OwnerID: user1.ID,
7132+
Name: taskName,
7133+
TemplateVersionID: tv.ID,
7134+
Prompt: "Test prompt",
7135+
})
7136+
require.NotEqual(t, uuid.Nil, task1.ID)
7137+
7138+
tests := []struct {
7139+
name string
7140+
ownerID uuid.UUID
7141+
taskName string
7142+
wantErr bool
7143+
}{
7144+
{
7145+
name: "duplicate task name same user",
7146+
ownerID: user1.ID,
7147+
taskName: taskName,
7148+
wantErr: true,
7149+
},
7150+
{
7151+
name: "duplicate task name different case same user",
7152+
ownerID: user1.ID,
7153+
taskName: "MY-TASK",
7154+
wantErr: true,
7155+
},
7156+
{
7157+
name: "same task name different user",
7158+
ownerID: user2.ID,
7159+
taskName: taskName,
7160+
wantErr: false,
7161+
},
7162+
}
7163+
7164+
for _, tt := range tests {
7165+
t.Run(tt.name, func(t *testing.T) {
7166+
t.Parallel()
7167+
7168+
ctx := testutil.Context(t, testutil.WaitShort)
7169+
7170+
task, err := db.InsertTask(ctx, database.InsertTaskParams{
7171+
OrganizationID: org.ID,
7172+
OwnerID: tt.ownerID,
7173+
Name: tt.taskName,
7174+
TemplateVersionID: tv.ID,
7175+
TemplateParameters: json.RawMessage("{}"),
7176+
Prompt: "Test prompt",
7177+
CreatedAt: dbtime.Now(),
7178+
})
7179+
if tt.wantErr {
7180+
require.Error(t, err)
7181+
} else {
7182+
require.NoError(t, err)
7183+
require.NotEqual(t, uuid.Nil, task.ID)
7184+
require.NotEqual(t, task1.ID, task.ID)
7185+
}
7186+
})
7187+
}
7188+
}
7189+
71087190
func TestUsageEventsTrigger(t *testing.T) {
71097191
t.Parallel()
71107192

coderd/database/unique_constraint.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)