UNPKG

@microfocus/alm-octane-js-rest-sdk

Version:

NodeJS wrapper for the OpenText Core Software Delivery Platform API

728 lines (671 loc) 25.3 kB
/* * Copyright 2020-2025 Open Text. * * The only warranties for products and services of Open Text and * its affiliates and licensors (“Open Text”) are as may be set forth * in the express warranty statements accompanying such products and services. * Nothing herein should be construed as constituting an additional warranty. * Open Text shall not be liable for technical or editorial errors or * omissions contained herein. The information contained herein is subject * to change without notice. * * Except as specifically indicated otherwise, this document contains * confidential information and a valid license is required for possession, * use or copying. If this work is provided to the U.S. Government, * consistent with FAR 12.211 and 12.212, Commercial Computer Software, * Computer Software Documentation, and Technical Data for Commercial Items are * licensed to the U.S. Government under vendor's standard commercial license. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-env mocha */ import assert from 'assert'; import { AxiosInstance } from 'axios'; import MockAdapter from 'axios-mock-adapter'; import RequestHandler from '../../lib/root/requestHandler'; const testServerUrl = 'http://localhost'; const uri = '/some/uri'; const user = 'MyUser'; const password = 'MyPassword'; const token = 'TestToken123'; const UNAUTHORIZED_ERROR = { name: 'Error', response: { status: 401, }, }; export class BuildServerResponses { scope: MockAdapter; authenticatedCookie: string; loginCookieName: string; constructor(requester: AxiosInstance) { this.scope = new MockAdapter(requester); this.authenticatedCookie = 'Authenticated'; this.loginCookieName = 'LWSSO_COOKIE_KEY'; } withSuccessfulAuthentication(user?: string, password?: string) { this.scope .onPost('/authentication/sign_in', { asymmetricMatch(actual: { user: string; password: string }) { if (user) { assert.strictEqual(actual.user, user); } if (password) { assert.strictEqual(actual.password, password); } return true; }, }) .reply(200, undefined, { 'Set-Cookie': this.loginCookieName + '=' + this.authenticatedCookie, }); return this; } withUnsuccessfulAuthentication() { this.scope .onPost('/authentication/sign_in') .reply(401, undefined, { 'Set-Cookie': this.loginCookieName + '=' }); return this; } withUnsuccessfulGetRequest(uri: string, errorBody: object) { this.scope.onGet(uri).reply(400, errorBody); return this; } withUnsuccessfulDeleteRequest(uri: string, errorBody: object) { this.scope.onDelete(uri).reply(400, errorBody); return this; } withUnsuccessfulUpdateRequest(uri: string, errorBody: object) { this.scope.onPut(uri).reply(400, errorBody); return this; } withUnsuccessfulCreateRequest(uri: string, errorBody: object) { this.scope.onPost(uri).reply(400, errorBody); return this; } withGetRequest(uri: string, statusCode: number, replyData?: any) { this.scope.onGet(uri).reply(statusCode, replyData); return this; } withDeleteRequest(uri: string, statusCode: number) { this.scope.onDelete(uri).reply(statusCode); return this; } withUpdateRequest( uri: string, statusCode: number, body?: object, replyData?: any ) { this.scope.onPut(uri, body).reply(statusCode, replyData); return this; } withCreateRequest( uri: string, statusCode: number, body?: object, replyData?: any ) { this.scope.onPost(uri, body).reply(statusCode, replyData); return this; } withUnauthorizedUpdateRequest(uri: string, body?: object, replyData?: any) { return this.withUpdateRequest(uri, 401, body, replyData); } withUnauthorizedGetRequest(uri: string) { return this.withGetRequest(uri, 401); } withUnauthorizedDeleteRequest(uri: string) { return this.withDeleteRequest(uri, 401); } withUnauthorizedCreateRequest(uri: string, body?: object, replyData?: any) { return this.withCreateRequest(uri, 401, body, replyData); } withSuccessfulSignOut() { this.scope .onPost('/authentication/sign_out') .reply(200, undefined, { 'Set-Cookie': this.loginCookieName + '=' }); return this; } withUnsuccessfulSignOut() { this.scope .onPost('/authentication/sign_out') .reply(401, undefined, { 'Set-Cookie': 'LWSSO_COOKIE_KEY=' }); return this; } withTokenGetRequest(uri: string, token: string, statusCode: number, replyData?: any) { this.scope .onGet(uri) .reply(function(config) { // Verify the Authorization header contains the token if (config.headers?.Authorization === `Bearer ${token}`) { return [statusCode, replyData]; } return [401, { error: 'Invalid token' }]; }); return this; } withUnauthorizedTokenGetRequest(uri: string, token: string) { return this.withTokenGetRequest(uri, token, 401); } withTokenDeleteRequest(uri: string, token: string, statusCode: number) { this.scope .onDelete(uri) .reply(function(config) { if (config.headers?.Authorization === `Bearer ${token}`) { return [statusCode]; } return [401, { error: 'Invalid token' }]; }); return this; } withUnauthorizedTokenDeleteRequest(uri: string, token: string) { return this.withTokenDeleteRequest(uri, token, 401); } withTokenUpdateRequest(uri: string, token: string, statusCode: number, body?: object, replyData?: any) { this.scope .onPut(uri, body) .reply(function(config) { if (config.headers?.Authorization === `Bearer ${token}`) { return [statusCode, replyData]; } return [401, { error: 'Invalid token' }]; }); return this; } withTokenCreateRequest(uri: string, token: string, statusCode: number, body?: object, replyData?: any) { this.scope .onPost(uri, body) .reply(function(config) { if (config.headers?.Authorization === `Bearer ${token}`) { return [statusCode, replyData]; } return [401, { error: 'Invalid token' }]; }); return this; } build() { return this.scope; } } describe('requestHandler', function () { // @ts-ignore this.slow(350); let requestHandler: RequestHandler; beforeEach(() => { const requestHandlerInitialization = { user: user, password: password, server: testServerUrl, sharedSpace: 1001, workspace: 1002, }; requestHandler = new RequestHandler(requestHandlerInitialization); }); async function throwsUnauthorizedException(promise: Promise<any>) { await throwsException(promise, 401, undefined); } async function throwsException( promise: Promise<any>, statusCode?: number, errorBody?: any ) { try { await promise; assert.fail(); } catch (e: any) { if (errorBody) { assert.strictEqual( JSON.stringify(e.response.data), JSON.stringify(errorBody) ); } if (statusCode) { assert.strictEqual(e.response.status, statusCode); } } } describe('#authenticate', () => { it('makes a successful authentication request with the given username and password', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withSuccessfulAuthentication(user, password) .build(); const shouldBeUndefined = await requestHandler.authenticate(); await assert.strictEqual(shouldBeUndefined.data, undefined); scope.reset(); }); it('throws an error after failed authentication', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulAuthentication() .build(); await throwsUnauthorizedException(requestHandler.authenticate()); scope.reset(); }); }); describe('#get', () => { it('throws an error in case of an error response different from 401', async () => { const error = { error: 'Failed as intended' }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulGetRequest(uri, error) .build(); await throwsException(requestHandler.get(uri), 400, error); scope.reset(); }); it('throws an error if request was answered with status code 401 two times', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedGetRequest(uri) .withSuccessfulAuthentication() .withUnauthorizedGetRequest(uri) .build(); await throwsUnauthorizedException(requestHandler.get(uri)); scope.reset(); }); it('throws an error if request was answered with status code 401 and authentication fails', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedGetRequest(uri) .withUnsuccessfulAuthentication() .build(); await throwsUnauthorizedException(requestHandler.get(uri)); scope.reset(); }); it('returns the data even if the 1st request was with the status code 401', async function () { const objectToGet = 'Request was successful'; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedGetRequest(uri) .withSuccessfulAuthentication() .withGetRequest(uri, 200, objectToGet) .build(); const objectGot = await requestHandler.get(uri); assert.strictEqual(objectGot.data, objectToGet); scope.reset(); }); it('makes a successful get request', async function () { const objectToGet = 'Request was successful'; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withGetRequest(uri, 200, objectToGet) .build(); const objectGot = await requestHandler.get(uri); assert.strictEqual(objectGot.data, objectToGet); scope.reset(); }); }); describe('#delete', () => { it('throws an error in case of an error response different from 401', async () => { const error = { error: 'Failed as intended' }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulDeleteRequest(uri, error) .build(); await throwsException(requestHandler.delete(uri), 400, error); scope.reset(); }); it('throws an error if request was answered with status code 401 two times', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedDeleteRequest(uri) .withSuccessfulAuthentication() .withUnauthorizedDeleteRequest(uri) .build(); await throwsUnauthorizedException(requestHandler.delete(uri)); scope.reset(); }); it('throws an error if request was answered with status code 401 and authentication fails', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedDeleteRequest(uri) .withUnsuccessfulAuthentication() .build(); await throwsUnauthorizedException(requestHandler.delete(uri)); scope.reset(); }); it('request is made even if 1st request was with the status code 401', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedDeleteRequest(uri) .withSuccessfulAuthentication() .withDeleteRequest(uri, 200) .build(); const shouldBeUndefined = await requestHandler.delete(uri); assert.strictEqual(shouldBeUndefined?.data, undefined); scope.reset(); }); it('makes a successful delete request', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withDeleteRequest(uri, 200) .build(); const shouldBeUndefined = await requestHandler.delete(uri); assert.strictEqual(shouldBeUndefined.data, undefined); scope.reset(); }); }); describe('#update', () => { it('throws an error in case of an error response different from 401', async () => { const error = { error: 'Failed as intended' }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulUpdateRequest(uri, error) .build(); await throwsException(requestHandler.update(uri), 400, error); scope.reset(); }); it('throws an error if request was answered with status code 401 two times', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedUpdateRequest(uri) .withSuccessfulAuthentication() .withUnauthorizedUpdateRequest(uri) .build(); await throwsUnauthorizedException(requestHandler.update(uri)); scope.reset(); }); it('throws an error if request was answered with status code 401 and authentication fails', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedUpdateRequest(uri) .withUnsuccessfulAuthentication() .build(); await throwsUnauthorizedException(requestHandler.update(uri)); scope.reset(); }); it('request is made even if 1st request was with the status code 401', async function () { const updatedObject = 'Success'; const updateBody = { parameter1: 'para 1', parameter2: 2, }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedUpdateRequest(uri, updateBody, updatedObject) .withSuccessfulAuthentication() .withUpdateRequest(uri, 200, updateBody, updatedObject) .build(); const responseObject = await requestHandler.update(uri, updateBody); assert.strictEqual(responseObject.data, updatedObject); scope.reset(); }); it('makes a successful update request', async function () { const updatedObject = 'Success'; const updateBody = { parameter1: 'para 1', parameter2: 2, }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUpdateRequest(uri, 200, updateBody, updatedObject) .build(); const responseObject = await requestHandler.update(uri, updateBody); assert.strictEqual(responseObject.data, updatedObject); scope.reset(); }); }); describe('#create', () => { it('throws an error in case of an error response different from 401', async () => { const error = { error: 'Failed as intended' }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulCreateRequest(uri, error) .build(); await throwsException(requestHandler.create(uri), 400, error); scope.reset(); }); it('throws an error if request was answered with status code 401 two times', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedCreateRequest(uri) .withSuccessfulAuthentication() .withUnauthorizedCreateRequest(uri) .build(); await throwsUnauthorizedException(requestHandler.create(uri)); scope.reset(); }); it('throws an error if request was answered with status code 401 and authentication fails', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedCreateRequest(uri) .withUnsuccessfulAuthentication() .build(); await throwsUnauthorizedException(requestHandler.create(uri)); scope.reset(); }); it('request is made even if 1st request was with the status code 401', async function () { const createObject = 'Success'; const createBody = { parameter1: 'para 1', parameter2: 2, }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnauthorizedCreateRequest(uri, createBody, createObject) .withSuccessfulAuthentication() .withCreateRequest(uri, 200, createBody, createObject) .build(); const responseObject = await requestHandler.create(uri, createBody); assert.strictEqual(responseObject.data, createObject); scope.reset(); }); it('makes a successful update request', async function () { const createObject = { data: 'Success' }; const createBody = { parameter1: 'para 1', parameter2: 2, }; // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withCreateRequest(uri, 200, createBody, createObject) .build(); const responseObject = await requestHandler.create(uri, createBody); assert.strictEqual( JSON.stringify(responseObject.data), JSON.stringify(createObject) ); scope.reset(); }); }); describe('#reauthenicate', () => { it('throws the error given if it does not have the status code 401', async () => { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulAuthentication() .build(); const errorWithoutStatusCode = { response: { data: 'Error' } }; const errorWithStatusCode = { response: { status: 400, data: 'Error' } }; await throwsException( // @ts-ignore requestHandler._reauthenticate(errorWithoutStatusCode), undefined, errorWithoutStatusCode.response.data ); await throwsException( // @ts-ignore requestHandler._reauthenticate(errorWithStatusCode), 400, errorWithStatusCode.response.data ); scope.reset(); }); it('throws an error if the error received has status code 401 and the authentication fails ', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulAuthentication() .build(); await throwsUnauthorizedException( // @ts-ignore requestHandler._reauthenticate(UNAUTHORIZED_ERROR) ); scope.reset(); }); it('authenticates again if the received error has the status code 401', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withSuccessfulAuthentication() .build(); // @ts-ignore const shouldBeUndefined = await requestHandler._reauthenticate( UNAUTHORIZED_ERROR ); assert.strictEqual(shouldBeUndefined?.data, undefined); scope.reset(); }); }); describe('#signOut', () => { it('throws an error if the request fails', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withUnsuccessfulSignOut() .build(); await throwsUnauthorizedException(requestHandler.signOut()); scope.reset(); }); it('throws an error if the request fails', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandler._requestor) .withSuccessfulSignOut() .build(); const shouldBeUndefined = await requestHandler.signOut(); assert.strictEqual(shouldBeUndefined.data, undefined); scope.reset(); }); }); }); describe('requestHandler with token authentication', function () { let requestHandlerWithToken: RequestHandler; beforeEach(() => { requestHandlerWithToken = new RequestHandler({ token: token, server: testServerUrl, sharedSpace: 1001, workspace: 1002, }); }); describe('#get with token', () => { it('makes a successful get request using token authentication', async function () { const objectToGet = 'Request was successful with token'; // @ts-ignore const scope = new BuildServerResponses(requestHandlerWithToken._requestor) .withTokenGetRequest(uri, token, 200, objectToGet) .build(); const objectGot = await requestHandlerWithToken.get(uri); assert.strictEqual(objectGot.data, objectToGet); scope.reset(); }); it('throws an error if token authentication fails', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandlerWithToken._requestor) .withUnauthorizedTokenGetRequest(uri, token) .build(); try { await requestHandlerWithToken.get(uri); assert.fail('Expected an error to be thrown'); } catch (err: any) { assert.ok( err.message.includes('Token authentication failed'), `Expected error message about token failure, but got: ${err.message}` ); } scope.reset(); }); }); describe('#delete with token', () => { it('makes a successful delete request using token authentication', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandlerWithToken._requestor) .withTokenDeleteRequest(uri, token, 200) .build(); const shouldBeUndefined = await requestHandlerWithToken.delete(uri); assert.strictEqual(shouldBeUndefined.data, undefined); scope.reset(); }); it('throws an error if token authentication fails', async function () { // @ts-ignore const scope = new BuildServerResponses(requestHandlerWithToken._requestor) .withUnauthorizedTokenDeleteRequest(uri, token) .build(); try { await requestHandlerWithToken.delete(uri); assert.fail('Expected an error to be thrown'); } catch (err: any) { assert.ok( err.message.includes('Token authentication failed'), `Expected error message about token failure, but got: ${err.message}` ); } scope.reset(); }); }); describe('#update with token', () => { it('makes a successful update request using token authentication', async function () { const updatedObject = 'Success'; const updateBody = { parameter1: 'parameter1', parameter2: 2, }; // @ts-ignore const scope = new BuildServerResponses(requestHandlerWithToken._requestor) .withTokenUpdateRequest(uri, token, 200, updateBody, updatedObject) .build(); const responseObject = await requestHandlerWithToken.update(uri, updateBody); assert.strictEqual(responseObject.data, updatedObject); scope.reset(); }); }); describe('#create with token', () => { it('makes a successful create request using token authentication', async function () { const createObject = { data: 'Success' }; const createBody = { parameter1: 'parameter1', parameter2: 2, }; // @ts-ignore const scope = new BuildServerResponses(requestHandlerWithToken._requestor) .withTokenCreateRequest(uri, token, 200, createBody, createObject) .build(); const responseObject = await requestHandlerWithToken.create(uri, createBody); assert.strictEqual( JSON.stringify(responseObject.data), JSON.stringify(createObject) ); scope.reset(); }); }); }); // Test the hybrid flow (credentials + token) describe('requestHandler with credentials and token (hybrid)', function () { let requestHandlerHybrid: RequestHandler; beforeEach(() => { requestHandlerHybrid = new RequestHandler({ user: user, password: password, token: token, server: testServerUrl, sharedSpace: 1001, workspace: 1002, }); }); it('check if credentials were specifically used when having hybrid setup', async function () { const objectToGet = 'Request was successful'; // @ts-ignore const scope = new BuildServerResponses(requestHandlerHybrid._requestor) .withSuccessfulAuthentication() .withGetRequest(uri, 200, objectToGet) .build(); const objectGot = await requestHandlerHybrid.get(uri); assert.strictEqual(objectGot.data, objectToGet); scope.reset(); }); });