From 8c533dafde2402bb50d3458ed130f0542bc81f9b Mon Sep 17 00:00:00 2001 From: Kevin Grozav Date: Wed, 6 Sep 2017 13:10:13 -0700 Subject: [PATCH] Update to Async/Await Description: Promisify post from request module, Convert callback style functions to implement async/await Remove done flag from unit test and check function Make unit test async/await --- .babelrc | 25 ++++-- .eslintrc | 7 +- package.json | 13 ++- src/app.js | 214 +++++++++++++++++++++++++---------------------- src/test/test.js | 28 +++---- 5 files changed, 157 insertions(+), 130 deletions(-) diff --git a/.babelrc b/.babelrc index 8da4517..b11ae50 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,19 @@ -{ - "compact": false, - "presets": ["es2015"], - "auxiliaryCommentBefore": "istanbul ignore next", - "sourceMaps": "inline" -} +{ + "presets": [ + ["env", { + "targets": { + "node": "0.12" + } + }] + ], + "compact": false, + "sourceMaps": "inline", + "plugins": [ + "transform-async-to-bluebird", + "transform-promise-to-bluebird", + ["transform-runtime", { + "polyfill": false, + "regenerator": true + }] + ] +} \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 8df42c4..dc8b573 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,10 +15,11 @@ globals: spy: true stub: true -parserOptions: - sourceType: "module" +parserOptions: + sourceType: "module" + ecmaVersion: 8 -rules: + rules: # ERRORS space-before-blocks: 2 indent: [2, 2, { "SwitchCase": 1 }] diff --git a/package.json b/package.json index 12f4ab9..a814e0c 100644 --- a/package.json +++ b/package.json @@ -35,16 +35,21 @@ "dependencies": { "babel-preset-es2015": "^6.6.0", "debug": "^2.2.0", - "request": "^2.73.0" + "request": "^2.73.0", + "babel-preset-env": "^1.5.1", + "bluebird": "^3.5.0" }, "devDependencies": { - "babel-cli": "^6.10.1", "chai": "^3.5.0", "eslint": "^3.3.0", - "mocha": "^2.5.3" + "mocha": "^2.5.3", + "babel-cli": "^6.24.1", + "babel-plugin-transform-async-to-bluebird": "^1.1.1", + "babel-plugin-transform-promise-to-bluebird": "^1.1.1", + "babel-plugin-transform-runtime": "^6.23.0" }, "engines": { "node": ">=6.9.1", "npm": ">=3.10.8" } -} +} \ No newline at end of file diff --git a/src/app.js b/src/app.js index 00ed378..b11175b 100644 --- a/src/app.js +++ b/src/app.js @@ -6,68 +6,87 @@ import * as request from 'request'; import debug from 'debug'; +import Promise from 'bluebird'; // Debug log const log = debug('watsonwork-helloworld-app'); -// Return the list of spaces the app belongs to -const spaces = (tok, cb) => { - request.post('https://api.watsonwork.ibm.com/graphql', { - headers: { - jwt: tok, - 'Content-Type': 'application/graphql' - }, - - // This is a GraphQL query, used to retrieve the list of spaces - // visible to the app (given the app OAuth token) - body: ` - query { - spaces (first: 50) { - items { - title - id +// Promisify to enable async/await construct +const post = Promise.promisify(request.post); + + +// Return a list of spaces the app belongs to +const spaces = async (tok) => { + try { + const res = await post('https://api.watsonwork.ibm.com/graphql', { + headers: { + jwt: tok, + 'Content-Type': 'application/graphql' + }, + // This is a GraphQL query, used to retrieve the list of spaces + // visible to the app (given the app OAuth token) + body: ` + query { + spaces (first: 50) { + items { + title + id + } } - } - }` - }, (err, res) => { - if(err || res.statusCode !== 200) { - log('Error retrieving spaces %o', err || res.statusCode); - cb(err || new Error(res.statusCode)); - return; - } + }` + }); + // Throw an error if request unsuccesful + if(res.statusCode !== 200) + throw new Error(res.statusCode); + // Return the list of spaces const body = JSON.parse(res.body); log('Space query result %o', body.data.spaces.items); - cb(null, body.data.spaces.items); - }); + return body.data.spaces.items; + } + catch(err) { + log('Space query result %o', body.data.spaces.items); + throw err; + } }; + // Return an OAuth token for the app -const token = (appId, secret, cb) => { - request.post('https://api.watsonwork.ibm.com/oauth/token', { - auth: { - user: appId, - pass: secret - }, - json: true, - form: { - grant_type: 'client_credentials' - } - }, (err, res) => { - if(err || res.statusCode !== 200) { - log('Error getting OAuth token %o', err || res.statusCode); - cb(err || new Error(res.statusCode)); - return; - } - cb(null, res.body.access_token); - }); +const token = async (appId, secret) => { + try { + // Authentication request with client_credentials + const res = await post('https://api.watsonwork.ibm.com/oauth/token', { + auth: { + user: appId, + pass: secret + }, + json: true, + form: { + grant_type: 'client_credentials' + } + }); + + // Throw an error if request unsuccesful + if(res.statusCode !== 200) + throw new Error(res.statusCode); + + // Return the OAuth token + return res.body.access_token; + } + catch(err) { + log('Error getting OAuth token %o', err || res.statusCode); + throw err; + } + }; + // Send an app message to the conversation in a space -const send = (spaceId, text, tok, cb) => { - request.post( - 'https://api.watsonwork.ibm.com/v1/spaces/' + spaceId + '/messages', { +const send = async (spaceId, text, tok) => { + + try { + const res = await post('https://api.watsonwork.ibm.com/v1/spaces/' + spaceId + '/messages', { headers: { Authorization: 'Bearer ' + tok }, @@ -90,64 +109,61 @@ const send = (spaceId, text, tok, cb) => { } }] } - }, (err, res) => { - if(err || res.statusCode !== 201) { - log('Error sending message %o', err || res.statusCode); - cb(err || new Error(res.statusCode)); - return; - } - log('Send result %d, %o', res.statusCode, res.body); - cb(null, res.body); }); + + // Throw an error if request unsuccesful + if(res.statusCode !== 201) + throw new Error(res.statusCode); + + // Return the body of the message response + log('Send result %d, %o', res.statusCode, res.body); + return res.body; + } + catch(err) { + log('Error sending message %o', err); + throw err; + } }; + // Main app entry point // Send a message to the conversation in the space matching the given name -export const main = (name, text, appId, secret, cb) => { - // Get an OAuth token for the app - token(appId, secret, (err, tok) => { - if(err) { - cb(err); - return; - } - +export const main = async (name, text, appId, secret) => { + try { + // Get an OAuth token for the app + var tok = await token(appId, secret); // List the spaces the app belongs to - spaces(tok, (err, slist) => { - if(err) { - cb(err); - return; - } - - // Find a space matching the given name - const space = slist.filter((s) => s.title === name)[0]; - if(!space) { - cb(new Error('Space not found')); - return; - } - - // Send the message - log('Sending \'%s\' to space %s', text, space.title); - send(space.id, - text || 'Hello World! Welcome to **Watson Work**!', - tok, (err, res) => { - if(err) { - cb(err); - return; - } - log('Sent message to space %s', space.title); - cb(null); - }); - }); - }); + var spaceList = await spaces(tok); + // Find a space matching the given name + const space = spaceList.filter((s) => s.title === name)[0]; + if (!space) + throw new Error('Space not found'); + + log('Sending \'%s\' to space %s', text, space.title); + + // Send the message + await send(space.id, + text || 'Hello World! Welcome to **Watson Work**!', + tok); + log('Sent message to space %s', space.title); + + } catch(err) { + log('Error starting the application %o', error); + throw err; + } }; -if(require.main === module) - // Run the app - main(process.argv[2], process.argv[3], - // Expect the app id and secret to be configured in env variables - process.env.HELLOWORLD_APP_ID, process.env.HELLOWORLD_APP_SECRET, - (err) => { - if(err) - console.log('Error sending message:', err); - }); +// Run program as an IIFE (top level await not supported in node) +(async () => { + if(require.main === module) + // Run the app + main(process.argv[2], process.argv[3], + // Expect the app id and secret to be configured in env variables + process.env.HELLOWORLD_APP_ID, process.env.HELLOWORLD_APP_SECRET, + (err) => { + if(err) + console.log('Error sending message:', err); + }); +})(); + \ No newline at end of file diff --git a/src/test/test.js b/src/test/test.js index 66a4a15..447e7b3 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -16,14 +16,7 @@ require.cache[require.resolve('request')].exports = { const helloworld = require('../app'); describe('watsonwork-helloworld', () => { - it('sends a hello world message to a space', (done) => { - - // Check async callbacks - let checks = 0; - const check = () => { - if(++checks === 4) - done(); - }; + it('sends a hello world message to a space', async () => { postspy = (uri, opt, cb) => { // Expect a call to get an OAuth token for the app @@ -44,7 +37,7 @@ describe('watsonwork-helloworld', () => { access_token: 'testaccesstoken' } })); - check(); + // check(); return; } @@ -78,7 +71,6 @@ describe('watsonwork-helloworld', () => { } }) })); - check(); return; } @@ -111,17 +103,17 @@ describe('watsonwork-helloworld', () => { body: { } })); - check(); } }; - // Run the HelloWorld app - helloworld.main('Test Space', 'Hey', 'testappid', 'testsecret', - (err, res) => { - // Expect a successful run - expect(err).to.equal(null); - check(); - }); + try { + // Run the HelloWorld app + await helloworld.main('Test Space', 'Hey', 'testappid', 'testsecret'); + } + catch(err) { + throw err; + } + }); });