diff --git a/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap b/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap index 76d488bcf8bbf9b..0d28800baf0d89b 100644 --- a/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap @@ -151,6 +151,7 @@ exports[`modules/platform/bitbucket-server/index endpoint with no path getPrList exports[`modules/platform/bitbucket-server/index endpoint with no path initPlatform() should init 1`] = ` { "endpoint": "https://stash.renovatebot.com/", + "gitAuthor": "Abc Def ", } `; @@ -355,6 +356,7 @@ exports[`modules/platform/bitbucket-server/index endpoint with path getPrList() exports[`modules/platform/bitbucket-server/index endpoint with path initPlatform() should init 1`] = ` { "endpoint": "https://stash.renovatebot.com/", + "gitAuthor": "Abc Def ", } `; diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts index d11230612388a2f..61bdbb2a287bb83 100644 --- a/lib/modules/platform/bitbucket-server/index.spec.ts +++ b/lib/modules/platform/bitbucket-server/index.spec.ts @@ -10,6 +10,7 @@ import { import type { logger as _logger } from '../../../logger'; import type * as _git from '../../../util/git'; import type { LongCommitSha } from '../../../util/git/types'; +import { ensureTrailingSlash } from '../../../util/url'; import type { Platform } from '../types'; jest.mock('timers/promises'); @@ -190,6 +191,11 @@ describe('modules/platform/bitbucket-server/index', () => { let logger: jest.Mocked; const username = 'abc'; const password = '123'; + const userInfo = { + name: username, + emailAddress: 'abc@def.com', + displayName: 'Abc Def', + }; async function initRepo(config = {}): Promise { const scope = httpMock @@ -234,6 +240,10 @@ describe('modules/platform/bitbucket-server/index', () => { .scope(urlHost) .get(`${urlPath}/rest/api/1.0/application-properties`) .reply(200, { version: '8.0.0' }); + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/users/${username}`) + .reply(200, userInfo); await bitbucket.initPlatform({ endpoint, username, @@ -292,6 +302,10 @@ describe('modules/platform/bitbucket-server/index', () => { .scope('https://stash.renovatebot.com') .get('/rest/api/1.0/application-properties') .reply(403); + httpMock + .scope('https://stash.renovatebot.com') + .get(`/rest/api/1.0/users/${username}`) + .reply(200, userInfo); await bitbucket.initPlatform({ endpoint: 'https://stash.renovatebot.com', @@ -304,8 +318,38 @@ describe('modules/platform/bitbucket-server/index', () => { ); }); + it('should not throw if user info fetch fails', async () => { + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/application-properties`) + .reply(200, { version: '8.0.0' }); + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/users/${username}`) + .reply(404); + + expect( + await bitbucket.initPlatform({ + endpoint: url.href, + username, + password, + }), + ).toEqual({ + endpoint: ensureTrailingSlash(url.href), + }); + expect(logger.debug).toHaveBeenCalledWith( + expect.any(Object), + 'Failed to get user info, fallback gitAuthor will be used', + ); + }); + it('should skip api call to fetch version when platform version is set in environment', async () => { process.env.RENOVATE_X_PLATFORM_VERSION = '8.0.0'; + httpMock + .scope('https://stash.renovatebot.com') + .get(`/rest/api/1.0/users/${username}`) + .reply(200, userInfo); + await expect( bitbucket.initPlatform({ endpoint: 'https://stash.renovatebot.com', @@ -316,11 +360,72 @@ describe('modules/platform/bitbucket-server/index', () => { delete process.env.RENOVATE_X_PLATFORM_VERSION; }); + it('should skip users api call when gitAuthor is configured', async () => { + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/application-properties`) + .reply(200, { version: '8.0.0' }); + + expect( + await bitbucket.initPlatform({ + endpoint: url.href, + username: 'def', + password: '123', + gitAuthor: `Def Abc `, + }), + ).toEqual({ + endpoint: ensureTrailingSlash(url.href), + }); + }); + + it('should skip users api call when no username', async () => { + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/application-properties`) + .reply(200, { version: '8.0.0' }); + + expect( + await bitbucket.initPlatform({ + endpoint: url.href, + token: '123', + }), + ).toEqual({ + endpoint: ensureTrailingSlash(url.href), + }); + }); + + it('should fetch user info if token with username', async () => { + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/application-properties`) + .reply(200, { version: '8.0.0' }); + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/users/${username}`) + .reply(200, userInfo); + + expect( + await bitbucket.initPlatform({ + endpoint: url.href, + token: '123', + username, + }), + ).toEqual({ + endpoint: ensureTrailingSlash(url.href), + gitAuthor: `${userInfo.displayName} <${userInfo.emailAddress}>`, + }); + }); + it('should init', async () => { httpMock .scope('https://stash.renovatebot.com') .get('/rest/api/1.0/application-properties') .reply(200, { version: '8.0.0' }); + httpMock + .scope('https://stash.renovatebot.com') + .get(`/rest/api/1.0/users/${username}`) + .reply(200, userInfo); + expect( await bitbucket.initPlatform({ endpoint: 'https://stash.renovatebot.com', diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index b7c0b235d42950d..badd5e9e0f65519 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -17,7 +17,7 @@ import { BitbucketServerHttp, setBaseUrl, } from '../../../util/http/bitbucket-server'; -import type { HttpResponse } from '../../../util/http/types'; +import type { HttpOptions, HttpResponse } from '../../../util/http/types'; import { newlineRegex, regEx } from '../../../util/regex'; import { sanitize } from '../../../util/sanitize'; import { ensureTrailingSlash, getQueryString } from '../../../util/url'; @@ -40,6 +40,7 @@ import type { } from '../types'; import { getNewBranchName, repoFingerprint } from '../util'; import { smartTruncate } from '../utils/pr-body'; +import { UserSchema } from './schema'; import type { BbsConfig, BbsPr, @@ -88,6 +89,7 @@ export async function initPlatform({ token, username, password, + gitAuthor, }: PlatformParams): Promise { if (!endpoint) { throw new Error('Init: You must configure a Bitbucket Server endpoint'); @@ -132,6 +134,39 @@ export async function initPlatform({ ); } + if (!gitAuthor && username) { + logger.debug(`Attempting to confirm gitAuthor from username`); + const options: HttpOptions = { + memCache: false, + }; + + if (token) { + options.token = token; + } else { + options.username = username; + options.password = password; + } + + try { + const { displayName, emailAddress } = ( + await bitbucketServerHttp.getJson( + `./rest/api/1.0/users/${username}`, + options, + UserSchema, + ) + ).body; + + platformConfig.gitAuthor = `${displayName} <${emailAddress}>`; + + logger.debug(`Detected gitAuthor: ${platformConfig.gitAuthor}`); + } catch (err) { + logger.debug( + { err }, + 'Failed to get user info, fallback gitAuthor will be used', + ); + } + } + return platformConfig; } diff --git a/lib/modules/platform/bitbucket-server/schema.ts b/lib/modules/platform/bitbucket-server/schema.ts new file mode 100644 index 000000000000000..895d1d52abfbe1a --- /dev/null +++ b/lib/modules/platform/bitbucket-server/schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const UserSchema = z.object({ + displayName: z.string(), + emailAddress: z.string(), +});