Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof gitlabApi.Users.all>
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', () => {
Expand Down
25 changes: 18 additions & 7 deletions apps/server-nestjs/src/modules/gitlab/gitlab-client.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down