diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index f83838dd4b..04eecd5d86 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6197,6 +6197,20 @@ func (q *FakeQuerier) InsertOrganizationMember(_ context.Context, arg database.I q.mutex.Lock() defer q.mutex.Unlock() + if slices.IndexFunc(q.data.organizationMembers, func(member database.OrganizationMember) bool { + return member.OrganizationID == arg.OrganizationID && member.UserID == arg.UserID + }) >= 0 { + // Error pulled from a live db error + return database.OrganizationMember{}, &pq.Error{ + Severity: "ERROR", + Code: "23505", + Message: "duplicate key value violates unique constraint \"organization_members_pkey\"", + Detail: "Key (organization_id, user_id)=(f7de1f4e-5833-4410-a28d-0a105f96003f, 36052a80-4a7f-4998-a7ca-44cefa608d3e) already exists.", + Table: "organization_members", + Constraint: "organization_members_pkey", + } + } + //nolint:gosimple organizationMember := database.OrganizationMember{ OrganizationID: arg.OrganizationID, diff --git a/coderd/members.go b/coderd/members.go index 3352eefc3b..2528a17878 100644 --- a/coderd/members.go +++ b/coderd/members.go @@ -43,6 +43,12 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request) httpapi.ResourceNotFound(rw) return } + if database.IsUniqueViolation(err, database.UniqueOrganizationMembersPkey) { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Organization member already exists in this organization", + }) + return + } if err != nil { httpapi.InternalServerError(rw, err) return diff --git a/coderd/members_test.go b/coderd/members_test.go index db80b28ad1..3db296ef60 100644 --- a/coderd/members_test.go +++ b/coderd/members_test.go @@ -50,6 +50,19 @@ func TestAddMember(t *testing.T) { db2sdk.List(members, onlyIDs)) }) + t.Run("AlreadyMember", func(t *testing.T) { + t.Parallel() + owner := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, owner) + _, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID) + + ctx := testutil.Context(t, testutil.WaitMedium) + // Add user to org, even though they already exist + // nolint:gocritic // must be an owner to see the user + _, err := owner.PostOrganizationMember(ctx, first.OrganizationID, user.Username) + require.ErrorContains(t, err, "already exists") + }) + t.Run("UserNotExists", func(t *testing.T) { t.Parallel() owner := coderdtest.New(t, nil)