mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
fix(coderd/database): ensure task name uniqueness (#20236)
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 coder/coder#20212
This commit is contained in:
Generated
+4
@@ -3345,6 +3345,10 @@ CREATE INDEX tasks_organization_id_idx ON tasks USING btree (organization_id);
|
||||
|
||||
CREATE INDEX tasks_owner_id_idx ON tasks USING btree (owner_id);
|
||||
|
||||
CREATE UNIQUE INDEX tasks_owner_id_name_unique_idx ON tasks USING btree (owner_id, lower(name)) WHERE (deleted_at IS NULL);
|
||||
|
||||
COMMENT ON INDEX tasks_owner_id_name_unique_idx IS 'Index to ensure uniqueness for task owner/name';
|
||||
|
||||
CREATE INDEX tasks_workspace_id_idx ON tasks USING btree (workspace_id);
|
||||
|
||||
CREATE INDEX template_usage_stats_start_time_idx ON template_usage_stats USING btree (start_time DESC);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS tasks_owner_id_name_unique_idx;
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS tasks_owner_id_name_unique_idx ON tasks (owner_id, LOWER(name)) WHERE deleted_at IS NULL;
|
||||
COMMENT ON INDEX tasks_owner_id_name_unique_idx IS 'Index to ensure uniqueness for task owner/name';
|
||||
@@ -7105,6 +7105,88 @@ func TestGetTaskByWorkspaceID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskNameUniqueness(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
user1 := dbgen.User(t, db, database.User{})
|
||||
user2 := dbgen.User(t, db, database.User{})
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user1.ID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user1.ID,
|
||||
})
|
||||
|
||||
taskName := "my-task"
|
||||
|
||||
// Create initial task for user1.
|
||||
task1 := dbgen.Task(t, db, database.TaskTable{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user1.ID,
|
||||
Name: taskName,
|
||||
TemplateVersionID: tv.ID,
|
||||
Prompt: "Test prompt",
|
||||
})
|
||||
require.NotEqual(t, uuid.Nil, task1.ID)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ownerID uuid.UUID
|
||||
taskName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "duplicate task name same user",
|
||||
ownerID: user1.ID,
|
||||
taskName: taskName,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "duplicate task name different case same user",
|
||||
ownerID: user1.ID,
|
||||
taskName: "MY-TASK",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "same task name different user",
|
||||
ownerID: user2.ID,
|
||||
taskName: taskName,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
task, err := db.InsertTask(ctx, database.InsertTaskParams{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: tt.ownerID,
|
||||
Name: tt.taskName,
|
||||
TemplateVersionID: tv.ID,
|
||||
TemplateParameters: json.RawMessage("{}"),
|
||||
Prompt: "Test prompt",
|
||||
CreatedAt: dbtime.Now(),
|
||||
})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, uuid.Nil, task.ID)
|
||||
require.NotEqual(t, task1.ID, task.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsageEventsTrigger(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ const (
|
||||
UniqueNotificationMessagesDedupeHashIndex UniqueConstraint = "notification_messages_dedupe_hash_idx" // CREATE UNIQUE INDEX notification_messages_dedupe_hash_idx ON notification_messages USING btree (dedupe_hash);
|
||||
UniqueOrganizationsSingleDefaultOrg UniqueConstraint = "organizations_single_default_org" // CREATE UNIQUE INDEX organizations_single_default_org ON organizations USING btree (is_default) WHERE (is_default = true);
|
||||
UniqueProvisionerKeysOrganizationIDNameIndex UniqueConstraint = "provisioner_keys_organization_id_name_idx" // CREATE UNIQUE INDEX provisioner_keys_organization_id_name_idx ON provisioner_keys USING btree (organization_id, lower((name)::text));
|
||||
UniqueTasksOwnerIDNameUniqueIndex UniqueConstraint = "tasks_owner_id_name_unique_idx" // CREATE UNIQUE INDEX tasks_owner_id_name_unique_idx ON tasks USING btree (owner_id, lower(name)) WHERE (deleted_at IS NULL);
|
||||
UniqueTemplateUsageStatsStartTimeTemplateIDUserIDIndex UniqueConstraint = "template_usage_stats_start_time_template_id_user_id_idx" // CREATE UNIQUE INDEX template_usage_stats_start_time_template_id_user_id_idx ON template_usage_stats USING btree (start_time, template_id, user_id);
|
||||
UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false);
|
||||
UniqueUserLinksLinkedIDLoginTypeIndex UniqueConstraint = "user_links_linked_id_login_type_idx" // CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text);
|
||||
|
||||
Reference in New Issue
Block a user