Files
renovate/lib/modules/platform/azure/azure-got-wrapper.spec.ts
Sebastian Poxhofer 05e1271376 test: use expect().resolves instead of awaiting inside expect (#45672)
Enables the vitest/prefer-expect-resolves oxlint rule and applies its
autofix across the spec suite.
2026-09-04 10:40:46 +00:00

287 lines
8.9 KiB
TypeScript

import type { WebApi } from 'azure-devops-node-api';
import type { DeploymentFlags } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';
import { buildTestJwt } from '~test/jwt-util.ts';
import { logger } from '~test/util.ts';
import type * as _hostRules from '../../../util/host-rules.ts';
describe('modules/platform/azure/azure-got-wrapper', () => {
let azure: typeof import('./azure-got-wrapper.ts');
let hostRules: typeof _hostRules;
beforeEach(async () => {
// reset module
vi.resetModules();
hostRules = await vi.importActual('../../../util/host-rules.ts');
azure = await vi.importActual('./azure-got-wrapper.ts');
});
describe('gitApi', () => {
it('should throw an error if no config found', () => {
expect(azure.gitApi).toThrow('No config found for azure');
expect(azure.coreApi).toThrow('No config found for azure');
expect(azure.policyApi).toThrow('No config found for azure');
expect(azure.workItemTrackingApi).toThrow('No config found for azure');
});
it('should set personal access token and endpoint', () => {
hostRules.add({
hostType: 'azure',
token: '123test',
matchHost: 'https://dev.azure.com/renovate1',
});
azure.setEndpoint('https://dev.azure.com/renovate1');
const res = azure.azureObj();
delete res.rest.client.userAgent;
delete res.vsoClient.restClient.client.userAgent;
expect(res).toMatchObject({
serverUrl: 'https://dev.azure.com/renovate1',
authHandler: {
token: '123test',
},
});
});
it('should set bearer token and endpoint', () => {
const token = buildTestJwt(
{ typ: 'JWT', alg: 'RS256' },
{ aud: '499b84ac', sub: 'test', exp: 9999999999 },
'fake-sig',
);
hostRules.add({
hostType: 'azure',
token,
matchHost: 'https://dev.azure.com/renovate2',
});
azure.setEndpoint('https://dev.azure.com/renovate2');
const res = azure.azureObj();
delete res.rest.client.userAgent;
delete res.vsoClient.restClient.client.userAgent;
expect(res).toMatchObject({
serverUrl: 'https://dev.azure.com/renovate2',
authHandler: {
token,
},
});
});
it('should set password and endpoint', () => {
hostRules.add({
hostType: 'azure',
username: 'user',
password: 'pass',
matchHost: 'https://dev.azure.com/renovate3',
});
azure.setEndpoint('https://dev.azure.com/renovate3');
const res = azure.azureObj();
delete res.rest.client.userAgent;
delete res.vsoClient.restClient.client.userAgent;
expect(res).toMatchObject({
serverUrl: 'https://dev.azure.com/renovate3',
authHandler: {
username: 'user',
password: 'pass',
},
});
});
it('should use BearerCredentialHandler for JWT tokens', () => {
const jwt = buildTestJwt(
{ typ: 'JWT', alg: 'RS256' },
{ aud: '499b84ac', sub: 'test', exp: 9999999999 },
'fake-sig',
);
hostRules.add({
hostType: 'azure',
token: jwt,
matchHost: 'https://dev.azure.com/renovate4',
});
azure.setEndpoint('https://dev.azure.com/renovate4');
const res = azure.azureObj();
expect(res.authHandler).toHaveProperty('token', jwt);
expect(res.authHandler.constructor.name).toBe('BearerCredentialHandler');
});
it('should use PersonalAccessTokenHandler for PAT tokens', () => {
const pat = 'a'.repeat(52);
hostRules.add({
hostType: 'azure',
token: pat,
matchHost: 'https://dev.azure.com/renovate5',
});
azure.setEndpoint('https://dev.azure.com/renovate5');
const res = azure.azureObj();
expect(res.authHandler).toHaveProperty('token', pat);
expect(res.authHandler.constructor.name).toBe(
'PersonalAccessTokenCredentialHandler',
);
});
it('should use PersonalAccessTokenHandler for short opaque tokens', () => {
hostRules.add({
hostType: 'azure',
token: 'shorttoken',
matchHost: 'https://dev.azure.com/renovate6',
});
azure.setEndpoint('https://dev.azure.com/renovate6');
const res = azure.azureObj();
expect(res.authHandler).toHaveProperty('token', 'shorttoken');
expect(res.authHandler.constructor.name).toBe(
'PersonalAccessTokenCredentialHandler',
);
});
});
describe('isHosted', () => {
let sdk: typeof import('azure-devops-node-api');
beforeEach(async () => {
sdk = await vi.importActual('azure-devops-node-api');
hostRules.add({
hostType: 'azure',
token: '123test',
matchHost: 'https://dev.azure.com/renovate7',
});
azure.setEndpoint('https://dev.azure.com/renovate7');
});
it('returns true when deployment type is Hosted', async () => {
// DeploymentFlags.Hosted === 1
vi.spyOn(sdk.WebApi.prototype, 'connect').mockResolvedValue({
deploymentType: 1,
});
await expect(azure.isHosted()).resolves.toBe(true);
});
it('returns true when deployment type is the serialized enum name', async () => {
vi.spyOn(sdk.WebApi.prototype, 'connect').mockResolvedValue({
deploymentType: 'hosted' as unknown as DeploymentFlags,
});
await expect(azure.isHosted()).resolves.toBe(true);
});
it('returns false when deployment type is OnPremises', async () => {
// DeploymentFlags.OnPremises === 2
vi.spyOn(sdk.WebApi.prototype, 'connect').mockResolvedValue({
deploymentType: 2,
});
await expect(azure.isHosted()).resolves.toBe(false);
});
it('returns false when deployment type is the on-premises enum name', async () => {
vi.spyOn(sdk.WebApi.prototype, 'connect').mockResolvedValue({
deploymentType: 'onPremises' as unknown as DeploymentFlags,
});
await expect(azure.isHosted()).resolves.toBe(false);
});
it('returns false when connectionData cannot be read', async () => {
vi.spyOn(sdk.WebApi.prototype, 'connect').mockRejectedValue(
new Error('boom'),
);
await expect(azure.isHosted()).resolves.toBe(false);
});
});
describe('getAuthenticatedUserId', () => {
let sdk: typeof import('azure-devops-node-api');
beforeEach(async () => {
sdk = await vi.importActual('azure-devops-node-api');
azure.setEndpoint('https://dev.azure.com/renovate8');
});
it('returns the authenticated user ID using PAT credentials', async () => {
const connect = vi
.spyOn(sdk.WebApi.prototype, 'connect')
.mockResolvedValue({ authenticatedUser: { id: 'user-id' } });
await expect(
azure.getAuthenticatedUserId({ token: '123test' }),
).resolves.toBe('user-id');
const context = connect.mock.contexts[0] as WebApi;
expect(context.authHandler.constructor.name).toBe(
'PersonalAccessTokenCredentialHandler',
);
});
it('returns the authenticated user ID using JWT credentials', async () => {
const token = buildTestJwt(
{ typ: 'JWT', alg: 'RS256' },
{ aud: '499b84ac', sub: 'test', exp: 9999999999 },
'fake-sig',
);
const connect = vi
.spyOn(sdk.WebApi.prototype, 'connect')
.mockResolvedValue({ authenticatedUser: { id: 'user-id' } });
await expect(azure.getAuthenticatedUserId({ token })).resolves.toBe(
'user-id',
);
const context = connect.mock.contexts[0] as WebApi;
expect(context.authHandler.constructor.name).toBe(
'BearerCredentialHandler',
);
});
it('returns the authenticated user ID using username and password', async () => {
const connect = vi
.spyOn(sdk.WebApi.prototype, 'connect')
.mockResolvedValue({ authenticatedUser: { id: 'user-id' } });
await expect(
azure.getAuthenticatedUserId({
username: 'user',
password: 'pass',
}),
).resolves.toBe('user-id');
const context = connect.mock.contexts[0] as WebApi;
expect(context.authHandler).toMatchObject({
username: 'user',
password: 'pass',
});
});
it('returns undefined when the authenticated user ID is unavailable', async () => {
vi.spyOn(sdk.WebApi.prototype, 'connect').mockResolvedValue({});
await expect(
azure.getAuthenticatedUserId({ token: '123test' }),
).resolves.toBeUndefined();
});
it('returns undefined when connection data cannot be read', async () => {
vi.spyOn(sdk.WebApi.prototype, 'connect').mockRejectedValue(
new Error('boom'),
);
await expect(
azure.getAuthenticatedUserId({ token: '123test' }),
).resolves.toBeUndefined();
expect(logger.logger.debug).toHaveBeenCalledWith(
{ err: new Error('boom') },
'Azure: could not determine authenticated user ID',
);
});
});
});