diff --git a/api/VsoClient.ts b/api/VsoClient.ts index bcf51990..f8c9de27 100644 --- a/api/VsoClient.ts +++ b/api/VsoClient.ts @@ -183,41 +183,37 @@ export class VsoClient { return url.resolve(this.baseUrl, path.join(this.basePath, relativeUrl)); } - private getSerializedObject(queryValue: any, object: any): string { - let value:string = ""; - let first:boolean = true; - - for (let property in object) { - if (object.hasOwnProperty(property)) { - let prop = object[property]; - let valueString = this.getValueString(property, prop); - if (first && prop !== undefined) { - value += valueString; - first = false; - } else if (prop !== undefined) { - value += "&" + valueString; + private queryParamsToStringHelper(queryParams: any, prefix: string): string { + if (queryParams === undefined) { + return ''; + } + let queryString: string = ''; + + if(typeof(queryParams) !== 'string') { + for (let property in queryParams) { + if (queryParams.hasOwnProperty(property)) { + const prop = queryParams[property]; + const newPrefix = prefix + encodeURIComponent(property.toString()) + '.'; + queryString += this.queryParamsToStringHelper(prop, newPrefix); } } } - if (value == ""){ - value += queryValue + "=" + object.toString(); + if(queryString === '' && prefix.length > 0){ + // Will always need to chop period off of end of prefix + queryString = prefix.slice(0,-1) + '=' + encodeURIComponent(queryParams.toString()) + '&'; } - return value; + return queryString; } - private getValueString(queryValue, value) { - let valueString = null; - if (typeof(value) === 'object') { - valueString = this.getSerializedObject(queryValue, value); - } else { - valueString = queryValue + "=" + encodeURIComponent(value); - } - return valueString; + private queryParamsToString(queryParams: any): string { + const queryString: string = '?' + this.queryParamsToStringHelper(queryParams, ''); + + // Will always need to slice either a ? or & off of the end + return queryString.slice(0,-1); } protected getRequestUrl(routeTemplate: string, area: string, resource: string, routeValues: any, queryParams?: any): string { - // Add area/resource route values (based on the location) routeValues = routeValues || {}; if (!routeValues.area) { @@ -230,22 +226,12 @@ export class VsoClient { // Replace templated route values let relativeUrl = this.replaceRouteValues(routeTemplate, routeValues); - //append query parameters to the end - let first = true; - for (let queryValue in queryParams) { - if (queryParams[queryValue] != null) { - let value = queryParams[queryValue]; - let valueString = this.getValueString(queryValue, value); - if (first) { - relativeUrl += "?" + valueString; - first = false; - } else { - relativeUrl += "&" + valueString; - } - } + // Append query parameters to the end + if (queryParams) { + relativeUrl += this.queryParamsToString(queryParams); } - //resolve the relative url with the base + // Resolve the relative url with the base return url.resolve(this.baseUrl, path.join(this.basePath, relativeUrl)); } diff --git a/package.json b/package.json index 00a92a85..b2fb25c1 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": "6.6.1", + "version": "6.6.2", "main": "./WebApi.js", "types": "./WebApi.d.ts", "scripts": { diff --git a/test/units/tests.ts b/test/units/tests.ts index e76de0c2..0385dee7 100644 --- a/test/units/tests.ts +++ b/test/units/tests.ts @@ -138,7 +138,28 @@ describe('VSOClient Units', function () { //Assert assert(res.apiVersion === '1'); - assert(res.requestUrl === 'https://dev.azure.com/testTemplate?innerstatus=2'); + assert(res.requestUrl === 'https://dev.azure.com/testTemplate?status.innerstatus=2'); + }); + + it('gets versioning data with complex nested query params', async () => { + //Arrange + nock('https://dev.azure.com/_apis/testArea5', { + reqheaders: { + 'accept': 'application/json', + 'user-agent': 'testAgent' + }}) + .options('') + .reply(200, { + value: [{id: 'testLocation', maxVersion: '1', releasedVersion: '1', routeTemplate: 'testTemplate', area: 'testArea5', resourceName: 'testName', resourceVersion: '1'}] + }); + + //Act + const queryParams = {status: {innerstatus: 2}, version: '1', nestedObject: {nestedField: 'value', innerNestedObject: {key: 'val2'}}}; + const res: vsom.ClientVersioningData = await vsoClient.getVersioningData('1', 'testArea5', 'testLocation', {'testKey': 'testValue'}, queryParams); + + //Assert + assert.equal(res.apiVersion, '1'); + assert.equal(res.requestUrl, 'https://dev.azure.com/testTemplate?status.innerstatus=2&version=1&nestedObject.nestedField=value&nestedObject.innerNestedObject.key=val2'); }); it('gets versioning data after an initialization promise', async () => {