@microfocus/alm-octane-js-rest-sdk
Version:
NodeJS wrapper for the OpenText Core Software Delivery Platform API
527 lines (493 loc) • 18.8 kB
text/typescript
/*
* 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 = '';
const uri = '/some/uri';
const user = 'MyUser';
const password = 'MyPassword';
const UNAUTHORIZED_ERROR = {
name: 'Error',
response: {
status: 401,
},
};
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();
});
});
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;
}
build() {
return this.scope;
}
}
});