diff --git a/auth/authenticators/authenticator-interface.ts b/auth/authenticators/authenticator-interface.ts index e32fb379a..3ae121d56 100644 --- a/auth/authenticators/authenticator-interface.ts +++ b/auth/authenticators/authenticator-interface.ts @@ -23,9 +23,6 @@ export interface AuthenticateOptions { [propName: string]: any; } -// callback can send one arg, error or null -export type AuthenticateCallback = (result: null | Error) => void; - export interface AuthenticatorInterface { - authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void + authenticate(options: AuthenticateOptions): Promise } diff --git a/auth/authenticators/authenticator.ts b/auth/authenticators/authenticator.ts index afe96805a..4653b6101 100644 --- a/auth/authenticators/authenticator.ts +++ b/auth/authenticators/authenticator.ts @@ -15,7 +15,7 @@ */ import { OutgoingHttpHeaders } from 'http'; -import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; +import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; export class Authenticator implements AuthenticatorInterface { /** @@ -31,7 +31,8 @@ export class Authenticator implements AuthenticatorInterface { } } - public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void { - throw new Error('Should be implemented by subclass!'); + public authenticate(options: AuthenticateOptions): Promise { + const error = new Error('Should be implemented by subclass!'); + return Promise.reject(error); } } \ No newline at end of file diff --git a/auth/authenticators/basic-authenticator.ts b/auth/authenticators/basic-authenticator.ts index b4c6a120f..6a7683201 100644 --- a/auth/authenticators/basic-authenticator.ts +++ b/auth/authenticators/basic-authenticator.ts @@ -17,7 +17,7 @@ import extend = require('extend'); import { computeBasicAuthHeader, validateInput } from '../utils'; import { Authenticator } from './authenticator'; -import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; +import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; export type Options = { username?: string; @@ -48,11 +48,13 @@ export class BasicAuthenticator extends Authenticator implements AuthenticatorIn this.password = options.password; } - public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void { - const authHeaderString = computeBasicAuthHeader(this.username, this.password); - const authHeader = { Authorization: authHeaderString } + public authenticate(options: AuthenticateOptions): Promise { + return new Promise((resolve, reject) => { + const authHeaderString = computeBasicAuthHeader(this.username, this.password); + const authHeader = { Authorization: authHeaderString } - options.headers = extend(true, {}, options.headers, authHeader); - callback(null); + options.headers = extend(true, {}, options.headers, authHeader); + resolve(); + }); } } \ No newline at end of file diff --git a/auth/authenticators/bearer-token-authenticator.ts b/auth/authenticators/bearer-token-authenticator.ts index 65271cd39..ab24117fb 100644 --- a/auth/authenticators/bearer-token-authenticator.ts +++ b/auth/authenticators/bearer-token-authenticator.ts @@ -17,7 +17,7 @@ import extend = require('extend'); import { validateInput } from '../utils'; import { Authenticator } from './authenticator'; -import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; +import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; export type Options = { bearerToken: string; @@ -48,9 +48,11 @@ export class BearerTokenAuthenticator extends Authenticator implements Authentic this.bearerToken = bearerToken; } - public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void { - const authHeader = { Authorization: `Bearer ${this.bearerToken}` }; - options.headers = extend(true, {}, options.headers, authHeader); - callback(null); + public authenticate(options: AuthenticateOptions): Promise { + return new Promise((resolve, reject) => { + const authHeader = { Authorization: `Bearer ${this.bearerToken}` }; + options.headers = extend(true, {}, options.headers, authHeader); + resolve(); + }); } } diff --git a/auth/authenticators/no-auth-authenticator.ts b/auth/authenticators/no-auth-authenticator.ts index cc6a923fe..70f9b9faa 100644 --- a/auth/authenticators/no-auth-authenticator.ts +++ b/auth/authenticators/no-auth-authenticator.ts @@ -15,7 +15,7 @@ */ import { Authenticator } from './authenticator'; -import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; +import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; export class NoAuthAuthenticator extends Authenticator implements AuthenticatorInterface { /** @@ -29,8 +29,8 @@ export class NoAuthAuthenticator extends Authenticator implements AuthenticatorI super(); } - public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void { + public authenticate(options: AuthenticateOptions): Promise { // immediately proceed to request. it will probably fail - callback(null); + return Promise.resolve(); } } diff --git a/auth/authenticators/token-request-based-authenticator.ts b/auth/authenticators/token-request-based-authenticator.ts index cb5f4d2f8..bb0d0d60d 100644 --- a/auth/authenticators/token-request-based-authenticator.ts +++ b/auth/authenticators/token-request-based-authenticator.ts @@ -18,7 +18,7 @@ import extend = require('extend'); import { OutgoingHttpHeaders } from 'http'; import { JwtTokenManager } from '../token-managers'; import { Authenticator } from './authenticator'; -import { AuthenticateCallback, AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; +import { AuthenticateOptions, AuthenticatorInterface } from './authenticator-interface'; export type BaseOptions = { headers?: OutgoingHttpHeaders; @@ -84,15 +84,11 @@ export class TokenRequestBasedAuthenticator extends Authenticator implements Aut this.tokenManager.setHeaders(this.headers); } - public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void { - this.tokenManager.getToken((err, token) => { - if (err) { - callback(err); - } else { - const authHeader = { Authorization: `Bearer ${token}` }; - options.headers = extend(true, {}, options.headers, authHeader); - callback(null); - } + public authenticate(options: AuthenticateOptions): Promise { + return this.tokenManager.getToken().then(token => { + const authHeader = { Authorization: `Bearer ${token}` }; + options.headers = extend(true, {}, options.headers, authHeader); + return; }); } } diff --git a/auth/token-managers/cp4d-token-manager.ts b/auth/token-managers/cp4d-token-manager.ts index b60f67b6c..1f5080e7f 100644 --- a/auth/token-managers/cp4d-token-manager.ts +++ b/auth/token-managers/cp4d-token-manager.ts @@ -76,20 +76,12 @@ export class Cp4dTokenManager extends JwtTokenManager { this.password = options.password; } - /** - * Callback for handling response. - * - * @callback requestTokenCallback - * @param {Error} An error if there is one, null otherwise - * @param {Object} The response if request is successful, null otherwise - */ /** * Request an CP4D token using a basic auth header. * - * @param {requestTokenCallback} callback - The callback that handles the response. - * @returns {void} + * @returns {Promise} */ - protected requestToken(callback: Function): void { + protected requestToken(): Promise { // these cannot be overwritten const requiredHeaders = { Authorization: computeBasicAuthHeader(this.username, this.password), @@ -103,6 +95,6 @@ export class Cp4dTokenManager extends JwtTokenManager { rejectUnauthorized: !this.disableSslVerification, } }; - this.requestWrapperInstance.sendRequest(parameters, callback); + return this.requestWrapperInstance.sendRequest(parameters); } } diff --git a/auth/token-managers/iam-token-manager.ts b/auth/token-managers/iam-token-manager.ts index d678d95d7..32ee0ff0d 100644 --- a/auth/token-managers/iam-token-manager.ts +++ b/auth/token-managers/iam-token-manager.ts @@ -111,20 +111,12 @@ export class IamTokenManager extends JwtTokenManager { } } - /** - * Callback for handling response. - * - * @callback requestTokenCallback - * @param {Error} An error if there is one, null otherwise - * @param {Object} The response if request is successful, null otherwise - */ /** * Request an IAM token using an API key. * - * @param {requestTokenCallback} callback - The callback that handles the response. - * @returns {void} + * @returns {Promise} */ - protected requestToken(callback: Function): void { + protected requestToken(): Promise { // these cannot be overwritten const requiredHeaders = { 'Content-type': 'application/x-www-form-urlencoded', @@ -148,6 +140,7 @@ export class IamTokenManager extends JwtTokenManager { rejectUnauthorized: !this.disableSslVerification, } }; - this.requestWrapperInstance.sendRequest(parameters, callback); + + return this.requestWrapperInstance.sendRequest(parameters); } } diff --git a/auth/token-managers/jwt-token-manager.ts b/auth/token-managers/jwt-token-manager.ts index 8a316e51d..4925b7ecd 100644 --- a/auth/token-managers/jwt-token-manager.ts +++ b/auth/token-managers/jwt-token-manager.ts @@ -36,7 +36,7 @@ export class JwtTokenManager { protected tokenName: string; protected disableSslVerification: boolean; protected headers: OutgoingHttpHeaders; - protected requestWrapperInstance; + protected requestWrapperInstance: RequestWrapper; private tokenInfo: any; private expireTime: number; @@ -70,33 +70,24 @@ export class JwtTokenManager { } /** - * This function sends an access token back through a callback. The source of the token - * is determined by the following logic: + * This function returns a Promise that resolves with an access token, if successful. + * The source of the token is determined by the following logic: * 1. If user provides their own managed access token, assume it is valid and send it * 2. a) If this class is managing tokens and does not yet have one, make a request for one * b) If this class is managing tokens and the token has expired, request a new one * 3. If this class is managing tokens and has a valid token stored, send it * - * @param {Function} cb - callback function that the token will be passed to */ - public getToken(cb: Function) { + public getToken(): Promise { if (!this.tokenInfo[this.tokenName] || this.isTokenExpired()) { // 1. request a new token - this.requestToken((err, tokenResponse) => { - if (!err) { - try { - this.saveTokenInfo(tokenResponse.result); - } catch(e) { - // send lower level error through callback for user to handle - err = e; - } - } - // return null for access_token if there is an error - return cb(err, this.tokenInfo[this.tokenName] || null); + return this.requestToken().then(tokenResponse => { + this.saveTokenInfo(tokenResponse.result); + return this.tokenInfo[this.tokenName]; }); } else { // 2. use valid, managed token - return cb(null, this.tokenInfo[this.tokenName]); + return Promise.resolve(this.tokenInfo[this.tokenName]); } } @@ -129,12 +120,11 @@ export class JwtTokenManager { /** * Request a JWT using an API key. * - * @param {Function} cb - The callback that handles the response. - * @returns {void} + * @returns {Promise} */ - protected requestToken(cb: Function): void { + protected requestToken(): Promise { const err = new Error('`requestToken` MUST be overridden by a subclass of JwtTokenManagerV1.'); - cb(err, null); + return Promise.reject(err); } /** diff --git a/lib/base_service.ts b/lib/base_service.ts index 242353417..2b74a4844 100644 --- a/lib/base_service.ts +++ b/lib/base_service.ts @@ -149,18 +149,18 @@ export class BaseService { * @param {Object} parameters.defaultOptions * @param {string} parameters.defaultOptions.serviceUrl - the base URL of the service * @param {OutgoingHttpHeaders} parameters.defaultOptions.headers - additional headers to be passed on the request. - * @param {Function} callback - callback function to pass the response back to - * @returns {ReadableStream|undefined} + * @returns {Promise} */ - protected createRequest(parameters, callback) { + protected createRequest(parameters): Promise { // validate serviceUrl parameter has been set const serviceUrl = parameters.defaultOptions && parameters.defaultOptions.serviceUrl; if (!serviceUrl || typeof serviceUrl !== 'string') { - return callback(new Error('The service URL is required'), null); + return Promise.reject(new Error('The service URL is required')); } - this.authenticator.authenticate(parameters.defaultOptions, err => { - err ? callback(err) : this.requestWrapperInstance.sendRequest(parameters, callback); + return this.authenticator.authenticate(parameters.defaultOptions).then(() => { + // resolve() handles rejection as well, so resolving the result of sendRequest should allow for proper handling later + return this.requestWrapperInstance.sendRequest(parameters); }); } diff --git a/lib/requestwrapper.ts b/lib/requestwrapper.ts index 519106281..fd26f18f1 100644 --- a/lib/requestwrapper.ts +++ b/lib/requestwrapper.ts @@ -120,7 +120,7 @@ export class RequestWrapper { * @returns {ReadableStream|undefined} * @throws {Error} */ - public sendRequest(parameters, _callback) { + public sendRequest(parameters): Promise { const options = extend(true, {}, parameters.defaultOptions, parameters.options); const { path, body, form, formData, qs, method, serviceUrl } = options; let { headers, url } = options; @@ -201,14 +201,8 @@ export class RequestWrapper { }, }; - this.axiosInstance(requestParams) - // placing `catch` block first because it is for catching request errors - // if it is after the `then` block, it will also catch errors if they occur - // inside of the `then` block - .catch(error => { - _callback(this.formatError(error)); - }) - .then(res => { + return this.axiosInstance(requestParams).then( + res => { // sometimes error responses will still trigger the `then` block - escape that behavior here if (!res) { return }; @@ -221,17 +215,23 @@ export class RequestWrapper { res.result = res.data; delete res.data; - _callback(null, res); - }); + // return another promise that resolves with 'res' to be handled in generated code + return res; + }, + err => { + // return another promise that rejects with 'err' to be handled in generated code + throw this.formatError(err); + } + ); } /** * Format error returned by axios - * @param {Function} cb the request callback + * @param {object} the object returned by axios via rejection * @private - * @returns {request.RequestCallback} + * @returns {Error} */ - public formatError(axiosError: any) { + public formatError(axiosError: any): Error { // return an actual error object, // but make it flexible so we can add properties like 'body' const error: any = new Error(); diff --git a/test/unit/base-service.test.js b/test/unit/base-service.test.js index aef63e165..7fc5ffd76 100644 --- a/test/unit/base-service.test.js +++ b/test/unit/base-service.test.js @@ -34,7 +34,6 @@ NoAuthAuthenticator.mockImplementation(() => { const { BaseService } = require('../../lib/base_service'); // constants -const NO_OP = () => {}; const DEFAULT_URL = 'https://gateway.watsonplatform.net/test/api'; const DEFAULT_NAME = 'test'; const AUTHENTICATOR = new NoAuthAuthenticator(); @@ -47,7 +46,7 @@ describe('Base Service', () => { beforeEach(() => { // set defualt mocks, these may be overridden in the individual tests readExternalSourcesMock.mockImplementation(() => EMPTY_OBJECT); - authenticateMock.mockImplementation((_, callback) => callback(null)); + authenticateMock.mockImplementation(() => Promise.resolve(null)); }); afterEach(() => { @@ -216,7 +215,7 @@ describe('Base Service', () => { Accept: 'application/json', }, options: { - serviceUrl: '/v2/assistants/{assistant_id}/sessions', + url: '/v2/assistants/{assistant_id}/sessions', method: 'POST', path: { id: '123', @@ -224,15 +223,14 @@ describe('Base Service', () => { }, }; - testService.createRequest(parameters, NO_OP); + testService.createRequest(parameters); const args = authenticateMock.mock.calls[0]; expect(authenticateMock).toHaveBeenCalled(); expect(args[0]).toEqual(parameters.defaultOptions); - expect(args[1]).toBeInstanceOf(Function); }); - it('should call sendRequest on authenticate() success', () => { + it('should call sendRequest on authenticate() success', async done => { const testService = new TestService({ authenticator: AUTHENTICATOR, }); @@ -243,7 +241,7 @@ describe('Base Service', () => { Accept: 'application/json', }, options: { - serviceUrl: '/v2/assistants/{assistant_id}/sessions', + url: '/v2/assistants/{assistant_id}/sessions', method: 'POST', path: { id: '123', @@ -251,18 +249,18 @@ describe('Base Service', () => { }, }; - testService.createRequest(parameters, NO_OP); + await testService.createRequest(parameters); expect(authenticateMock).toHaveBeenCalled(); expect(sendRequestMock).toHaveBeenCalled(); const args = sendRequestMock.mock.calls[0]; expect(args[0]).toEqual(parameters); - expect(args[1]).toBe(NO_OP); expect(testService.requestWrapperInstance.sendRequest).toBe(sendRequestMock); // verify it is calling the instance + done(); }); - it('should call callback with an error if `serviceUrl` is not set', done => { + it('createRequest should reject with an error if `serviceUrl` is not set', async done => { const testService = new TestService({ authenticator: AUTHENTICATOR, }); @@ -281,23 +279,29 @@ describe('Base Service', () => { }, }; - testService.createRequest(parameters, (err, res) => { - // assert results - expect(err).toBeInstanceOf(Error); - expect(res).toBeNull(); - done(); - }); + let res; + let err; + try { + res = await testService.createRequest(parameters); + } catch (e) { + err = e; + } + + // assert results + expect(err).toBeInstanceOf(Error); + expect(res).toBeUndefined(); + done(); }); - it('should send error back to user on authenticate() failure', done => { + it('should send error back to user on authenticate() failure', async done => { const testService = new TestService({ authenticator: AUTHENTICATOR, }); - // note that the NoAuthAuthenticator can't actually call the callback with an error, + // note that the NoAuthAuthenticator can't actually reject with an error, // but others can const fakeError = new Error('token request failed'); - authenticateMock.mockImplementation((_, callback) => callback(fakeError)); + authenticateMock.mockImplementation(() => Promise.reject(fakeError)); const parameters = { defaultOptions: { @@ -305,11 +309,16 @@ describe('Base Service', () => { }, }; - testService.createRequest(parameters, err => { - expect(err).toBe(fakeError); - expect(authenticateMock).toHaveBeenCalled(); - done(); - }); + let err; + try { + await testService.createRequest(parameters); + } catch (e) { + err = e; + } + + expect(err).toBe(fakeError); + expect(authenticateMock).toHaveBeenCalled(); + done(); }); it('readOptionsFromExternalConfig should return an empty object if no properties are found', () => { diff --git a/test/unit/basic-authenticator.test.js b/test/unit/basic-authenticator.test.js index 936fcf7bc..399b99c7c 100644 --- a/test/unit/basic-authenticator.test.js +++ b/test/unit/basic-authenticator.test.js @@ -41,15 +41,13 @@ describe('Basic Authenticator', () => { }).toThrow(/Revise these credentials/); }); - it('should update the options and send `null` in the callback', done => { + it('should update the options and resolve the Promise with `null`', async done => { const authenticator = new BasicAuthenticator(CONFIG); - const options = {}; + const result = await authenticator.authenticate(options); - authenticator.authenticate(options, err => { - expect(err).toBeNull(); - expect(options.headers.Authorization).toBe('Basic ZGF2ZTpncm9obA=='); - done(); - }); + expect(result).toBeUndefined(); + expect(options.headers.Authorization).toBe('Basic ZGF2ZTpncm9obA=='); + done(); }); }); diff --git a/test/unit/bearer-token-authenticator.test.js b/test/unit/bearer-token-authenticator.test.js index 708789b87..72ae28916 100644 --- a/test/unit/bearer-token-authenticator.test.js +++ b/test/unit/bearer-token-authenticator.test.js @@ -19,16 +19,14 @@ describe('Bearer Token Authenticator', () => { }).toThrow(); }); - it('should update the options and send `null` in the callback', done => { + it('should update the options and resolve with `null`', async done => { const authenticator = new BearerTokenAuthenticator(config); - const options = {}; + const result = await authenticator.authenticate(options); - authenticator.authenticate(options, err => { - expect(err).toBeNull(); - expect(options.headers.Authorization).toBe(`Bearer ${config.bearerToken}`); - done(); - }); + expect(result).toBeUndefined(); + expect(options.headers.Authorization).toBe(`Bearer ${config.bearerToken}`); + done(); }); it('should re-set the bearer token using the setter', () => { diff --git a/test/unit/cp4d-authenticator.test.js b/test/unit/cp4d-authenticator.test.js index 4afbd2dae..302d2555b 100644 --- a/test/unit/cp4d-authenticator.test.js +++ b/test/unit/cp4d-authenticator.test.js @@ -23,9 +23,10 @@ const mockedTokenManager = new Cp4dTokenManager({ password: PASSWORD, url: URL, }); -const getTokenSpy = jest.spyOn(mockedTokenManager, 'getToken').mockImplementation(callback => { - callback(null, fakeToken); -}); + +const getTokenSpy = jest + .spyOn(mockedTokenManager, 'getToken') + .mockImplementation(() => Promise.resolve(fakeToken)); describe('CP4D Authenticator', () => { it('should store all CONFIG options on the class', () => { @@ -79,23 +80,22 @@ describe('CP4D Authenticator', () => { }).toThrow(/Revise these credentials/); }); - it('should update the options and send `null` in the callback', done => { + it('should update the options and resolve with `null`', async done => { const authenticator = new CloudPakForDataAuthenticator(CONFIG); // override the created token manager with the mocked one authenticator.tokenManager = mockedTokenManager; const options = { headers: { 'X-Some-Header': 'user-supplied header' } }; + const result = await authenticator.authenticate(options); - authenticator.authenticate(options, err => { - expect(err).toBeNull(); - expect(options.headers.Authorization).toBe(`Bearer ${fakeToken}`); - expect(getTokenSpy).toHaveBeenCalled(); + expect(result).toBeUndefined(); + expect(options.headers.Authorization).toBe(`Bearer ${fakeToken}`); + expect(getTokenSpy).toHaveBeenCalled(); - // verify that the original options are kept intact - expect(options.headers['X-Some-Header']).toBe('user-supplied header'); - done(); - }); + // verify that the original options are kept intact + expect(options.headers['X-Some-Header']).toBe('user-supplied header'); + done(); }); it('should re-set disableSslVerification using the setter', () => { diff --git a/test/unit/cp4d-token-manager.test.js b/test/unit/cp4d-token-manager.test.js index 8bd3fe2ef..32d929efb 100644 --- a/test/unit/cp4d-token-manager.test.js +++ b/test/unit/cp4d-token-manager.test.js @@ -89,18 +89,16 @@ describe('CP4D Token Manager', () => { describe('requestToken', () => { it('should call sendRequest with all request options', () => { - const noop = () => {}; const instance = new Cp4dTokenManager({ url: URL, username: USERNAME, password: PASSWORD, }); - instance.requestToken(noop); + instance.requestToken(); // extract arguments sendRequest was called with const params = mockSendRequest.mock.calls[0][0]; - const callback = mockSendRequest.mock.calls[0][1]; expect(mockSendRequest).toHaveBeenCalled(); expect(params.options).toBeDefined(); @@ -108,9 +106,7 @@ describe('CP4D Token Manager', () => { expect(params.options.method).toBe('GET'); expect(params.options.rejectUnauthorized).toBe(true); expect(params.options.headers).toBeDefined(); - expect(params.options.headers.Authorization).toBe('Basic c2hlcmxvY2s6aG9sbWVz'); - expect(callback).toBe(noop); }); }); }); diff --git a/test/unit/iam-authenticator.test.js b/test/unit/iam-authenticator.test.js index fcc3e2814..02234451d 100644 --- a/test/unit/iam-authenticator.test.js +++ b/test/unit/iam-authenticator.test.js @@ -6,9 +6,10 @@ const { IamTokenManager } = require('../../auth'); // mock the `getToken` method in the token manager - dont make any rest calls const fakeToken = 'iam-acess-token'; const mockedTokenManager = new IamTokenManager({ apikey: '123' }); -const getTokenSpy = jest.spyOn(mockedTokenManager, 'getToken').mockImplementation(callback => { - callback(null, fakeToken); -}); + +const getTokenSpy = jest + .spyOn(mockedTokenManager, 'getToken') + .mockImplementation(() => Promise.resolve(fakeToken)); describe('IAM Authenticator', () => { const config = { @@ -48,23 +49,22 @@ describe('IAM Authenticator', () => { }).toThrow(/Revise these credentials/); }); - it('should update the options and send `null` in the callback', done => { + it('should update the options and resolve with `null`', async done => { const authenticator = new IamAuthenticator({ apikey: 'testjustanapikey' }); // override the created token manager with the mocked one authenticator.tokenManager = mockedTokenManager; const options = { headers: { 'X-Some-Header': 'user-supplied header' } }; + const result = await authenticator.authenticate(options); - authenticator.authenticate(options, err => { - expect(err).toBeNull(); - expect(options.headers.Authorization).toBe(`Bearer ${fakeToken}`); - expect(getTokenSpy).toHaveBeenCalled(); + expect(result).toBeUndefined(); + expect(options.headers.Authorization).toBe(`Bearer ${fakeToken}`); + expect(getTokenSpy).toHaveBeenCalled(); - // verify that the original options are kept intact - expect(options.headers['X-Some-Header']).toBe('user-supplied header'); - done(); - }); + // verify that the original options are kept intact + expect(options.headers['X-Some-Header']).toBe('user-supplied header'); + done(); }); it('should re-set the client id and secret using the setter', () => { diff --git a/test/unit/iam-token-manager.test.js b/test/unit/iam-token-manager.test.js index e65577e5f..053e0e1c6 100644 --- a/test/unit/iam-token-manager.test.js +++ b/test/unit/iam-token-manager.test.js @@ -45,20 +45,18 @@ describe('iam_token_manager_v1', function() { expect(() => new IamTokenManager()).toThrow(); }); - it('should turn an iam apikey into an access token', function(done) { + it('should turn an iam apikey into an access token', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234' }); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, IAM_RESPONSE); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function(err, token) { - expect(token).toBe(ACCESS_TOKEN); - done(); - }); + const token = await instance.getToken(); + + expect(token).toBe(ACCESS_TOKEN); + done(); }); - it('should refresh an expired access token', function(done) { + it('should refresh an expired access token', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234' }); const requestMock = jest.spyOn(instance, 'requestToken'); @@ -72,18 +70,15 @@ describe('iam_token_manager_v1', function() { instance.tokenInfo = currentTokenInfo; - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, IAM_RESPONSE); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function(err, token) { - expect(token).toBe(ACCESS_TOKEN); - expect(requestMock).toHaveBeenCalled(); - done(); - }); + const token = await instance.getToken(); + expect(token).toBe(ACCESS_TOKEN); + expect(requestMock).toHaveBeenCalled(); + done(); }); - it('should use a valid access token if one is stored', function(done) { + it('should use a valid access token if one is stored', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234' }); const requestMock = jest.spyOn(instance, 'requestToken'); @@ -99,14 +94,13 @@ describe('iam_token_manager_v1', function() { instance.timeToLive = currentTokenInfo.expires_in; instance.expireTime = currentTokenInfo.expiration; - instance.getToken(function(err, token) { - expect(token).toBe(ACCESS_TOKEN); - expect(requestMock).not.toHaveBeenCalled(); - done(); - }); + const token = await instance.getToken(); + expect(token).toBe(ACCESS_TOKEN); + expect(requestMock).not.toHaveBeenCalled(); + done(); }); - it('should refresh an access token without expires_in field', function(done) { + it('should refresh an access token without expires_in field', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234' }); const requestMock = jest.spyOn(instance, 'requestToken'); @@ -119,18 +113,15 @@ describe('iam_token_manager_v1', function() { instance.tokenInfo = currentTokenInfo; - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, IAM_RESPONSE); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function(err, token) { - expect(token).toBe(ACCESS_TOKEN); - expect(requestMock).toHaveBeenCalled(); - done(); - }); + const token = await instance.getToken(); + expect(token).toBe(ACCESS_TOKEN); + expect(requestMock).toHaveBeenCalled(); + done(); }); - it('should request a new token when refresh token does not have expiration field', function(done) { + it('should request a new token when refresh token does not have expiration field', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234' }); const currentTokenInfo = { @@ -141,51 +132,42 @@ describe('iam_token_manager_v1', function() { instance.tokenInfo = currentTokenInfo; - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, IAM_RESPONSE); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function(err, token) { - expect(token).toBe(ACCESS_TOKEN); - done(); - }); + const token = await instance.getToken(); + expect(token).toBe(ACCESS_TOKEN); + done(); }); - it('should not specify an Authorization header if user provides no clientid, no secret', function(done) { + it('should not specify an Authorization header if user provides no clientid, no secret', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234' }); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).not.toBeDefined(); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBeUndefined(); + done(); }); - it('should use an Authorization header based on client id and secret via ctor', function(done) { + it('should use an Authorization header based on client id and secret via ctor', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234', clientId: 'foo', clientSecret: 'bar', }); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).toBe('Basic Zm9vOmJhcg=='); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBe('Basic Zm9vOmJhcg=='); + done(); }); - it('should not use an Authorization header - clientid only via ctor', function(done) { + it('should not use an Authorization header - clientid only via ctor', async done => { jest.spyOn(console, 'log').mockImplementation(() => {}); const instance = new IamTokenManager({ @@ -198,19 +180,16 @@ describe('iam_token_manager_v1', function() { expect(console.log.mock.calls[0][0]).toBe(CLIENT_ID_SECRET_WARNING); console.log.mockRestore(); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).not.toBeDefined(); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBeUndefined(); + done(); }); - it('should not use an Authorization header - secret only via ctor', function(done) { + it('should not use an Authorization header - secret only via ctor', async done => { jest.spyOn(console, 'log').mockImplementation(() => {}); const instance = new IamTokenManager({ apikey: 'abcd-1234', @@ -222,38 +201,32 @@ describe('iam_token_manager_v1', function() { expect(console.log.mock.calls[0][0]).toBe(CLIENT_ID_SECRET_WARNING); console.log.mockRestore(); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).not.toBeDefined(); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBeUndefined(); + done(); }); - it('should use an Authorization header based on client id and secret via setter', function(done) { + it('should use an Authorization header based on client id and secret via setter', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234', }); instance.setClientIdAndSecret('foo', 'bar'); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).toBe('Basic Zm9vOmJhcg=='); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBe('Basic Zm9vOmJhcg=='); + done(); }); - it('should not use an Authorization header -- clientid only via setter', function(done) { + it('should not use an Authorization header -- clientid only via setter', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234', }); @@ -267,19 +240,16 @@ describe('iam_token_manager_v1', function() { expect(console.log.mock.calls[0][0]).toBe(CLIENT_ID_SECRET_WARNING); console.log.mockRestore(); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).not.toBeDefined(); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBeUndefined(); + done(); }); - it('should not use an Authorization header - secret only via setter', function(done) { + it('should not use an Authorization header - secret only via setter', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234', }); @@ -293,34 +263,28 @@ describe('iam_token_manager_v1', function() { expect(console.log.mock.calls[0][0]).toBe(CLIENT_ID_SECRET_WARNING); console.log.mockRestore(); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).not.toBeDefined(); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBeUndefined(); + done(); }); - it('should not use an Authorization header - nulls passed to setter', function(done) { + it('should not use an Authorization header - nulls passed to setter', async done => { const instance = new IamTokenManager({ apikey: 'abcd-1234', }); instance.setClientIdAndSecret(null, null); - mockSendRequest.mockImplementation((parameters, _callback) => { - _callback(null, { access_token: 'abcd' }); - }); + mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE)); - instance.getToken(function() { - const sendRequestArgs = mockSendRequest.mock.calls[0][0]; - const authHeader = sendRequestArgs.options.headers.Authorization; - expect(authHeader).not.toBeDefined(); - done(); - }); + await instance.getToken(); + const sendRequestArgs = mockSendRequest.mock.calls[0][0]; + const authHeader = sendRequestArgs.options.headers.Authorization; + expect(authHeader).toBeUndefined(); + done(); }); }); diff --git a/test/unit/jwt-token-manager.test.js b/test/unit/jwt-token-manager.test.js index a4b852330..c5e7ea9ba 100644 --- a/test/unit/jwt-token-manager.test.js +++ b/test/unit/jwt-token-manager.test.js @@ -27,7 +27,7 @@ describe('JWT Token Manager', () => { }); describe('getToken', () => { - it('should request a token if no token is stored', done => { + it('should request a token if no token is stored', async done => { const instance = new JwtTokenManager(); const saveTokenInfoSpy = jest.spyOn(instance, 'saveTokenInfo'); @@ -37,23 +37,21 @@ describe('JWT Token Manager', () => { const requestTokenSpy = jest .spyOn(instance, 'requestToken') - .mockImplementation(cb => cb(null, { result: { access_token: ACCESS_TOKEN } })); - - instance.getToken((err, res) => { - expect(requestTokenSpy).toHaveBeenCalled(); - expect(saveTokenInfoSpy).toHaveBeenCalled(); - expect(decodeSpy).toHaveBeenCalled(); - expect(err).toBeNull(); - expect(res).toBe(ACCESS_TOKEN); - - saveTokenInfoSpy.mockRestore(); - decodeSpy.mockRestore(); - requestTokenSpy.mockRestore(); - done(); - }); + .mockImplementation(() => Promise.resolve({ result: { access_token: ACCESS_TOKEN } })); + + const token = await instance.getToken(); + expect(requestTokenSpy).toHaveBeenCalled(); + expect(saveTokenInfoSpy).toHaveBeenCalled(); + expect(decodeSpy).toHaveBeenCalled(); + expect(token).toBe(ACCESS_TOKEN); + + saveTokenInfoSpy.mockRestore(); + decodeSpy.mockRestore(); + requestTokenSpy.mockRestore(); + done(); }); - it('should request a token if token is stored but expired', done => { + it('should request a token if token is stored but expired', async done => { const instance = new JwtTokenManager(); instance.tokenInfo.access_token = '987zxc'; @@ -64,82 +62,96 @@ describe('JWT Token Manager', () => { const requestTokenSpy = jest .spyOn(instance, 'requestToken') - .mockImplementation(cb => cb(null, { result: { access_token: ACCESS_TOKEN } })); - - instance.getToken((err, res) => { - expect(requestTokenSpy).toHaveBeenCalled(); - expect(saveTokenInfoSpy).toHaveBeenCalled(); - expect(decodeSpy).toHaveBeenCalled(); - expect(err).toBeNull(); - expect(res).toBe(ACCESS_TOKEN); - - saveTokenInfoSpy.mockRestore(); - decodeSpy.mockRestore(); - requestTokenSpy.mockRestore(); - done(); - }); + .mockImplementation(() => Promise.resolve({ result: { access_token: ACCESS_TOKEN } })); + + const token = await instance.getToken(); + expect(requestTokenSpy).toHaveBeenCalled(); + expect(saveTokenInfoSpy).toHaveBeenCalled(); + expect(decodeSpy).toHaveBeenCalled(); + expect(token).toBe(ACCESS_TOKEN); + + saveTokenInfoSpy.mockRestore(); + decodeSpy.mockRestore(); + requestTokenSpy.mockRestore(); + done(); }); - it('should not save token info if token request returned an error', done => { + it('should not save token info if token request returned an error', async done => { const instance = new JwtTokenManager(); const saveTokenInfoSpy = jest.spyOn(instance, 'saveTokenInfo'); const requestTokenSpy = jest .spyOn(instance, 'requestToken') - .mockImplementation(cb => cb(new Error(), null)); - - instance.getToken((err, res) => { - expect(requestTokenSpy).toHaveBeenCalled(); - expect(saveTokenInfoSpy).not.toHaveBeenCalled(); - expect(err).toBeInstanceOf(Error); - expect(res).toBe(null); - - saveTokenInfoSpy.mockRestore(); - requestTokenSpy.mockRestore(); - done(); - }); + .mockImplementation(() => Promise.reject(new Error())); + + let token; + let err; + try { + token = await instance.getToken(); + } catch (e) { + err = e; + } + expect(requestTokenSpy).toHaveBeenCalled(); + expect(saveTokenInfoSpy).not.toHaveBeenCalled(); + expect(err).toBeInstanceOf(Error); + expect(token).toBeUndefined(); + + saveTokenInfoSpy.mockRestore(); + requestTokenSpy.mockRestore(); + done(); }); - it('should catch lower level errors and send through callback', done => { + it('should catch and reject lower level errors', async done => { const instance = new JwtTokenManager(); const saveTokenInfoSpy = jest.spyOn(instance, 'saveTokenInfo'); // because there is no access token, calling `saveTokenInfo` will - // throw an error to be caught and returned in the callback + // throw an error to be caught and rejected in the Promise const requestTokenSpy = jest .spyOn(instance, 'requestToken') - .mockImplementation(cb => cb(null, { arbitrary_data: '12345' })); + .mockImplementation(() => Promise.resolve({ result: { arbitrary_data: '12345' } })); - instance.getToken((err, res) => { - expect(err).toBeInstanceOf(Error); - expect(res).toBeNull(); + let token; + let err; + try { + token = await instance.getToken(); + } catch (e) { + err = e; + } - saveTokenInfoSpy.mockRestore(); - requestTokenSpy.mockRestore(); - done(); - }); + expect(err).toBeInstanceOf(Error); + expect(err.message).toBe('Access token not present in response'); + expect(token).toBeUndefined(); + + saveTokenInfoSpy.mockRestore(); + requestTokenSpy.mockRestore(); + done(); }); - it('should use an sdk-managed token if present and not expired', done => { + it('should use an sdk-managed token if present and not expired', async done => { const instance = new JwtTokenManager(); instance.tokenInfo.access_token = ACCESS_TOKEN; instance.expireTime = getCurrentTime() + 1000; - instance.getToken((err, res) => { - expect(err).toBeNull(); - expect(res).toBe(ACCESS_TOKEN); - done(); - }); + const token = await instance.getToken(); + expect(token).toBe(ACCESS_TOKEN); + done(); }); }); - it('should callback with error if requestToken is not overriden', done => { + it('should reject with error if requestToken is not overriden', async done => { const instance = new JwtTokenManager(); - instance.requestToken((err, res) => { - expect(err).toBeInstanceOf(Error); - expect(res).toBeNull(); - done(); - }); + let err; + let token; + try { + token = await instance.requestToken(); + } catch (e) { + err = e; + } + + expect(err).toBeInstanceOf(Error); + expect(token).toBeUndefined(); + done(); }); describe('isTokenExpired', () => { diff --git a/test/unit/no-auth-authenticator.test.js b/test/unit/no-auth-authenticator.test.js index 432708e05..dd0a397a4 100644 --- a/test/unit/no-auth-authenticator.test.js +++ b/test/unit/no-auth-authenticator.test.js @@ -3,11 +3,11 @@ const { NoAuthAuthenticator } = require('../../auth'); describe('NoAuth Authenticator', () => { - it('should call callback on authenticate', done => { + it('should resolve Promise on authenticate', async done => { const authenticator = new NoAuthAuthenticator(); - authenticator.authenticate({}, err => { - expect(err).toBeNull(); - done(); - }); + const result = await authenticator.authenticate({}); + + expect(result).toBeUndefined(); + done(); }); }); diff --git a/test/unit/request-token-based-authenticator.test.js b/test/unit/request-token-based-authenticator.test.js index 979a0b9a5..0a9f291a8 100644 --- a/test/unit/request-token-based-authenticator.test.js +++ b/test/unit/request-token-based-authenticator.test.js @@ -35,17 +35,22 @@ describe('Request Based Token Authenticator', () => { expect(authenticator.tokenManager.headers).toEqual(config.headers); }); - it('should call the callback in authenticate with an error if the token request fails', done => { + it('should reject the Promise in authenticate with an error if the token request fails', done => { const authenticator = new TokenRequestBasedAuthenticator(config); const fakeError = new Error('fake error'); const getTokenSpy = jest .spyOn(authenticator.tokenManager, 'getToken') - .mockImplementation(cb => cb(fakeError)); - - authenticator.authenticate({}, err => { - expect(getTokenSpy).toHaveBeenCalled(); - expect(err).toBe(fakeError); - done(); - }); + .mockImplementation(() => Promise.reject(fakeError)); + + authenticator.authenticate({}).then( + res => { + done(`Promise unexpectedly resolved with value: ${res}`); + }, + err => { + expect(getTokenSpy).toHaveBeenCalled(); + expect(err).toBe(fakeError); + done(); + } + ); }); }); diff --git a/test/unit/requestWrapper.test.js b/test/unit/requestWrapper.test.js index 178632a26..f104be709 100644 --- a/test/unit/requestWrapper.test.js +++ b/test/unit/requestWrapper.test.js @@ -52,7 +52,7 @@ describe('sendRequest', () => { mockAxiosInstance.mockReset(); }); - it('should send a request with default parameters', done => { + it('should send a request with default parameters', async done => { const parameters = { defaultOptions: { body: 'post=body', @@ -70,27 +70,26 @@ describe('sendRequest', () => { mockAxiosInstance.mockResolvedValue(axiosResolveValue); - requestWrapperInstance.sendRequest(parameters, (err, res) => { - // assert results - expect(mockAxiosInstance.mock.calls[0][0].data).toEqual('post=body'); - expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( - 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' - ); - expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ - // 'Accept-Encoding': 'gzip', - 'test-header': 'test-header-value', - }); - expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.defaultOptions.method); - expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual( - parameters.defaultOptions.responseType - ); - expect(res).toEqual(expectedResult); - expect(mockAxiosInstance.mock.calls.length).toBe(1); - done(); + const res = await requestWrapperInstance.sendRequest(parameters); + // assert results + expect(mockAxiosInstance.mock.calls[0][0].data).toEqual('post=body'); + expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( + 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' + ); + expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ + // 'Accept-Encoding': 'gzip', + 'test-header': 'test-header-value', }); + expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.defaultOptions.method); + expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual( + parameters.defaultOptions.responseType + ); + expect(res).toEqual(expectedResult); + expect(mockAxiosInstance.mock.calls.length).toBe(1); + done(); }); - it('should call formatError if request failed', done => { + it('should call formatError if request failed', async done => { const parameters = { defaultOptions: { body: 'post=body', @@ -108,15 +107,20 @@ describe('sendRequest', () => { mockAxiosInstance.mockRejectedValue('error'); - requestWrapperInstance.sendRequest(parameters, (err, res) => { - // assert results - expect(err).toEqual(expect.anything()); - expect(res).toBeUndefined(); - done(); - }); + let res; + let err; + try { + res = await requestWrapperInstance.sendRequest(parameters); + } catch (e) { + err = e; + } + // assert results + expect(err).toBeInstanceOf(Error); + expect(res).toBeUndefined(); + done(); }); - it('should send a request where option parameters overrides defaults', done => { + it('should send a request where option parameters overrides defaults', async done => { const parameters = { defaultOptions: { formData: '', @@ -154,30 +158,29 @@ describe('sendRequest', () => { return Promise.resolve(axiosResolveValue); }); - requestWrapperInstance.sendRequest(parameters, (err, res) => { - // assert results - expect(serializedParams).toBe('version=2018-10-15&array_style=a%2Cb'); - expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( - 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' - ); - expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ - // 'Accept-Encoding': 'gzip', - 'test-header': 'override-header-value', - 'add-header': 'add-header-value', - }); - expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.options.method); - expect(mockAxiosInstance.mock.calls[0][0].params).toEqual({ - array_style: 'a,b', - version: '2018-10-15', - }); - expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); - expect(res).toEqual(expectedResult); - expect(mockAxiosInstance.mock.calls.length).toBe(1); - done(); + const res = await requestWrapperInstance.sendRequest(parameters); + // assert results + expect(serializedParams).toBe('version=2018-10-15&array_style=a%2Cb'); + expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( + 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' + ); + expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ + // 'Accept-Encoding': 'gzip', + 'test-header': 'override-header-value', + 'add-header': 'add-header-value', + }); + expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.options.method); + expect(mockAxiosInstance.mock.calls[0][0].params).toEqual({ + array_style: 'a,b', + version: '2018-10-15', }); + expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); + expect(res).toEqual(expectedResult); + expect(mockAxiosInstance.mock.calls.length).toBe(1); + done(); }); - it('should send a request with multiform data', done => { + it('should send a request with multiform data', async done => { const parameters = { defaultOptions: { formData: '', @@ -224,52 +227,51 @@ describe('sendRequest', () => { return Promise.resolve(axiosResolveValue); }); - requestWrapperInstance.sendRequest(parameters, (err, res) => { - // assert results - expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( - 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' - ); - expect(mockAxiosInstance.mock.calls[0][0].headers).toMatchObject({ - // 'Accept-Encoding': 'gzip', - 'test-header': 'override-header-value', - 'add-header': 'add-header-value', - }); - expect(mockAxiosInstance.mock.calls[0][0].headers['content-type']).toMatch( - 'multipart/form-data; boundary=--------------------------' - ); - expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.defaultOptions.method); - expect(mockAxiosInstance.mock.calls[0][0].params).toEqual(parameters.options.qs); - expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); - expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).toMatch( - 'Content-Disposition: form-data; name=\\"object_item\\"' - ); - expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).toMatch( - 'Content-Disposition: form-data; name=\\"array_item\\"' - ); - // There should be two "array_item" parts - expect( - ( - JSON.stringify(mockAxiosInstance.mock.calls[0][0].data).match(/name=\\"array_item\\"/g) || - [] - ).length - ).toEqual(2); - expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).toMatch( - 'Content-Disposition: form-data; name=\\"custom_file\\"' - ); - expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).not.toMatch( - 'Content-Disposition: form-data; name=\\"null_item\\"' - ); - expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).not.toMatch( - 'Content-Disposition: form-data; name=\\"no_data\\"' - ); - - expect(res).toEqual(expectedResult); - expect(mockAxiosInstance.mock.calls.length).toBe(1); - done(); + const res = await requestWrapperInstance.sendRequest(parameters); + // assert results + expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( + 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' + ); + expect(mockAxiosInstance.mock.calls[0][0].headers).toMatchObject({ + // 'Accept-Encoding': 'gzip', + 'test-header': 'override-header-value', + 'add-header': 'add-header-value', }); + expect(mockAxiosInstance.mock.calls[0][0].headers['content-type']).toMatch( + 'multipart/form-data; boundary=--------------------------' + ); + expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.defaultOptions.method); + expect(mockAxiosInstance.mock.calls[0][0].params).toEqual(parameters.options.qs); + expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); + expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).toMatch( + 'Content-Disposition: form-data; name=\\"object_item\\"' + ); + expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).toMatch( + 'Content-Disposition: form-data; name=\\"array_item\\"' + ); + // There should be two "array_item" parts + expect( + ( + JSON.stringify(mockAxiosInstance.mock.calls[0][0].data).match(/name=\\"array_item\\"/g) || + [] + ).length + ).toEqual(2); + expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).toMatch( + 'Content-Disposition: form-data; name=\\"custom_file\\"' + ); + expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).not.toMatch( + 'Content-Disposition: form-data; name=\\"null_item\\"' + ); + expect(JSON.stringify(mockAxiosInstance.mock.calls[0][0])).not.toMatch( + 'Content-Disposition: form-data; name=\\"no_data\\"' + ); + + expect(res).toEqual(expectedResult); + expect(mockAxiosInstance.mock.calls.length).toBe(1); + done(); }); - it('should send a request with form data', done => { + it('should send a request with form data', async done => { const parameters = { defaultOptions: { form: { a: 'a', b: 'b' }, @@ -305,30 +307,29 @@ describe('sendRequest', () => { return Promise.resolve(axiosResolveValue); }); - requestWrapperInstance.sendRequest(parameters, (err, res) => { - // assert results - expect(mockAxiosInstance.mock.calls[0][0].data).toEqual('a=a&b=b'); - expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( - 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' - ); - expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ - 'Accept-Encoding': 'compress', - 'test-header': 'override-header-value', - 'add-header': 'add-header-value', - 'Content-type': 'application/x-www-form-urlencoded', - }); - expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.options.method); - expect(mockAxiosInstance.mock.calls[0][0].params).toEqual(parameters.options.qs); - expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); - expect(res).toEqual(expectedResult); - expect(mockAxiosInstance.mock.calls.length).toBe(1); - done(); + const res = await requestWrapperInstance.sendRequest(parameters); + // assert results + expect(mockAxiosInstance.mock.calls[0][0].data).toEqual('a=a&b=b'); + expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( + 'https://example.ibm.com/v1/environments/environment-id/configurations/configuration-id' + ); + expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ + 'Accept-Encoding': 'compress', + 'test-header': 'override-header-value', + 'add-header': 'add-header-value', + 'Content-type': 'application/x-www-form-urlencoded', }); + expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.options.method); + expect(mockAxiosInstance.mock.calls[0][0].params).toEqual(parameters.options.qs); + expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); + expect(res).toEqual(expectedResult); + expect(mockAxiosInstance.mock.calls.length).toBe(1); + done(); }); // Need to rewrite this to test instantiation with userOptions - // it('should keep parameters in options that are not explicitly set in requestwrapper', done => { + // it('should keep parameters in options that are not explicitly set in requestwrapper', async done => { // const parameters = { // defaultOptions: { // body: 'post=body',