diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..e73bd06 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ + "esversion": 10 +} \ No newline at end of file diff --git a/__tests__/index.test.js b/__tests__/index.test.js index eae67a6..67370ae 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -1,179 +1,219 @@ /** - * @jest-environment node -*/ + * @jest-environment node + */ -const { - Authenticator, - Environment, - HttpClient -} = require('./../index') +const { Authenticator, Environment, HttpClient } = require("./../index"); -const Request = require('request') -const PackageJson = require('../package.json') +const Request = require("request"); +const PackageJson = require("../package.json"); -const nock = require('nock') +const nock = require("nock"); -describe('index', () => { +describe("index", () => { beforeEach(() => { - authenticator = new Authenticator(Environment.SANDBOX(), 'c6058849d0a056fc743203acb8e6a850dad103485c3edc51b16a9260cc7a7688', 'aa6a415fce31967501662c1960fcbfbf4745acff99acb19dbc1aae6f76c9c619') - - nock(authenticator.environment.baseUrl) - .post('/oauth/token') - .reply(200, { - access_token: 'new-token', - token_type: 'bearer', - expires_in: 2592000, - scope: 'api', - created_at: 1519753273 - }) - }) - - describe('Authenticator#getAccessToken', () => { - describe('when no access token exists', () => { - test('returns a new access token', () => { - return authenticator.getAccessToken().then( - (accessToken) => expect(accessToken).toEqual('new-token') - ) - }) - }) - - describe('when a valid access token already exists', () => { + authenticator = new Authenticator( + Environment.SANDBOX(), + "c6058849d0a056fc743203acb8e6a850dad103485c3edc51b16a9260cc7a7688", + "aa6a415fce31967501662c1960fcbfbf4745acff99acb19dbc1aae6f76c9c619" + ); + + nock(authenticator.environment.baseUrl).post("/oauth/token").reply(200, { + access_token: "new-token", + token_type: "bearer", + expires_in: 2592000, + scope: "api", + created_at: 1519753273, + }); + }); + + describe("Authenticator#getAccessToken", () => { + describe("when no access token exists", () => { + test("returns a new access token", () => { + return authenticator + .getAccessToken() + .then((accessToken) => expect(accessToken).toEqual("new-token")); + }); + }); + + describe("when a valid access token already exists", () => { beforeEach(() => { - authenticator.accessToken = authenticator.oauthClient.createToken('current-valid-token', null, 'client_credentials', {}) - }) - - it('returns the current valid access token', () => { - return authenticator.getAccessToken().then( - (accessToken) => expect(accessToken).toEqual('current-valid-token') - ) - }) - }) - - describe('when an expired access token already exists', () => { + authenticator.accessToken = authenticator.oauthClient.createToken( + "current-valid-token", + null, + "client_credentials", + {} + ); + }); + + it("returns the current valid access token", () => { + return authenticator + .getAccessToken() + .then((accessToken) => + expect(accessToken).toEqual("current-valid-token") + ); + }); + }); + + describe("when an expired access token already exists", () => { beforeEach(() => { - authenticator.accessToken = authenticator.oauthClient.createToken('current-valid-token', null, 'client_credentials', {}) + authenticator.accessToken = authenticator.oauthClient.createToken( + "current-valid-token", + null, + "client_credentials", + {} + ); authenticator.accessToken.expired = jest.fn(() => { - return true - }) - }) - - it('returns the current valid access token', () => { - return authenticator.getAccessToken().then( - (accessToken) => expect(accessToken).toEqual('new-token') - ) - }) - }) - }) - - describe('HttpClient#performGet', () => { + return true; + }); + }); + + it("returns the current valid access token", () => { + return authenticator + .getAccessToken() + .then((accessToken) => expect(accessToken).toEqual("new-token")); + }); + }); + }); + + describe("HttpClient#performGet", () => { beforeEach(() => { - httpClient = new HttpClient(authenticator) + httpClient = new HttpClient(authenticator); nock(httpClient.authenticator.environment.baseUrl) - .get('/some-url') + .get("/some-url") .reply(200, { - some: 'response' - }) + some: "response", + }); nock(httpClient.authenticator.environment.baseUrl) - .get('/some-url?param1=one¶m2=two') + .get("/some-url?param1=one¶m2=two") .reply(200, { - some: 'response' - }) + some: "response", + }); nock(httpClient.authenticator.environment.baseUrl) - .post('/some-url') + .post("/some-url") .reply(200, { - some: 'response' - }) - }) + some: "response", + }); + }); - it('sends a get http request with correct parameters', () => { - const spy = jest.spyOn(Request, 'get') - return httpClient.performGet('/some-url').then(() => { + it("sends a get http request with correct parameters", () => { + const spy = jest.spyOn(Request, "get"); + return httpClient.performGet("/some-url").then(() => { expect(spy).toHaveBeenCalledWith( - {'headers': {'Authorization': 'Bearer new-token', 'Content-Type': 'application/json', 'User-Agent': 'stuart-client-js/' + PackageJson.version}, - 'url': 'https://api.sandbox.stuart.com/some-url'}, - expect.anything()) - }) - }) - - it('sends a get http request with correct query string parameters', () => { - const spy = jest.spyOn(Request, 'get') - const params = {param1: 'one', param2: 'two'} - return httpClient.performGet('/some-url', params).then(() => { + { + headers: { + Authorization: "Bearer new-token", + "Content-Type": "application/json", + "User-Agent": "stuart-client-js/" + PackageJson.version, + }, + url: "https://api.sandbox.stuart.com/some-url", + }, + expect.anything() + ); + }); + }); + + it("sends a get http request with correct query string parameters", () => { + const spy = jest.spyOn(Request, "get"); + const params = { param1: "one", param2: "two" }; + return httpClient.performGet("/some-url", params).then(() => { expect(spy).toHaveBeenCalledWith( - {'headers': {'Authorization': 'Bearer new-token', 'Content-Type': 'application/json', 'User-Agent': 'stuart-client-js/' + PackageJson.version}, - 'url': 'https://api.sandbox.stuart.com/some-url', - 'qs': {'param1': 'one', 'param2': 'two'} + { + headers: { + Authorization: "Bearer new-token", + "Content-Type": "application/json", + "User-Agent": "stuart-client-js/" + PackageJson.version, + }, + url: "https://api.sandbox.stuart.com/some-url", + qs: { param1: "one", param2: "two" }, }, expect.anything() - ) - }) - }) - }) + ); + }); + }); + }); - describe('HttpClient#performPost', () => { + describe("HttpClient#performPost", () => { beforeEach(() => { - httpClient = new HttpClient(authenticator) + httpClient = new HttpClient(authenticator); nock(httpClient.authenticator.environment.baseUrl) - .post('/some-url') + .post("/some-url") .reply(200, { - some: 'response' - }) + some: "response", + }); nock(httpClient.authenticator.environment.baseUrl) - .post('/some-url-that-returns-null-body') - .reply(204) + .post("/some-url-that-returns-null-body") + .reply(204); nock(httpClient.authenticator.environment.baseUrl) - .post('/make-tea') - .reply(418) - }) - - it('sends a post http request with correct parameters', () => { - const spy = jest.spyOn(Request, 'post') - return httpClient.performPost('/some-url', JSON.stringify({some: 'body'})).then(() => { - expect(spy).toHaveBeenCalledWith( - {'headers': {'Authorization': 'Bearer new-token', 'Content-Type': 'application/json', 'User-Agent': 'stuart-client-js/' + PackageJson.version}, - 'url': 'https://api.sandbox.stuart.com/some-url', - 'body': '{"some":"body"}'}, - expect.anything() - ) - }) - }) - - it('handles null response bodies', () => { - const spy = jest.spyOn(Request, 'post') - return httpClient.performPost('/some-url-that-returns-null-body').then(() => { - expect(spy).toHaveBeenCalledWith( - {'headers': {'Authorization': 'Bearer new-token', 'Content-Type': 'application/json', 'User-Agent': 'stuart-client-js/' + PackageJson.version}, - 'url': 'https://api.sandbox.stuart.com/some-url-that-returns-null-body'}, - expect.anything() - ) - }) - }) - - it('assumes 200 responses are successful', () => { - const spy = jest.spyOn(Request, 'post') - return httpClient.performPost('/some-url').then((response) => { - expect(response.success()).toBe(true) - }) - }) - - it('assumes 204 responses are successful', () => { - const spy = jest.spyOn(Request, 'post') - return httpClient.performPost('/some-url-that-returns-null-body').then((response) => { - expect(response.success()).toBe(true) - }) - }) - - it('assumes non 2xx responses are failures', () => { - const spy = jest.spyOn(Request, 'post') - return httpClient.performPost('/make-tea').then((response) => { - expect(response.success()).toBe(false) - }) - }) - }) -}) + .post("/make-tea") + .reply(418); + }); + + it("sends a post http request with correct parameters", () => { + const spy = jest.spyOn(Request, "post"); + return httpClient + .performPost("/some-url", JSON.stringify({ some: "body" })) + .then(() => { + expect(spy).toHaveBeenCalledWith( + { + headers: { + Authorization: "Bearer new-token", + "Content-Type": "application/json", + "User-Agent": "stuart-client-js/" + PackageJson.version, + }, + url: "https://api.sandbox.stuart.com/some-url", + body: '{"some":"body"}', + }, + expect.anything() + ); + }); + }); + + it("handles null response bodies", () => { + const spy = jest.spyOn(Request, "post"); + return httpClient + .performPost("/some-url-that-returns-null-body") + .then(() => { + expect(spy).toHaveBeenCalledWith( + { + headers: { + Authorization: "Bearer new-token", + "Content-Type": "application/json", + "User-Agent": "stuart-client-js/" + PackageJson.version, + }, + url: "https://api.sandbox.stuart.com/some-url-that-returns-null-body", + }, + expect.anything() + ); + }); + }); + + it("assumes 200 responses are successful", () => { + const spy = jest.spyOn(Request, "post"); + return httpClient.performPost("/some-url").then((response) => { + expect(response.success()).toBe(true); + }); + }); + + it("assumes 204 responses are successful", () => { + const spy = jest.spyOn(Request, "post"); + return httpClient + .performPost("/some-url-that-returns-null-body") + .then((response) => { + expect(response.success()).toBe(true); + }); + }); + + it("assumes non 2xx responses are failures", () => { + const spy = jest.spyOn(Request, "post"); + return httpClient.performPost("/make-tea").then((response) => { + expect(response.success()).toBe(false); + }); + }); + }); +}); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..bcfc1ec --- /dev/null +++ b/index.d.ts @@ -0,0 +1,32 @@ +declare class Environment { + static SANDBOX(): string; + static PRODUCTION(): string; +} + +declare class Authenticator { + constructor( + environment: string, + apiClientId: string, + apiClientSecret: string + ); + getAccessToken(): Promise; +} + +declare class ApiResponse { + constructor(statusCode: any, body: any, headers: any); + success(): boolean; +} + +declare class HttpClient { + constructor(authenticator: any); + performGet(resource: any, params?: any): Promise; + performPost(resource: any, body: any): Promise; + url(resource: any): any; + defaultHeaders(accessToken: any): { + Authorization: string; + "User-Agent": string; + "Content-Type": string; + }; +} + +export { Environment, Authenticator, ApiResponse, HttpClient }; diff --git a/index.js b/index.js index 089f058..d38a0c3 100644 --- a/index.js +++ b/index.js @@ -1,102 +1,131 @@ -const ClientOAuth2 = require('client-oauth2') -const Request = require('request') -const PackageJson = require('./package.json') +const ClientOAuth2 = require("client-oauth2"); +const Request = require("request"); +const PackageJson = require("./package.json"); class Environment { - static SANDBOX () { + static SANDBOX() { return { - baseUrl: 'https://api.sandbox.stuart.com' - } + baseUrl: "https://api.sandbox.stuart.com", + }; } - static PRODUCTION () { + static PRODUCTION() { return { - baseUrl: 'https://api.stuart.com' - } + baseUrl: "https://api.stuart.com", + }; } } class Authenticator { - constructor (environment, apiClientId, apiClientSecret) { - this.environment = environment + constructor(environment, apiClientId, apiClientSecret) { + this.environment = environment; this.oauthClient = new ClientOAuth2({ clientId: apiClientId, clientSecret: apiClientSecret, - accessTokenUri: environment.baseUrl + '/oauth/token' - }) + accessTokenUri: environment.baseUrl + "/oauth/token", + }); } - getAccessToken () { + getAccessToken() { return new Promise((resolve, reject) => { if (this.accessToken != null && !this.accessToken.expired()) { - resolve(this.accessToken.accessToken) + resolve(this.accessToken.accessToken); } else { - this.oauthClient.credentials.getToken().then((accessToken) => { - this.accessToken = accessToken - resolve(this.accessToken.accessToken) - }).catch(error => { reject(error) }) + this.oauthClient.credentials + .getToken() + .then((accessToken) => { + this.accessToken = accessToken; + resolve(this.accessToken.accessToken); + }) + .catch((error) => { + reject(error); + }); } - }) + }); } } class ApiResponse { - constructor (statusCode, body, headers) { - this.statusCode = statusCode - this.body = body - this.headers = headers + constructor(statusCode, body, headers) { + this.statusCode = statusCode; + this.body = body; + this.headers = headers; } - success () { - return this.statusCode >= 200 && this.statusCode < 300 + success() { + return this.statusCode >= 200 && this.statusCode < 300; } } class HttpClient { - constructor (authenticator) { - this.authenticator = authenticator + constructor(authenticator) { + this.authenticator = authenticator; } - performGet (resource, params) { + performGet(resource, params) { return new Promise((resolve, reject) => { - this.authenticator.getAccessToken().then((accessToken) => { - let options = { - url: this.url(resource), - headers: this.defaultHeaders(accessToken) - } - if (params) options.qs = params + this.authenticator + .getAccessToken() + .then((accessToken) => { + let options = { + url: this.url(resource), + headers: this.defaultHeaders(accessToken), + }; + if (params) options.qs = params; - Request.get(options, (err, res) => resolve( - new ApiResponse(res.statusCode, JSON.parse(res.body), res.headers))) - }).catch(error => { reject(error) }) - }) - }; + Request.get(options, (err, res) => { + if (err) reject(err); + else + resolve( + new ApiResponse( + res.statusCode, + JSON.parse(res.body), + res.headers + ) + ); + }); + }) + .catch((error) => reject(error)); + }); + } - performPost (resource, body) { + performPost(resource, body) { return new Promise((resolve, reject) => { - this.authenticator.getAccessToken().then((accessToken) => { - let options = { - url: this.url(resource), - headers: this.defaultHeaders(accessToken), - body: body - } + this.authenticator + .getAccessToken() + .then((accessToken) => { + let options = { + url: this.url(resource), + headers: this.defaultHeaders(accessToken), + body: body, + }; - Request.post(options, (err, res) => resolve( - new ApiResponse(res.statusCode, JSON.parse(res.body || '{}'), res.headers))) - }).catch(error => { reject(error) }) - }) - }; + Request.post(options, (err, res) => { + if (err) reject(err); + else + resolve( + new ApiResponse( + res.statusCode, + JSON.parse(res.body || "{}"), + res.headers + ) + ); + }); + }) + .catch((error) => reject(error)); + }); + } - url (resource) { - return this.authenticator.environment.baseUrl + resource + url(resource) { + return this.authenticator.environment.baseUrl + resource; } - defaultHeaders (accessToken) { + defaultHeaders(accessToken) { return { - 'Authorization': 'Bearer ' + accessToken, - 'User-Agent': 'stuart-client-js/' + PackageJson.version, - 'Content-Type': 'application/json' - } + Authorization: "Bearer " + accessToken, + "User-Agent": "stuart-client-js/" + PackageJson.version, + "Content-Type": "application/json", + }; } } @@ -104,5 +133,5 @@ module.exports = { Authenticator, Environment, ApiResponse, - HttpClient -} + HttpClient, +}; diff --git a/package.json b/package.json index 4b675de..654dbf9 100644 --- a/package.json +++ b/package.json @@ -38,4 +38,4 @@ "jest": "^26.5.0", "nock": "13.0.0" } -} +} \ No newline at end of file