From 414e6d818fd69f58c37ed4df18a00460ee889028 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:55:43 +0200 Subject: [PATCH 1/4] Add ServiceEndpointApi integration to WebApi - Imported ServiceEndpointApi module. - Updated _resourceAreas type to allow undefined. - Implemented getServiceEndpointApi method to retrieve ServiceEndpointApi instance. - Cleaned up whitespace in the encryption key handling section. --- api/ServiceEndpointApi.ts | 14 + api/ServiceEndpointApiBase.ts | 1395 +++++++++++++++++++++++++++++++++ api/WebApi.ts | 13 +- 3 files changed, 1419 insertions(+), 3 deletions(-) create mode 100644 api/ServiceEndpointApi.ts create mode 100644 api/ServiceEndpointApiBase.ts diff --git a/api/ServiceEndpointApi.ts b/api/ServiceEndpointApi.ts new file mode 100644 index 0000000..2c92e2f --- /dev/null +++ b/api/ServiceEndpointApi.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import VsoBaseInterfaces = require('./interfaces/common/VsoBaseInterfaces'); +import serviceendpointbasem = require('./ServiceEndpointApiBase'); + +export interface IServiceEndpointApi extends serviceendpointbasem.IServiceEndpointApiBase { +} + +export class ServiceEndpointApi extends serviceendpointbasem.ServiceEndpointApiBase implements IServiceEndpointApi { + constructor(baseUrl: string, handlers: VsoBaseInterfaces.IRequestHandler[], options?: VsoBaseInterfaces.IRequestOptions, userAgent?: string) { + super(baseUrl, handlers, options, userAgent); + } +} diff --git a/api/ServiceEndpointApiBase.ts b/api/ServiceEndpointApiBase.ts new file mode 100644 index 0000000..96f7cc8 --- /dev/null +++ b/api/ServiceEndpointApiBase.ts @@ -0,0 +1,1395 @@ +/* + * --------------------------------------------------------- + * Copyright(C) Microsoft Corporation. All rights reserved. + * --------------------------------------------------------- + * + * --------------------------------------------------------- + * Generated file, DO NOT EDIT + * --------------------------------------------------------- + */ + +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import * as restm from 'typed-rest-client/RestClient'; +import vsom = require('./VsoClient'); +import basem = require('./ClientApiBases'); +import VsoBaseInterfaces = require('./interfaces/common/VsoBaseInterfaces'); +import ServiceEndpointInterfaces = require("./interfaces/ServiceEndpointInterfaces"); +import VSSInterfaces = require("./interfaces/common/VSSInterfaces"); + +export interface IServiceEndpointApiBase extends basem.ClientApiBase { + getAzureManagementGroups(): Promise; + getAzureSubscriptions(): Promise; + executeServiceEndpointRequest(serviceEndpointRequest: ServiceEndpointInterfaces.ServiceEndpointRequest, project: string, endpointId: string): Promise; + queryServiceEndpoint(binding: ServiceEndpointInterfaces.DataSourceBinding, project: string): Promise; + createServiceEndpoint(endpoint: ServiceEndpointInterfaces.ServiceEndpoint): Promise; + deleteServiceEndpoint(endpointId: string, projectIds: string[], deep?: boolean): Promise; + getServiceEndpointsByTypeAndOwner(type: string, owner: string): Promise; + shareServiceEndpoint(endpointProjectReferences: ServiceEndpointInterfaces.ServiceEndpointProjectReference[], endpointId: string): Promise; + updateServiceEndpoint(endpoint: ServiceEndpointInterfaces.ServiceEndpoint, endpointId: string, operation?: string): Promise; + updateServiceEndpoints(endpoints: ServiceEndpointInterfaces.ServiceEndpoint[]): Promise; + getServiceEndpointDetails(project: string, endpointId: string, actionFilter?: ServiceEndpointInterfaces.ServiceEndpointActionFilter, loadConfidentialData?: boolean): Promise; + getServiceEndpoints(project: string, type?: string, authSchemes?: string[], endpointIds?: string[], owner?: string, includeFailed?: boolean, includeDetails?: boolean, actionFilter?: ServiceEndpointInterfaces.ServiceEndpointActionFilter): Promise; + getServiceEndpointsByNames(project: string, endpointNames: string[], type?: string, authSchemes?: string[], owner?: string, includeFailed?: boolean, includeDetails?: boolean): Promise; + getServiceEndpointsWithRefreshedAuthentication(refreshAuthenticationParameters: ServiceEndpointInterfaces.RefreshAuthenticationParameters[], project: string, endpointIds: string[]): Promise; + getServiceEndpointExecutionRecords(project: string, endpointId: string, top?: number, continuationToken?: number): Promise>; + addServiceEndpointExecutionRecords(input: ServiceEndpointInterfaces.ServiceEndpointExecutionRecordsInput, project: string): Promise; + createOAuthConfiguration(configurationParams: ServiceEndpointInterfaces.OAuthConfigurationParams): Promise; + deleteOAuthConfiguration(configurationId: string): Promise; + getOAuthConfiguration(configurationId: string): Promise; + getOAuthConfigurations(endpointType?: string, actionFilter?: ServiceEndpointInterfaces.OAuthConfigurationActionFilter): Promise; + updateOAuthConfiguration(configurationParams: ServiceEndpointInterfaces.OAuthConfigurationParams, configurationId: string): Promise; + querySharedProjects(endpointId: string, project: string): Promise; + shareEndpointWithProject(endpointId: string, fromProject: string, withProject: string): Promise; + getServiceEndpointTypes(type?: string, scheme?: string): Promise; + getFilteredServiceEndpointTypes(typesFilter: string[]): Promise; + createAadOAuthRequest(tenantId: string, redirectUri: string, promptOption?: ServiceEndpointInterfaces.AadLoginPromptOption, completeCallbackPayload?: string, completeCallbackByAuthCode?: boolean): Promise; + getVstsAadTenantId(): Promise; +} + +export class ServiceEndpointApiBase extends basem.ClientApiBase implements IServiceEndpointApiBase { + constructor(baseUrl: string, handlers: VsoBaseInterfaces.IRequestHandler[], options?: VsoBaseInterfaces.IRequestOptions, userAgent?: string) { + super(baseUrl, handlers, userAgent || 'node-ServiceEndpoint-api', options); + } + + public static readonly RESOURCE_AREA_ID = "1814ab31-2f4f-4a9f-8761-f4d77dc5a5d7"; + + /** + * Returns list of azure subscriptions + * + */ + public async getAzureManagementGroups( + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "9acb984c-4f88-4e13-9691-2e688dddc047", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Returns list of azure subscriptions + * + */ + public async getAzureSubscriptions( + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "18e8f65d-4e19-4a01-a621-cf0f2d938108", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Proxy for a GET request defined by a service endpoint. + * + * @param {ServiceEndpointInterfaces.ServiceEndpointRequest} serviceEndpointRequest - Service endpoint request. + * @param {string} project - Project ID or project name + * @param {string} endpointId - Id of the service endpoint. + */ + public async executeServiceEndpointRequest( + serviceEndpointRequest: ServiceEndpointInterfaces.ServiceEndpointRequest, + project: string, + endpointId: string + ): Promise { + if (endpointId == null) { + throw new TypeError('endpointId can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project + }; + + let queryValues: any = { + endpointId: endpointId, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "cc63bb57-2a5f-4a7a-b79c-c142d308657e", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, serviceEndpointRequest, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpointRequestResult, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Proxy for a GET request defined by a service endpoint. The request is authorized using a data source in service endpoint. The response is filtered using an XPath/Json based selector. + * + * @param {ServiceEndpointInterfaces.DataSourceBinding} binding - Describes the data source to fetch. + * @param {string} project - Project ID or project name + */ + public async queryServiceEndpoint( + binding: ServiceEndpointInterfaces.DataSourceBinding, + project: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "cc63bb57-2a5f-4a7a-b79c-c142d308657e", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, binding, options); + + let ret = this.formatResponse(res.result, + null, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Creates a new service endpoint + * + * @param {ServiceEndpointInterfaces.ServiceEndpoint} endpoint - Service endpoint to create + */ + public async createServiceEndpoint( + endpoint: ServiceEndpointInterfaces.ServiceEndpoint + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "14e48fdc-2c8b-41ce-a0c3-e26f6cc55bd0", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, endpoint, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Delete a service endpoint + * + * @param {string} endpointId - Endpoint Id of endpoint to delete + * @param {string[]} projectIds - project Ids from which endpoint needs to be deleted + * @param {boolean} deep - delete the spn created by endpoint + */ + public async deleteServiceEndpoint( + endpointId: string, + projectIds: string[], + deep?: boolean + ): Promise { + if (projectIds == null) { + throw new TypeError('projectIds can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + endpointId: endpointId + }; + + let queryValues: any = { + projectIds: projectIds && projectIds.join(","), + deep: deep, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "14e48fdc-2c8b-41ce-a0c3-e26f6cc55bd0", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.del(url, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get service endpoints for org by type and owner. Returns only id, name and url and used only internally by licensing service + * + * @param {string} type - Type of the service endpoints. + * @param {string} owner - Owner for service endpoints. + */ + public async getServiceEndpointsByTypeAndOwner( + type: string, + owner: string + ): Promise { + if (type == null) { + throw new TypeError('type can not be null or undefined'); + } + if (owner == null) { + throw new TypeError('owner can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + let queryValues: any = { + type: type, + owner: owner, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "14e48fdc-2c8b-41ce-a0c3-e26f6cc55bd0", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Share service endpoint across projects + * + * @param {ServiceEndpointInterfaces.ServiceEndpointProjectReference[]} endpointProjectReferences - Project reference details of the target project + * @param {string} endpointId - Endpoint Id of the endpoint to share + */ + public async shareServiceEndpoint( + endpointProjectReferences: ServiceEndpointInterfaces.ServiceEndpointProjectReference[], + endpointId: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + endpointId: endpointId + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "14e48fdc-2c8b-41ce-a0c3-e26f6cc55bd0", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.update(url, endpointProjectReferences, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Update the service endpoint + * + * @param {ServiceEndpointInterfaces.ServiceEndpoint} endpoint - Updated data for the endpoint + * @param {string} endpointId - Endpoint Id of the endpoint to update + * @param {string} operation - operation type + */ + public async updateServiceEndpoint( + endpoint: ServiceEndpointInterfaces.ServiceEndpoint, + endpointId: string, + operation?: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + endpointId: endpointId + }; + + let queryValues: any = { + operation: operation, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "14e48fdc-2c8b-41ce-a0c3-e26f6cc55bd0", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.replace(url, endpoint, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Update the service endpoints. + * + * @param {ServiceEndpointInterfaces.ServiceEndpoint[]} endpoints - Names of the service endpoints to update. + */ + public async updateServiceEndpoints( + endpoints: ServiceEndpointInterfaces.ServiceEndpoint[] + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "14e48fdc-2c8b-41ce-a0c3-e26f6cc55bd0", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.replace(url, endpoints, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get the service endpoint details. + * + * @param {string} project - Project ID or project name + * @param {string} endpointId - Id of the service endpoint. + * @param {ServiceEndpointInterfaces.ServiceEndpointActionFilter} actionFilter - Action filter for the service connection. It specifies the action which can be performed on the service connection. + * @param {boolean} loadConfidentialData - Flag to include confidential details of service endpoint. This is for internal use only. + */ + public async getServiceEndpointDetails( + project: string, + endpointId: string, + actionFilter?: ServiceEndpointInterfaces.ServiceEndpointActionFilter, + loadConfidentialData?: boolean + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project, + endpointId: endpointId + }; + + let queryValues: any = { + actionFilter: actionFilter, + loadConfidentialData: loadConfidentialData, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "e85f1c62-adfc-4b74-b618-11a150fb195e", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get the service endpoints. + * + * @param {string} project - Project ID or project name + * @param {string} type - Type of the service endpoints. + * @param {string[]} authSchemes - Authorization schemes used for service endpoints. + * @param {string[]} endpointIds - Ids of the service endpoints. + * @param {string} owner - Owner for service endpoints. + * @param {boolean} includeFailed - Failed flag for service endpoints. + * @param {boolean} includeDetails - Flag to include more details for service endpoints. This is for internal use only and the flag will be treated as false for all other requests + * @param {ServiceEndpointInterfaces.ServiceEndpointActionFilter} actionFilter - The "actionFilter" parameter allows users to evaluate requestor permissions and retrieve a list of endpoints that match the specified conditions, ensuring that only relevant endpoints are returned based on their permissions + */ + public async getServiceEndpoints( + project: string, + type?: string, + authSchemes?: string[], + endpointIds?: string[], + owner?: string, + includeFailed?: boolean, + includeDetails?: boolean, + actionFilter?: ServiceEndpointInterfaces.ServiceEndpointActionFilter + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project + }; + + let queryValues: any = { + type: type, + authSchemes: authSchemes && authSchemes.join(","), + endpointIds: endpointIds && endpointIds.join(","), + owner: owner, + includeFailed: includeFailed, + includeDetails: includeDetails, + actionFilter: actionFilter, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "e85f1c62-adfc-4b74-b618-11a150fb195e", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get the service endpoints by name. + * + * @param {string} project - Project ID or project name + * @param {string[]} endpointNames - Names of the service endpoints. + * @param {string} type - Type of the service endpoints. + * @param {string[]} authSchemes - Authorization schemes used for service endpoints. + * @param {string} owner - Owner for service endpoints. + * @param {boolean} includeFailed - Failed flag for service endpoints. + * @param {boolean} includeDetails - Flag to include more details for service endpoints. This is for internal use only and the flag will be treated as false for all other requests + */ + public async getServiceEndpointsByNames( + project: string, + endpointNames: string[], + type?: string, + authSchemes?: string[], + owner?: string, + includeFailed?: boolean, + includeDetails?: boolean + ): Promise { + if (endpointNames == null) { + throw new TypeError('endpointNames can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project + }; + + let queryValues: any = { + endpointNames: endpointNames && endpointNames.join(","), + type: type, + authSchemes: authSchemes && authSchemes.join(","), + owner: owner, + includeFailed: includeFailed, + includeDetails: includeDetails, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "e85f1c62-adfc-4b74-b618-11a150fb195e", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Gets the service endpoints and patch new authorization parameters + * + * @param {ServiceEndpointInterfaces.RefreshAuthenticationParameters[]} refreshAuthenticationParameters - Scope, Validity of Token requested. + * @param {string} project - Project ID or project name + * @param {string[]} endpointIds - Ids of the service endpoints. + */ + public async getServiceEndpointsWithRefreshedAuthentication( + refreshAuthenticationParameters: ServiceEndpointInterfaces.RefreshAuthenticationParameters[], + project: string, + endpointIds: string[] + ): Promise { + if (endpointIds == null) { + throw new TypeError('endpointIds can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project + }; + + let queryValues: any = { + endpointIds: endpointIds && endpointIds.join(","), + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.4", + "serviceendpoint", + "e85f1c62-adfc-4b74-b618-11a150fb195e", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, refreshAuthenticationParameters, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpoint, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get service endpoint execution records. + * + * @param {string} project - Project ID or project name + * @param {string} endpointId - Id of the service endpoint. + * @param {number} top - Number of service endpoint execution records to get. + * @param {number} continuationToken - A continuation token, returned by a previous call to this method, that can be used to return the next set of records + */ + public async getServiceEndpointExecutionRecords( + project: string, + endpointId: string, + top?: number, + continuationToken?: number + ): Promise> { + + return new Promise>(async (resolve, reject) => { + let routeValues: any = { + project: project, + endpointId: endpointId + }; + + let queryValues: any = { + top: top, + continuationToken: continuationToken, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "10a16738-9299-4cd1-9a81-fd23ad6200d0", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse>; + res = await this.rest.get>(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpointExecutionRecord, + true); + if (res.headers && res.headers['x-ms-continuationtoken']) { + ret.continuationToken = res.headers['x-ms-continuationtoken']; + } + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Add service endpoint execution records. + * + * @param {ServiceEndpointInterfaces.ServiceEndpointExecutionRecordsInput} input - Service endpoint execution records to add. + * @param {string} project - Project ID or project name + */ + public async addServiceEndpointExecutionRecords( + input: ServiceEndpointInterfaces.ServiceEndpointExecutionRecordsInput, + project: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + project: project + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "55b9ed4b-5404-41b1-b9d2-7ed757d02bb0", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, input, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpointExecutionRecord, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {ServiceEndpointInterfaces.OAuthConfigurationParams} configurationParams + */ + public async createOAuthConfiguration( + configurationParams: ServiceEndpointInterfaces.OAuthConfigurationParams + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "702edb4e-3952-43fe-a4eb-288938f3ba35", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, configurationParams, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.OAuthConfiguration, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {string} configurationId + */ + public async deleteOAuthConfiguration( + configurationId: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + configurationId: configurationId + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "702edb4e-3952-43fe-a4eb-288938f3ba35", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.del(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.OAuthConfiguration, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {string} configurationId + */ + public async getOAuthConfiguration( + configurationId: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + configurationId: configurationId + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "702edb4e-3952-43fe-a4eb-288938f3ba35", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.OAuthConfiguration, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {string} endpointType + * @param {ServiceEndpointInterfaces.OAuthConfigurationActionFilter} actionFilter + */ + public async getOAuthConfigurations( + endpointType?: string, + actionFilter?: ServiceEndpointInterfaces.OAuthConfigurationActionFilter + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + let queryValues: any = { + endpointType: endpointType, + actionFilter: actionFilter, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "702edb4e-3952-43fe-a4eb-288938f3ba35", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.OAuthConfiguration, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {ServiceEndpointInterfaces.OAuthConfigurationParams} configurationParams + * @param {string} configurationId + */ + public async updateOAuthConfiguration( + configurationParams: ServiceEndpointInterfaces.OAuthConfigurationParams, + configurationId: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + configurationId: configurationId + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "702edb4e-3952-43fe-a4eb-288938f3ba35", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.replace(url, configurationParams, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.OAuthConfiguration, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {string} endpointId + * @param {string} project + */ + public async querySharedProjects( + endpointId: string, + project: string + ): Promise { + if (project == null) { + throw new TypeError('project can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + endpointId: endpointId + }; + + let queryValues: any = { + project: project, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "86e77201-c1f7-46c9-8672-9dfc2f6f568a", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + null, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {string} endpointId + * @param {string} fromProject + * @param {string} withProject + */ + public async shareEndpointWithProject( + endpointId: string, + fromProject: string, + withProject: string + ): Promise { + if (fromProject == null) { + throw new TypeError('fromProject can not be null or undefined'); + } + if (withProject == null) { + throw new TypeError('withProject can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + endpointId: endpointId + }; + + let queryValues: any = { + fromProject: fromProject, + withProject: withProject, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "86e77201-c1f7-46c9-8672-9dfc2f6f568a", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, null, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get service endpoint types. + * + * @param {string} type - Type of service endpoint. + * @param {string} scheme - Scheme of service endpoint. + */ + public async getServiceEndpointTypes( + type?: string, + scheme?: string + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + let queryValues: any = { + type: type, + scheme: scheme, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "5a7938a4-655e-486c-b562-b78c54a7e87b", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpointType, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * Get service endpoint types with passed types filter. + * + * @param {string[]} typesFilter - Filter to limit returned types + */ + public async getFilteredServiceEndpointTypes( + typesFilter: string[] + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "a01a273f-aa29-4066-b042-3ea628ef6848", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, typesFilter, options); + + let ret = this.formatResponse(res.result, + ServiceEndpointInterfaces.TypeInfo.ServiceEndpointType, + true); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + * @param {string} tenantId + * @param {string} redirectUri + * @param {ServiceEndpointInterfaces.AadLoginPromptOption} promptOption + * @param {string} completeCallbackPayload + * @param {boolean} completeCallbackByAuthCode + */ + public async createAadOAuthRequest( + tenantId: string, + redirectUri: string, + promptOption?: ServiceEndpointInterfaces.AadLoginPromptOption, + completeCallbackPayload?: string, + completeCallbackByAuthCode?: boolean + ): Promise { + if (tenantId == null) { + throw new TypeError('tenantId can not be null or undefined'); + } + if (redirectUri == null) { + throw new TypeError('redirectUri can not be null or undefined'); + } + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + let queryValues: any = { + tenantId: tenantId, + redirectUri: redirectUri, + promptOption: promptOption, + completeCallbackPayload: completeCallbackPayload, + completeCallbackByAuthCode: completeCallbackByAuthCode, + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "47911d38-53e1-467a-8c32-d871599d5498", + routeValues, + queryValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.create(url, null, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + + /** + */ + public async getVstsAadTenantId( + ): Promise { + + return new Promise(async (resolve, reject) => { + let routeValues: any = { + }; + + try { + let verData: vsom.ClientVersioningData = await this.vsoClient.getVersioningData( + "7.2-preview.1", + "serviceendpoint", + "47911d38-53e1-467a-8c32-d871599d5498", + routeValues); + + let url: string = verData.requestUrl!; + let options: restm.IRequestOptions = this.createRequestOptions('application/json', + verData.apiVersion); + + let res: restm.IRestResponse; + res = await this.rest.get(url, options); + + let ret = this.formatResponse(res.result, + null, + false); + + this.extractRateLimitHeaders(res.headers, ret); + resolve(ret); + + } + catch (err) { + this.extractRateLimitHeaders(err?.responseHeaders, err); + reject(err); + } + }); + } + +} diff --git a/api/WebApi.ts b/api/WebApi.ts index ab56707..5cf8838 100644 --- a/api/WebApi.ts +++ b/api/WebApi.ts @@ -20,6 +20,7 @@ import profilem = require('./ProfileApi'); import projectm = require('./ProjectAnalysisApi'); import releasem = require('./ReleaseApi'); import securityrolesm = require('./SecurityRolesApi'); +import serviceendpointm = require('./ServiceEndpointApi'); import taskagentm = require('./TaskAgentApi'); import taskm = require('./TaskApi'); import testm = require('./TestApi'); @@ -314,6 +315,12 @@ export class WebApi { return new securityrolesm.SecurityRolesApi(serverUrl, handlers, this.options, this.userAgent); } + public async getServiceEndpointApi(serverUrl?: string, handlers?: VsoBaseInterfaces.IRequestHandler[]): Promise { + serverUrl = await this._getResourceAreaUrl(serverUrl || this.serverUrl, "1814ab31-2f4f-4a9f-8761-f4d77dc5a5d7"); + handlers = handlers || [this.authHandler]; + return new serviceendpointm.ServiceEndpointApi(serverUrl, handlers, this.options, this.userAgent); + } + public async getReleaseApi(serverUrl?: string, handlers?: VsoBaseInterfaces.IRequestHandler[]): Promise { // TODO: Load RESOURCE_AREA_ID correctly. serverUrl = await this._getResourceAreaUrl(serverUrl || this.serverUrl, "efc2f575-36ef-48e9-b672-0c6fb4a48ac5"); @@ -477,16 +484,16 @@ export class WebApi { let keyFile = Buffer.from(lookupInfo[0], 'base64').toString('utf8'); let keyAndIv = fs.readFileSync(keyFile, 'utf8'); - + let [keyBase64, ivBase64] = keyAndIv.split(':', 2); - + if (!keyBase64 || !ivBase64) { throw new Error( 'Invalid encryption key format. Expected "key:iv" format from azure-pipelines-task-lib 5.2.4+. ' + 'This version of azure-devops-node-api (15.2.0+) is not compatible with task-lib <5.2.4.' ); } - + let encryptKey = Buffer.from(keyBase64, 'base64'); let iv = Buffer.from(ivBase64, 'base64'); From 486dad84f6c0b46c7cec11b620ee3730ff848a17 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:00:47 +0200 Subject: [PATCH 2/4] Bump version to 15.3.0 in package.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef2e8f5..e770763 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-devops-node-api", - "version": "15.2.0", + "version": "15.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "azure-devops-node-api", - "version": "15.2.0", + "version": "15.3.0", "license": "MIT", "dependencies": { "tunnel": "0.0.6", diff --git a/package.json b/package.json index 921c1be..08ea3f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azure-devops-node-api", "description": "Node client for Azure DevOps and TFS REST APIs", - "version": "15.2.0", + "version": "15.3.0", "main": "./WebApi.js", "types": "./WebApi.d.ts", "scripts": { From 26384a8c48b872c6a4ef1a12477a003f7d9f86aa Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:02:24 +0200 Subject: [PATCH 3/4] Bump version to 16.0.0 in package.json and package-lock.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e770763..930cd84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-devops-node-api", - "version": "15.3.0", + "version": "16.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "azure-devops-node-api", - "version": "15.3.0", + "version": "16.0.0", "license": "MIT", "dependencies": { "tunnel": "0.0.6", diff --git a/package.json b/package.json index 08ea3f6..bc47480 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azure-devops-node-api", "description": "Node client for Azure DevOps and TFS REST APIs", - "version": "15.3.0", + "version": "16.0.0", "main": "./WebApi.js", "types": "./WebApi.d.ts", "scripts": { From 7de07500a2bb087e0fba6d2321caf14f2733d82b Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:50:08 +0200 Subject: [PATCH 4/4] Update package-lock.json to version 16.0.0 and add unit tests for ServiceEndpointApi --- test/package-lock.json | 2 +- test/units/tests.ts | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/test/package-lock.json b/test/package-lock.json index a9d69cf..71444bb 100644 --- a/test/package-lock.json +++ b/test/package-lock.json @@ -15,7 +15,7 @@ }, "../_build": { "name": "azure-devops-node-api", - "version": "15.2.0", + "version": "16.0.0", "license": "MIT", "dependencies": { "tunnel": "0.0.6", diff --git a/test/units/tests.ts b/test/units/tests.ts index dc99b47..57f4a2c 100644 --- a/test/units/tests.ts +++ b/test/units/tests.ts @@ -467,6 +467,116 @@ describe('WebApi Units', function () { assert.equal(myWebApi.isNoProxyHost('https://my-tfs-instance.host/myproject'), true); assert.equal(myWebApi.isNoProxyHost('https://my-other-tfs-instance.host/myproject'), false); }); + + describe('getServiceEndpointApi', function () { + const baseUrl: string = 'https://dev.azure.com/'; + const serviceEndpointResourceAreaId: string = '1814ab31-2f4f-4a9f-8761-f4d77dc5a5d7'; + const resourceAreasLocationId: string = 'e81700f7-3be2-46de-8624-2eb35882fcaa'; + + afterEach(() => { + nock.cleanAll(); + }); + + // Mocks the Location-area OPTIONS discovery call so the resourceAreas GET routes to `_apis/resourceAreas`. + function mockLocationOptions(server: string): void { + nock(server + '_apis/Location') + .options('') + .reply(200, { + value: [{ + id: resourceAreasLocationId, + maxVersion: '7.2', + releasedVersion: '7.2', + routeTemplate: '_apis/resourceAreas', + area: 'Location', + resourceName: 'ResourceAreas', + resourceVersion: '1' + }] + }); + } + + it('returns a ServiceEndpointApi using the resolved resource area URL', async () => { + // Arrange + const resolvedUrl: string = 'https://serviceendpoint.dev.azure.com/'; + mockLocationOptions(baseUrl); + nock(baseUrl) + .get('/_apis/resourceAreas') + .reply(200, { + value: [{ + id: serviceEndpointResourceAreaId, + name: 'serviceendpoint', + locationUrl: resolvedUrl + }] + }); + const myWebApi: WebApi.WebApi = new WebApi.WebApi(baseUrl, WebApi.getBasicHandler('user', 'password')); + + // Act + const serviceEndpointApi = await myWebApi.getServiceEndpointApi(); + + // Assert + assert(serviceEndpointApi, 'ServiceEndpointApi should be created'); + assert.equal(serviceEndpointApi.baseUrl, resolvedUrl, 'baseUrl should match the resolved resource area locationUrl'); + }); + + it('falls back to the server URL when resource areas are empty (on-prem)', async () => { + // Arrange + const onPremUrl: string = 'https://my-tfs-instance.host/'; + mockLocationOptions(onPremUrl); + nock(onPremUrl) + .get('/_apis/resourceAreas') + .reply(200, { count: 0, value: null }); + const myWebApi: WebApi.WebApi = new WebApi.WebApi(onPremUrl, WebApi.getBasicHandler('user', 'password')); + + // Act + const serviceEndpointApi = await myWebApi.getServiceEndpointApi(); + + // Assert + assert(serviceEndpointApi, 'ServiceEndpointApi should be created'); + assert.equal(serviceEndpointApi.baseUrl, onPremUrl, 'baseUrl should fall back to the server URL on-prem'); + }); + + it('uses provided handlers instead of the default auth handler', async () => { + // Arrange + const resolvedUrl: string = 'https://serviceendpoint.dev.azure.com/'; + mockLocationOptions(baseUrl); + nock(baseUrl) + .get('/_apis/resourceAreas') + .reply(200, { + value: [{ + id: serviceEndpointResourceAreaId, + name: 'serviceendpoint', + locationUrl: resolvedUrl + }] + }); + const customHandler = WebApi.getBearerHandler('custom-token'); + const myWebApi: WebApi.WebApi = new WebApi.WebApi(baseUrl, WebApi.getBasicHandler('user', 'password')); + + // Act + const serviceEndpointApi = await myWebApi.getServiceEndpointApi(undefined, [customHandler]); + + // Assert + assert(serviceEndpointApi, 'ServiceEndpointApi should be created when custom handlers are provided'); + assert.equal(serviceEndpointApi.baseUrl, resolvedUrl, 'baseUrl should still resolve from resource areas'); + }); + + it('uses the provided serverUrl as the fallback baseUrl when resource areas are empty', async () => { + // Arrange + const defaultUrl: string = baseUrl; + const customUrl: string = 'https://custom-tfs.contoso.com/'; + // Resource area discovery happens against the WebApi's default serverUrl (via its own LocationsApi), + // but the custom serverUrl is used as the fallback when no resource areas are returned. + mockLocationOptions(defaultUrl); + nock(defaultUrl) + .get('/_apis/resourceAreas') + .reply(200, { count: 0, value: null }); + const myWebApi: WebApi.WebApi = new WebApi.WebApi(defaultUrl, WebApi.getBasicHandler('user', 'password')); + + // Act + const serviceEndpointApi = await myWebApi.getServiceEndpointApi(customUrl); + + // Assert + assert.equal(serviceEndpointApi.baseUrl, customUrl, 'baseUrl should use the custom serverUrl fallback, not the WebApi default'); + }); + }); }); describe('Auth Handlers Units', function () {