Skip to content

Commit 7b66ae5

Browse files
author
damccorm
authored
Added more detailed user agent string (#223)
* Added more detailed user agent string * Added tests for user agent * Getting rid of file that has been renamed * Responding to feedback * Responding to rest of feedback * Added nock test for user agent * Updated tests to use assert.equal instead of assert
1 parent e88c657 commit 7b66ae5

File tree

5 files changed

+101
-6
lines changed

5 files changed

+101
-6
lines changed

api/WebApi.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ import * as rm from 'typed-rest-client/RestClient';
3535
import vsom = require('./VsoClient');
3636
import lim = require("./interfaces/LocationsInterfaces");
3737

38-
import fs = require('fs');
3938
import crypto = require('crypto');
39+
import fs = require('fs');
40+
import os = require('os');
4041

4142
/**
4243
* Methods to return handler objects (see handlers folder)
@@ -67,6 +68,11 @@ export function getHandlerFromToken(token: string): VsoBaseInterfaces.IRequestHa
6768
}
6869
}
6970

71+
export interface IWebApiRequestSettings {
72+
productName: string,
73+
productVersion: string
74+
};
75+
7076
// ---------------------------------------------------------------------------
7177
// Factory to return client apis
7278
// When new APIs are added, a method must be added here to instantiate the API
@@ -86,7 +92,7 @@ export class WebApi {
8692
* @param defaultUrl default server url to use when creating new apis from factory methods
8793
* @param authHandler default authentication credentials to use when creating new apis from factory methods
8894
*/
89-
constructor(defaultUrl: string, authHandler: VsoBaseInterfaces.IRequestHandler, options?: VsoBaseInterfaces.IRequestOptions) {
95+
constructor(defaultUrl: string, authHandler: VsoBaseInterfaces.IRequestHandler, options?: VsoBaseInterfaces.IRequestOptions, requestSettings?: IWebApiRequestSettings) {
9096
this.serverUrl = defaultUrl;
9197
this.authHandler = authHandler;
9298
this.options = options || {};
@@ -124,7 +130,19 @@ export class WebApi {
124130
this.options.ignoreSslError = !!global['_vsts_task_lib_skip_cert_validation'];
125131
}
126132

127-
this.rest = new rm.RestClient('vsts-node-api', null, [this.authHandler], this.options);
133+
let userAgent: string;
134+
const nodeApiName: string = 'azure-devops-node-api';
135+
const nodeApiVersion: string = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
136+
const osName: string = os.platform();
137+
const osVersion: string = os.release();
138+
139+
if(requestSettings) {
140+
userAgent = `${requestSettings.productName}/${requestSettings.productVersion} (${nodeApiName} ${nodeApiVersion}; ${osName} ${osVersion})`;
141+
}
142+
else {
143+
userAgent = `${nodeApiName}/${nodeApiVersion} (${osName} ${osVersion})`;
144+
}
145+
this.rest = new rm.RestClient(userAgent, null, [this.authHandler], this.options);
128146
this.vsoClient = new vsom.VsoClient(defaultUrl, this.rest);
129147
}
130148

package-lock.json

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
],
2525
"license": "MIT",
2626
"dependencies": {
27+
"os": "0.1.1",
2728
"tunnel": "0.0.4",
2829
"typed-rest-client": "1.0.9",
2930
"underscore": "1.8.3"

samples/package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/units/vsoClientTests.ts renamed to test/units/tests.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import assert = require('assert');
2+
import fs = require('fs');
23
import nock = require('nock');
4+
import os = require('os');
35
import vsom = require('../../api/VsoClient');
6+
import WebApi = require('../../api/WebApi');
47
import * as rm from 'typed-rest-client/RestClient';
5-
import { resolve } from 'path';
68
import { ApiResourceLocation } from '../../api/interfaces/common/VsoBaseInterfaces';
79

810
describe('VSOClient Units', function () {
@@ -27,7 +29,6 @@ describe('VSOClient Units', function () {
2729
it('constructs', () => {
2830
//Arrange
2931
this.timeout(1000);
30-
const baseUrl = 'https://dev.azure.com/';
3132
const userAgent: string = "testAgent";
3233
const rest: rm.RestClient = new rm.RestClient(userAgent, null, []);
3334

@@ -182,4 +183,69 @@ describe('VSOClient Units', function () {
182183
//Assert
183184
assert(res.id === "testLocation");
184185
});
186+
});
187+
188+
describe('WebApi Units', function () {
189+
const osName: string = os.platform();
190+
const osVersion: string = os.release();
191+
const nodeApiName: string = 'azure-devops-node-api';
192+
const nodeApiVersion: string = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
193+
194+
it('sets the user agent correctly when request settings are specified', async () => {
195+
const myWebApi: WebApi.WebApi = new WebApi.WebApi('microsoft.com', WebApi.getBasicHandler('user', 'password'),
196+
undefined, {productName: 'name', productVersion: '1.2.3'});
197+
const userAgent: string = `name/1.2.3 (${nodeApiName} ${nodeApiVersion}; ${osName} ${osVersion})`;
198+
assert.equal(userAgent, myWebApi.rest.client.userAgent, 'User agent should be: ' + userAgent);
199+
});
200+
201+
it('sets the user agent correctly when request settings are not specified', async () => {
202+
const myWebApi: WebApi.WebApi = new WebApi.WebApi('microsoft.com', WebApi.getBasicHandler('user', 'password'), undefined);
203+
const userAgent: string = `${nodeApiName}/${nodeApiVersion} (${osName} ${osVersion})`;
204+
assert.equal(userAgent, myWebApi.rest.client.userAgent, 'User agent should be: ' + userAgent);
205+
});
206+
207+
it('connects to the server with the correct user agent when request settings are specified', async () => {
208+
const myWebApi: WebApi.WebApi = new WebApi.WebApi('https://dev.azure.com/', WebApi.getBasicHandler('user', 'password'),
209+
undefined, {productName: 'name', productVersion: '1.2.3'});
210+
const userAgent: string = `name/1.2.3 (${nodeApiName} ${nodeApiVersion}; ${osName} ${osVersion})`;
211+
nock('https://dev.azure.com/_apis/testArea', {
212+
reqheaders: {
213+
'accept': 'application/json',
214+
'user-agent': userAgent
215+
}})
216+
.options('')
217+
.reply(200, {
218+
value: [{id: 'testLocation', maxVersion: '1', releasedVersion: '1', routeTemplate: 'testTemplate', area: 'testArea', resourceName: 'testName', resourceVersion: '1'}]
219+
});
220+
221+
// Act
222+
const res: vsom.ClientVersioningData = await myWebApi.vsoClient.getVersioningData('1', 'testArea', 'testLocation', {'testKey': 'testValue'}, null);
223+
224+
// Assert
225+
assert.equal(res.apiVersion, '1');
226+
assert.equal(res.requestUrl, 'https://dev.azure.com/testTemplate');
227+
228+
});
229+
230+
it('connects to the server with the correct user agent when request settings are not specified', async () => {
231+
// Arrange
232+
const myWebApi: WebApi.WebApi = new WebApi.WebApi('https://dev.azure.com/', WebApi.getBasicHandler('user', 'password'), null);
233+
const userAgent: string = `${nodeApiName}/${nodeApiVersion} (${osName} ${osVersion})`;
234+
nock('https://dev.azure.com/_apis/testArea', {
235+
reqheaders: {
236+
'accept': 'application/json',
237+
'user-agent': userAgent
238+
}})
239+
.options('')
240+
.reply(200, {
241+
value: [{id: 'testLocation', maxVersion: '1', releasedVersion: '1', routeTemplate: 'testTemplate', area: 'testArea', resourceName: 'testName', resourceVersion: '1'}]
242+
});
243+
244+
// Act
245+
const res: vsom.ClientVersioningData = await myWebApi.vsoClient.getVersioningData('1', 'testArea', 'testLocation', {'testKey': 'testValue'}, null);
246+
247+
// Assert
248+
assert.equal(res.apiVersion, '1');
249+
assert.equal(res.requestUrl, 'https://dev.azure.com/testTemplate');
250+
});
185251
});

0 commit comments

Comments
 (0)