diff --git a/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.spec.ts b/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.spec.ts index 75e83d5bf8..e1a7343983 100644 --- a/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.spec.ts +++ b/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.spec.ts @@ -917,6 +917,37 @@ describe('gitlab-client', () => { skipConfirmation: true, })) }) + + it('should return the existing user on 409 (already auto-provisioned via OIDC)', async () => { + const email = 'user@example.com' + const username = 'user' + const name = 'User Name' + const existing = makeExpandedUserSchema({ id: 2, email, username }) + + gitlabApi.Users.create.mockRejectedValueOnce( + makeGitbeakerRequestError({ status: 409, description: 'Username has already been taken' }), + ) + const allMock = gitlabApi.Users.all as MockedFunction + allMock.mockResolvedValueOnce([existing]) + + const result = await service.createUser({ email, username, name }) + + expect(result).toEqual(existing) + expect(gitlabApi.Users.create).toHaveBeenCalledTimes(1) + }) + + it('should propagate a non-collision error', async () => { + const email = 'user@example.com' + const username = 'user' + const name = 'User Name' + + gitlabApi.Users.create.mockRejectedValue( + makeGitbeakerRequestError({ status: 500, description: 'Internal Server Error' }), + ) + + await expect(service.createUser({ email, username, name })).rejects.toThrow() + expect(gitlabApi.Users.create).toHaveBeenCalledTimes(1) + }) }) describe('commitMirror', () => { diff --git a/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.ts b/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.ts index d4ab8510db..a53183e093 100644 --- a/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.ts +++ b/apps/server-nestjs/src/modules/gitlab/gitlab-client.service.ts @@ -404,13 +404,24 @@ export class GitlabClientService { async createUser(user: EditUserOptions) { this.logger.log(`Creating a GitLab user (email=${user.email}, username=${user.username})`) - return await this.client.Users.create({ - ...user, - canCreateGroup: false, - forceRandomPassword: true, - projectsLimit: 0, - skipConfirmation: true, - }) as UserSchema + try { + return await this.client.Users.create({ + ...user, + canCreateGroup: false, + forceRandomPassword: true, + projectsLimit: 0, + skipConfirmation: true, + }) as UserSchema + } catch (error) { + // GitLab auto-provisions users via OIDC, so a 409 means the user already + // exists (email index race in getUserByEmail). Return it instead of failing. + if (error instanceof GitbeakerRequestError && error.cause?.description?.includes('has already been taken')) { + const existing = user.email ? await this.getUserByEmail(user.email) : null + if (existing) return existing as UserSchema + throw error + } + throw error + } } async upsertUser(