Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "yoti",
"version": "3.7.1",
"version": "3.7.2",
"description": "Yoti NodeJS SDK for back-end integration",
"author": "Yoti LTD <[email protected]> (https://www.yoti.com/developers)",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
sonar.projectKey = yoti-web-sdk:node
sonar.projectName = node-sdk
sonar.projectVersion = 3.7.1
sonar.projectVersion = 3.7.2
sonar.exclusions=tests/**,examples/**,node_modules/**,coverage/**
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.verbose = true
2 changes: 1 addition & 1 deletion src/request/request.builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class RequestBuilder {

// Merge provided query params with nonce and timestamp.
const queryString = buildQueryString(Object.assign(
this.queryParams || {},
this.queryParams,
{
nonce: uuid.v4(),
timestamp: Date.now(),
Expand Down
31 changes: 24 additions & 7 deletions src/request/request.handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,46 @@ const yotiCommon = require('../yoti_common');
* Default HTTP request handler.
*
* @param {YotiRequest} yotiRequest
* @param {boolean} buffer Return the response as a Buffer.
*
* @returns {Promise} Resolves {YotiResponse}
*/
module.exports.execute = yotiRequest => new Promise((resolve, reject) => {
module.exports.execute = (yotiRequest, buffer = false) => new Promise((resolve, reject) => {
const request = superagent(yotiRequest.getMethod(), yotiRequest.getUrl());

if (yotiCommon.requestCanSendPayload(yotiRequest.getMethod())) {
request.send(yotiRequest.getPayload().getPayloadJSON());
}

if (buffer === true) {
request.buffer(buffer);
}

if (yotiRequest.getHeaders()) {
request.set(yotiRequest.getHeaders());
}

request
.then((response) => {
try {
const parsedResponse = JSON.parse(response.text);
const receipt = parsedResponse.receipt || null;
return resolve(new YotiResponse(parsedResponse, response.statusCode, receipt));
} catch (err) {
return reject(err);
let parsedResponse = null;
let body = null;
let receipt = null;

if (response.body instanceof Buffer) {
body = response.body;
parsedResponse = response.body;
} else if (response.text) {
body = response.text;
parsedResponse = response.headers['content-type'] ? response.body : JSON.parse(response.text);
receipt = parsedResponse.receipt || null;
}

return resolve(new YotiResponse(
parsedResponse,
response.statusCode,
receipt,
body
));
})
.catch((err) => {
console.log(`Error getting data from Connect API: ${err.message}`);
Expand Down
6 changes: 4 additions & 2 deletions src/request/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ class YotiRequest {
/**
* Executes the request.
*
* @param {boolean} buffer Return the response as a Buffer.
*
* @returns {Promise} Resolves {YotiResponse}
*/
execute() {
return requestHandler.execute(this);
execute(buffer = false) {
return requestHandler.execute(this, buffer);
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/request/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ class YotiResponse {
* @param {*} parsedResponse
* @param {int} statusCode
* @param {Object|null} receipt
* @param {Buffer|string|null} body
*/
constructor(parsedResponse, statusCode, receipt = null) {
constructor(parsedResponse, statusCode, receipt = null, body = null) {
this.parsedResponse = parsedResponse;
this.statusCode = statusCode;
this.receipt = receipt;
this.body = body;
}

/**
Expand All @@ -29,6 +31,13 @@ class YotiResponse {
return this.parsedResponse;
}

/**
* @returns {Buffer|string|null} The response body.
*/
getBody() {
return this.body;
}

/**
* @returns {int} Response status code.
*/
Expand Down
24 changes: 24 additions & 0 deletions tests/request/request.builder.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,30 @@ describe('RequestBuilder', () => {
assertExpectedRequest(request, done);
});
});
describe('#withGet', () => {
it('should set method to GET', () => {
const request = new RequestBuilder()
.withBaseUrl(API_BASE_URL)
.withPemFilePath(PEM_FILE_PATH)
.withEndpoint(API_ENDPOINT)
.withGet()
.build();

expect(request.getMethod()).toBe('GET');
});
});
describe('#withPost', () => {
it('should set method to POST', () => {
const request = new RequestBuilder()
.withBaseUrl(API_BASE_URL)
.withPemFilePath(PEM_FILE_PATH)
.withEndpoint(API_ENDPOINT)
.withPost()
.build();

expect(request.getMethod()).toBe('POST');
});
});
describe('#withHeader', () => {
it('should only accept string header value', () => {
expect(() => {
Expand Down
176 changes: 176 additions & 0 deletions tests/request/request.handler.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
const nock = require('nock');
const fs = require('fs');
const yotiRequestHandler = require('../../src/request/request.handler');
const { RequestBuilder } = require('../../src/request/request.builder');
const { Payload } = require('../../src/request/payload');

const SOME_BASE_URL = 'https://someapi.yoti.com';
const SOME_ENDPOINT = '/some/endpoint';
const SOME_ENDPOINT_REG_EXP = new RegExp(`^${SOME_ENDPOINT}`);
const SOME_PEM_STRING = fs.readFileSync('./tests/sample-data/keys/node-sdk-test.pem', 'utf8');
const ALLOWED_METHODS = ['POST', 'PUT', 'PATCH', 'GET', 'DELETE'];
const SOME_JSON_DATA = { some: 'json' };
const SOME_JSON_DATA_STRING = JSON.stringify(SOME_JSON_DATA);
const SOME_JSON_RECEIPT_DATA = { receipt: 'some receipt' };
const SOME_JSON_RECEIPT_DATA_STRING = JSON.stringify(SOME_JSON_RECEIPT_DATA);
const SOME_DATA = 'someData';
const SOME_REQUEST = new RequestBuilder()
.withBaseUrl(SOME_BASE_URL)
.withEndpoint(SOME_ENDPOINT)
.withMethod('GET')
.withPemString(SOME_PEM_STRING)
.build();

/**
* @param {string} method
* @param {string} uri
* @param {integer} responseCode
* @param {string} body
*/
const mockResponse = (method, uri, responseCode, body, headers) => {
const scope = nock(SOME_BASE_URL);
const interceptor = scope[method.toLowerCase()](uri);
interceptor.reply(responseCode, body, headers);
};

describe('yotiRequest', () => {
afterEach((done) => {
nock.cleanAll();
done();
});

ALLOWED_METHODS.forEach((ALLOWED_METHOD) => {
describe(`when empty response is returned for ${ALLOWED_METHOD} method`, () => {
beforeEach((done) => {
mockResponse(ALLOWED_METHOD, SOME_ENDPOINT_REG_EXP, 200, '', {
'content-type': 'application/json',
});
done();
});

it('should return YotiResponse', (done) => {
const request = new RequestBuilder()
.withBaseUrl(SOME_BASE_URL)
.withEndpoint(SOME_ENDPOINT)
.withMethod(ALLOWED_METHOD)
.withPayload(new Payload(''))
.withPemString(SOME_PEM_STRING)
.build();

yotiRequestHandler
.execute(request)
.then((response) => {
expect(response.getParsedResponse()).toBeNull();
expect(response.getBody()).toBeNull();
expect(response.getStatusCode()).toBe(200);
done();
})
.catch(done);
});
});
describe(`when JSON response is returned for ${ALLOWED_METHOD} method`, () => {
beforeEach((done) => {
mockResponse(ALLOWED_METHOD, SOME_ENDPOINT_REG_EXP, 200, SOME_JSON_DATA_STRING, {
'content-type': 'application/json',
});
done();
});

it('should return YotiResponse', (done) => {
const request = new RequestBuilder()
.withBaseUrl(SOME_BASE_URL)
.withEndpoint(SOME_ENDPOINT)
.withMethod(ALLOWED_METHOD)
.withPayload(new Payload(''))
.withPemString(SOME_PEM_STRING)
.build();

yotiRequestHandler
.execute(request)
.then((response) => {
expect(response.getParsedResponse()).toStrictEqual(SOME_JSON_DATA);
expect(response.getBody()).toBe(SOME_JSON_DATA_STRING);
expect(response.getReceipt()).toBeNull();
expect(response.getStatusCode()).toBe(200);
done();
})
.catch(done);
});
});
});
describe('when receipt is returned', () => {
beforeEach((done) => {
nock(SOME_BASE_URL)
.get(SOME_ENDPOINT_REG_EXP)
.reply(200, SOME_JSON_RECEIPT_DATA_STRING, {
'content-type': 'application/json',
});
done();
});

it('should return YotiResponse', (done) => {
yotiRequestHandler
.execute(SOME_REQUEST)
.then((response) => {
expect(response.getParsedResponse()).toStrictEqual(SOME_JSON_RECEIPT_DATA);
expect(response.getBody()).toBe(SOME_JSON_RECEIPT_DATA_STRING);
expect(response.getReceipt()).toStrictEqual(SOME_JSON_RECEIPT_DATA.receipt);
expect(response.getStatusCode()).toBe(200);
done();
})
.catch(done);
});
});
[
'application/octet-stream',
'application/pdf',
'image/jpeg',
'image/png',
].forEach((mimeType) => {
describe(`when ${mimeType} content is returned`, () => {
beforeEach((done) => {
nock(SOME_BASE_URL)
.get(SOME_ENDPOINT_REG_EXP)
.reply(200, SOME_DATA, {
'Content-Type': mimeType,
});
done();
});
it('should return YotiResponse', (done) => {
yotiRequestHandler
.execute(SOME_REQUEST, true)
.then((response) => {
expect(response.getParsedResponse()).toBeInstanceOf(Buffer);
expect(response.getParsedResponse().toString()).toBe(SOME_DATA);
expect(response.getBody().toString()).toBe(SOME_DATA);
expect(response.getStatusCode()).toBe(200);
done();
})
.catch(done);
});
});
});
[
'text/plain',
].forEach((mimeType) => {
describe(`when ${mimeType} content is returned`, () => {
beforeEach((done) => {
nock(SOME_BASE_URL)
.get(SOME_ENDPOINT_REG_EXP)
.reply(200, SOME_DATA, {
'Content-Type': mimeType,
});
done();
});
it('should return YotiResponse', (done) => {
yotiRequestHandler
.execute(SOME_REQUEST)
.then((response) => {
expect(response.getBody()).toBe(SOME_DATA);
done();
})
.catch(done);
});
});
});
});
17 changes: 17 additions & 0 deletions tests/request/request.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const { YotiRequest } = require('../../src/request/request');
const requestHandler = require('../../src/request/request.handler');
const { Payload } = require('../../src/request/payload');

jest.mock('../../src/request/request.handler');

const SOME_URL = 'https://api.example.com/some-endpoint';
const SOME_METHOD = 'POST';
const SOME_PAYLOAD = new Payload('some payload');
Expand All @@ -10,6 +13,20 @@ const SOME_HEADERS = {
const SOME_REQUEST = new YotiRequest(SOME_METHOD, SOME_URL, SOME_HEADERS, SOME_PAYLOAD);

describe('YotiRequest', () => {
describe('#execute', () => {
it('should execute the request handler', () => {
SOME_REQUEST.execute();
expect(requestHandler.execute).toHaveBeenCalledWith(SOME_REQUEST, false);
});
it('should execute the request handler with buffer disabled', () => {
SOME_REQUEST.execute(false);
expect(requestHandler.execute).toHaveBeenCalledWith(SOME_REQUEST, false);
});
it('should execute the request handler with buffer enabled', () => {
SOME_REQUEST.execute(true);
expect(requestHandler.execute).toHaveBeenCalledWith(SOME_REQUEST, true);
});
});
describe('#getUrl', () => {
it('should return the URL', () => {
expect(SOME_REQUEST.getUrl()).toBe(SOME_URL);
Expand Down
Loading