diff --git a/.gitignore b/.gitignore index 319e21b6ee..2b4a0a4df8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ test/encrypted/nodejs-docs-samples.json *.iml .idea/ key.json -*/yarn.lock \ No newline at end of file +*/yarn.lock +.nyc_output diff --git a/.travis.yml b/.travis.yml index cd707f50a3..2c163ad28e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,14 +21,6 @@ cache: directories: - $HOME/gcloud/ - node_modules/ - - 1-hello-world/node_modules/ - - 2-structured-data/node_modules/ - - 3-binary-data/node_modules/ - - 4-auth/node_modules/ - - 5-logging/node_modules/ - - 6-pubsub/node_modules/ - - 7-gce/node_modules/ - - optional-container-engine/node_modules/ env: global: @@ -39,9 +31,10 @@ env: before_install: - openssl aes-256-cbc -K $encrypted_06352980ac5c_key -iv $encrypted_06352980ac5c_iv -in key.json.enc -out key.json -d - npm set progress=false + - npm install -g nyc codecov -before_script: - - npm run unify +script: + - npm run cover after_success: -- npm run codecov + - nyc report --reporter=lcov > coverage.lcov && codecov diff --git a/1-hello-world/README.md b/1-hello-world/README.md index 5c1f533289..05ed049aa6 100644 --- a/1-hello-world/README.md +++ b/1-hello-world/README.md @@ -5,3 +5,55 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-1]: https://cloud.google.com/nodejs/getting-started/hello-world + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/1-hello-world + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + gcloud app deploy + +1. View the deployed app at [https://YOUR_PROJECT_ID.appspot.com](https://YOUR_PROJECT_ID.appspot.com). diff --git a/1-hello-world/app.js b/1-hello-world/app.js index be60b7e34a..0807407254 100644 --- a/1-hello-world/app.js +++ b/1-hello-world/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -27,7 +27,7 @@ app.get('/', (req, res) => { if (module === require.main) { // [START server] // Start the server - const server = app.listen(process.env.PORT || 8080, () => { + const server = app.listen(process.env.PORT || 8081, () => { const port = server.address().port; console.log(`App listening on port ${port}`); }); diff --git a/1-hello-world/app.yaml b/1-hello-world/app.yaml index 6cfa22422e..a755062044 100644 --- a/1-hello-world/app.yaml +++ b/1-hello-world/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -15,7 +15,3 @@ runtime: nodejs env: flex # [END runtime] - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ diff --git a/1-hello-world/package.json b/1-hello-world/package.json index 3a445cc9e2..8051730e47 100644 --- a/1-hello-world/package.json +++ b/1-hello-world/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node app.js", - "monitor": "nodemon app.js", - "deploy": "gcloud app deploy app.yaml", - "test": "mocha test/index.js -t 30000" + "test": "ava -t 30s test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html" }, "author": "Google Inc.", "contributors": [ @@ -23,9 +22,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -37,12 +40,13 @@ ] }, "dependencies": { - "express": "^4.14.0" + "express": "~4.15.2" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "supertest": "^2.0.0" + "supertest": "~3.0.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/1-hello-world/test/config.js b/1-hello-world/test/_test-config.js similarity index 85% rename from 1-hello-world/test/config.js rename to 1-hello-world/test/_test-config.js index b6d7ec2c90..1aa8bdbc7b 100644 --- a/1-hello-world/test/config.js +++ b/1-hello-world/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,11 +15,17 @@ const path = require(`path`); +const PORT = 8081; + module.exports = { test: `1-hello-world`, cwd: path.resolve(path.join(__dirname, '../')), cmd: `node`, args: [`app.js`], - port: 8081, + port: PORT, + env: { + PORT: PORT + }, + url: `http://localhost:${PORT}`, msg: `Hello, world!` }; diff --git a/1-hello-world/test/app.test.js b/1-hello-world/test/app.test.js index 0df0601a5b..63f5ad9fb1 100644 --- a/1-hello-world/test/app.test.js +++ b/1-hello-world/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,24 +13,22 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const utils = require(`nodejs-repo-tools`); +const test = require(`ava`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should create an express app`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(200) - .expect((response) => { - assert.equal(response.text, config.msg); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, t => { + utils.testLocalApp(testConfig, t.end); }); +} + +test.cb(`should create an express app`, t => { + utils.getRequest(testConfig) + .get(`/`) + .expect(200) + .expect((response) => { + t.is(response.text, testConfig.msg); + }) + .end(t.end); }); diff --git a/1-hello-world/test/index.js b/1-hello-world/test/index.js deleted file mode 100644 index 92884d9b78..0000000000 --- a/1-hello-world/test/index.js +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); -}); diff --git a/2-structured-data/README.md b/2-structured-data/README.md index 6288230ab0..1ca85d496a 100644 --- a/2-structured-data/README.md +++ b/2-structured-data/README.md @@ -5,3 +5,94 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-2]: https://cloud.google.com/nodejs/getting-started/using-structured-data + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Acquire local credentials for authenticating with Google Cloud Platform + services: + + gcloud beta auth application-default login + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/2-structured-data + +1. Create a `config.json` file (copied from the `config-default.json` file): + + cp config-default.json config.json + + * Set `GCLOUD_PROJECT` in `config.json` to your Google Cloud Platform + project ID. + * Set `DATA_BACKEND` in `config.json` to one of `"datastore"`, `"cloudsql"`, + or `"mongodb"`. + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Configure the backing store: + + * If `DATA_BACKEND` is set to `"cloudsql"`: + + 1. Create a Cloud SQL instance, and download and start the Cloud SQL + proxy: + + Instructions for doing so: https://cloud.google.com/nodejs/getting-started/using-cloud-sql#creating_a_cloud_sql_instance + 1. Set `MYSQL_USER` in `config.json`, e.g. `"my-cloudsql-username"`. + 1. Set `MYSQL_PASSWORD` in `config.json`, e.g. `"my-cloudsql-password"`. + 1. Set `INSTANCE_CONNECTION_NAME` in `config.json`, e.g. `"YOUR_PROJECT_ID:YOUR_REGION:YOUR_INSTANCE_ID"`. + 1. Run the script to setup the table: + + * Using NPM: + + npm run init-cloudsql + + * Using Yarn: + + yarn run init-cloudsql + + * If `DATA_BACKEND` is set to `"mongodb"`: + + 1. Set `MONGO_URL` in `config.json`, e.g. `"mongodb://username:password@123.45.67.890:27017"`. + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + gcloud app deploy + +1. View the deployed app at [https://YOUR_PROJECT_ID.appspot.com](https://YOUR_PROJECT_ID.appspot.com). diff --git a/2-structured-data/app.js b/2-structured-data/app.js index 0865e03fd1..b8fd1c86f8 100644 --- a/2-structured-data/app.js +++ b/2-structured-data/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/2-structured-data/app.yaml b/2-structured-data/app.yaml index 7b396d0773..054a32e739 100644 --- a/2-structured-data/app.yaml +++ b/2-structured-data/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -13,7 +13,3 @@ # runtime: nodejs env: flex - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ diff --git a/2-structured-data/books/api.js b/2-structured-data/books/api.js index a5afadb6e5..98b65fc587 100644 --- a/2-structured-data/books/api.js +++ b/2-structured-data/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/2-structured-data/books/crud.js b/2-structured-data/books/crud.js index a1ce65bc3a..ebfa9674ac 100644 --- a/2-structured-data/books/crud.js +++ b/2-structured-data/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/2-structured-data/books/model-cloudsql.js b/2-structured-data/books/model-cloudsql.js index 8855aa58f3..dd80e0f150 100644 --- a/2-structured-data/books/model-cloudsql.js +++ b/2-structured-data/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -17,24 +17,21 @@ const extend = require('lodash').assign; const mysql = require('mysql'); const config = require('../config'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + // [START list] function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -46,13 +43,11 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } // [END list] // [START create] function create (data, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -60,12 +55,10 @@ function create (data, cb) { } read(res.insertId, cb); }); - connection.end(); } // [END create] function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -81,12 +74,10 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } // [START update] function update (id, data, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -95,14 +86,11 @@ function update (id, data, cb) { } read(id, cb); }); - connection.end(); } // [END update] function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/2-structured-data/books/model-datastore.js b/2-structured-data/books/model-datastore.js index 89f0891305..9f8cec4cc3 100644 --- a/2-structured-data/books/model-datastore.js +++ b/2-structured-data/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -40,8 +40,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's diff --git a/2-structured-data/books/model-mongodb.js b/2-structured-data/books/model-mongodb.js index 622628c1fb..740b6388b0 100644 --- a/2-structured-data/books/model-mongodb.js +++ b/2-structured-data/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -141,7 +141,6 @@ function update (id, data, cb) { return; } read(id, cb); - return; } ); }); diff --git a/2-structured-data/config.js b/2-structured-data/config.js index 1d8ac32992..5f810dcbea 100644 --- a/2-structured-data/config.js +++ b/2-structured-data/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -25,10 +25,12 @@ nconf .env([ 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'INSTANCE_CONNECTION_NAME', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', 'MYSQL_PASSWORD', + 'NODE_ENV', 'PORT' ]) // 3. Config file @@ -52,7 +54,6 @@ nconf MYSQL_USER: '', MYSQL_PASSWORD: '', - // Port the HTTP server PORT: 8080 }); diff --git a/2-structured-data/package.json b/2-structured-data/package.json index 02060b6870..90333d634d 100644 --- a/2-structured-data/package.json +++ b/2-structured-data/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node app.js", - "monitor": "nodemon app.js", - "deploy": "gcloud app deploy app.yaml", - "test": "mocha test/index.js -t 30000", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -24,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -38,22 +41,22 @@ ] }, "dependencies": { - "body-parser": "^1.15.2", - "express": "^4.14.0", - "@google-cloud/datastore": "^0.1.1", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "prompt": "^1.0.0" + "@google-cloud/datastore": "~1.0.0", + "body-parser": "~1.17.1", + "express": "~4.15.2", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "prompt": "~1.0.0" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "sinon": "^1.17.5" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/2-structured-data/test/_api-tests.js b/2-structured-data/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/2-structured-data/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/2-structured-data/test/_crud-tests.js b/2-structured-data/test/_crud-tests.js new file mode 100644 index 0000000000..e721761e9a --- /dev/null +++ b/2-structured-data/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .send(`title=my%20book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb((t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .send(`title=my%20other%20book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/2-structured-data/test/config.js b/2-structured-data/test/_test-config.js similarity index 85% rename from 2-structured-data/test/config.js rename to 2-structured-data/test/_test-config.js index 81d7ff7eaf..058340a4de 100644 --- a/2-structured-data/test/config.js +++ b/2-structured-data/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,11 +15,17 @@ const path = require(`path`); +const PORT = 8082; + module.exports = { test: `2-structured-data`, cwd: path.resolve(path.join(__dirname, `../`)), cmd: `node`, args: [`app.js`], - port: 8082, + port: PORT, + env: { + PORT: PORT + }, + url: `http://localhost:${PORT}`, msg: `Bookshelf` }; diff --git a/2-structured-data/test/api.test.js b/2-structured-data/test/api.test.js deleted file mode 100644 index 763cccfaa2..0000000000 --- a/2-structured-data/test/api.test.js +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; - diff --git a/2-structured-data/test/app.test.js b/2-structured-data/test/app.test.js index e02f0249db..d989f019f1 100644 --- a/2-structured-data/test/app.test.js +++ b/2-structured-data/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -11,81 +11,73 @@ // See the License for the specific language governing permissions and // limitations under the License. -`use strict`; +'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); + +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = `project`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/2-structured-data/test/cloudsql.test.js b/2-structured-data/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/2-structured-data/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/2-structured-data/test/crud.test.js b/2-structured-data/test/crud.test.js deleted file mode 100644 index c64fd6652f..0000000000 --- a/2-structured-data/test/crud.test.js +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .send(`title=my%20book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .send(`title=my%20other%20book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - console.log('RT', response.text); - console.log('expected', expected); - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/2-structured-data/test/datastore.test.js b/2-structured-data/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/2-structured-data/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/2-structured-data/test/index.js b/2-structured-data/test/index.js deleted file mode 100644 index 7c479e29f6..0000000000 --- a/2-structured-data/test/index.js +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); - describe(`books/`, () => { - const appConfig = require(`../config`); - const DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - if (DATA_BACKEND === `datastore` || process.env.TEST_DATASTORE) { - require(`./api.test`)(`datastore`); - require(`./crud.test`)(`datastore`); - } - if (DATA_BACKEND === `cloudsql` || process.env.TEST_CLOUDSQL) { - require(`./api.test`)(`cloudsql`); - require(`./crud.test`)(`cloudsql`); - } - if (DATA_BACKEND === `mongodb` || process.env.TEST_MONGODB) { - require(`./api.test`)(`mongodb`); - require(`./crud.test`)(`mongodb`); - } - }); -}); diff --git a/2-structured-data/test/mongodb.test.js b/2-structured-data/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/2-structured-data/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/2-structured-data/views/base.jade b/2-structured-data/views/base.jade index 7a93bf7156..13a8d6ca29 100644 --- a/2-structured-data/views/base.jade +++ b/2-structured-data/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/2-structured-data/views/books/form.jade b/2-structured-data/views/books/form.jade index 24fcc7c0bc..5d0dd03bf2 100644 --- a/2-structured-data/views/books/form.jade +++ b/2-structured-data/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/2-structured-data/views/books/list.jade b/2-structured-data/views/books/list.jade index 3ecd5b5f05..7fe931779a 100644 --- a/2-structured-data/views/books/list.jade +++ b/2-structured-data/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/2-structured-data/views/books/view.jade b/2-structured-data/views/books/view.jade index 760730fed6..ad390ee04c 100644 --- a/2-structured-data/views/books/view.jade +++ b/2-structured-data/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/3-binary-data/README.md b/3-binary-data/README.md index 4c5d333077..d02baf4d22 100644 --- a/3-binary-data/README.md +++ b/3-binary-data/README.md @@ -5,3 +5,96 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-3]: https://cloud.google.com/nodejs/getting-started/using-cloud-storage + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Acquire local credentials for authenticating with Google Cloud Platform + services: + + gcloud beta auth application-default login + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/3-binary-data + +1. Create a `config.json` file (copied from the `config-default.json` file): + + cp config-default.json config.json + + * Set `GCLOUD_PROJECT` in `config.json` to your Google Cloud Platform + project ID. + * Set `DATA_BACKEND` in `config.json` to one of `"datastore"`, `"cloudsql"`, + or `"mongodb"`. + * Set `CLOUD_BUCKET` in `config.json` to the name of your Google Cloud + Storage bucket. + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Configure the backing store: + + * If `DATA_BACKEND` is set to `"cloudsql"`: + + 1. Create a Cloud SQL instance, and download and start the Cloud SQL + proxy: + + Instructions for doing so: https://cloud.google.com/nodejs/getting-started/using-cloud-sql#creating_a_cloud_sql_instance + 1. Set `MYSQL_USER` in `config.json`, e.g. `"my-cloudsql-username"`. + 1. Set `MYSQL_PASSWORD` in `config.json`, e.g. `"my-cloudsql-password"`. + 1. Set `INSTANCE_CONNECTION_NAME` in `config.json`, e.g. `"YOUR_PROJECT_ID:YOUR_REGION:YOUR_INSTANCE_ID"`. + 1. Run the script to setup the table: + + * Using NPM: + + npm run init-cloudsql + + * Using Yarn: + + yarn run init-cloudsql + + * If `DATA_BACKEND` is set to `"mongodb"`: + + 1. Set `MONGO_URL` in `config.json`, e.g. `"mongodb://username:password@123.45.67.890:27017"`. + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + gcloud app deploy + +1. View the deployed app at [https://YOUR_PROJECT_ID.appspot.com](https://YOUR_PROJECT_ID.appspot.com). diff --git a/3-binary-data/app.js b/3-binary-data/app.js index 0865e03fd1..b8fd1c86f8 100644 --- a/3-binary-data/app.js +++ b/3-binary-data/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/3-binary-data/app.yaml b/3-binary-data/app.yaml index 7b396d0773..15bb61da94 100644 --- a/3-binary-data/app.yaml +++ b/3-binary-data/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 diff --git a/3-binary-data/books/api.js b/3-binary-data/books/api.js index a5afadb6e5..98b65fc587 100644 --- a/3-binary-data/books/api.js +++ b/3-binary-data/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/3-binary-data/books/crud.js b/3-binary-data/books/crud.js index bb2019e298..debf0db525 100644 --- a/3-binary-data/books/crud.js +++ b/3-binary-data/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,15 +14,18 @@ 'use strict'; const express = require('express'); -const config = require('../config'); +const bodyParser = require('body-parser'); const images = require('../lib/images'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); +// Automatically parse request body as form data +router.use(bodyParser.urlencoded({ extended: false })); + // Set Content-Type for all responses for these routes router.use((req, res, next) => { res.set('Content-Type', 'text/html'); diff --git a/3-binary-data/books/model-cloudsql.js b/3-binary-data/books/model-cloudsql.js index 001c7f35d6..b78f8015e4 100644 --- a/3-binary-data/books/model-cloudsql.js +++ b/3-binary-data/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -17,23 +17,20 @@ const extend = require('lodash').assign; const mysql = require('mysql'); const config = require('../config'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -45,11 +42,9 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } function create (data, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -57,11 +52,9 @@ function create (data, cb) { } read(res.insertId, cb); }); - connection.end(); } function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -77,11 +70,9 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } function update (id, data, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -90,13 +81,10 @@ function update (id, data, cb) { } read(id, cb); }); - connection.end(); } function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/3-binary-data/books/model-datastore.js b/3-binary-data/books/model-datastore.js index a8bbacf854..5473fd9f20 100644 --- a/3-binary-data/books/model-datastore.js +++ b/3-binary-data/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -38,8 +38,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's diff --git a/3-binary-data/books/model-mongodb.js b/3-binary-data/books/model-mongodb.js index 0c22846b36..c5c9986bed 100644 --- a/3-binary-data/books/model-mongodb.js +++ b/3-binary-data/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/3-binary-data/config.js b/3-binary-data/config.js index 44c5a53f7a..fd8048186d 100644 --- a/3-binary-data/config.js +++ b/3-binary-data/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -26,10 +26,12 @@ nconf 'CLOUD_BUCKET', 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'INSTANCE_CONNECTION_NAME', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', 'MYSQL_PASSWORD', + 'NODE_ENV', 'PORT' ]) // 3. Config file @@ -56,7 +58,6 @@ nconf MYSQL_USER: '', MYSQL_PASSWORD: '', - // Port the HTTP server PORT: 8080 }); diff --git a/3-binary-data/lib/images.js b/3-binary-data/lib/images.js index d16c00e56f..879058c6fa 100644 --- a/3-binary-data/lib/images.js +++ b/3-binary-data/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/3-binary-data/package.json b/3-binary-data/package.json index 63e32dbaaf..8456c56e53 100644 --- a/3-binary-data/package.json +++ b/3-binary-data/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node app.js", - "monitor": "nodemon app.js", - "deploy": "gcloud app deploy app.yaml", - "test": "mocha test/index.js -t 30000", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -24,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -38,24 +41,24 @@ ] }, "dependencies": { - "@google-cloud/storage": "^0.1.1", - "body-parser": "^1.15.2", - "express": "^4.14.0", - "@google-cloud/datastore": "^0.1.1", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "prompt": "^1.0.0" + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "body-parser": "~1.17.1", + "express": "~4.15.2", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "prompt": "~1.0.0" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "sinon": "^1.17.5" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/3-binary-data/test/_api-tests.js b/3-binary-data/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/3-binary-data/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/3-binary-data/test/_crud-tests.js b/3-binary-data/test/_crud-tests.js new file mode 100644 index 0000000000..a3da03c520 --- /dev/null +++ b/3-binary-data/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb(`should delete a book`, (t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .send(`title=my%20book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb((t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .send(`title=my%20other%20book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/3-binary-data/test/config.js b/3-binary-data/test/_test-config.js similarity index 85% rename from 3-binary-data/test/config.js rename to 3-binary-data/test/_test-config.js index cd7efb811b..10da3146e6 100644 --- a/3-binary-data/test/config.js +++ b/3-binary-data/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,11 +15,17 @@ const path = require(`path`); +const PORT = 8083; + module.exports = { test: `3-binary-data`, cwd: path.resolve(path.join(__dirname, `../`)), cmd: `node`, args: [`app.js`], - port: 8083, + port: PORT, + env: { + PORT: PORT + }, + url: `http://localhost:${PORT}`, msg: `Bookshelf` }; diff --git a/3-binary-data/test/api.test.js b/3-binary-data/test/api.test.js deleted file mode 100644 index 151759d8a8..0000000000 --- a/3-binary-data/test/api.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/3-binary-data/test/app.test.js b/3-binary-data/test/app.test.js index 96956f663e..9084a0dbb4 100644 --- a/3-binary-data/test/app.test.js +++ b/3-binary-data/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,84 +13,74 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); + +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = 'project'; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`CLOUD_BUCKET`)); - - nconfMock.CLOUD_BUCKET = `bucket`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.throws(testFunc, Error, getMsg(`CLOUD_BUCKET`)); + nconfMock.CLOUD_BUCKET = `bucket`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/3-binary-data/test/cloudsql.test.js b/3-binary-data/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/3-binary-data/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/3-binary-data/test/crud.test.js b/3-binary-data/test/crud.test.js deleted file mode 100644 index 0f07993e9c..0000000000 --- a/3-binary-data/test/crud.test.js +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .field(`title`, `my book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .field(`title`, `my other book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/3-binary-data/test/datastore.test.js b/3-binary-data/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/3-binary-data/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/3-binary-data/test/index.js b/3-binary-data/test/index.js deleted file mode 100644 index 7c479e29f6..0000000000 --- a/3-binary-data/test/index.js +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); - describe(`books/`, () => { - const appConfig = require(`../config`); - const DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - if (DATA_BACKEND === `datastore` || process.env.TEST_DATASTORE) { - require(`./api.test`)(`datastore`); - require(`./crud.test`)(`datastore`); - } - if (DATA_BACKEND === `cloudsql` || process.env.TEST_CLOUDSQL) { - require(`./api.test`)(`cloudsql`); - require(`./crud.test`)(`cloudsql`); - } - if (DATA_BACKEND === `mongodb` || process.env.TEST_MONGODB) { - require(`./api.test`)(`mongodb`); - require(`./crud.test`)(`mongodb`); - } - }); -}); diff --git a/3-binary-data/test/mongodb.test.js b/3-binary-data/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/3-binary-data/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/3-binary-data/views/base.jade b/3-binary-data/views/base.jade index 7a93bf7156..13a8d6ca29 100644 --- a/3-binary-data/views/base.jade +++ b/3-binary-data/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/3-binary-data/views/books/form.jade b/3-binary-data/views/books/form.jade index 36a6308a9d..9b76d983bf 100644 --- a/3-binary-data/views/books/form.jade +++ b/3-binary-data/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/3-binary-data/views/books/list.jade b/3-binary-data/views/books/list.jade index dd453386ba..f7e901c3b3 100644 --- a/3-binary-data/views/books/list.jade +++ b/3-binary-data/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/3-binary-data/views/books/view.jade b/3-binary-data/views/books/view.jade index 55e7a4532c..ec14edb6c1 100644 --- a/3-binary-data/views/books/view.jade +++ b/3-binary-data/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/4-auth/README.md b/4-auth/README.md index 4346802b98..8d46f501de 100644 --- a/4-auth/README.md +++ b/4-auth/README.md @@ -5,3 +5,98 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-4]: https://cloud.google.com/nodejs/getting-started/authenticate-users + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Acquire local credentials for authenticating with Google Cloud Platform + services: + + gcloud beta auth application-default login + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/4-auth + +1. Create a `config.json` file (copied from the `config-default.json` file): + + cp config-default.json config.json + + * Set `GCLOUD_PROJECT` in `config.json` to your Google Cloud Platform + project ID. + * Set `DATA_BACKEND` in `config.json` to one of `"datastore"`, `"cloudsql"`, + or `"mongodb"`. + * Set `CLOUD_BUCKET` in `config.json` to the name of your Google Cloud + Storage bucket. + * Set `OAUTH2_CLIENT_ID` in `config.json`. + * Set `OAUTH2_CLIENT_SECRET` in `config.json`. + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Configure the backing store: + + * If `DATA_BACKEND` is set to `"cloudsql"`: + + 1. Create a Cloud SQL instance, and download and start the Cloud SQL + proxy: + + Instructions for doing so: https://cloud.google.com/nodejs/getting-started/using-cloud-sql#creating_a_cloud_sql_instance + 1. Set `MYSQL_USER` in `config.json`, e.g. `"my-cloudsql-username"`. + 1. Set `MYSQL_PASSWORD` in `config.json`, e.g. `"my-cloudsql-password"`. + 1. Set `INSTANCE_CONNECTION_NAME` in `config.json`, e.g. `"YOUR_PROJECT_ID:YOUR_REGION:YOUR_INSTANCE_ID"`. + 1. Run the script to setup the table: + + * Using NPM: + + npm run init-cloudsql + + * Using Yarn: + + yarn run init-cloudsql + + * If `DATA_BACKEND` is set to `"mongodb"`: + + 1. Set `MONGO_URL` in `config.json`, e.g. `"mongodb://username:password@123.45.67.890:27017"`. + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + gcloud app deploy + +1. View the deployed app at [https://YOUR_PROJECT_ID.appspot.com](https://YOUR_PROJECT_ID.appspot.com). diff --git a/4-auth/app.js b/4-auth/app.js index 8a5b4bed41..67ccc689d0 100644 --- a/4-auth/app.js +++ b/4-auth/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -38,7 +38,7 @@ const sessionConfig = { // In production use the App Engine Memcache instance to store session data, // otherwise fallback to the default MemoryStore in development. -if (config.get('NODE_ENV') === 'production') { +if (config.get('NODE_ENV') === 'production' && config.get('MEMCACHE_URL')) { sessionConfig.store = new MemcachedStore({ hosts: [config.get('MEMCACHE_URL')] }); diff --git a/4-auth/app.yaml b/4-auth/app.yaml index 7b396d0773..054a32e739 100644 --- a/4-auth/app.yaml +++ b/4-auth/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -13,7 +13,3 @@ # runtime: nodejs env: flex - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ diff --git a/4-auth/books/api.js b/4-auth/books/api.js index f23ad7ffe9..98b65fc587 100644 --- a/4-auth/books/api.js +++ b/4-auth/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); @@ -107,7 +106,7 @@ router.delete('/:book', (req, res, next) => { /** * Errors on "/api/books/*" routes. */ -router.use(() => (err, req, res, next) => { +router.use((err, req, res, next) => { // Format error and forward to generic error handler for logging and // responding to the request err.response = { diff --git a/4-auth/books/crud.js b/4-auth/books/crud.js index 22f6439938..447358e344 100644 --- a/4-auth/books/crud.js +++ b/4-auth/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,12 +14,11 @@ 'use strict'; const express = require('express'); -const config = require('../config'); const images = require('../lib/images'); const oauth2 = require('../lib/oauth2'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/4-auth/books/model-cloudsql.js b/4-auth/books/model-cloudsql.js index 228d6ad8f8..d3a0d36a52 100644 --- a/4-auth/books/model-cloudsql.js +++ b/4-auth/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -17,23 +17,20 @@ const extend = require('lodash').assign; const mysql = require('mysql'); const config = require('../config'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -45,13 +42,11 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } // [START listby] function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `createdById` = ? LIMIT ? OFFSET ?', [userId, limit, token], @@ -63,12 +58,10 @@ function listBy (userId, limit, token, cb) { const hasMore = results.length === limit ? token + results.length : false; cb(null, results, hasMore); }); - connection.end(); } // [END listby] function create (data, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -76,11 +69,9 @@ function create (data, cb) { } read(res.insertId, cb); }); - connection.end(); } function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -96,11 +87,9 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } function update (id, data, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -109,13 +98,10 @@ function update (id, data, cb) { } read(id, cb); }); - connection.end(); } function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/4-auth/books/model-datastore.js b/4-auth/books/model-datastore.js index f49cb5c5fa..5f42d49e77 100644 --- a/4-auth/books/model-datastore.js +++ b/4-auth/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -38,8 +38,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's diff --git a/4-auth/books/model-mongodb.js b/4-auth/books/model-mongodb.js index c1a83c800a..cdbc9548ff 100644 --- a/4-auth/books/model-mongodb.js +++ b/4-auth/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/4-auth/config.js b/4-auth/config.js index 558e3892e0..eae5e6fe23 100644 --- a/4-auth/config.js +++ b/4-auth/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -26,6 +26,7 @@ nconf 'CLOUD_BUCKET', 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'MEMCACHE_URL', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', @@ -68,9 +69,9 @@ nconf OAUTH2_CLIENT_SECRET: '', OAUTH2_CALLBACK: 'http://localhost:8080/auth/google/callback', - // Port the HTTP server PORT: 8080, + // Set this a secret string of your choosing SECRET: 'keyboardcat' }); diff --git a/4-auth/lib/images.js b/4-auth/lib/images.js index 8207b41b0f..a8391e09bf 100644 --- a/4-auth/lib/images.js +++ b/4-auth/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/4-auth/lib/oauth2.js b/4-auth/lib/oauth2.js index a4620ab228..fa63e72703 100644 --- a/4-auth/lib/oauth2.js +++ b/4-auth/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/4-auth/package.json b/4-auth/package.json index 9edb4229c0..8816525336 100644 --- a/4-auth/package.json +++ b/4-auth/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node app.js", - "monitor": "nodemon app.js", - "deploy": "gcloud app deploy app.yaml", - "test": "mocha test/index.js -t 30000", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -24,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -38,29 +41,28 @@ ] }, "dependencies": { - "@google-cloud/storage": "^0.1.1", - "body-parser": "^1.15.2", - "connect-memcached": "^0.2.0", - "express": "^4.14.0", - "express-session": "^1.14.0", - "@google-cloud/datastore": "^0.1.1", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "passport": "^0.3.2", - "passport-google-oauth20": "^1.0.0", - "prompt": "^1.0.0" + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "body-parser": "~1.17.1", + "connect-memcached": "~0.2.0", + "express": "~4.15.2", + "express-session": "~1.15.2", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "passport": "~0.3.2", + "passport-google-oauth20": "~1.0.0", + "prompt": "~1.0.0" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "sinon": "^1.17.5", - "supertest": "^2.0.0" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/4-auth/test/_api-tests.js b/4-auth/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/4-auth/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/4-auth/test/_crud-tests.js b/4-auth/test/_crud-tests.js new file mode 100644 index 0000000000..403f3c6e2d --- /dev/null +++ b/4-auth/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb(`should delete a book`, (t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .field(`title`, `my book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb((t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .field(`title`, `my other book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/4-auth/test/config.js b/4-auth/test/_test-config.js similarity index 85% rename from 4-auth/test/config.js rename to 4-auth/test/_test-config.js index bbb4eb11a3..17e478561d 100644 --- a/4-auth/test/config.js +++ b/4-auth/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,11 +15,17 @@ const path = require(`path`); +const PORT = 8084; + module.exports = { test: `4-auth`, cwd: path.resolve(path.join(__dirname, `../`)), cmd: `node`, args: [`app.js`], - port: 8084, + port: PORT, + env: { + PORT: PORT + }, + url: `http://localhost:${PORT}`, msg: `Bookshelf` }; diff --git a/4-auth/test/api.test.js b/4-auth/test/api.test.js deleted file mode 100644 index 151759d8a8..0000000000 --- a/4-auth/test/api.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/4-auth/test/app.test.js b/4-auth/test/app.test.js index 3178f25eec..d1948edc8c 100644 --- a/4-auth/test/app.test.js +++ b/4-auth/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,94 +13,80 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} + +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = `project`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`CLOUD_BUCKET`)); - - nconfMock.CLOUD_BUCKET = `bucket`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_ID`)); - - nconfMock.OAUTH2_CLIENT_ID = `foo`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); - - nconfMock.OAUTH2_CLIENT_SECRET = `bar`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.throws(testFunc, Error, getMsg(`CLOUD_BUCKET`)); + nconfMock.CLOUD_BUCKET = `bucket`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_ID`)); + nconfMock.OAUTH2_CLIENT_ID = `foo`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); + nconfMock.OAUTH2_CLIENT_SECRET = `bar`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/4-auth/test/cloudsql.test.js b/4-auth/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/4-auth/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/4-auth/test/crud.test.js b/4-auth/test/crud.test.js deleted file mode 100644 index 0f07993e9c..0000000000 --- a/4-auth/test/crud.test.js +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .field(`title`, `my book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .field(`title`, `my other book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/4-auth/test/datastore.test.js b/4-auth/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/4-auth/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/4-auth/test/index.js b/4-auth/test/index.js deleted file mode 100644 index ff0ceb4b49..0000000000 --- a/4-auth/test/index.js +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); - describe(`books/`, () => { - const appConfig = require(`../config`); - const DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - if (DATA_BACKEND === `datastore` || process.env.TEST_DATASTORE) { - require(`./api.test`)(`datastore`); - require(`./crud.test`)(`datastore`); - } - if (DATA_BACKEND === `cloudsql` || process.env.TEST_CLOUDSQL) { - require(`./api.test`)(`cloudsql`); - require(`./crud.test`)(`cloudsql`); - } - if (DATA_BACKEND === `mongodb` || process.env.TEST_MONGODB) { - require(`./api.test`)(`mongodb`); - require(`./crud.test`)(`mongodb`); - } - }); - if (!process.env.E2E_TESTS) { - describe(`lib/`, () => { - require(`./oauth2.test`); - }); - } -}); diff --git a/4-auth/test/mongodb.test.js b/4-auth/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/4-auth/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/4-auth/test/oauth2.test.js b/4-auth/test/oauth2.test.js index 5d710c6e95..a384a0f3ef 100644 --- a/4-auth/test/oauth2.test.js +++ b/4-auth/test/oauth2.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,199 +13,200 @@ 'use strict'; -const assert = require(`assert`); +const test = require(`ava`); const sinon = require(`sinon`); const request = require(`supertest`); const proxyquire = require(`proxyquire`).noPreserveCache(); -describe(`oauth2.js`, () => { - let passportMock; +const getPassportMock = () => { + return { + initialize: sinon.stub().returns((req, res, next) => { + next(); + }), + session: sinon.stub().returns((req, res, next) => { + next(); + }), + use: sinon.stub(), + serializeUser: sinon.stub(), + deserializeUser: sinon.stub(), + authenticate: sinon.stub().returns((req, res, next) => { + req.session.oauth2return = `/another/path`; + next(); + }) + }; +}; - beforeEach(() => { - passportMock = { - initialize: sinon.stub().returns((req, res, next) => { - next(); - }), - session: sinon.stub().returns((req, res, next) => { - next(); - }), - use: sinon.stub(), - serializeUser: sinon.stub(), - deserializeUser: sinon.stub(), - authenticate: sinon.stub().returns((req, res, next) => { - req.session.oauth2return = `/another/path`; - next(); - }) - }; +test.cb(`should start authorization`, (t) => { + const passportMock = getPassportMock(); + passportMock.authenticate = sinon.stub().returns((req, res, next) => { + t.is(req.session.oauth2return, `/some/path`); + res.redirect(`/auth/google/callback?code=foo`); }); - - it(`should start authorization`, (done) => { - passportMock.authenticate = sinon.stub().returns((req, res, next) => { - assert.equal(req.session.oauth2return, `/some/path`); - res.redirect(`/auth/google/callback?code=foo`); - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/login?return=%2Fsome%2Fpath`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /auth/google/callback?code=foo`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); - }); - - it(`should finish authorization`, (done) => { - const oauth2 = proxyquire(`../lib/oauth2`, { + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': oauth2 - }); - request(app) - .get(`/auth/google/callback?code=foo`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /another/path`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - assert.deepEqual( - oauth2.extractProfile({ - photos: [{ value: `image.jpg` }], - id: 1, - displayName: `Joe Developer` - }), - { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - ); - const serializeUser = passportMock.serializeUser.firstCall.args[0]; - const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; - const user = {}; - const obj = {}; - serializeUser(user, (err, _user) => { - assert.equal(err, null); - assert.strictEqual(_user, user); - }); - deserializeUser(obj, (err, _obj) => { - assert.equal(err, null); - assert.strictEqual(_obj, obj); - }); - }) - .end(done); + }) }); + request(app) + .get(`/auth/login?return=%2Fsome%2Fpath`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/auth\/google\/callback\?code=foo/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - it(`should logout`, (done) => { - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/logout`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); +test.cb(`should finish authorization`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock + }); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': oauth2 }); + request(app) + .get(`/auth/google/callback?code=foo`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/another\/path/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + t.deepEqual( + oauth2.extractProfile({ + photos: [{ value: `image.jpg` }], + id: 1, + displayName: `Joe Developer` + }), + { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + ); + const serializeUser = passportMock.serializeUser.firstCall.args[0]; + const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; + const user = {}; + const obj = {}; + serializeUser(user, (err, _user) => { + t.is(err, null); + t.is(_user, user); + }); + deserializeUser(obj, (err, _obj) => { + t.is(err, null); + t.is(_obj, obj); + }); + }) + .end(t.end); +}); - it(`should require authentication`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { +test.cb(`should logout`, (t) => { + const passportMock = getPassportMock(); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: {}, - session: {} - }; - const res = { - redirect: sinon.stub() - }; - const next = sinon.stub(); - oauth2.required(req, res, next); - assert(next.calledOnce); + }) + }); + request(app) + .get(`/auth/logout`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \//); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - req.user = undefined; - oauth2.required(req, res, next); - assert(next.calledOnce); - assert.equal(req.session.oauth2return, req.originalUrl); - assert(res.redirect.calledOnce); - assert.equal(res.redirect.firstCall.args[0], `/auth/login`); +test(`should require authentication`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: {}, + session: {} + }; + const res = { + redirect: sinon.stub() + }; + const next = sinon.stub(); + oauth2.required(req, res, next); + t.true(next.calledOnce); - it(`should add template variables`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { - passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - }; - const res = { - locals: {} - }; - const next = sinon.stub(); - oauth2.template(req, res, next); - assert(next.calledOnce); - assert.strictEqual(res.locals.profile, req.user); - assert.equal( - res.locals.login, `/auth/login?return=${encodeURIComponent(req.originalUrl)}` - ); - assert.equal( - res.locals.logout, `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` - ); + req.user = undefined; + oauth2.required(req, res, next); + t.true(next.calledOnce); + t.is(req.session.oauth2return, req.originalUrl); + t.true(res.redirect.calledOnce); + t.is(res.redirect.firstCall.args[0], `/auth/login`); +}); + +test(`should add template variables`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + }; + const res = { + locals: {} + }; + const next = sinon.stub(); + oauth2.template(req, res, next); + t.true(next.calledOnce); + t.is(res.locals.profile, req.user); + t.is( + res.locals.login, `/auth/login?return=${encodeURIComponent(req.originalUrl)}` + ); + t.is( + res.locals.logout, `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` + ); }); diff --git a/4-auth/views/base.jade b/4-auth/views/base.jade index e080fb4450..7d62a01f22 100644 --- a/4-auth/views/base.jade +++ b/4-auth/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/4-auth/views/books/form.jade b/4-auth/views/books/form.jade index 8368619eeb..2a96f6d50e 100644 --- a/4-auth/views/books/form.jade +++ b/4-auth/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/4-auth/views/books/list.jade b/4-auth/views/books/list.jade index dd453386ba..f7e901c3b3 100644 --- a/4-auth/views/books/list.jade +++ b/4-auth/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/4-auth/views/books/view.jade b/4-auth/views/books/view.jade index fb0123b05c..92776d30d1 100644 --- a/4-auth/views/books/view.jade +++ b/4-auth/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/5-logging/README.md b/5-logging/README.md index 9c7d9c5061..2acc5acfc2 100644 --- a/5-logging/README.md +++ b/5-logging/README.md @@ -5,3 +5,98 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-5]: https://cloud.google.com/nodejs/getting-started/logging-application-events + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Acquire local credentials for authenticating with Google Cloud Platform + services: + + gcloud beta auth application-default login + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/5-logging + +1. Create a `config.json` file (copied from the `config-default.json` file): + + cp config-default.json config.json + + * Set `GCLOUD_PROJECT` in `config.json` to your Google Cloud Platform + project ID. + * Set `DATA_BACKEND` in `config.json` to one of `"datastore"`, `"cloudsql"`, + or `"mongodb"`. + * Set `CLOUD_BUCKET` in `config.json` to the name of your Google Cloud + Storage bucket. + * Set `OAUTH2_CLIENT_ID` in `config.json`. + * Set `OAUTH2_CLIENT_SECRET` in `config.json`. + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Configure the backing store: + + * If `DATA_BACKEND` is set to `"cloudsql"`: + + 1. Create a Cloud SQL instance, and download and start the Cloud SQL + proxy: + + Instructions for doing so: https://cloud.google.com/nodejs/getting-started/using-cloud-sql#creating_a_cloud_sql_instance + 1. Set `MYSQL_USER` in `config.json`, e.g. `"my-cloudsql-username"`. + 1. Set `MYSQL_PASSWORD` in `config.json`, e.g. `"my-cloudsql-password"`. + 1. Set `INSTANCE_CONNECTION_NAME` in `config.json`, e.g. `"YOUR_PROJECT_ID:YOUR_REGION:YOUR_INSTANCE_ID"`. + 1. Run the script to setup the table: + + * Using NPM: + + npm run init-cloudsql + + * Using Yarn: + + yarn run init-cloudsql + + * If `DATA_BACKEND` is set to `"mongodb"`: + + 1. Set `MONGO_URL` in `config.json`, e.g. `"mongodb://username:password@123.45.67.890:27017"`. + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + gcloud app deploy + +1. View the deployed app at [https://YOUR_PROJECT_ID.appspot.com](https://YOUR_PROJECT_ID.appspot.com). diff --git a/5-logging/app.js b/5-logging/app.js index 6feb4b3b2c..12ca089510 100644 --- a/5-logging/app.js +++ b/5-logging/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -17,7 +17,7 @@ // Activate Google Cloud Trace and Debug when in production if (process.env.NODE_ENV === 'production') { require('@google/cloud-trace').start(); - require('@google/cloud-debug'); + require('@google/cloud-debug').start(); } // [END debug] @@ -52,7 +52,7 @@ const sessionConfig = { // In production use the App Engine Memcache instance to store session data, // otherwise fallback to the default MemoryStore in development. -if (config.get('NODE_ENV') === 'production') { +if (config.get('NODE_ENV') === 'production' && config.get('MEMCACHE_URL')) { sessionConfig.store = new MemcachedStore({ hosts: [config.get('MEMCACHE_URL')] }); diff --git a/5-logging/app.yaml b/5-logging/app.yaml index 0e6e6db7ee..1ad7911659 100644 --- a/5-logging/app.yaml +++ b/5-logging/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -13,7 +13,3 @@ # runtime: nodejs env: flex - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ diff --git a/5-logging/books/api.js b/5-logging/books/api.js index a5afadb6e5..98b65fc587 100644 --- a/5-logging/books/api.js +++ b/5-logging/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/5-logging/books/crud.js b/5-logging/books/crud.js index 0e03027a1e..2f2c4e60d2 100644 --- a/5-logging/books/crud.js +++ b/5-logging/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,12 +14,11 @@ 'use strict'; const express = require('express'); -const config = require('../config'); const images = require('../lib/images'); const oauth2 = require('../lib/oauth2'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/5-logging/books/model-cloudsql.js b/5-logging/books/model-cloudsql.js index 75bbc53e5f..5076f00b98 100644 --- a/5-logging/books/model-cloudsql.js +++ b/5-logging/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -17,23 +17,20 @@ const extend = require('lodash').assign; const mysql = require('mysql'); const config = require('../config'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -45,12 +42,10 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `createdById` = ? LIMIT ? OFFSET ?', [userId, limit, token], @@ -62,11 +57,9 @@ function listBy (userId, limit, token, cb) { const hasMore = results.length === limit ? token + results.length : false; cb(null, results, hasMore); }); - connection.end(); } function create (data, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -74,11 +67,9 @@ function create (data, cb) { } read(res.insertId, cb); }); - connection.end(); } function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -94,11 +85,9 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } function update (id, data, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -107,13 +96,10 @@ function update (id, data, cb) { } read(id, cb); }); - connection.end(); } function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/5-logging/books/model-datastore.js b/5-logging/books/model-datastore.js index bd5f920196..c96972208d 100644 --- a/5-logging/books/model-datastore.js +++ b/5-logging/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -38,8 +38,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's diff --git a/5-logging/books/model-mongodb.js b/5-logging/books/model-mongodb.js index 5c1e06fea0..919395c657 100644 --- a/5-logging/books/model-mongodb.js +++ b/5-logging/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/5-logging/config.js b/5-logging/config.js index 558e3892e0..eae5e6fe23 100644 --- a/5-logging/config.js +++ b/5-logging/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -26,6 +26,7 @@ nconf 'CLOUD_BUCKET', 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'MEMCACHE_URL', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', @@ -68,9 +69,9 @@ nconf OAUTH2_CLIENT_SECRET: '', OAUTH2_CALLBACK: 'http://localhost:8080/auth/google/callback', - // Port the HTTP server PORT: 8080, + // Set this a secret string of your choosing SECRET: 'keyboardcat' }); diff --git a/5-logging/lib/images.js b/5-logging/lib/images.js index 8207b41b0f..a8391e09bf 100644 --- a/5-logging/lib/images.js +++ b/5-logging/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/5-logging/lib/logging.js b/5-logging/lib/logging.js index b97b847953..24c5506176 100644 --- a/5-logging/lib/logging.js +++ b/5-logging/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/5-logging/lib/oauth2.js b/5-logging/lib/oauth2.js index a4620ab228..fa63e72703 100644 --- a/5-logging/lib/oauth2.js +++ b/5-logging/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/5-logging/package.json b/5-logging/package.json index 97d0ec4032..34acd1a82e 100644 --- a/5-logging/package.json +++ b/5-logging/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node app.js", - "monitor": "nodemon app.js", - "deploy": "gcloud app deploy app.yaml", - "test": "mocha test/index.js -t 30000", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -24,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -38,33 +41,33 @@ ] }, "dependencies": { - "@google/cloud-debug": "^0.8.3", - "@google/cloud-trace": "^0.5.5", - "@google-cloud/datastore": "^0.1.1", - "@google-cloud/storage": "^0.1.1", - "body-parser": "^1.15.2", - "connect-memcached": "^0.2.0", - "express": "^4.14.0", - "express-session": "^1.14.0", - "express-winston": "^2.0.0", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "passport": "^0.3.2", - "passport-google-oauth20": "^1.0.0", - "prompt": "^1.0.0", - "winston": "^2.2.0" + "@google-cloud/debug-agent": "~1.0.0", + "@google-cloud/trace-agent": "~1.0.4", + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "body-parser": "~1.17.1", + "connect-memcached": "0.2.0", + "express": "~4.15.2", + "express-session": "~1.15.2", + "express-winston": "~2.3.0", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "passport": "~0.3.2", + "passport-google-oauth20": "~1.0.0", + "prompt": "~1.0.0", + "winston": "~2.3.1" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "sinon": "^1.17.5", - "supertest": "^2.0.0" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "supertest": "~3.0.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/5-logging/test/_api-tests.js b/5-logging/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/5-logging/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/5-logging/test/_crud-tests.js b/5-logging/test/_crud-tests.js new file mode 100644 index 0000000000..403f3c6e2d --- /dev/null +++ b/5-logging/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb(`should delete a book`, (t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .field(`title`, `my book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb((t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .field(`title`, `my other book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/5-logging/test/config.js b/5-logging/test/_test-config.js similarity index 85% rename from 5-logging/test/config.js rename to 5-logging/test/_test-config.js index 6427303f9f..4ca631654f 100644 --- a/5-logging/test/config.js +++ b/5-logging/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,11 +15,17 @@ const path = require(`path`); +const PORT = 8085; + module.exports = { test: `5-logging`, cwd: path.resolve(path.join(__dirname, `../`)), cmd: `node`, args: [`app.js`], - port: 8085, + port: PORT, + env: { + PORT: PORT + }, + url: `http://localhost:${PORT}`, msg: `Bookshelf` }; diff --git a/5-logging/test/api.test.js b/5-logging/test/api.test.js deleted file mode 100644 index 151759d8a8..0000000000 --- a/5-logging/test/api.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/5-logging/test/app.test.js b/5-logging/test/app.test.js index 3178f25eec..d1948edc8c 100644 --- a/5-logging/test/app.test.js +++ b/5-logging/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,94 +13,80 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} + +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = `project`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`CLOUD_BUCKET`)); - - nconfMock.CLOUD_BUCKET = `bucket`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_ID`)); - - nconfMock.OAUTH2_CLIENT_ID = `foo`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); - - nconfMock.OAUTH2_CLIENT_SECRET = `bar`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.throws(testFunc, Error, getMsg(`CLOUD_BUCKET`)); + nconfMock.CLOUD_BUCKET = `bucket`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_ID`)); + nconfMock.OAUTH2_CLIENT_ID = `foo`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); + nconfMock.OAUTH2_CLIENT_SECRET = `bar`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/5-logging/test/cloudsql.test.js b/5-logging/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/5-logging/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/5-logging/test/crud.test.js b/5-logging/test/crud.test.js deleted file mode 100644 index 0f07993e9c..0000000000 --- a/5-logging/test/crud.test.js +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .field(`title`, `my book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .field(`title`, `my other book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/5-logging/test/datastore.test.js b/5-logging/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/5-logging/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/5-logging/test/index.js b/5-logging/test/index.js deleted file mode 100644 index 743e84d0c3..0000000000 --- a/5-logging/test/index.js +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require('./config'); -const utils = require('nodejs-repo-tools'); - -describe(`${config.test}/`, () => { - if (!process.env.E2E_TESTS) { - it('should install dependencies', (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require('./app.test'); - describe('books/', () => { - const appConfig = require('../config'); - const DATA_BACKEND = appConfig.get('DATA_BACKEND'); - if (DATA_BACKEND === 'datastore' || process.env.TEST_DATASTORE) { - require('./api.test')('datastore'); - require('./crud.test')('datastore'); - } - if (DATA_BACKEND === 'cloudsql' || process.env.TEST_CLOUDSQL) { - require('./api.test')('cloudsql'); - require('./crud.test')('cloudsql'); - } - if (DATA_BACKEND === 'mongodb' || process.env.TEST_MONGODB) { - require('./api.test')('mongodb'); - require('./crud.test')('mongodb'); - } - }); - if (!process.env.E2E_TESTS) { - describe('lib/', () => { - require('./oauth2.test'); - }); - } -}); diff --git a/5-logging/test/mongodb.test.js b/5-logging/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/5-logging/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/5-logging/test/oauth2.test.js b/5-logging/test/oauth2.test.js index 4f960e9ea5..a384a0f3ef 100644 --- a/5-logging/test/oauth2.test.js +++ b/5-logging/test/oauth2.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,201 +13,200 @@ 'use strict'; -const assert = require(`assert`); +const test = require(`ava`); const sinon = require(`sinon`); const request = require(`supertest`); const proxyquire = require(`proxyquire`).noPreserveCache(); -describe(`oauth2.js`, () => { - let passportMock; +const getPassportMock = () => { + return { + initialize: sinon.stub().returns((req, res, next) => { + next(); + }), + session: sinon.stub().returns((req, res, next) => { + next(); + }), + use: sinon.stub(), + serializeUser: sinon.stub(), + deserializeUser: sinon.stub(), + authenticate: sinon.stub().returns((req, res, next) => { + req.session.oauth2return = `/another/path`; + next(); + }) + }; +}; - beforeEach(() => { - passportMock = { - initialize: sinon.stub().returns((req, res, next) => { - next(); - }), - session: sinon.stub().returns((req, res, next) => { - next(); - }), - use: sinon.stub(), - serializeUser: sinon.stub(), - deserializeUser: sinon.stub(), - authenticate: sinon.stub().returns((req, res, next) => { - req.session.oauth2return = `/another/path`; - next(); - }) - }; +test.cb(`should start authorization`, (t) => { + const passportMock = getPassportMock(); + passportMock.authenticate = sinon.stub().returns((req, res, next) => { + t.is(req.session.oauth2return, `/some/path`); + res.redirect(`/auth/google/callback?code=foo`); }); - - it(`should start authorization`, (done) => { - passportMock.authenticate = sinon.stub().returns((req, res, next) => { - assert.equal(req.session.oauth2return, `/some/path`); - res.redirect(`/auth/google/callback?code=foo`); - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/login?return=%2Fsome%2Fpath`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /auth/google/callback?code=foo`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); - }); - - it(`should finish authorization`, (done) => { - const oauth2 = proxyquire(`../lib/oauth2`, { + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': oauth2 - }); - request(app) - .get(`/auth/google/callback?code=foo`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /another/path`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - assert.deepEqual( - oauth2.extractProfile({ - photos: [{ value: `image.jpg` }], - id: 1, - displayName: `Joe Developer` - }), - { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - ); - const serializeUser = passportMock.serializeUser.firstCall.args[0]; - const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; - const user = {}; - const obj = {}; - serializeUser(user, (err, _user) => { - assert.equal(err, null); - assert.strictEqual(_user, user); - }); - deserializeUser(obj, (err, _obj) => { - assert.equal(err, null); - assert.strictEqual(_obj, obj); - }); - }) - .end(done); + }) }); + request(app) + .get(`/auth/login?return=%2Fsome%2Fpath`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/auth\/google\/callback\?code=foo/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - it(`should logout`, (done) => { - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/logout`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); +test.cb(`should finish authorization`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock + }); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': oauth2 }); + request(app) + .get(`/auth/google/callback?code=foo`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/another\/path/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + t.deepEqual( + oauth2.extractProfile({ + photos: [{ value: `image.jpg` }], + id: 1, + displayName: `Joe Developer` + }), + { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + ); + const serializeUser = passportMock.serializeUser.firstCall.args[0]; + const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; + const user = {}; + const obj = {}; + serializeUser(user, (err, _user) => { + t.is(err, null); + t.is(_user, user); + }); + deserializeUser(obj, (err, _obj) => { + t.is(err, null); + t.is(_obj, obj); + }); + }) + .end(t.end); +}); - it(`should require authentication`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { +test.cb(`should logout`, (t) => { + const passportMock = getPassportMock(); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: {}, - session: {} - }; - const res = { - redirect: sinon.stub() - }; - const next = sinon.stub(); - oauth2.required(req, res, next); - assert(next.calledOnce); + }) + }); + request(app) + .get(`/auth/logout`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \//); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - req.user = undefined; - oauth2.required(req, res, next); - assert(next.calledOnce); - assert.equal(req.session.oauth2return, req.originalUrl); - assert(res.redirect.calledOnce); - assert.equal(res.redirect.firstCall.args[0], `/auth/login`); +test(`should require authentication`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: {}, + session: {} + }; + const res = { + redirect: sinon.stub() + }; + const next = sinon.stub(); + oauth2.required(req, res, next); + t.true(next.calledOnce); - it(`should add template variables`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { - passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - }; - const res = { - locals: {} - }; - const next = sinon.stub(); - oauth2.template(req, res, next); - assert(next.calledOnce); - assert.strictEqual(res.locals.profile, req.user); - assert.equal( - res.locals.login, - `/auth/login?return=${encodeURIComponent(req.originalUrl)}` - ); - assert.equal( - res.locals.logout, - `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` - ); + req.user = undefined; + oauth2.required(req, res, next); + t.true(next.calledOnce); + t.is(req.session.oauth2return, req.originalUrl); + t.true(res.redirect.calledOnce); + t.is(res.redirect.firstCall.args[0], `/auth/login`); +}); + +test(`should add template variables`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + }; + const res = { + locals: {} + }; + const next = sinon.stub(); + oauth2.template(req, res, next); + t.true(next.calledOnce); + t.is(res.locals.profile, req.user); + t.is( + res.locals.login, `/auth/login?return=${encodeURIComponent(req.originalUrl)}` + ); + t.is( + res.locals.logout, `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` + ); }); diff --git a/5-logging/views/base.jade b/5-logging/views/base.jade index 4d1c80d45d..72a8e8a7c1 100644 --- a/5-logging/views/base.jade +++ b/5-logging/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/5-logging/views/books/form.jade b/5-logging/views/books/form.jade index 8368619eeb..2a96f6d50e 100644 --- a/5-logging/views/books/form.jade +++ b/5-logging/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/5-logging/views/books/list.jade b/5-logging/views/books/list.jade index dd453386ba..f7e901c3b3 100644 --- a/5-logging/views/books/list.jade +++ b/5-logging/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/5-logging/views/books/view.jade b/5-logging/views/books/view.jade index fb0123b05c..92776d30d1 100644 --- a/5-logging/views/books/view.jade +++ b/5-logging/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/6-pubsub/README.md b/6-pubsub/README.md index 5abdc3386d..473c2733bc 100644 --- a/6-pubsub/README.md +++ b/6-pubsub/README.md @@ -5,3 +5,101 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-6]: https://cloud.google.com/nodejs/getting-started/using-pub-sub + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Acquire local credentials for authenticating with Google Cloud Platform + services: + + gcloud beta auth application-default login + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/5-logging + +1. Create a `config.json` file (copied from the `config-default.json` file): + + cp config-default.json config.json + + * Set `GCLOUD_PROJECT` in `config.json` to your Google Cloud Platform + project ID. + * Set `DATA_BACKEND` in `config.json` to one of `"datastore"`, `"cloudsql"`, + or `"mongodb"`. + * Set `CLOUD_BUCKET` in `config.json` to the name of your Google Cloud + Storage bucket. + * Set `OAUTH2_CLIENT_ID` in `config.json`. + * Set `OAUTH2_CLIENT_SECRET` in `config.json`. + * Set `TOPIC_NAME` in `config.json`. + * Set `SUBSCRIPTION_NAME` in `config.json`. + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Configure the backing store: + + * If `DATA_BACKEND` is set to `"cloudsql"`: + + 1. Create a Cloud SQL instance, and download and start the Cloud SQL + proxy: + + Instructions for doing so: https://cloud.google.com/nodejs/getting-started/using-cloud-sql#creating_a_cloud_sql_instance + 1. Set `MYSQL_USER` in `config.json`, e.g. `"my-cloudsql-username"`. + 1. Set `MYSQL_PASSWORD` in `config.json`, e.g. `"my-cloudsql-password"`. + 1. Set `INSTANCE_CONNECTION_NAME` in `config.json`, e.g. `"YOUR_PROJECT_ID:YOUR_REGION:YOUR_INSTANCE_ID"`. + 1. Run the script to setup the table: + + * Using NPM: + + npm run init-cloudsql + + * Using Yarn: + + yarn run init-cloudsql + + * If `DATA_BACKEND` is set to `"mongodb"`: + + 1. Set `MONGO_URL` in `config.json`, e.g. `"mongodb://username:password@123.45.67.890:27017"`. + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + gcloud app deploy + +1. View the deployed app at [https://YOUR_PROJECT_ID.appspot.com](https://YOUR_PROJECT_ID.appspot.com). + diff --git a/6-pubsub/app.js b/6-pubsub/app.js index 2ef967dda1..eb4fcf1e5f 100644 --- a/6-pubsub/app.js +++ b/6-pubsub/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,7 +16,7 @@ // Activate Google Cloud Trace and Debug when in production if (process.env.NODE_ENV === 'production') { require('@google/cloud-trace').start(); - require('@google/cloud-debug'); + require('@google/cloud-debug').start(); } const path = require('path'); @@ -48,7 +48,7 @@ const sessionConfig = { // In production use the App Engine Memcache instance to store session data, // otherwise fallback to the default MemoryStore in development. -if (config.get('NODE_ENV') === 'production') { +if (config.get('NODE_ENV') === 'production' && config.get('MEMCACHE_URL')) { sessionConfig.store = new MemcachedStore({ hosts: [config.get('MEMCACHE_URL')] }); diff --git a/6-pubsub/app.yaml b/6-pubsub/app.yaml index 0e6e6db7ee..1ad7911659 100644 --- a/6-pubsub/app.yaml +++ b/6-pubsub/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -13,7 +13,3 @@ # runtime: nodejs env: flex - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ diff --git a/6-pubsub/books/api.js b/6-pubsub/books/api.js index a92b261658..9e9691fe5a 100644 --- a/6-pubsub/books/api.js +++ b/6-pubsub/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/6-pubsub/books/crud.js b/6-pubsub/books/crud.js index d53889b663..46ef8e6846 100644 --- a/6-pubsub/books/crud.js +++ b/6-pubsub/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,12 +14,11 @@ 'use strict'; const express = require('express'); -const config = require('../config'); const images = require('../lib/images'); const oauth2 = require('../lib/oauth2'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/6-pubsub/books/model-cloudsql.js b/6-pubsub/books/model-cloudsql.js index e32afe2f01..fd63b1d867 100644 --- a/6-pubsub/books/model-cloudsql.js +++ b/6-pubsub/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -18,23 +18,20 @@ const mysql = require('mysql'); const config = require('../config'); const background = require('../lib/background'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -46,12 +43,10 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `createdById` = ? LIMIT ? OFFSET ?', [userId, limit, token], @@ -63,12 +58,10 @@ function listBy (userId, limit, token, cb) { const hasMore = results.length === limit ? token + results.length : false; cb(null, results, hasMore); }); - connection.end(); } // [START create] function create (data, queueBook, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -79,12 +72,10 @@ function create (data, queueBook, cb) { } read(res.insertId, cb); }); - connection.end(); } // [END create] function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -100,12 +91,10 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } // [START update] function update (id, data, queueBook, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -117,14 +106,11 @@ function update (id, data, queueBook, cb) { } read(id, cb); }); - connection.end(); } // [END update] function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/6-pubsub/books/model-datastore.js b/6-pubsub/books/model-datastore.js index 426563c384..284c1b6320 100644 --- a/6-pubsub/books/model-datastore.js +++ b/6-pubsub/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -39,8 +39,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's diff --git a/6-pubsub/books/model-mongodb.js b/6-pubsub/books/model-mongodb.js index 240067b710..3009f20ec1 100644 --- a/6-pubsub/books/model-mongodb.js +++ b/6-pubsub/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -77,7 +77,7 @@ function list (limit, token, cb) { }); } -function listBy (userid, limit, token, cb) { +function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; if (isNaN(token)) { cb(new Error('invalid token')); @@ -85,9 +85,10 @@ function listBy (userid, limit, token, cb) { } getCollection((err, collection) => { if (err) { - return err; + cb(err); + return; } - collection.find({createdById: userid}) + collection.find({ createdById: userId }) .skip(token) .limit(limit) .toArray((err, results) => { @@ -168,7 +169,7 @@ function update (id, data, queueBook, cb) { if (queueBook) { background.queueBook(id); } - return read(id, cb); + read(id, cb); } ); }); diff --git a/6-pubsub/config.js b/6-pubsub/config.js index 6250ddfa99..a9590d531e 100644 --- a/6-pubsub/config.js +++ b/6-pubsub/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -26,6 +26,7 @@ nconf 'CLOUD_BUCKET', 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'MEMCACHE_URL', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', @@ -70,9 +71,9 @@ nconf OAUTH2_CLIENT_SECRET: '', OAUTH2_CALLBACK: 'http://localhost:8080/auth/google/callback', - // Port the HTTP server PORT: 8080, + // Set this a secret string of your choosing SECRET: 'keyboardcat', SUBSCRIPTION_NAME: 'shared-worker-subscription', diff --git a/6-pubsub/lib/background.js b/6-pubsub/lib/background.js index 8a4ab651df..7872f79173 100644 --- a/6-pubsub/lib/background.js +++ b/6-pubsub/lib/background.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -38,7 +38,6 @@ function getTopic (cb) { return; } cb(err, topic); - return; }); } // [END topic] @@ -66,8 +65,7 @@ function subscribe (cb) { } topic.subscribe(subscriptionName, { - autoAck: true, - reuseExisting: true + autoAck: true }, (err, sub) => { if (err) { cb(err); diff --git a/6-pubsub/lib/images.js b/6-pubsub/lib/images.js index 32136a8e7e..452ac62c86 100644 --- a/6-pubsub/lib/images.js +++ b/6-pubsub/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/6-pubsub/lib/logging.js b/6-pubsub/lib/logging.js index 0c9e385560..15ad77d1eb 100644 --- a/6-pubsub/lib/logging.js +++ b/6-pubsub/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,10 +13,10 @@ 'use strict'; -const winston = require(`winston`); -const expressWinston = require(`express-winston`); +const winston = require('winston'); +const expressWinston = require('express-winston'); -const colorize = process.env.NODE_ENV !== `production`; +const colorize = process.env.NODE_ENV !== 'production'; // Logger to capture all requests and output them to the console. const requestLogger = expressWinston.logger({ diff --git a/6-pubsub/lib/oauth2.js b/6-pubsub/lib/oauth2.js index 9ee3a72c3e..fa63e72703 100644 --- a/6-pubsub/lib/oauth2.js +++ b/6-pubsub/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -21,7 +21,7 @@ const passport = require('passport'); const GoogleStrategy = require('passport-google-oauth20').Strategy; function extractProfile (profile) { - let imageUrl = ``; + let imageUrl = ''; if (profile.photos && profile.photos.length) { imageUrl = profile.photos[0].value; } diff --git a/6-pubsub/package.json b/6-pubsub/package.json index 9bebd2bd42..7d9a4315e4 100644 --- a/6-pubsub/package.json +++ b/6-pubsub/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node ${SCRIPT:-app.js}", - "monitor": "nodemon ${SCRIPT:-app.js}", - "deploy": "gcloud app deploy app.yaml worker.yaml", - "test": "mocha test/index.js -t 30000", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -24,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -38,36 +41,36 @@ ] }, "dependencies": { - "@google/cloud-debug": "^0.8.3", - "@google/cloud-trace": "^0.5.5", - "@google-cloud/datastore": "^0.1.1", - "@google-cloud/storage": "^0.1.1", - "@google-cloud/pubsub": "^0.2.0", - "async": "^2.0.1", - "body-parser": "^1.15.2", - "connect-memcached": "^0.2.0", - "express": "^4.14.0", - "express-session": "^1.14.0", - "express-winston": "^2.0.0", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "passport": "^0.3.2", - "passport-google-oauth20": "^1.0.0", - "prompt": "^1.0.0", - "request": "^2.74.0", - "winston": "^2.2.0" + "@google-cloud/debug-agent": "~1.0.0", + "@google-cloud/trace-agent": "~1.0.4", + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "@google-cloud/pubsub": "~0.10.0", + "async": "~2.3.0", + "body-parser": "~1.17.1", + "connect-memcached": "~0.2.0", + "express": "~4.15.2", + "express-session": "~1.15.2", + "express-winston": "~2.3.0", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "passport": "~0.3.2", + "passport-google-oauth20": "~1.0.0", + "prompt": "~1.0.0", + "request": "~2.81.0", + "winston": "~2.3.1" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "sinon": "^1.17.5", - "supertest": "^2.0.0" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "supertest": "~3.0.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/6-pubsub/test/_api-tests.js b/6-pubsub/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/6-pubsub/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/6-pubsub/test/_crud-tests.js b/6-pubsub/test/_crud-tests.js new file mode 100644 index 0000000000..aaf1513cc6 --- /dev/null +++ b/6-pubsub/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .field(`title`, `my book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .field(`title`, `my other book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/6-pubsub/test/config.js b/6-pubsub/test/_test-config.js similarity index 77% rename from 6-pubsub/test/config.js rename to 6-pubsub/test/_test-config.js index 4a06dc597d..06990ba3cd 100644 --- a/6-pubsub/test/config.js +++ b/6-pubsub/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,15 +16,19 @@ const path = require(`path`); const test = `6-pubsub`; +const PORT = 8086; + module.exports = { test: test, cwd: path.resolve(path.join(__dirname, `../`)), cmd: `node`, args: [`app.js`], msg: `Bookshelf`, - port: 8086, + port: PORT, + url: `http://localhost:${PORT}`, env: { - SUBSCRIPTION_NAME: `${test}-shared-worker-subscription`, - TOPIC_NAME: `${test}-book-process-queue` + PORT: PORT, + SUBSCRIPTION_NAME: `shared-worker-subscription-${test}`, + TOPIC_NAME: `book-process-queue-${test}` } }; diff --git a/6-pubsub/test/config.worker.js b/6-pubsub/test/_test-config.worker.js similarity index 96% rename from 6-pubsub/test/config.worker.js rename to 6-pubsub/test/_test-config.worker.js index 1fe5180f77..b878287eb7 100644 --- a/6-pubsub/test/config.worker.js +++ b/6-pubsub/test/_test-config.worker.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/6-pubsub/test/api.test.js b/6-pubsub/test/api.test.js deleted file mode 100644 index 151759d8a8..0000000000 --- a/6-pubsub/test/api.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/6-pubsub/test/app.test.js b/6-pubsub/test/app.test.js index ccbe12661e..d1948edc8c 100644 --- a/6-pubsub/test/app.test.js +++ b/6-pubsub/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -11,96 +11,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -`use strict`; +'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} + +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = `project`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`CLOUD_BUCKET`)); - - nconfMock.CLOUD_BUCKET = `bucket`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_ID`)); - - nconfMock.OAUTH2_CLIENT_ID = `foo`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); - - nconfMock.OAUTH2_CLIENT_SECRET = `bar`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.throws(testFunc, Error, getMsg(`CLOUD_BUCKET`)); + nconfMock.CLOUD_BUCKET = `bucket`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_ID`)); + nconfMock.OAUTH2_CLIENT_ID = `foo`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); + nconfMock.OAUTH2_CLIENT_SECRET = `bar`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/6-pubsub/test/background.test.js b/6-pubsub/test/background.test.js index 4027a5f2d4..bc63b26ac7 100644 --- a/6-pubsub/test/background.test.js +++ b/6-pubsub/test/background.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,415 +13,384 @@ 'use strict'; -const assert = require(`assert`); -const sinon = require(`sinon`); const proxyquire = require(`proxyquire`).noPreserveCache(); +const sinon = require(`sinon`); +const test = require(`ava`); + let background; const mocks = {}; -describe(`background.js`, () => { - beforeEach(() => { - // Mock dependencies used by background.js - mocks.config = { - GCLOUD_PROJECT: process.env.GCLOUD_PROJECT, - SUBSCRIPTION_NAME: `shared-worker-subscription`, - TOPIC_NAME: `book-process-queue` - }; - mocks.config.get = function (key) { - return this[key]; - }; - mocks.subscription = { - on: sinon.stub() - }; - mocks.topic = { - subscribe: sinon.stub().callsArgWith(2, null, mocks.subscription), - publish: sinon.stub().callsArg(1) - }; - mocks.pubsub = { - createTopic: sinon.stub().callsArgWith(1, null, mocks.topic), - topic: sinon.stub().returns(mocks.topic) - }; - mocks.Pubsub = sinon.stub().returns(mocks.pubsub); - mocks.logging = { - info: sinon.stub(), - error: sinon.stub() - }; - // Load background.js with provided mocks - background = proxyquire(`../lib/background`, { - '@google-cloud/pubsub': mocks.Pubsub, - '../config': mocks.config, - './logging': mocks.logging - }); +test.beforeEach((t) => { + // Mock dependencies used by background.js + mocks.config = { + GCLOUD_PROJECT: process.env.GCLOUD_PROJECT, + SUBSCRIPTION_NAME: `shared-worker-subscription`, + TOPIC_NAME: `book-process-queue` + }; + mocks.config.get = function (key) { + return this[key]; + }; + mocks.subscription = { + on: sinon.stub() + }; + mocks.topic = { + subscribe: sinon.stub().callsArgWith(2, null, mocks.subscription), + publish: sinon.stub().callsArg(1) + }; + mocks.pubsub = { + createTopic: sinon.stub().callsArgWith(1, null, mocks.topic), + topic: sinon.stub().returns(mocks.topic) + }; + mocks.Pubsub = sinon.stub().returns(mocks.pubsub); + mocks.logging = { + info: sinon.stub(), + error: sinon.stub() + }; + // Load background.js with provided mocks + background = proxyquire(`../lib/background`, { + '@google-cloud/pubsub': mocks.Pubsub, + '../config': mocks.config, + './logging': mocks.logging + }); + + t.truthy( + mocks.Pubsub.calledOnce, + `Pubsub() should have been called once` + ); +}); - assert.ok( - mocks.Pubsub.calledOnce, - `Pubsub() should have been called once` +test.serial.cb(`should subscribe and log message`, (t) => { + // Setup + const testMessage = `test message`; + + // Run target functionality + background.subscribe((err, message) => { + // Assertions + t.truthy( + err === null, + `err should be null` + ); + t.is(message, testMessage, `should have message`); + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.subscribe.calledOnce, + `topic.subscribe should have been called once` + ); + t.is( + mocks.topic.subscribe.firstCall.args[0], + `shared-worker-subscription`, + `topic.subscribe() should have been called with the right arguments` ); + t.deepEqual( + mocks.topic.subscribe.firstCall.args[1], + { + autoAck: true + }, + `topic.subscribe() should have been called with the right arguments` + ); + t.truthy( + mocks.subscription.on.calledTwice, + `subscription.on should have been called twice` + ); + t.is( + mocks.subscription.on.firstCall.args[0], + `message`, + `subscription.on() should have been called with the right arguments` + ); + t.truthy( + typeof mocks.subscription.on.firstCall.args[1] === `function`, + `subscription.on() should have been called with the right arguments` + ); + t.is( + mocks.subscription.on.secondCall.args[0], + `error`, + `subscription.on() should have been called with the right arguments` + ); + t.truthy( + typeof mocks.subscription.on.secondCall.args[1] === `function`, + `subscription.on() should have been called with the right arguments` + ); + t.end(); }); - describe(`subscribe()`, () => { - it(`should subscribe and log message`, (done) => { - // Setup - const testMessage = `test message`; + // Trigger a message + setTimeout(() => { + mocks.subscription.on.firstCall.args[1]({ + data: testMessage + }); + }, 10); +}); - // Run target functionality - background.subscribe((err, message) => { - // Assertions - assert.ok( - err === null, - `err should be null` - ); - assert.equal(message, testMessage, `should have message`); - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.subscribe.calledOnce, - `topic.subscribe should have been called once` - ); - assert.equal( - mocks.topic.subscribe.firstCall.args[0], - `shared-worker-subscription`, - `topic.subscribe() should have been called with the right arguments` - ); - assert.deepEqual( - mocks.topic.subscribe.firstCall.args[1], - { - autoAck: true, - reuseExisting: true - }, - `topic.subscribe() should have been called with the right arguments` - ); - assert.ok( - mocks.subscription.on.calledTwice, - `subscription.on should have been called twice` - ); - assert.equal( - mocks.subscription.on.firstCall.args[0], - `message`, - `subscription.on() should have been called with the right arguments` - ); - assert.ok( - typeof mocks.subscription.on.firstCall.args[1] === `function`, - `subscription.on() should have been called with the right arguments` - ); - assert.equal( - mocks.subscription.on.secondCall.args[0], - `error`, - `subscription.on() should have been called with the right arguments` - ); - assert.ok( - typeof mocks.subscription.on.secondCall.args[1] === `function`, - `subscription.on() should have been called with the right arguments` - ); - done(); - }); +test.serial.cb(`should return topic error, if any`, (t) => { + // Setup + const testErrorMsg = `test error`; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); - // Trigger a message - setTimeout(() => { - mocks.subscription.on.firstCall.args[1]({ - data: testMessage - }); - }, 10); - }); - it(`should return topic error, if any`, (done) => { - // Setup - const testErrorMsg = `test error`; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + // Run target functionality + background.subscribe((data) => { + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.is(data, testErrorMsg); + t.is( + mocks.topic.subscribe.callCount, + 0, + `topic.subscribe() should NOT have been called` + ); + t.is( + mocks.subscription.on.callCount, + 0, + `subscription.on() should NOT have been called` + ); + t.end(); + }); +}); - // Run target functionality - background.subscribe((data) => { - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.equal(data, testErrorMsg); - assert.equal( - mocks.topic.subscribe.callCount, - 0, - `topic.subscribe() should NOT have been called` - ); - assert.equal( - mocks.subscription.on.callCount, - 0, - `subscription.on() should NOT have been called` - ); - done(); - }); - }); - it(`should return subscription error, if any`, (done) => { - // Setup - const testErrorMsg = `test error`; - mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); +test.serial.cb(`should return subscription error, if any`, (t) => { + // Setup + const testErrorMsg = `test error`; + mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); - // Run target functionality - background.subscribe((data) => { - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.subscribe.calledOnce, - `topic.subscribe should have been called once` - ); - assert.equal( - mocks.topic.subscribe.firstCall.args[0], - `shared-worker-subscription`, - `topic.subscribe() should have been called with the right arguments` - ); - assert.deepEqual( - mocks.topic.subscribe.firstCall.args[1], - { - autoAck: true, - reuseExisting: true - }, - `topic.subscribe() should have been called with the right arguments` - ); - assert.equal(data, testErrorMsg); - assert.equal( - mocks.subscription.on.callCount, - 0, - `subscription.on() should NOT have been called` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - done(); - }); - }); + // Run target functionality + background.subscribe((data) => { + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.subscribe.calledOnce, + `topic.subscribe should have been called once` + ); + t.is( + mocks.topic.subscribe.firstCall.args[0], + `shared-worker-subscription`, + `topic.subscribe() should have been called with the right arguments` + ); + t.deepEqual( + mocks.topic.subscribe.firstCall.args[1], + { + autoAck: true + }, + `topic.subscribe() should have been called with the right arguments` + ); + t.is(data, testErrorMsg); + t.is( + mocks.subscription.on.callCount, + 0, + `subscription.on() should NOT have been called` + ); + t.is( + mocks.logging.info.callCount, + 0, + `logging.info() should NOT have been called` + ); + t.end(); }); +}); - describe(`queueBook()`, () => { - it(`should queue a book and log message`, () => { - // Setup - const testBookId = 1; +test.serial(`should queue a book and log message`, (t) => { + // Setup + const testBookId = 1; - // Run target functionality - background.queueBook(testBookId); + // Run target functionality + background.queueBook(testBookId); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.ok( - mocks.logging.info.calledOnce, - `logging.info() should have been called` - ); - assert.equal( - mocks.logging.info.firstCall.args[0], - `Book ${testBookId} queued for background processing`, - `logging.info() should have been called with the right arguments` - ); - }); - it(`should queue a book and log message even if topic exists`, () => { - // Setup - const testBookId = 1; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { - code: 409 - }); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); + t.deepEqual( + mocks.topic.publish.firstCall.args[0], + { + data: { + action: `processBook`, + bookId: testBookId + } + }, + `topic.publish() should have been called with the right arguments` + ); + t.truthy( + mocks.logging.info.calledOnce, + `logging.info() should have been called` + ); + t.is( + mocks.logging.info.firstCall.args[0], + `Book ${testBookId} queued for background processing`, + `logging.info() should have been called with the right arguments` + ); +}); - // Run target functionality - background.queueBook(testBookId); +test.serial(`should queue a book and log message even if topic exists`, (t) => { + // Setup + const testBookId = 1; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { + code: 409 + }); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.ok( - mocks.pubsub.topic.calledOnce, - `pubsub.topic() should have been called once` - ); - assert.equal( - mocks.pubsub.topic.firstCall.args[0], - `book-process-queue`, - `pubsub.topic() should have been called with the right arguments` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.ok( - mocks.logging.info.calledOnce, - `logging.info() should have been called` - ); - assert.equal( - mocks.logging.info.firstCall.args[0], - `Book ${testBookId} queued for background processing`, - `logging.info() should have been called with the right arguments` - ); - }); - it(`should log error if cannot get topic`, () => { - // Setup - const testBookId = 1; - const testErrorMsg = `test error`; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + // Run target functionality + background.queueBook(testBookId); - // Run target functionality - background.queueBook(testBookId); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.truthy( + mocks.pubsub.topic.calledOnce, + `pubsub.topic() should have been called once` + ); + t.is( + mocks.pubsub.topic.firstCall.args[0], + `book-process-queue`, + `pubsub.topic() should have been called with the right arguments` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); + t.deepEqual( + mocks.topic.publish.firstCall.args[0], + { + data: { + action: `processBook`, + bookId: testBookId + } + }, + `topic.publish() should have been called with the right arguments` + ); + t.truthy( + mocks.logging.info.calledOnce, + `logging.info() should have been called` + ); + t.is( + mocks.logging.info.firstCall.args[0], + `Book ${testBookId} queued for background processing`, + `logging.info() should have been called with the right arguments` + ); +}); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.equal( - mocks.topic.publish.callCount, - 0, - `topic.publish() should NOT have been called` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - assert.ok( - mocks.logging.error.calledOnce, - `logging.error() should have been called` - ); - assert.deepEqual( - mocks.logging.error.firstCall.args, - [`Error occurred while getting pubsub topic`, testErrorMsg], - `logging.error() should have been called with the right arguments` - ); - }); - it(`should log error if cannot publish message`, () => { - // Setup - const testBookId = 1; - const testErrorMsg = `test error`; - mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); +test.serial(`should log error if cannot get topic`, (t) => { + // Setup + const testBookId = 1; + const testErrorMsg = `test error`; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); - // Run target functionality - background.queueBook(testBookId); + // Run target functionality + background.queueBook(testBookId); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - assert.ok( - mocks.logging.error.calledOnce, - `logging.error() should have been called` - ); - assert.deepEqual( - mocks.logging.error.firstCall.args, - [`Error occurred while queuing background task`, testErrorMsg], - `logging.error() should have been called with the right arguments` - ); - }); - }); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.is( + mocks.topic.publish.callCount, + 0, + `topic.publish() should NOT have been called` + ); + t.is( + mocks.logging.info.callCount, + 0, + `logging.info() should NOT have been called` + ); + t.truthy( + mocks.logging.error.calledOnce, + `logging.error() should have been called` + ); +}); + +test.serial(`should log error if cannot publish message`, (t) => { + // Setup + const testBookId = 1; + const testErrorMsg = `test error`; + mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); + + // Run target functionality + background.queueBook(testBookId); + + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); }); diff --git a/6-pubsub/test/cloudsql.test.js b/6-pubsub/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/6-pubsub/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/6-pubsub/test/crud.test.js b/6-pubsub/test/crud.test.js deleted file mode 100644 index 0f07993e9c..0000000000 --- a/6-pubsub/test/crud.test.js +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .field(`title`, `my book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .field(`title`, `my other book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/6-pubsub/test/datastore.test.js b/6-pubsub/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/6-pubsub/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/6-pubsub/test/index.js b/6-pubsub/test/index.js deleted file mode 100644 index 9f963c4649..0000000000 --- a/6-pubsub/test/index.js +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - let topicName; - - before(() => { - const appConfig = require(`../config`); - topicName = appConfig.get(`TOPIC_NAME`); - appConfig.set(`TOPIC_NAME`, `${topicName}-${config.test}`); - }); - - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); - require(`./worker.test`); - describe(`books/`, () => { - const appConfig = require(`../config`); - const DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - if (DATA_BACKEND === `datastore` || process.env.TEST_DATASTORE) { - require(`./api.test`)(`datastore`); - require(`./crud.test`)(`datastore`); - } - if (DATA_BACKEND === `cloudsql` || process.env.TEST_CLOUDSQL) { - require(`./api.test`)(`cloudsql`); - require(`./crud.test`)(`cloudsql`); - } - if (DATA_BACKEND === `mongodb` || process.env.TEST_MONGODB) { - require(`./api.test`)(`mongodb`); - require(`./crud.test`)(`mongodb`); - } - }); - if (!process.env.E2E_TESTS) { - describe(`lib/`, () => { - require(`./background.test`); - require(`./oauth2.test`); - }); - } - - afterEach(() => { - const appConfig = require(`../config`); - appConfig.set(`TOPIC_NAME`, topicName); - }); -}); diff --git a/6-pubsub/test/mongodb.test.js b/6-pubsub/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/6-pubsub/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/6-pubsub/test/oauth2.test.js b/6-pubsub/test/oauth2.test.js index 4f960e9ea5..a384a0f3ef 100644 --- a/6-pubsub/test/oauth2.test.js +++ b/6-pubsub/test/oauth2.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,201 +13,200 @@ 'use strict'; -const assert = require(`assert`); +const test = require(`ava`); const sinon = require(`sinon`); const request = require(`supertest`); const proxyquire = require(`proxyquire`).noPreserveCache(); -describe(`oauth2.js`, () => { - let passportMock; +const getPassportMock = () => { + return { + initialize: sinon.stub().returns((req, res, next) => { + next(); + }), + session: sinon.stub().returns((req, res, next) => { + next(); + }), + use: sinon.stub(), + serializeUser: sinon.stub(), + deserializeUser: sinon.stub(), + authenticate: sinon.stub().returns((req, res, next) => { + req.session.oauth2return = `/another/path`; + next(); + }) + }; +}; - beforeEach(() => { - passportMock = { - initialize: sinon.stub().returns((req, res, next) => { - next(); - }), - session: sinon.stub().returns((req, res, next) => { - next(); - }), - use: sinon.stub(), - serializeUser: sinon.stub(), - deserializeUser: sinon.stub(), - authenticate: sinon.stub().returns((req, res, next) => { - req.session.oauth2return = `/another/path`; - next(); - }) - }; +test.cb(`should start authorization`, (t) => { + const passportMock = getPassportMock(); + passportMock.authenticate = sinon.stub().returns((req, res, next) => { + t.is(req.session.oauth2return, `/some/path`); + res.redirect(`/auth/google/callback?code=foo`); }); - - it(`should start authorization`, (done) => { - passportMock.authenticate = sinon.stub().returns((req, res, next) => { - assert.equal(req.session.oauth2return, `/some/path`); - res.redirect(`/auth/google/callback?code=foo`); - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/login?return=%2Fsome%2Fpath`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /auth/google/callback?code=foo`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); - }); - - it(`should finish authorization`, (done) => { - const oauth2 = proxyquire(`../lib/oauth2`, { + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': oauth2 - }); - request(app) - .get(`/auth/google/callback?code=foo`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /another/path`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - assert.deepEqual( - oauth2.extractProfile({ - photos: [{ value: `image.jpg` }], - id: 1, - displayName: `Joe Developer` - }), - { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - ); - const serializeUser = passportMock.serializeUser.firstCall.args[0]; - const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; - const user = {}; - const obj = {}; - serializeUser(user, (err, _user) => { - assert.equal(err, null); - assert.strictEqual(_user, user); - }); - deserializeUser(obj, (err, _obj) => { - assert.equal(err, null); - assert.strictEqual(_obj, obj); - }); - }) - .end(done); + }) }); + request(app) + .get(`/auth/login?return=%2Fsome%2Fpath`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/auth\/google\/callback\?code=foo/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - it(`should logout`, (done) => { - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/logout`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); +test.cb(`should finish authorization`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock + }); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': oauth2 }); + request(app) + .get(`/auth/google/callback?code=foo`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/another\/path/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + t.deepEqual( + oauth2.extractProfile({ + photos: [{ value: `image.jpg` }], + id: 1, + displayName: `Joe Developer` + }), + { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + ); + const serializeUser = passportMock.serializeUser.firstCall.args[0]; + const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; + const user = {}; + const obj = {}; + serializeUser(user, (err, _user) => { + t.is(err, null); + t.is(_user, user); + }); + deserializeUser(obj, (err, _obj) => { + t.is(err, null); + t.is(_obj, obj); + }); + }) + .end(t.end); +}); - it(`should require authentication`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { +test.cb(`should logout`, (t) => { + const passportMock = getPassportMock(); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: {}, - session: {} - }; - const res = { - redirect: sinon.stub() - }; - const next = sinon.stub(); - oauth2.required(req, res, next); - assert(next.calledOnce); + }) + }); + request(app) + .get(`/auth/logout`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \//); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - req.user = undefined; - oauth2.required(req, res, next); - assert(next.calledOnce); - assert.equal(req.session.oauth2return, req.originalUrl); - assert(res.redirect.calledOnce); - assert.equal(res.redirect.firstCall.args[0], `/auth/login`); +test(`should require authentication`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: {}, + session: {} + }; + const res = { + redirect: sinon.stub() + }; + const next = sinon.stub(); + oauth2.required(req, res, next); + t.true(next.calledOnce); - it(`should add template variables`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { - passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - }; - const res = { - locals: {} - }; - const next = sinon.stub(); - oauth2.template(req, res, next); - assert(next.calledOnce); - assert.strictEqual(res.locals.profile, req.user); - assert.equal( - res.locals.login, - `/auth/login?return=${encodeURIComponent(req.originalUrl)}` - ); - assert.equal( - res.locals.logout, - `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` - ); + req.user = undefined; + oauth2.required(req, res, next); + t.true(next.calledOnce); + t.is(req.session.oauth2return, req.originalUrl); + t.true(res.redirect.calledOnce); + t.is(res.redirect.firstCall.args[0], `/auth/login`); +}); + +test(`should add template variables`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + }; + const res = { + locals: {} + }; + const next = sinon.stub(); + oauth2.template(req, res, next); + t.true(next.calledOnce); + t.is(res.locals.profile, req.user); + t.is( + res.locals.login, `/auth/login?return=${encodeURIComponent(req.originalUrl)}` + ); + t.is( + res.locals.logout, `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` + ); }); diff --git a/6-pubsub/test/worker.test.js b/6-pubsub/test/worker.test.js index dc98593ccc..60c65c8fe9 100644 --- a/6-pubsub/test/worker.test.js +++ b/6-pubsub/test/worker.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,18 +13,18 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config.worker`); +const testConfig = require(`./_test-config.worker`); const path = require(`path`); const proxyquire = require(`proxyquire`); const sinon = require(`sinon`); const supertest = require(`supertest`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); const projectId = process.env.GCLOUD_PROJECT; function getUrl () { - return `http://${config.test}-dot-worker-dot-${projectId}.appspot-preview.com`; + return `http://${testConfig.test}-dot-worker-dot-${projectId}.appspot-preview.com`; } function getRequest () { @@ -34,68 +34,66 @@ function getRequest () { return supertest(proxyquire(path.join(__dirname, `../worker`), {}).app); } -describe(`worker.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should return number of processed books`, (done) => { - getRequest(config) - .get(`/`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`This worker has processed`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.serial.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} - it(`should do a health check`, (done) => { - getRequest(config) - .get(`/_ah/health`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `ok`); - }) - .end(done); - }); +test.serial.cb(`should return number of processed books`, (t) => { + getRequest(testConfig) + .get(`/`) + .expect(200) + .expect((response) => { + t.regex(response.text, /This worker has processed/); + }) + .end(t.end); +}); + +test.serial.cb(`should do a health check`, (t) => { + getRequest(testConfig) + .get(`/_ah/health`) + .expect(200) + .expect((response) => { + t.is(response.text, `ok`); + }) + .end(t.end); +}); - it(`should process a book`, (done) => { - const appConfig = require(`../config`); - const loggingStub = { - error: sinon.stub(), - info: sinon.stub(), - warn: sinon.stub() - }; - const stubs = { - './lib/logging': loggingStub, - '@google/cloud-trace': { - start: sinon.stub(), - '@noCallThru': true - }, - '@google/cloud-debug': { - '@noCallThru': true - } - }; - stubs[`./books/model-${appConfig.get('DATA_BACKEND')}`] = { - read: (bookId, cb) => { - cb(null, {}); - }, - update: (bookId, book, queueBook, cb) => { - cb(); - } - }; - const worker = proxyquire(path.join(__dirname, `../worker`), stubs); - const processBook = worker.processBook; +test.serial.cb(`should process a book`, (t) => { + const appConfig = require(`../config`); + const loggingStub = { + error: sinon.stub(), + info: sinon.stub(), + warn: sinon.stub() + }; + const stubs = { + './lib/logging': loggingStub, + '@google/cloud-trace': { + start: sinon.stub(), + '@noCallThru': true + }, + '@google/cloud-debug': { + '@noCallThru': true + } + }; + stubs[`./books/model-${appConfig.get('DATA_BACKEND')}`] = { + read: (bookId, cb) => { + cb(null, {}); + }, + update: (bookId, book, queueBook, cb) => { + cb(); + } + }; + const worker = proxyquire(path.join(__dirname, `../worker`), stubs); + const processBook = worker.processBook; - processBook(1, (err, bookId) => { - if (err) { - return done(err); - } - assert(loggingStub.info.calledOnce); - assert.equal(loggingStub.info.firstCall.args[0], `Updated book 1`); - done(); - }); + processBook(1, (err, bookId) => { + if (err) { + return t.end(err); + } + t.true(loggingStub.info.calledOnce); + t.is(loggingStub.info.firstCall.args[0], `Updated book 1`); + t.end(); }); }); diff --git a/6-pubsub/views/base.jade b/6-pubsub/views/base.jade index 4d1c80d45d..72a8e8a7c1 100644 --- a/6-pubsub/views/base.jade +++ b/6-pubsub/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/6-pubsub/views/books/form.jade b/6-pubsub/views/books/form.jade index 8368619eeb..2a96f6d50e 100644 --- a/6-pubsub/views/books/form.jade +++ b/6-pubsub/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/6-pubsub/views/books/list.jade b/6-pubsub/views/books/list.jade index dd453386ba..f7e901c3b3 100644 --- a/6-pubsub/views/books/list.jade +++ b/6-pubsub/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/6-pubsub/views/books/view.jade b/6-pubsub/views/books/view.jade index fb0123b05c..92776d30d1 100644 --- a/6-pubsub/views/books/view.jade +++ b/6-pubsub/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/6-pubsub/worker.js b/6-pubsub/worker.js index c6aba97a55..ce741ab27a 100644 --- a/6-pubsub/worker.js +++ b/6-pubsub/worker.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,7 +16,7 @@ // Activate Google Cloud Trace and Debug when in production if (process.env.NODE_ENV === 'production') { require('@google/cloud-trace').start(); - require('@google/cloud-debug'); + require('@google/cloud-debug').start(); } const request = require('request'); diff --git a/6-pubsub/worker.yaml b/6-pubsub/worker.yaml index fd12ec7001..190799b468 100644 --- a/6-pubsub/worker.yaml +++ b/6-pubsub/worker.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -18,8 +18,4 @@ env: flex env_variables: SCRIPT: worker.js - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ # [END yaml] diff --git a/7-gce/.gitignore b/7-gce/.gitignore index eb03e3e1e4..48fd1f403d 100644 --- a/7-gce/.gitignore +++ b/7-gce/.gitignore @@ -1,2 +1,3 @@ node_modules +config.json *.log diff --git a/7-gce/README.md b/7-gce/README.md index 3db900b8fa..d38ab0be5b 100644 --- a/7-gce/README.md +++ b/7-gce/README.md @@ -5,3 +5,98 @@ tutorial. Please refer to the tutorial for instructions on configuring, running, and deploying this sample. [step-7]: https://cloud.google.com/nodejs/getting-started/run-on-compute-engine + +# Simple instructions + +1. Install [Node.js](https://nodejs.org/en/). + + * Optional: Install [Yarn](https://yarnpkg.com/). + +1. Install [git](https://git-scm.com/). +1. Create a [Google Cloud Platform project](https://console.cloud.google.com). +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). + + * After downloading the SDK, initialize it: + + gcloud init + +1. Acquire local credentials for authenticating with Google Cloud Platform + services: + + gcloud beta auth application-default login + +1. Clone the repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git + +1. Change directory: + + cd nodejs-getting-started/5-logging + +1. Create a `config.json` file (copied from the `config-default.json` file): + + cp config-default.json config.json + + * Set `GCLOUD_PROJECT` in `config.json` to your Google Cloud Platform + project ID. + * Set `DATA_BACKEND` in `config.json` to one of `"datastore"`, `"cloudsql"`, + or `"mongodb"`. + * Set `CLOUD_BUCKET` in `config.json` to the name of your Google Cloud + Storage bucket. + * Set `OAUTH2_CLIENT_ID` in `config.json`. + * Set `OAUTH2_CLIENT_SECRET` in `config.json`. + * Set `TOPIC_NAME` in `config.json`. + * Set `SUBSCRIPTION_NAME` in `config.json`. + +1. Install dependencies using NPM or Yarn: + + * Using NPM: + + npm install + + * Using Yarn: + + yarn install + +1. Configure the backing store: + + * If `DATA_BACKEND` is set to `"cloudsql"`: + + 1. Create a Cloud SQL instance, and download and start the Cloud SQL + proxy: + + Instructions for doing so: https://cloud.google.com/nodejs/getting-started/using-cloud-sql#creating_a_cloud_sql_instance + 1. Set `MYSQL_USER` in `config.json`, e.g. `"my-cloudsql-username"`. + 1. Set `MYSQL_PASSWORD` in `config.json`, e.g. `"my-cloudsql-password"`. + 1. Set `INSTANCE_CONNECTION_NAME` in `config.json`, e.g. `"YOUR_PROJECT_ID:YOUR_REGION:YOUR_INSTANCE_ID"`. + 1. Run the script to setup the table: + + * Using NPM: + + npm run init-cloudsql + + * Using Yarn: + + yarn run init-cloudsql + + * If `DATA_BACKEND` is set to `"mongodb"`: + + 1. Set `MONGO_URL` in `config.json`, e.g. `"mongodb://username:password@123.45.67.890:27017"`. + +1. Start the app using NPM or Yarn: + + * Using NPM: + + npm start + + * Using Yarn: + + yarn start + +1. View the app at [http://localhost:8080](http://localhost:8080). + +1. Stop the app by pressing `Ctrl+C`. + +1. Deploy the app: + + See https://cloud.google.com/nodejs/tutorials/bookshelf-on-compute-engine diff --git a/7-gce/app.js b/7-gce/app.js index 3cc8054e76..3f4a49da08 100644 --- a/7-gce/app.js +++ b/7-gce/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,7 +16,7 @@ // Activate Google Cloud Trace and Debug when in production if (process.env.NODE_ENV === 'production') { require('@google/cloud-trace').start(); - require('@google/cloud-debug'); + require('@google/cloud-debug').start(); } const path = require('path'); @@ -48,7 +48,7 @@ const sessionConfig = { // In production use the App Engine Memcache instance to store session data, // otherwise fallback to the default MemoryStore in development. -if (config.get('NODE_ENV') === 'production') { +if (config.get('NODE_ENV') === 'production' && config.get('MEMCACHE_URL')) { sessionConfig.store = new MemcachedStore({ hosts: [config.get('MEMCACHE_URL')] }); diff --git a/7-gce/app.yaml b/7-gce/app.yaml index 0e6e6db7ee..1ad7911659 100644 --- a/7-gce/app.yaml +++ b/7-gce/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -13,7 +13,3 @@ # runtime: nodejs env: flex - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ diff --git a/7-gce/books/api.js b/7-gce/books/api.js index a92b261658..9e9691fe5a 100644 --- a/7-gce/books/api.js +++ b/7-gce/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/7-gce/books/crud.js b/7-gce/books/crud.js index d53889b663..46ef8e6846 100644 --- a/7-gce/books/crud.js +++ b/7-gce/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,12 +14,11 @@ 'use strict'; const express = require('express'); -const config = require('../config'); const images = require('../lib/images'); const oauth2 = require('../lib/oauth2'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/7-gce/books/model-cloudsql.js b/7-gce/books/model-cloudsql.js index 547b1adeae..1f7bded298 100644 --- a/7-gce/books/model-cloudsql.js +++ b/7-gce/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -18,23 +18,20 @@ const mysql = require('mysql'); const config = require('../config'); const background = require('../lib/background'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -46,12 +43,10 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `createdById` = ? LIMIT ? OFFSET ?', [userId, limit, token], @@ -63,11 +58,9 @@ function listBy (userId, limit, token, cb) { const hasMore = results.length === limit ? token + results.length : false; cb(null, results, hasMore); }); - connection.end(); } function create (data, queueBook, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -78,11 +71,9 @@ function create (data, queueBook, cb) { } read(res.insertId, cb); }); - connection.end(); } function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -98,11 +89,9 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } function update (id, data, queueBook, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -114,13 +103,10 @@ function update (id, data, queueBook, cb) { } read(id, cb); }); - connection.end(); } function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/7-gce/books/model-datastore.js b/7-gce/books/model-datastore.js index 63d2bb07ed..1e8179f484 100644 --- a/7-gce/books/model-datastore.js +++ b/7-gce/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -39,8 +39,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's diff --git a/7-gce/books/model-mongodb.js b/7-gce/books/model-mongodb.js index f9fee073ce..01016bf14f 100644 --- a/7-gce/books/model-mongodb.js +++ b/7-gce/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -77,7 +77,7 @@ function list (limit, token, cb) { }); } -function listBy (userid, limit, token, cb) { +function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; if (isNaN(token)) { cb(new Error('invalid token')); @@ -88,7 +88,7 @@ function listBy (userid, limit, token, cb) { cb(err); return; } - collection.find({ createdById: userid }) + collection.find({ createdById: userId }) .skip(token) .limit(limit) .toArray((err, results) => { @@ -167,7 +167,6 @@ function update (id, data, queueBook, cb) { background.queueBook(id); } read(id, cb); - return; } ); }); diff --git a/7-gce/config.js b/7-gce/config.js index 6250ddfa99..a9590d531e 100644 --- a/7-gce/config.js +++ b/7-gce/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -26,6 +26,7 @@ nconf 'CLOUD_BUCKET', 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'MEMCACHE_URL', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', @@ -70,9 +71,9 @@ nconf OAUTH2_CLIENT_SECRET: '', OAUTH2_CALLBACK: 'http://localhost:8080/auth/google/callback', - // Port the HTTP server PORT: 8080, + // Set this a secret string of your choosing SECRET: 'keyboardcat', SUBSCRIPTION_NAME: 'shared-worker-subscription', diff --git a/7-gce/gce/startup-script.sh b/7-gce/gce/startup-script.sh index 0d1e11d33b..1acbd21a73 100644 --- a/7-gce/gce/startup-script.sh +++ b/7-gce/gce/startup-script.sh @@ -1,6 +1,6 @@ #! /bin/bash -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 diff --git a/7-gce/lib/background.js b/7-gce/lib/background.js index 2cb58a03d8..6d2dd8f91b 100644 --- a/7-gce/lib/background.js +++ b/7-gce/lib/background.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -52,7 +52,7 @@ function subscribe (cb) { cb(null, message.data); } function handleError (err) { - console.error(err); + logging.error(err); } getTopic((err, topic) => { @@ -62,8 +62,7 @@ function subscribe (cb) { } topic.subscribe(subscriptionName, { - autoAck: true, - reuseExisting: true + autoAck: true }, (err, sub) => { if (err) { cb(err); diff --git a/7-gce/lib/images.js b/7-gce/lib/images.js index 8e3ddf8aef..715f674c4f 100644 --- a/7-gce/lib/images.js +++ b/7-gce/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/7-gce/lib/logging.js b/7-gce/lib/logging.js index a2a436db0a..15ad77d1eb 100644 --- a/7-gce/lib/logging.js +++ b/7-gce/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/7-gce/lib/oauth2.js b/7-gce/lib/oauth2.js index a4620ab228..fa63e72703 100644 --- a/7-gce/lib/oauth2.js +++ b/7-gce/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/7-gce/package.json b/7-gce/package.json index 9bebd2bd42..7d9a4315e4 100644 --- a/7-gce/package.json +++ b/7-gce/package.json @@ -1,14 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node ${SCRIPT:-app.js}", - "monitor": "nodemon ${SCRIPT:-app.js}", - "deploy": "gcloud app deploy app.yaml worker.yaml", - "test": "mocha test/index.js -t 30000", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -24,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -38,36 +41,36 @@ ] }, "dependencies": { - "@google/cloud-debug": "^0.8.3", - "@google/cloud-trace": "^0.5.5", - "@google-cloud/datastore": "^0.1.1", - "@google-cloud/storage": "^0.1.1", - "@google-cloud/pubsub": "^0.2.0", - "async": "^2.0.1", - "body-parser": "^1.15.2", - "connect-memcached": "^0.2.0", - "express": "^4.14.0", - "express-session": "^1.14.0", - "express-winston": "^2.0.0", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "passport": "^0.3.2", - "passport-google-oauth20": "^1.0.0", - "prompt": "^1.0.0", - "request": "^2.74.0", - "winston": "^2.2.0" + "@google-cloud/debug-agent": "~1.0.0", + "@google-cloud/trace-agent": "~1.0.4", + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "@google-cloud/pubsub": "~0.10.0", + "async": "~2.3.0", + "body-parser": "~1.17.1", + "connect-memcached": "~0.2.0", + "express": "~4.15.2", + "express-session": "~1.15.2", + "express-winston": "~2.3.0", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "passport": "~0.3.2", + "passport-google-oauth20": "~1.0.0", + "prompt": "~1.0.0", + "request": "~2.81.0", + "winston": "~2.3.1" }, "devDependencies": { - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "sinon": "^1.17.5", - "supertest": "^2.0.0" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "supertest": "~3.0.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/7-gce/test/_api-tests.js b/7-gce/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/7-gce/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/7-gce/test/_crud-tests.js b/7-gce/test/_crud-tests.js new file mode 100644 index 0000000000..aaf1513cc6 --- /dev/null +++ b/7-gce/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .field(`title`, `my book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .field(`title`, `my other book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/7-gce/test/config.js b/7-gce/test/_test-config.js similarity index 77% rename from 7-gce/test/config.js rename to 7-gce/test/_test-config.js index cb95c60fb4..9cfe02b284 100644 --- a/7-gce/test/config.js +++ b/7-gce/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,15 +16,19 @@ const path = require(`path`); const test = `7-gce`; +const PORT = 8087; + module.exports = { test: test, cwd: path.resolve(path.join(__dirname, `../`)), cmd: `node`, args: [`app.js`], msg: `Bookshelf`, - port: 8087, + port: PORT, + url: `http://localhost:${PORT}`, env: { - SUBSCRIPTION_NAME: `${test}-shared-worker-subscription`, - TOPIC_NAME: `${test}-book-process-queue` + PORT: PORT, + SUBSCRIPTION_NAME: `shared-worker-subscription-${test}`, + TOPIC_NAME: `book-process-queue-${test}` } }; diff --git a/7-gce/test/config.worker.js b/7-gce/test/_test-config.worker.js similarity index 96% rename from 7-gce/test/config.worker.js rename to 7-gce/test/_test-config.worker.js index 2658ba2fc7..63e5f09e3a 100644 --- a/7-gce/test/config.worker.js +++ b/7-gce/test/_test-config.worker.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/7-gce/test/api.test.js b/7-gce/test/api.test.js deleted file mode 100644 index 151759d8a8..0000000000 --- a/7-gce/test/api.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/7-gce/test/app.test.js b/7-gce/test/app.test.js index ccbe12661e..d1948edc8c 100644 --- a/7-gce/test/app.test.js +++ b/7-gce/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -11,96 +11,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -`use strict`; +'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} + +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = `project`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`CLOUD_BUCKET`)); - - nconfMock.CLOUD_BUCKET = `bucket`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_ID`)); - - nconfMock.OAUTH2_CLIENT_ID = `foo`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); - - nconfMock.OAUTH2_CLIENT_SECRET = `bar`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.throws(testFunc, Error, getMsg(`CLOUD_BUCKET`)); + nconfMock.CLOUD_BUCKET = `bucket`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_ID`)); + nconfMock.OAUTH2_CLIENT_ID = `foo`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); + nconfMock.OAUTH2_CLIENT_SECRET = `bar`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/7-gce/test/background.test.js b/7-gce/test/background.test.js index 4027a5f2d4..bc63b26ac7 100644 --- a/7-gce/test/background.test.js +++ b/7-gce/test/background.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,415 +13,384 @@ 'use strict'; -const assert = require(`assert`); -const sinon = require(`sinon`); const proxyquire = require(`proxyquire`).noPreserveCache(); +const sinon = require(`sinon`); +const test = require(`ava`); + let background; const mocks = {}; -describe(`background.js`, () => { - beforeEach(() => { - // Mock dependencies used by background.js - mocks.config = { - GCLOUD_PROJECT: process.env.GCLOUD_PROJECT, - SUBSCRIPTION_NAME: `shared-worker-subscription`, - TOPIC_NAME: `book-process-queue` - }; - mocks.config.get = function (key) { - return this[key]; - }; - mocks.subscription = { - on: sinon.stub() - }; - mocks.topic = { - subscribe: sinon.stub().callsArgWith(2, null, mocks.subscription), - publish: sinon.stub().callsArg(1) - }; - mocks.pubsub = { - createTopic: sinon.stub().callsArgWith(1, null, mocks.topic), - topic: sinon.stub().returns(mocks.topic) - }; - mocks.Pubsub = sinon.stub().returns(mocks.pubsub); - mocks.logging = { - info: sinon.stub(), - error: sinon.stub() - }; - // Load background.js with provided mocks - background = proxyquire(`../lib/background`, { - '@google-cloud/pubsub': mocks.Pubsub, - '../config': mocks.config, - './logging': mocks.logging - }); +test.beforeEach((t) => { + // Mock dependencies used by background.js + mocks.config = { + GCLOUD_PROJECT: process.env.GCLOUD_PROJECT, + SUBSCRIPTION_NAME: `shared-worker-subscription`, + TOPIC_NAME: `book-process-queue` + }; + mocks.config.get = function (key) { + return this[key]; + }; + mocks.subscription = { + on: sinon.stub() + }; + mocks.topic = { + subscribe: sinon.stub().callsArgWith(2, null, mocks.subscription), + publish: sinon.stub().callsArg(1) + }; + mocks.pubsub = { + createTopic: sinon.stub().callsArgWith(1, null, mocks.topic), + topic: sinon.stub().returns(mocks.topic) + }; + mocks.Pubsub = sinon.stub().returns(mocks.pubsub); + mocks.logging = { + info: sinon.stub(), + error: sinon.stub() + }; + // Load background.js with provided mocks + background = proxyquire(`../lib/background`, { + '@google-cloud/pubsub': mocks.Pubsub, + '../config': mocks.config, + './logging': mocks.logging + }); + + t.truthy( + mocks.Pubsub.calledOnce, + `Pubsub() should have been called once` + ); +}); - assert.ok( - mocks.Pubsub.calledOnce, - `Pubsub() should have been called once` +test.serial.cb(`should subscribe and log message`, (t) => { + // Setup + const testMessage = `test message`; + + // Run target functionality + background.subscribe((err, message) => { + // Assertions + t.truthy( + err === null, + `err should be null` + ); + t.is(message, testMessage, `should have message`); + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.subscribe.calledOnce, + `topic.subscribe should have been called once` + ); + t.is( + mocks.topic.subscribe.firstCall.args[0], + `shared-worker-subscription`, + `topic.subscribe() should have been called with the right arguments` ); + t.deepEqual( + mocks.topic.subscribe.firstCall.args[1], + { + autoAck: true + }, + `topic.subscribe() should have been called with the right arguments` + ); + t.truthy( + mocks.subscription.on.calledTwice, + `subscription.on should have been called twice` + ); + t.is( + mocks.subscription.on.firstCall.args[0], + `message`, + `subscription.on() should have been called with the right arguments` + ); + t.truthy( + typeof mocks.subscription.on.firstCall.args[1] === `function`, + `subscription.on() should have been called with the right arguments` + ); + t.is( + mocks.subscription.on.secondCall.args[0], + `error`, + `subscription.on() should have been called with the right arguments` + ); + t.truthy( + typeof mocks.subscription.on.secondCall.args[1] === `function`, + `subscription.on() should have been called with the right arguments` + ); + t.end(); }); - describe(`subscribe()`, () => { - it(`should subscribe and log message`, (done) => { - // Setup - const testMessage = `test message`; + // Trigger a message + setTimeout(() => { + mocks.subscription.on.firstCall.args[1]({ + data: testMessage + }); + }, 10); +}); - // Run target functionality - background.subscribe((err, message) => { - // Assertions - assert.ok( - err === null, - `err should be null` - ); - assert.equal(message, testMessage, `should have message`); - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.subscribe.calledOnce, - `topic.subscribe should have been called once` - ); - assert.equal( - mocks.topic.subscribe.firstCall.args[0], - `shared-worker-subscription`, - `topic.subscribe() should have been called with the right arguments` - ); - assert.deepEqual( - mocks.topic.subscribe.firstCall.args[1], - { - autoAck: true, - reuseExisting: true - }, - `topic.subscribe() should have been called with the right arguments` - ); - assert.ok( - mocks.subscription.on.calledTwice, - `subscription.on should have been called twice` - ); - assert.equal( - mocks.subscription.on.firstCall.args[0], - `message`, - `subscription.on() should have been called with the right arguments` - ); - assert.ok( - typeof mocks.subscription.on.firstCall.args[1] === `function`, - `subscription.on() should have been called with the right arguments` - ); - assert.equal( - mocks.subscription.on.secondCall.args[0], - `error`, - `subscription.on() should have been called with the right arguments` - ); - assert.ok( - typeof mocks.subscription.on.secondCall.args[1] === `function`, - `subscription.on() should have been called with the right arguments` - ); - done(); - }); +test.serial.cb(`should return topic error, if any`, (t) => { + // Setup + const testErrorMsg = `test error`; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); - // Trigger a message - setTimeout(() => { - mocks.subscription.on.firstCall.args[1]({ - data: testMessage - }); - }, 10); - }); - it(`should return topic error, if any`, (done) => { - // Setup - const testErrorMsg = `test error`; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + // Run target functionality + background.subscribe((data) => { + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.is(data, testErrorMsg); + t.is( + mocks.topic.subscribe.callCount, + 0, + `topic.subscribe() should NOT have been called` + ); + t.is( + mocks.subscription.on.callCount, + 0, + `subscription.on() should NOT have been called` + ); + t.end(); + }); +}); - // Run target functionality - background.subscribe((data) => { - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.equal(data, testErrorMsg); - assert.equal( - mocks.topic.subscribe.callCount, - 0, - `topic.subscribe() should NOT have been called` - ); - assert.equal( - mocks.subscription.on.callCount, - 0, - `subscription.on() should NOT have been called` - ); - done(); - }); - }); - it(`should return subscription error, if any`, (done) => { - // Setup - const testErrorMsg = `test error`; - mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); +test.serial.cb(`should return subscription error, if any`, (t) => { + // Setup + const testErrorMsg = `test error`; + mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); - // Run target functionality - background.subscribe((data) => { - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.subscribe.calledOnce, - `topic.subscribe should have been called once` - ); - assert.equal( - mocks.topic.subscribe.firstCall.args[0], - `shared-worker-subscription`, - `topic.subscribe() should have been called with the right arguments` - ); - assert.deepEqual( - mocks.topic.subscribe.firstCall.args[1], - { - autoAck: true, - reuseExisting: true - }, - `topic.subscribe() should have been called with the right arguments` - ); - assert.equal(data, testErrorMsg); - assert.equal( - mocks.subscription.on.callCount, - 0, - `subscription.on() should NOT have been called` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - done(); - }); - }); + // Run target functionality + background.subscribe((data) => { + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.subscribe.calledOnce, + `topic.subscribe should have been called once` + ); + t.is( + mocks.topic.subscribe.firstCall.args[0], + `shared-worker-subscription`, + `topic.subscribe() should have been called with the right arguments` + ); + t.deepEqual( + mocks.topic.subscribe.firstCall.args[1], + { + autoAck: true + }, + `topic.subscribe() should have been called with the right arguments` + ); + t.is(data, testErrorMsg); + t.is( + mocks.subscription.on.callCount, + 0, + `subscription.on() should NOT have been called` + ); + t.is( + mocks.logging.info.callCount, + 0, + `logging.info() should NOT have been called` + ); + t.end(); }); +}); - describe(`queueBook()`, () => { - it(`should queue a book and log message`, () => { - // Setup - const testBookId = 1; +test.serial(`should queue a book and log message`, (t) => { + // Setup + const testBookId = 1; - // Run target functionality - background.queueBook(testBookId); + // Run target functionality + background.queueBook(testBookId); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.ok( - mocks.logging.info.calledOnce, - `logging.info() should have been called` - ); - assert.equal( - mocks.logging.info.firstCall.args[0], - `Book ${testBookId} queued for background processing`, - `logging.info() should have been called with the right arguments` - ); - }); - it(`should queue a book and log message even if topic exists`, () => { - // Setup - const testBookId = 1; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { - code: 409 - }); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); + t.deepEqual( + mocks.topic.publish.firstCall.args[0], + { + data: { + action: `processBook`, + bookId: testBookId + } + }, + `topic.publish() should have been called with the right arguments` + ); + t.truthy( + mocks.logging.info.calledOnce, + `logging.info() should have been called` + ); + t.is( + mocks.logging.info.firstCall.args[0], + `Book ${testBookId} queued for background processing`, + `logging.info() should have been called with the right arguments` + ); +}); - // Run target functionality - background.queueBook(testBookId); +test.serial(`should queue a book and log message even if topic exists`, (t) => { + // Setup + const testBookId = 1; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { + code: 409 + }); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.ok( - mocks.pubsub.topic.calledOnce, - `pubsub.topic() should have been called once` - ); - assert.equal( - mocks.pubsub.topic.firstCall.args[0], - `book-process-queue`, - `pubsub.topic() should have been called with the right arguments` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.ok( - mocks.logging.info.calledOnce, - `logging.info() should have been called` - ); - assert.equal( - mocks.logging.info.firstCall.args[0], - `Book ${testBookId} queued for background processing`, - `logging.info() should have been called with the right arguments` - ); - }); - it(`should log error if cannot get topic`, () => { - // Setup - const testBookId = 1; - const testErrorMsg = `test error`; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + // Run target functionality + background.queueBook(testBookId); - // Run target functionality - background.queueBook(testBookId); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.truthy( + mocks.pubsub.topic.calledOnce, + `pubsub.topic() should have been called once` + ); + t.is( + mocks.pubsub.topic.firstCall.args[0], + `book-process-queue`, + `pubsub.topic() should have been called with the right arguments` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); + t.deepEqual( + mocks.topic.publish.firstCall.args[0], + { + data: { + action: `processBook`, + bookId: testBookId + } + }, + `topic.publish() should have been called with the right arguments` + ); + t.truthy( + mocks.logging.info.calledOnce, + `logging.info() should have been called` + ); + t.is( + mocks.logging.info.firstCall.args[0], + `Book ${testBookId} queued for background processing`, + `logging.info() should have been called with the right arguments` + ); +}); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.equal( - mocks.topic.publish.callCount, - 0, - `topic.publish() should NOT have been called` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - assert.ok( - mocks.logging.error.calledOnce, - `logging.error() should have been called` - ); - assert.deepEqual( - mocks.logging.error.firstCall.args, - [`Error occurred while getting pubsub topic`, testErrorMsg], - `logging.error() should have been called with the right arguments` - ); - }); - it(`should log error if cannot publish message`, () => { - // Setup - const testBookId = 1; - const testErrorMsg = `test error`; - mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); +test.serial(`should log error if cannot get topic`, (t) => { + // Setup + const testBookId = 1; + const testErrorMsg = `test error`; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); - // Run target functionality - background.queueBook(testBookId); + // Run target functionality + background.queueBook(testBookId); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - assert.ok( - mocks.logging.error.calledOnce, - `logging.error() should have been called` - ); - assert.deepEqual( - mocks.logging.error.firstCall.args, - [`Error occurred while queuing background task`, testErrorMsg], - `logging.error() should have been called with the right arguments` - ); - }); - }); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.is( + mocks.topic.publish.callCount, + 0, + `topic.publish() should NOT have been called` + ); + t.is( + mocks.logging.info.callCount, + 0, + `logging.info() should NOT have been called` + ); + t.truthy( + mocks.logging.error.calledOnce, + `logging.error() should have been called` + ); +}); + +test.serial(`should log error if cannot publish message`, (t) => { + // Setup + const testBookId = 1; + const testErrorMsg = `test error`; + mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); + + // Run target functionality + background.queueBook(testBookId); + + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); }); diff --git a/7-gce/test/cloudsql.test.js b/7-gce/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/7-gce/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/7-gce/test/crud.test.js b/7-gce/test/crud.test.js deleted file mode 100644 index 0f07993e9c..0000000000 --- a/7-gce/test/crud.test.js +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .field(`title`, `my book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .field(`title`, `my other book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/7-gce/test/datastore.test.js b/7-gce/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/7-gce/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/7-gce/test/index.js b/7-gce/test/index.js deleted file mode 100644 index 9f963c4649..0000000000 --- a/7-gce/test/index.js +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - let topicName; - - before(() => { - const appConfig = require(`../config`); - topicName = appConfig.get(`TOPIC_NAME`); - appConfig.set(`TOPIC_NAME`, `${topicName}-${config.test}`); - }); - - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); - require(`./worker.test`); - describe(`books/`, () => { - const appConfig = require(`../config`); - const DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - if (DATA_BACKEND === `datastore` || process.env.TEST_DATASTORE) { - require(`./api.test`)(`datastore`); - require(`./crud.test`)(`datastore`); - } - if (DATA_BACKEND === `cloudsql` || process.env.TEST_CLOUDSQL) { - require(`./api.test`)(`cloudsql`); - require(`./crud.test`)(`cloudsql`); - } - if (DATA_BACKEND === `mongodb` || process.env.TEST_MONGODB) { - require(`./api.test`)(`mongodb`); - require(`./crud.test`)(`mongodb`); - } - }); - if (!process.env.E2E_TESTS) { - describe(`lib/`, () => { - require(`./background.test`); - require(`./oauth2.test`); - }); - } - - afterEach(() => { - const appConfig = require(`../config`); - appConfig.set(`TOPIC_NAME`, topicName); - }); -}); diff --git a/7-gce/test/mongodb.test.js b/7-gce/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/7-gce/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/7-gce/test/oauth2.test.js b/7-gce/test/oauth2.test.js index 4f960e9ea5..a384a0f3ef 100644 --- a/7-gce/test/oauth2.test.js +++ b/7-gce/test/oauth2.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,201 +13,200 @@ 'use strict'; -const assert = require(`assert`); +const test = require(`ava`); const sinon = require(`sinon`); const request = require(`supertest`); const proxyquire = require(`proxyquire`).noPreserveCache(); -describe(`oauth2.js`, () => { - let passportMock; +const getPassportMock = () => { + return { + initialize: sinon.stub().returns((req, res, next) => { + next(); + }), + session: sinon.stub().returns((req, res, next) => { + next(); + }), + use: sinon.stub(), + serializeUser: sinon.stub(), + deserializeUser: sinon.stub(), + authenticate: sinon.stub().returns((req, res, next) => { + req.session.oauth2return = `/another/path`; + next(); + }) + }; +}; - beforeEach(() => { - passportMock = { - initialize: sinon.stub().returns((req, res, next) => { - next(); - }), - session: sinon.stub().returns((req, res, next) => { - next(); - }), - use: sinon.stub(), - serializeUser: sinon.stub(), - deserializeUser: sinon.stub(), - authenticate: sinon.stub().returns((req, res, next) => { - req.session.oauth2return = `/another/path`; - next(); - }) - }; +test.cb(`should start authorization`, (t) => { + const passportMock = getPassportMock(); + passportMock.authenticate = sinon.stub().returns((req, res, next) => { + t.is(req.session.oauth2return, `/some/path`); + res.redirect(`/auth/google/callback?code=foo`); }); - - it(`should start authorization`, (done) => { - passportMock.authenticate = sinon.stub().returns((req, res, next) => { - assert.equal(req.session.oauth2return, `/some/path`); - res.redirect(`/auth/google/callback?code=foo`); - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/login?return=%2Fsome%2Fpath`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /auth/google/callback?code=foo`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); - }); - - it(`should finish authorization`, (done) => { - const oauth2 = proxyquire(`../lib/oauth2`, { + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': oauth2 - }); - request(app) - .get(`/auth/google/callback?code=foo`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /another/path`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - assert.deepEqual( - oauth2.extractProfile({ - photos: [{ value: `image.jpg` }], - id: 1, - displayName: `Joe Developer` - }), - { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - ); - const serializeUser = passportMock.serializeUser.firstCall.args[0]; - const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; - const user = {}; - const obj = {}; - serializeUser(user, (err, _user) => { - assert.equal(err, null); - assert.strictEqual(_user, user); - }); - deserializeUser(obj, (err, _obj) => { - assert.equal(err, null); - assert.strictEqual(_obj, obj); - }); - }) - .end(done); + }) }); + request(app) + .get(`/auth/login?return=%2Fsome%2Fpath`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/auth\/google\/callback\?code=foo/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - it(`should logout`, (done) => { - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/logout`) - .expect(302) - .expect((response) => { - const text = response.text; - assert.equal(text.includes(`Redirecting to /`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); +test.cb(`should finish authorization`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock + }); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': oauth2 }); + request(app) + .get(`/auth/google/callback?code=foo`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/another\/path/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + t.deepEqual( + oauth2.extractProfile({ + photos: [{ value: `image.jpg` }], + id: 1, + displayName: `Joe Developer` + }), + { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + ); + const serializeUser = passportMock.serializeUser.firstCall.args[0]; + const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; + const user = {}; + const obj = {}; + serializeUser(user, (err, _user) => { + t.is(err, null); + t.is(_user, user); + }); + deserializeUser(obj, (err, _obj) => { + t.is(err, null); + t.is(_obj, obj); + }); + }) + .end(t.end); +}); - it(`should require authentication`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { +test.cb(`should logout`, (t) => { + const passportMock = getPassportMock(); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: {}, - session: {} - }; - const res = { - redirect: sinon.stub() - }; - const next = sinon.stub(); - oauth2.required(req, res, next); - assert(next.calledOnce); + }) + }); + request(app) + .get(`/auth/logout`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \//); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - req.user = undefined; - oauth2.required(req, res, next); - assert(next.calledOnce); - assert.equal(req.session.oauth2return, req.originalUrl); - assert(res.redirect.calledOnce); - assert.equal(res.redirect.firstCall.args[0], `/auth/login`); +test(`should require authentication`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: {}, + session: {} + }; + const res = { + redirect: sinon.stub() + }; + const next = sinon.stub(); + oauth2.required(req, res, next); + t.true(next.calledOnce); - it(`should add template variables`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { - passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - }; - const res = { - locals: {} - }; - const next = sinon.stub(); - oauth2.template(req, res, next); - assert(next.calledOnce); - assert.strictEqual(res.locals.profile, req.user); - assert.equal( - res.locals.login, - `/auth/login?return=${encodeURIComponent(req.originalUrl)}` - ); - assert.equal( - res.locals.logout, - `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` - ); + req.user = undefined; + oauth2.required(req, res, next); + t.true(next.calledOnce); + t.is(req.session.oauth2return, req.originalUrl); + t.true(res.redirect.calledOnce); + t.is(res.redirect.firstCall.args[0], `/auth/login`); +}); + +test(`should add template variables`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + }; + const res = { + locals: {} + }; + const next = sinon.stub(); + oauth2.template(req, res, next); + t.true(next.calledOnce); + t.is(res.locals.profile, req.user); + t.is( + res.locals.login, `/auth/login?return=${encodeURIComponent(req.originalUrl)}` + ); + t.is( + res.locals.logout, `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` + ); }); diff --git a/7-gce/test/worker.test.js b/7-gce/test/worker.test.js index 191cb203e2..60c65c8fe9 100644 --- a/7-gce/test/worker.test.js +++ b/7-gce/test/worker.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,18 +13,18 @@ 'use strict'; -const assert = require('assert'); -const config = require('./config.worker'); -const path = require('path'); -const proxyquire = require('proxyquire'); -const sinon = require('sinon'); -const supertest = require('supertest'); -const utils = require('nodejs-repo-tools'); +const testConfig = require(`./_test-config.worker`); +const path = require(`path`); +const proxyquire = require(`proxyquire`); +const sinon = require(`sinon`); +const supertest = require(`supertest`); +const test = require(`ava`); +const utils = require(`nodejs-repo-tools`); const projectId = process.env.GCLOUD_PROJECT; function getUrl () { - return `http://${config.test}-dot-worker-dot-${projectId}.appspot-preview.com`; + return `http://${testConfig.test}-dot-worker-dot-${projectId}.appspot-preview.com`; } function getRequest () { @@ -34,68 +34,66 @@ function getRequest () { return supertest(proxyquire(path.join(__dirname, `../worker`), {}).app); } -describe(`worker.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should return number of processed books`, (done) => { - getRequest(config) - .get(`/`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`This worker has processed`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.serial.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} - it(`should do a health check`, (done) => { - getRequest(config) - .get(`/_ah/health`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `ok`); - }) - .end(done); - }); +test.serial.cb(`should return number of processed books`, (t) => { + getRequest(testConfig) + .get(`/`) + .expect(200) + .expect((response) => { + t.regex(response.text, /This worker has processed/); + }) + .end(t.end); +}); + +test.serial.cb(`should do a health check`, (t) => { + getRequest(testConfig) + .get(`/_ah/health`) + .expect(200) + .expect((response) => { + t.is(response.text, `ok`); + }) + .end(t.end); +}); - it(`should process a book`, (done) => { - const appConfig = require(`../config`); - const loggingStub = { - error: sinon.stub(), - info: sinon.stub(), - warn: sinon.stub() - }; - const stubs = { - './lib/logging': loggingStub, - '@google/cloud-trace': { - start: sinon.stub(), - '@noCallThru': true - }, - '@google/cloud-debug': { - '@noCallThru': true - } - }; - stubs[`./books/model-${appConfig.get('DATA_BACKEND')}`] = { - read: (bookId, cb) => { - cb(null, {}); - }, - update: (bookId, book, queueBook, cb) => { - cb(); - } - }; - const worker = proxyquire(path.join(__dirname, `../worker`), stubs); - const processBook = worker.processBook; +test.serial.cb(`should process a book`, (t) => { + const appConfig = require(`../config`); + const loggingStub = { + error: sinon.stub(), + info: sinon.stub(), + warn: sinon.stub() + }; + const stubs = { + './lib/logging': loggingStub, + '@google/cloud-trace': { + start: sinon.stub(), + '@noCallThru': true + }, + '@google/cloud-debug': { + '@noCallThru': true + } + }; + stubs[`./books/model-${appConfig.get('DATA_BACKEND')}`] = { + read: (bookId, cb) => { + cb(null, {}); + }, + update: (bookId, book, queueBook, cb) => { + cb(); + } + }; + const worker = proxyquire(path.join(__dirname, `../worker`), stubs); + const processBook = worker.processBook; - processBook(1, (err, bookId) => { - if (err) { - return done(err); - } - assert(loggingStub.info.calledOnce); - assert.equal(loggingStub.info.firstCall.args[0], `Updated book 1`); - done(); - }); + processBook(1, (err, bookId) => { + if (err) { + return t.end(err); + } + t.true(loggingStub.info.calledOnce); + t.is(loggingStub.info.firstCall.args[0], `Updated book 1`); + t.end(); }); }); diff --git a/7-gce/views/base.jade b/7-gce/views/base.jade index 4d1c80d45d..72a8e8a7c1 100644 --- a/7-gce/views/base.jade +++ b/7-gce/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/7-gce/views/books/form.jade b/7-gce/views/books/form.jade index 8368619eeb..2a96f6d50e 100644 --- a/7-gce/views/books/form.jade +++ b/7-gce/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/7-gce/views/books/list.jade b/7-gce/views/books/list.jade index dd453386ba..f7e901c3b3 100644 --- a/7-gce/views/books/list.jade +++ b/7-gce/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/7-gce/views/books/view.jade b/7-gce/views/books/view.jade index fb0123b05c..92776d30d1 100644 --- a/7-gce/views/books/view.jade +++ b/7-gce/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/7-gce/worker.js b/7-gce/worker.js index dd576a1f4b..4cf0879ec3 100644 --- a/7-gce/worker.js +++ b/7-gce/worker.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,7 +16,7 @@ // Activate Google Cloud Trace and Debug when in production if (process.env.NODE_ENV === 'production') { require('@google/cloud-trace').start(); - require('@google/cloud-debug'); + require('@google/cloud-debug').start(); } const request = require('request'); diff --git a/7-gce/worker.yaml b/7-gce/worker.yaml index fd12ec7001..190799b468 100644 --- a/7-gce/worker.yaml +++ b/7-gce/worker.yaml @@ -1,4 +1,4 @@ -# Copyright 2015-2016, Google, Inc. +# Copyright 2017, Google, Inc. # 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 @@ -18,8 +18,4 @@ env: flex env_variables: SCRIPT: worker.js - -# Temporary setting to keep gcloud from uploading node_modules -skip_files: - - ^node_modules$ # [END yaml] diff --git a/optional-container-engine/app.js b/optional-container-engine/app.js index 3cc8054e76..3f4a49da08 100644 --- a/optional-container-engine/app.js +++ b/optional-container-engine/app.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -16,7 +16,7 @@ // Activate Google Cloud Trace and Debug when in production if (process.env.NODE_ENV === 'production') { require('@google/cloud-trace').start(); - require('@google/cloud-debug'); + require('@google/cloud-debug').start(); } const path = require('path'); @@ -48,7 +48,7 @@ const sessionConfig = { // In production use the App Engine Memcache instance to store session data, // otherwise fallback to the default MemoryStore in development. -if (config.get('NODE_ENV') === 'production') { +if (config.get('NODE_ENV') === 'production' && config.get('MEMCACHE_URL')) { sessionConfig.store = new MemcachedStore({ hosts: [config.get('MEMCACHE_URL')] }); diff --git a/optional-container-engine/books/api.js b/optional-container-engine/books/api.js index a92b261658..9e9691fe5a 100644 --- a/optional-container-engine/books/api.js +++ b/optional-container-engine/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -15,10 +15,9 @@ const express = require('express'); const bodyParser = require('body-parser'); -const config = require('../config'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/optional-container-engine/books/crud.js b/optional-container-engine/books/crud.js index d53889b663..46ef8e6846 100644 --- a/optional-container-engine/books/crud.js +++ b/optional-container-engine/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,12 +14,11 @@ 'use strict'; const express = require('express'); -const config = require('../config'); const images = require('../lib/images'); const oauth2 = require('../lib/oauth2'); function getModel () { - return require(`./model-${config.get('DATA_BACKEND')}`); + return require(`./model-${require('../config').get('DATA_BACKEND')}`); } const router = express.Router(); diff --git a/optional-container-engine/books/model-cloudsql.js b/optional-container-engine/books/model-cloudsql.js index 547b1adeae..1f7bded298 100644 --- a/optional-container-engine/books/model-cloudsql.js +++ b/optional-container-engine/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -18,23 +18,20 @@ const mysql = require('mysql'); const config = require('../config'); const background = require('../lib/background'); -function getConnection () { - const options = { - user: config.get('MYSQL_USER'), - password: config.get('MYSQL_PASSWORD'), - database: 'bookshelf' - }; - - if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { - options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; - } +const options = { + user: config.get('MYSQL_USER'), + password: config.get('MYSQL_PASSWORD'), + database: 'bookshelf' +}; - return mysql.createConnection(options); +if (config.get('INSTANCE_CONNECTION_NAME') && config.get('NODE_ENV') === 'production') { + options.socketPath = `/cloudsql/${config.get('INSTANCE_CONNECTION_NAME')}`; } +const connection = mysql.createConnection(options); + function list (limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` LIMIT ? OFFSET ?', [limit, token], (err, results) => { @@ -46,12 +43,10 @@ function list (limit, token, cb) { cb(null, results, hasMore); } ); - connection.end(); } function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `createdById` = ? LIMIT ? OFFSET ?', [userId, limit, token], @@ -63,11 +58,9 @@ function listBy (userId, limit, token, cb) { const hasMore = results.length === limit ? token + results.length : false; cb(null, results, hasMore); }); - connection.end(); } function create (data, queueBook, cb) { - const connection = getConnection(); connection.query('INSERT INTO `books` SET ?', data, (err, res) => { if (err) { cb(err); @@ -78,11 +71,9 @@ function create (data, queueBook, cb) { } read(res.insertId, cb); }); - connection.end(); } function read (id, cb) { - const connection = getConnection(); connection.query( 'SELECT * FROM `books` WHERE `id` = ?', id, (err, results) => { if (err) { @@ -98,11 +89,9 @@ function read (id, cb) { } cb(null, results[0]); }); - connection.end(); } function update (id, data, queueBook, cb) { - const connection = getConnection(); connection.query( 'UPDATE `books` SET ? WHERE `id` = ?', [data, id], (err) => { if (err) { @@ -114,13 +103,10 @@ function update (id, data, queueBook, cb) { } read(id, cb); }); - connection.end(); } function _delete (id, cb) { - const connection = getConnection(); connection.query('DELETE FROM `books` WHERE `id` = ?', id, cb); - connection.end(); } module.exports = { diff --git a/optional-container-engine/books/model-datastore.js b/optional-container-engine/books/model-datastore.js index 7ae8836667..1e8179f484 100644 --- a/optional-container-engine/books/model-datastore.js +++ b/optional-container-engine/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -39,8 +39,8 @@ const kind = 'Book'; // property: value // } function fromDatastore (obj) { - obj.data.id = obj.key.id; - return obj.data; + obj.id = obj[Datastore.KEY].id; + return obj; } // Translates from the application's format to the datastore's @@ -97,7 +97,7 @@ function list (limit, token, cb) { cb(err); return; } - const hasMore = entities.length === limit ? nextQuery.startVal : false; + const hasMore = nextQuery.moreResults !== Datastore.NO_MORE_RESULTS ? nextQuery.endCursor : false; cb(null, entities.map(fromDatastore), hasMore); }); } @@ -115,7 +115,7 @@ function listBy (userId, limit, token, cb) { cb(err); return; } - const hasMore = entities.length === limit ? nextQuery.startVal : false; + const hasMore = nextQuery.moreResults !== Datastore.NO_MORE_RESULTS ? nextQuery.endCursor : false; cb(null, entities.map(fromDatastore), hasMore); }); } diff --git a/optional-container-engine/books/model-mongodb.js b/optional-container-engine/books/model-mongodb.js index 53699b56b3..01016bf14f 100644 --- a/optional-container-engine/books/model-mongodb.js +++ b/optional-container-engine/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -77,7 +77,7 @@ function list (limit, token, cb) { }); } -function listBy (userid, limit, token, cb) { +function listBy (userId, limit, token, cb) { token = token ? parseInt(token, 10) : 0; if (isNaN(token)) { cb(new Error('invalid token')); @@ -88,7 +88,7 @@ function listBy (userid, limit, token, cb) { cb(err); return; } - collection.find({ createdById: userid }) + collection.find({ createdById: userId }) .skip(token) .limit(limit) .toArray((err, results) => { @@ -166,7 +166,7 @@ function update (id, data, queueBook, cb) { if (queueBook) { background.queueBook(id); } - return read(id, cb); + read(id, cb); } ); }); diff --git a/optional-container-engine/config.js b/optional-container-engine/config.js index 05dd453ad4..91b0dedfee 100644 --- a/optional-container-engine/config.js +++ b/optional-container-engine/config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -26,6 +26,7 @@ nconf 'CLOUD_BUCKET', 'DATA_BACKEND', 'GCLOUD_PROJECT', + 'MEMCACHE_URL', 'MONGO_URL', 'MONGO_COLLECTION', 'MYSQL_USER', @@ -70,9 +71,9 @@ nconf OAUTH2_CLIENT_SECRET: 'your-client-secret', OAUTH2_CALLBACK: 'http://localhost:8080/auth/google/callback', - // Port the HTTP server PORT: 8080, + // Set this a secret string of your choosing SECRET: 'keyboardcat', SUBSCRIPTION_NAME: 'shared-worker-subscription', diff --git a/optional-container-engine/lib/background.js b/optional-container-engine/lib/background.js index 2cb58a03d8..6d2dd8f91b 100644 --- a/optional-container-engine/lib/background.js +++ b/optional-container-engine/lib/background.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -52,7 +52,7 @@ function subscribe (cb) { cb(null, message.data); } function handleError (err) { - console.error(err); + logging.error(err); } getTopic((err, topic) => { @@ -62,8 +62,7 @@ function subscribe (cb) { } topic.subscribe(subscriptionName, { - autoAck: true, - reuseExisting: true + autoAck: true }, (err, sub) => { if (err) { cb(err); diff --git a/optional-container-engine/lib/images.js b/optional-container-engine/lib/images.js index 6c8b8feb33..715f674c4f 100644 --- a/optional-container-engine/lib/images.js +++ b/optional-container-engine/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -67,7 +67,11 @@ function sendUploadToGCS (req, res, next) { const gcsname = Date.now() + req.file.originalname; const file = bucket.file(gcsname); - const stream = file.createWriteStream(); + const stream = file.createWriteStream({ + metadata: { + contentType: req.file.mimetype + } + }); stream.on('error', (err) => { req.file.cloudStorageError = err; @@ -84,15 +88,13 @@ function sendUploadToGCS (req, res, next) { } // Multer handles parsing multipart/form-data requests. -// This instance is configured to store images in memory and re-name to avoid -// conflicting with existing objects. This makes it straightforward to upload -// to Cloud Storage. -const multer = require('multer')({ - inMemory: true, - fileSize: 5 * 1024 * 1024, // no larger than 5mb - rename: (fieldname, filename) => { - // generate a unique filename - return filename.replace(/\W+/g, '-').toLowerCase() + Date.now(); +// This instance is configured to store images in memory. +// This makes it straightforward to upload to Cloud Storage. +const Multer = require('multer'); +const multer = Multer({ + storage: Multer.MemoryStorage, + limits: { + fileSize: 5 * 1024 * 1024 // no larger than 5mb } }); diff --git a/optional-container-engine/lib/logging.js b/optional-container-engine/lib/logging.js index a2a436db0a..15ad77d1eb 100644 --- a/optional-container-engine/lib/logging.js +++ b/optional-container-engine/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/optional-container-engine/lib/oauth2.js b/optional-container-engine/lib/oauth2.js index 9ee3a72c3e..fa63e72703 100644 --- a/optional-container-engine/lib/oauth2.js +++ b/optional-container-engine/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -21,7 +21,7 @@ const passport = require('passport'); const GoogleStrategy = require('passport-google-oauth20').Strategy; function extractProfile (profile) { - let imageUrl = ``; + let imageUrl = ''; if (profile.photos && profile.photos.length) { imageUrl = profile.photos[0].value; } diff --git a/optional-container-engine/package.json b/optional-container-engine/package.json index 865b220ac1..7d9a4315e4 100644 --- a/optional-container-engine/package.json +++ b/optional-container-engine/package.json @@ -1,16 +1,13 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "start": "node ${SCRIPT:-app.js}", - "monitor": "nodemon ${SCRIPT:-app.js}", - "deploy": "gcloud app deploy app.yaml worker.yaml", - "mocha": "mocha test/index.js -t 30000", - "lint": "semistandard \"**/*.js\"", - "test": "npm run lint && npm run mocha", + "test": "ava -t 30s --tap test/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -26,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -40,36 +41,36 @@ ] }, "dependencies": { - "@google/cloud-debug": "^0.8.3", - "@google/cloud-trace": "^0.5.5", - "@google-cloud/datastore": "^0.1.1", - "@google-cloud/storage": "^0.1.1", - "@google-cloud/pubsub": "^0.2.0", - "async": "^2.0.1", - "body-parser": "^1.15.2", - "connect-memcached": "^0.2.0", - "express": "^4.14.0", - "express-session": "^1.14.0", - "express-winston": "^2.0.0", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "passport": "^0.3.2", - "passport-google-oauth20": "^1.0.0", - "prompt": "^1.0.0", - "request": "^2.74.0", - "winston": "^2.2.0" + "@google-cloud/debug-agent": "~1.0.0", + "@google-cloud/trace-agent": "~1.0.4", + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "@google-cloud/pubsub": "~0.10.0", + "async": "~2.3.0", + "body-parser": "~1.17.1", + "connect-memcached": "~0.2.0", + "express": "~4.15.2", + "express-session": "~1.15.2", + "express-winston": "~2.3.0", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "passport": "~0.3.2", + "passport-google-oauth20": "~1.0.0", + "prompt": "~1.0.0", + "request": "~2.81.0", + "winston": "~2.3.1" }, "devDependencies": { - "mocha": "^2.4.5", - "proxyquire": "^1.7.4", - "semistandard": "^7.0.5", - "sinon": "^1.17.3", - "supertest": "^1.2.0" + "ava": "~0.19.1", + "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "supertest": "~3.0.0", + "tap-dot": "~1.0.5" }, "engines": { "node": ">=4.3.2" diff --git a/optional-container-engine/test/_api-tests.js b/optional-container-engine/test/_api-tests.js new file mode 100644 index 0000000000..a6f3ad270b --- /dev/null +++ b/optional-container-engine/test/_api-tests.js @@ -0,0 +1,69 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `beep` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `beep`); + }) + .end(t.end); + }); + + test.serial.cb(`should list books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + getRequest(testConfig) + .get(`/api/books`) + .expect(200) + .expect((response) => { + t.true(Array.isArray(response.body.items)); + t.true(response.body.items.length >= 1); + }) + .end(t.end); + }, 1000); + }); + + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .delete(`/api/books/${id}/`) + // .expect(200) + .expect((response) => { + t.is(response.text, `OK`); + }) + .end(t.end); + }); + + test.always.after(() => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + }); +}; diff --git a/optional-container-engine/test/_crud-tests.js b/optional-container-engine/test/_crud-tests.js new file mode 100644 index 0000000000..aaf1513cc6 --- /dev/null +++ b/optional-container-engine/test/_crud-tests.js @@ -0,0 +1,193 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const getRequest = require(`nodejs-repo-tools`).getRequest; +const test = require(`ava`); + +module.exports = (DATA_BACKEND) => { + let originalDataBackend, id, testConfig, appConfig; + + test.before(() => { + testConfig = require(`./_test-config`); + appConfig = require(`../config`); + originalDataBackend = appConfig.get(`DATA_BACKEND`); + appConfig.set(`DATA_BACKEND`, DATA_BACKEND); + }); + + // setup a book + test.serial.cb(`should create a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should show a list of books`, (t) => { + // Give Datastore time to become consistent + setTimeout(() => { + const expected = /
/; + getRequest(testConfig) + .get(`/books`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }, 2000); + }); + + test.serial.cb(`should handle error`, (t) => { + getRequest(testConfig) + .get(`/books`) + .query({ pageToken: `badrequest` }) + .expect(500) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + test.serial.cb(`should post to add book form`, (t) => { + const expected = /Redirecting to \/books\//; + getRequest(testConfig) + .post(`/books/add`) + .field(`title`, `my book`) + .expect(302) + .expect((response) => { + const location = response.headers.location; + const idPart = location.replace(`/books/`, ``); + if (DATA_BACKEND !== `mongodb`) { + id = parseInt(idPart, 10); + } else { + id = idPart; + } + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show add book form`, (t) => { + const expected = /Add book/; + getRequest(testConfig) + .get(`/books/add`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // delete the book + test.serial.cb((t) => { + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); + + // setup a book + test.serial.cb(`should delete a book`, (t) => { + getRequest(testConfig) + .post(`/api/books`) + .send({ title: `my book` }) + .expect(200) + .expect((response) => { + id = response.body.id; + t.truthy(response.body.id); + t.is(response.body.title, `my book`); + }) + .end(t.end); + }); + + test.serial.cb(`should update a book`, (t) => { + const expected = new RegExp(`Redirecting to /books/${id}`); + getRequest(testConfig) + .post(`/books/${id}/edit`) + .field(`title`, `my other book`) + .expect(302) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show edit book form`, (t) => { + const expected = + //; + getRequest(testConfig) + .get(`/books/${id}/edit`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should show a book`, (t) => { + const expected = /

my other book <\/small><\/h4>/; + getRequest(testConfig) + .get(`/books/${id}`) + .expect(200) + .expect((response) => { + t.regex(response.text, expected); + }) + .end(t.end); + }); + + test.serial.cb(`should delete a book`, (t) => { + const expected = /Redirecting to \/books/; + getRequest(testConfig) + .get(`/books/${id}/delete`) + .expect(302) + .expect((response) => { + id = undefined; + t.regex(response.text, expected); + }) + .end(t.end); + }); + + // clean up + test.always.after.cb((t) => { + appConfig.set(`DATA_BACKEND`, originalDataBackend); + + if (id) { + getRequest(testConfig) + .delete(`/api/books/${id}`) + .expect(200) + .end(t.end); + } else { + t.end(); + } + }); +}; diff --git a/optional-container-engine/test/config.js b/optional-container-engine/test/_test-config.js similarity index 75% rename from optional-container-engine/test/config.js rename to optional-container-engine/test/_test-config.js index 6b235cd304..8ceeb3e988 100644 --- a/optional-container-engine/test/config.js +++ b/optional-container-engine/test/_test-config.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -14,7 +14,9 @@ 'use strict'; const path = require(`path`); -const test = `optional-container-engine`; +const test = `optional`; + +const PORT = 8088; module.exports = { test: test, @@ -22,9 +24,11 @@ module.exports = { cmd: `node`, args: [`app.js`], msg: `Bookshelf`, - port: 8088, + port: PORT, + url: `http://localhost:${PORT}`, env: { - SUBSCRIPTION_NAME: `${test}-shared-worker-subscription`, - TOPIC_NAME: `${test}-book-process-queue` + PORT: PORT, + SUBSCRIPTION_NAME: `shared-worker-subscription-${test}`, + TOPIC_NAME: `book-process-queue-${test}` } }; diff --git a/optional-container-engine/test/config.worker.js b/optional-container-engine/test/_test-config.worker.js similarity index 96% rename from optional-container-engine/test/config.worker.js rename to optional-container-engine/test/_test-config.worker.js index 67655a5e67..9610d6b919 100644 --- a/optional-container-engine/test/config.worker.js +++ b/optional-container-engine/test/_test-config.worker.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/optional-container-engine/test/api.test.js b/optional-container-engine/test/api.test.js deleted file mode 100644 index 151759d8a8..0000000000 --- a/optional-container-engine/test/api.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`api.js`, () => { - let ORIG_DATA_BACKEND; - let id; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - it(`should create a book`, (done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `beep` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `beep`); - }) - .end(done); - }); - - it(`should list books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - utils.getRequest(config) - .get(`/api/books`) - .expect(200) - .expect((response) => { - assert.ok(Array.isArray(response.body.items)); - assert.ok(response.body.items.length >= 1); - }) - .end(done); - }, 1000); - }); - - it(`should delete a book`, (done) => { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `OK`); - }) - .end(done); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/optional-container-engine/test/app.test.js b/optional-container-engine/test/app.test.js index 3178f25eec..d1948edc8c 100644 --- a/optional-container-engine/test/app.test.js +++ b/optional-container-engine/test/app.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,94 +13,80 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config`); +const testConfig = require(`./_test-config`); const proxyquire = require(`proxyquire`).noPreserveCache(); const sinon = require(`sinon`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); -describe(`app.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should redirect / to /books`, (done) => { - utils.getRequest(config) - .get(`/`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /books`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} + +test.cb(`should redirect / to /books`, (t) => { + utils.getRequest(testConfig) + .get(`/`) + .expect(302) + .expect((response) => { + t.regex(response.text, /Redirecting to \/books/); + }) + .end(t.end); +}); - it(`should check config`, () => { - const nconfMock = { - argv: sinon.stub().returnsThis(), - env: sinon.stub().returnsThis(), - file: sinon.stub().returnsThis(), - defaults: sinon.stub().returnsThis(), - get: function (setting) { - return this[setting]; - } - }; - - function getMsg (setting) { - return `You must set ${setting} as an environment variable or in config.json!`; +test(`should check config`, (t) => { + const nconfMock = { + argv: sinon.stub().returnsThis(), + env: sinon.stub().returnsThis(), + file: sinon.stub().returnsThis(), + defaults: sinon.stub().returnsThis(), + get: function (setting) { + return this[setting]; } + }; - nconfMock.DATA_BACKEND = `datastore`; - - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`GCLOUD_PROJECT`)); - - nconfMock.GCLOUD_PROJECT = `project`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`CLOUD_BUCKET`)); - - nconfMock.CLOUD_BUCKET = `bucket`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_ID`)); - - nconfMock.OAUTH2_CLIENT_ID = `foo`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); - - nconfMock.OAUTH2_CLIENT_SECRET = `bar`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `cloudsql`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_USER`)); - nconfMock.MYSQL_USER = `user`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MYSQL_PASSWORD`)); - nconfMock.MYSQL_PASSWORD = `password`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - - nconfMock.DATA_BACKEND = `mongodb`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_URL`)); - nconfMock.MONGO_URL = `url`; - assert.throws(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }, Error, getMsg(`MONGO_COLLECTION`)); - nconfMock.MONGO_COLLECTION = `collection`; - assert.doesNotThrow(() => { - proxyquire(`../config`, { nconf: nconfMock }); - }); - }); + function getMsg (setting) { + return `You must set ${setting} as an environment variable or in config.json!`; + } + + const testFunc = () => { + proxyquire(`../config`, { nconf: nconfMock }); + }; + + nconfMock.DATA_BACKEND = `datastore`; + + t.throws(testFunc, Error, getMsg(`GCLOUD_PROJECT`)); + nconfMock.GCLOUD_PROJECT = `project`; + + t.throws(testFunc, Error, getMsg(`CLOUD_BUCKET`)); + nconfMock.CLOUD_BUCKET = `bucket`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_ID`)); + nconfMock.OAUTH2_CLIENT_ID = `foo`; + + t.throws(testFunc, Error, getMsg(`OAUTH2_CLIENT_SECRET`)); + nconfMock.OAUTH2_CLIENT_SECRET = `bar`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `cloudsql`; + + t.throws(testFunc, Error, getMsg(`MYSQL_USER`)); + nconfMock.MYSQL_USER = `user`; + + t.throws(testFunc, Error, getMsg(`MYSQL_PASSWORD`)); + nconfMock.MYSQL_PASSWORD = `password`; + + t.notThrows(testFunc); + + nconfMock.DATA_BACKEND = `mongodb`; + + t.throws(testFunc, Error, getMsg(`MONGO_URL`)); + nconfMock.MONGO_URL = `url`; + + t.throws(testFunc, Error, getMsg(`MONGO_COLLECTION`)); + nconfMock.MONGO_COLLECTION = `collection`; + + t.notThrows(testFunc); }); diff --git a/optional-container-engine/test/background.test.js b/optional-container-engine/test/background.test.js index 4027a5f2d4..bc63b26ac7 100644 --- a/optional-container-engine/test/background.test.js +++ b/optional-container-engine/test/background.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,415 +13,384 @@ 'use strict'; -const assert = require(`assert`); -const sinon = require(`sinon`); const proxyquire = require(`proxyquire`).noPreserveCache(); +const sinon = require(`sinon`); +const test = require(`ava`); + let background; const mocks = {}; -describe(`background.js`, () => { - beforeEach(() => { - // Mock dependencies used by background.js - mocks.config = { - GCLOUD_PROJECT: process.env.GCLOUD_PROJECT, - SUBSCRIPTION_NAME: `shared-worker-subscription`, - TOPIC_NAME: `book-process-queue` - }; - mocks.config.get = function (key) { - return this[key]; - }; - mocks.subscription = { - on: sinon.stub() - }; - mocks.topic = { - subscribe: sinon.stub().callsArgWith(2, null, mocks.subscription), - publish: sinon.stub().callsArg(1) - }; - mocks.pubsub = { - createTopic: sinon.stub().callsArgWith(1, null, mocks.topic), - topic: sinon.stub().returns(mocks.topic) - }; - mocks.Pubsub = sinon.stub().returns(mocks.pubsub); - mocks.logging = { - info: sinon.stub(), - error: sinon.stub() - }; - // Load background.js with provided mocks - background = proxyquire(`../lib/background`, { - '@google-cloud/pubsub': mocks.Pubsub, - '../config': mocks.config, - './logging': mocks.logging - }); +test.beforeEach((t) => { + // Mock dependencies used by background.js + mocks.config = { + GCLOUD_PROJECT: process.env.GCLOUD_PROJECT, + SUBSCRIPTION_NAME: `shared-worker-subscription`, + TOPIC_NAME: `book-process-queue` + }; + mocks.config.get = function (key) { + return this[key]; + }; + mocks.subscription = { + on: sinon.stub() + }; + mocks.topic = { + subscribe: sinon.stub().callsArgWith(2, null, mocks.subscription), + publish: sinon.stub().callsArg(1) + }; + mocks.pubsub = { + createTopic: sinon.stub().callsArgWith(1, null, mocks.topic), + topic: sinon.stub().returns(mocks.topic) + }; + mocks.Pubsub = sinon.stub().returns(mocks.pubsub); + mocks.logging = { + info: sinon.stub(), + error: sinon.stub() + }; + // Load background.js with provided mocks + background = proxyquire(`../lib/background`, { + '@google-cloud/pubsub': mocks.Pubsub, + '../config': mocks.config, + './logging': mocks.logging + }); + + t.truthy( + mocks.Pubsub.calledOnce, + `Pubsub() should have been called once` + ); +}); - assert.ok( - mocks.Pubsub.calledOnce, - `Pubsub() should have been called once` +test.serial.cb(`should subscribe and log message`, (t) => { + // Setup + const testMessage = `test message`; + + // Run target functionality + background.subscribe((err, message) => { + // Assertions + t.truthy( + err === null, + `err should be null` + ); + t.is(message, testMessage, `should have message`); + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.subscribe.calledOnce, + `topic.subscribe should have been called once` + ); + t.is( + mocks.topic.subscribe.firstCall.args[0], + `shared-worker-subscription`, + `topic.subscribe() should have been called with the right arguments` ); + t.deepEqual( + mocks.topic.subscribe.firstCall.args[1], + { + autoAck: true + }, + `topic.subscribe() should have been called with the right arguments` + ); + t.truthy( + mocks.subscription.on.calledTwice, + `subscription.on should have been called twice` + ); + t.is( + mocks.subscription.on.firstCall.args[0], + `message`, + `subscription.on() should have been called with the right arguments` + ); + t.truthy( + typeof mocks.subscription.on.firstCall.args[1] === `function`, + `subscription.on() should have been called with the right arguments` + ); + t.is( + mocks.subscription.on.secondCall.args[0], + `error`, + `subscription.on() should have been called with the right arguments` + ); + t.truthy( + typeof mocks.subscription.on.secondCall.args[1] === `function`, + `subscription.on() should have been called with the right arguments` + ); + t.end(); }); - describe(`subscribe()`, () => { - it(`should subscribe and log message`, (done) => { - // Setup - const testMessage = `test message`; + // Trigger a message + setTimeout(() => { + mocks.subscription.on.firstCall.args[1]({ + data: testMessage + }); + }, 10); +}); - // Run target functionality - background.subscribe((err, message) => { - // Assertions - assert.ok( - err === null, - `err should be null` - ); - assert.equal(message, testMessage, `should have message`); - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.subscribe.calledOnce, - `topic.subscribe should have been called once` - ); - assert.equal( - mocks.topic.subscribe.firstCall.args[0], - `shared-worker-subscription`, - `topic.subscribe() should have been called with the right arguments` - ); - assert.deepEqual( - mocks.topic.subscribe.firstCall.args[1], - { - autoAck: true, - reuseExisting: true - }, - `topic.subscribe() should have been called with the right arguments` - ); - assert.ok( - mocks.subscription.on.calledTwice, - `subscription.on should have been called twice` - ); - assert.equal( - mocks.subscription.on.firstCall.args[0], - `message`, - `subscription.on() should have been called with the right arguments` - ); - assert.ok( - typeof mocks.subscription.on.firstCall.args[1] === `function`, - `subscription.on() should have been called with the right arguments` - ); - assert.equal( - mocks.subscription.on.secondCall.args[0], - `error`, - `subscription.on() should have been called with the right arguments` - ); - assert.ok( - typeof mocks.subscription.on.secondCall.args[1] === `function`, - `subscription.on() should have been called with the right arguments` - ); - done(); - }); +test.serial.cb(`should return topic error, if any`, (t) => { + // Setup + const testErrorMsg = `test error`; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); - // Trigger a message - setTimeout(() => { - mocks.subscription.on.firstCall.args[1]({ - data: testMessage - }); - }, 10); - }); - it(`should return topic error, if any`, (done) => { - // Setup - const testErrorMsg = `test error`; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + // Run target functionality + background.subscribe((data) => { + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.is(data, testErrorMsg); + t.is( + mocks.topic.subscribe.callCount, + 0, + `topic.subscribe() should NOT have been called` + ); + t.is( + mocks.subscription.on.callCount, + 0, + `subscription.on() should NOT have been called` + ); + t.end(); + }); +}); - // Run target functionality - background.subscribe((data) => { - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.equal(data, testErrorMsg); - assert.equal( - mocks.topic.subscribe.callCount, - 0, - `topic.subscribe() should NOT have been called` - ); - assert.equal( - mocks.subscription.on.callCount, - 0, - `subscription.on() should NOT have been called` - ); - done(); - }); - }); - it(`should return subscription error, if any`, (done) => { - // Setup - const testErrorMsg = `test error`; - mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); +test.serial.cb(`should return subscription error, if any`, (t) => { + // Setup + const testErrorMsg = `test error`; + mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); - // Run target functionality - background.subscribe((data) => { - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right args` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.subscribe.calledOnce, - `topic.subscribe should have been called once` - ); - assert.equal( - mocks.topic.subscribe.firstCall.args[0], - `shared-worker-subscription`, - `topic.subscribe() should have been called with the right arguments` - ); - assert.deepEqual( - mocks.topic.subscribe.firstCall.args[1], - { - autoAck: true, - reuseExisting: true - }, - `topic.subscribe() should have been called with the right arguments` - ); - assert.equal(data, testErrorMsg); - assert.equal( - mocks.subscription.on.callCount, - 0, - `subscription.on() should NOT have been called` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - done(); - }); - }); + // Run target functionality + background.subscribe((data) => { + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right args` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.subscribe.calledOnce, + `topic.subscribe should have been called once` + ); + t.is( + mocks.topic.subscribe.firstCall.args[0], + `shared-worker-subscription`, + `topic.subscribe() should have been called with the right arguments` + ); + t.deepEqual( + mocks.topic.subscribe.firstCall.args[1], + { + autoAck: true + }, + `topic.subscribe() should have been called with the right arguments` + ); + t.is(data, testErrorMsg); + t.is( + mocks.subscription.on.callCount, + 0, + `subscription.on() should NOT have been called` + ); + t.is( + mocks.logging.info.callCount, + 0, + `logging.info() should NOT have been called` + ); + t.end(); }); +}); - describe(`queueBook()`, () => { - it(`should queue a book and log message`, () => { - // Setup - const testBookId = 1; +test.serial(`should queue a book and log message`, (t) => { + // Setup + const testBookId = 1; - // Run target functionality - background.queueBook(testBookId); + // Run target functionality + background.queueBook(testBookId); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.ok( - mocks.logging.info.calledOnce, - `logging.info() should have been called` - ); - assert.equal( - mocks.logging.info.firstCall.args[0], - `Book ${testBookId} queued for background processing`, - `logging.info() should have been called with the right arguments` - ); - }); - it(`should queue a book and log message even if topic exists`, () => { - // Setup - const testBookId = 1; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { - code: 409 - }); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); + t.deepEqual( + mocks.topic.publish.firstCall.args[0], + { + data: { + action: `processBook`, + bookId: testBookId + } + }, + `topic.publish() should have been called with the right arguments` + ); + t.truthy( + mocks.logging.info.calledOnce, + `logging.info() should have been called` + ); + t.is( + mocks.logging.info.firstCall.args[0], + `Book ${testBookId} queued for background processing`, + `logging.info() should have been called with the right arguments` + ); +}); - // Run target functionality - background.queueBook(testBookId); +test.serial(`should queue a book and log message even if topic exists`, (t) => { + // Setup + const testBookId = 1; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { + code: 409 + }); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.ok( - mocks.pubsub.topic.calledOnce, - `pubsub.topic() should have been called once` - ); - assert.equal( - mocks.pubsub.topic.firstCall.args[0], - `book-process-queue`, - `pubsub.topic() should have been called with the right arguments` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.ok( - mocks.logging.info.calledOnce, - `logging.info() should have been called` - ); - assert.equal( - mocks.logging.info.firstCall.args[0], - `Book ${testBookId} queued for background processing`, - `logging.info() should have been called with the right arguments` - ); - }); - it(`should log error if cannot get topic`, () => { - // Setup - const testBookId = 1; - const testErrorMsg = `test error`; - mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + // Run target functionality + background.queueBook(testBookId); - // Run target functionality - background.queueBook(testBookId); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.truthy( + mocks.pubsub.topic.calledOnce, + `pubsub.topic() should have been called once` + ); + t.is( + mocks.pubsub.topic.firstCall.args[0], + `book-process-queue`, + `pubsub.topic() should have been called with the right arguments` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); + t.deepEqual( + mocks.topic.publish.firstCall.args[0], + { + data: { + action: `processBook`, + bookId: testBookId + } + }, + `topic.publish() should have been called with the right arguments` + ); + t.truthy( + mocks.logging.info.calledOnce, + `logging.info() should have been called` + ); + t.is( + mocks.logging.info.firstCall.args[0], + `Book ${testBookId} queued for background processing`, + `logging.info() should have been called with the right arguments` + ); +}); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.equal( - mocks.topic.publish.callCount, - 0, - `topic.publish() should NOT have been called` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - assert.ok( - mocks.logging.error.calledOnce, - `logging.error() should have been called` - ); - assert.deepEqual( - mocks.logging.error.firstCall.args, - [`Error occurred while getting pubsub topic`, testErrorMsg], - `logging.error() should have been called with the right arguments` - ); - }); - it(`should log error if cannot publish message`, () => { - // Setup - const testBookId = 1; - const testErrorMsg = `test error`; - mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); +test.serial(`should log error if cannot get topic`, (t) => { + // Setup + const testBookId = 1; + const testErrorMsg = `test error`; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); - // Run target functionality - background.queueBook(testBookId); + // Run target functionality + background.queueBook(testBookId); - // Assertions - assert.ok( - mocks.pubsub.createTopic, - `pubsub.createTopic() should have been called once` - ); - assert.equal( - mocks.pubsub.createTopic.firstCall.args[0], - `book-process-queue`, - `pubsub.createTopic() should have been called with the right arguments` - ); - assert.equal( - mocks.pubsub.topic.callCount, - 0, - `pubsub.topic() should NOT have been called` - ); - assert.ok( - mocks.topic.publish, - `topic.publish() should have been called once` - ); - assert.deepEqual( - mocks.topic.publish.firstCall.args[0], - { - data: { - action: `processBook`, - bookId: testBookId - } - }, - `topic.publish() should have been called with the right arguments` - ); - assert.equal( - mocks.logging.info.callCount, - 0, - `logging.info() should NOT have been called` - ); - assert.ok( - mocks.logging.error.calledOnce, - `logging.error() should have been called` - ); - assert.deepEqual( - mocks.logging.error.firstCall.args, - [`Error occurred while queuing background task`, testErrorMsg], - `logging.error() should have been called with the right arguments` - ); - }); - }); + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.is( + mocks.topic.publish.callCount, + 0, + `topic.publish() should NOT have been called` + ); + t.is( + mocks.logging.info.callCount, + 0, + `logging.info() should NOT have been called` + ); + t.truthy( + mocks.logging.error.calledOnce, + `logging.error() should have been called` + ); +}); + +test.serial(`should log error if cannot publish message`, (t) => { + // Setup + const testBookId = 1; + const testErrorMsg = `test error`; + mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); + + // Run target functionality + background.queueBook(testBookId); + + // Assertions + t.truthy( + mocks.pubsub.createTopic, + `pubsub.createTopic() should have been called once` + ); + t.is( + mocks.pubsub.createTopic.firstCall.args[0], + `book-process-queue`, + `pubsub.createTopic() should have been called with the right arguments` + ); + t.is( + mocks.pubsub.topic.callCount, + 0, + `pubsub.topic() should NOT have been called` + ); + t.truthy( + mocks.topic.publish, + `topic.publish() should have been called once` + ); }); diff --git a/optional-container-engine/test/cloudsql.test.js b/optional-container-engine/test/cloudsql.test.js new file mode 100644 index 0000000000..ff2ba5fd52 --- /dev/null +++ b/optional-container-engine/test/cloudsql.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `cloudsql` || process.env.TEST_CLOUDSQL) { + require(`./_api-tests`)(`cloudsql`); + require(`./_crud-tests`)(`cloudsql`); +} else { + test(`Skipping Cloud SQL tests...`, (t) => t.pass()); +} diff --git a/optional-container-engine/test/crud.test.js b/optional-container-engine/test/crud.test.js deleted file mode 100644 index 0f07993e9c..0000000000 --- a/optional-container-engine/test/crud.test.js +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const assert = require(`assert`); -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -module.exports = (DATA_BACKEND) => { - describe(`crud.js`, () => { - let ORIG_DATA_BACKEND; - - before(() => { - const appConfig = require(`../config`); - ORIG_DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - appConfig.set(`DATA_BACKEND`, DATA_BACKEND); - }); - - describe(`/books`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should show a list of books`, (done) => { - // Give Datastore time to become consistent - setTimeout(() => { - const expected = `
`; - utils.getRequest(config) - .get(`/books`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }, 2000); - }); - - it(`should handle error`, (done) => { - utils.getRequest(config) - .get(`/books`) - .query({ pageToken: `badrequest` }) - .expect(500) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/add`, () => { - let id; - - it(`should post to add book form`, (done) => { - utils.getRequest(config) - .post(`/books/add`) - .field(`title`, `my book`) - .expect(302) - .expect((response) => { - const location = response.headers.location; - const idPart = location.replace(`/books/`, ``); - if (require(`../config`).get(`DATA_BACKEND`) !== `mongodb`) { - id = parseInt(idPart, 10); - } else { - id = idPart; - } - assert.equal(response.text.includes(`Redirecting to /books/`), true); - }) - .end(done); - }); - - it(`should show add book form`, (done) => { - utils.getRequest(config) - .get(`/books/add`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`Add book`), true); - }) - .end(done); - }); - - // delete the book - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - describe(`/books/:book/edit & /books/:book`, () => { - let id; - - // setup a book - before((done) => { - utils.getRequest(config) - .post(`/api/books`) - .send({ title: `my book` }) - .expect(200) - .expect((response) => { - id = response.body.id; - assert.ok(response.body.id); - assert.equal(response.body.title, `my book`); - }) - .end(done); - }); - - it(`should update a book`, (done) => { - const expected = `Redirecting to /books/${id}`; - utils.getRequest(config) - .post(`/books/${id}/edit`) - .field(`title`, `my other book`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show edit book form`, (done) => { - const expected = - ``; - utils.getRequest(config) - .get(`/books/${id}/edit`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should show a book`, (done) => { - const expected = `

my other book 

`; - utils.getRequest(config) - .get(`/books/${id}`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - it(`should delete a book`, (done) => { - const expected = `Redirecting to /books`; - utils.getRequest(config) - .get(`/books/${id}/delete`) - .expect(302) - .expect((response) => { - id = undefined; - assert.equal(response.text.includes(expected), true); - }) - .end(done); - }); - - // clean up if necessary - after((done) => { - if (id) { - utils.getRequest(config) - .delete(`/api/books/${id}`) - .expect(200) - .end(done); - } else { - done(); - } - }); - }); - - after(() => { - require(`../config`).set(`DATA_BACKEND`, ORIG_DATA_BACKEND); - }); - }); -}; diff --git a/optional-container-engine/test/datastore.test.js b/optional-container-engine/test/datastore.test.js new file mode 100644 index 0000000000..a4faa673b6 --- /dev/null +++ b/optional-container-engine/test/datastore.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `datastore` || process.env.TEST_DATASTORE) { + require(`./_api-tests`)(`datastore`); + require(`./_crud-tests`)(`datastore`); +} else { + test(`Skipping Cloud Datastore tests...`, (t) => t.pass()); +} diff --git a/optional-container-engine/test/index.js b/optional-container-engine/test/index.js deleted file mode 100644 index 9f963c4649..0000000000 --- a/optional-container-engine/test/index.js +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const config = require(`./config`); -const utils = require(`nodejs-repo-tools`); - -describe(`${config.test}/`, () => { - let topicName; - - before(() => { - const appConfig = require(`../config`); - topicName = appConfig.get(`TOPIC_NAME`); - appConfig.set(`TOPIC_NAME`, `${topicName}-${config.test}`); - }); - - if (!process.env.E2E_TESTS) { - it(`should install dependencies`, (done) => { - utils.testInstallation(config, done); - }).timeout(120 * 1000); - } - require(`./app.test`); - require(`./worker.test`); - describe(`books/`, () => { - const appConfig = require(`../config`); - const DATA_BACKEND = appConfig.get(`DATA_BACKEND`); - if (DATA_BACKEND === `datastore` || process.env.TEST_DATASTORE) { - require(`./api.test`)(`datastore`); - require(`./crud.test`)(`datastore`); - } - if (DATA_BACKEND === `cloudsql` || process.env.TEST_CLOUDSQL) { - require(`./api.test`)(`cloudsql`); - require(`./crud.test`)(`cloudsql`); - } - if (DATA_BACKEND === `mongodb` || process.env.TEST_MONGODB) { - require(`./api.test`)(`mongodb`); - require(`./crud.test`)(`mongodb`); - } - }); - if (!process.env.E2E_TESTS) { - describe(`lib/`, () => { - require(`./background.test`); - require(`./oauth2.test`); - }); - } - - afterEach(() => { - const appConfig = require(`../config`); - appConfig.set(`TOPIC_NAME`, topicName); - }); -}); diff --git a/optional-container-engine/test/mongodb.test.js b/optional-container-engine/test/mongodb.test.js new file mode 100644 index 0000000000..c7b49311b0 --- /dev/null +++ b/optional-container-engine/test/mongodb.test.js @@ -0,0 +1,23 @@ +// Copyright 2017, Google, Inc. +// 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. + +'use strict'; + +const test = require(`ava`); + +if (require(`../config`).get(`DATA_BACKEND`) === `mongodb` || process.env.TEST_MONGODB) { + require(`./_api-tests`)(`mongodb`); + require(`./_crud-tests`)(`mongodb`); +} else { + test(`Skipping MongoDB tests...`, (t) => t.pass()); +} diff --git a/optional-container-engine/test/oauth2.test.js b/optional-container-engine/test/oauth2.test.js index 881f520edf..a384a0f3ef 100644 --- a/optional-container-engine/test/oauth2.test.js +++ b/optional-container-engine/test/oauth2.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,201 +13,200 @@ 'use strict'; -const assert = require(`assert`); +const test = require(`ava`); const sinon = require(`sinon`); const request = require(`supertest`); const proxyquire = require(`proxyquire`).noPreserveCache(); -describe(`oauth2.js`, () => { - let passportMock; +const getPassportMock = () => { + return { + initialize: sinon.stub().returns((req, res, next) => { + next(); + }), + session: sinon.stub().returns((req, res, next) => { + next(); + }), + use: sinon.stub(), + serializeUser: sinon.stub(), + deserializeUser: sinon.stub(), + authenticate: sinon.stub().returns((req, res, next) => { + req.session.oauth2return = `/another/path`; + next(); + }) + }; +}; - beforeEach(() => { - passportMock = { - initialize: sinon.stub().returns((req, res, next) => { - next(); - }), - session: sinon.stub().returns((req, res, next) => { - next(); - }), - use: sinon.stub(), - serializeUser: sinon.stub(), - deserializeUser: sinon.stub(), - authenticate: sinon.stub().returns((req, res, next) => { - req.session.oauth2return = `/another/path`; - next(); - }) - }; +test.cb(`should start authorization`, (t) => { + const passportMock = getPassportMock(); + passportMock.authenticate = sinon.stub().returns((req, res, next) => { + t.is(req.session.oauth2return, `/some/path`); + res.redirect(`/auth/google/callback?code=foo`); }); - - it(`should start authorization`, (done) => { - passportMock.authenticate = sinon.stub().returns((req, res, next) => { - assert.equal(req.session.oauth2return, `/some/path`); - res.redirect(`/auth/google/callback?code=foo`); - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/login?return=%2Fsome%2Fpath`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /auth/google/callback?code=foo`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); - }); - - it(`should finish authorization`, (done) => { - const oauth2 = proxyquire(`../lib/oauth2`, { + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': oauth2 - }); - request(app) - .get(`/auth/google/callback?code=foo`) - .expect(302) - .expect((response) => { - assert.equal(response.text.includes(`Redirecting to /another/path`), true); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - assert.deepEqual( - oauth2.extractProfile({ - photos: [{ value: `image.jpg` }], - id: 1, - displayName: `Joe Developer` - }), - { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - ); - const serializeUser = passportMock.serializeUser.firstCall.args[0]; - const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; - const user = {}; - const obj = {}; - serializeUser(user, (err, _user) => { - assert.equal(err, null); - assert.strictEqual(_user, user); - }); - deserializeUser(obj, (err, _obj) => { - assert.equal(err, null); - assert.strictEqual(_obj, obj); - }); - }) - .end(done); + }) }); + request(app) + .get(`/auth/login?return=%2Fsome%2Fpath`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/auth\/google\/callback\?code=foo/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - it(`should logout`, (done) => { - const app = proxyquire(`../app`, { - passport: passportMock, - './lib/oauth2': proxyquire(`../lib/oauth2`, { - passport: passportMock - }) - }); - request(app) - .get(`/auth/logout`) - .expect(302) - .expect((response) => { - assert.equal( - response.text.includes(`Redirecting to /`), - true - ); - assert(passportMock.initialize.calledOnce); - assert(passportMock.session.calledOnce); - assert(passportMock.use.calledOnce); - assert(passportMock.serializeUser.calledOnce); - assert(passportMock.deserializeUser.calledOnce); - assert(passportMock.authenticate.calledTwice); - assert.equal(passportMock.authenticate.firstCall.args[0], `google`); - assert.deepEqual( - passportMock.authenticate.firstCall.args[1], - { scope: [`email`, `profile`] } - ); - assert.equal(passportMock.authenticate.secondCall.args[0], `google`); - assert.equal(passportMock.authenticate.secondCall.args[1], undefined); - }) - .end(done); +test.cb(`should finish authorization`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock + }); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': oauth2 }); + request(app) + .get(`/auth/google/callback?code=foo`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \/another\/path/); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + t.deepEqual( + oauth2.extractProfile({ + photos: [{ value: `image.jpg` }], + id: 1, + displayName: `Joe Developer` + }), + { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + ); + const serializeUser = passportMock.serializeUser.firstCall.args[0]; + const deserializeUser = passportMock.deserializeUser.firstCall.args[0]; + const user = {}; + const obj = {}; + serializeUser(user, (err, _user) => { + t.is(err, null); + t.is(_user, user); + }); + deserializeUser(obj, (err, _obj) => { + t.is(err, null); + t.is(_obj, obj); + }); + }) + .end(t.end); +}); - it(`should require authentication`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { +test.cb(`should logout`, (t) => { + const passportMock = getPassportMock(); + const app = proxyquire(`../app`, { + passport: passportMock, + './lib/oauth2': proxyquire(`../lib/oauth2`, { passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: {}, - session: {} - }; - const res = { - redirect: sinon.stub() - }; - const next = sinon.stub(); - oauth2.required(req, res, next); - assert(next.calledOnce); + }) + }); + request(app) + .get(`/auth/logout`) + .expect(302) + .expect((response) => { + const text = response.text; + t.regex(text, /Redirecting to \//); + t.true(passportMock.initialize.calledOnce); + t.true(passportMock.session.calledOnce); + t.true(passportMock.use.calledOnce); + t.true(passportMock.serializeUser.calledOnce); + t.true(passportMock.deserializeUser.calledOnce); + t.true(passportMock.authenticate.calledTwice); + t.is(passportMock.authenticate.firstCall.args[0], `google`); + t.deepEqual( + passportMock.authenticate.firstCall.args[1], + { scope: [`email`, `profile`] } + ); + t.is(passportMock.authenticate.secondCall.args[0], `google`); + t.is(passportMock.authenticate.secondCall.args[1], undefined); + }) + .end(t.end); +}); - req.user = undefined; - oauth2.required(req, res, next); - assert(next.calledOnce); - assert.equal(req.session.oauth2return, req.originalUrl); - assert(res.redirect.calledOnce); - assert.equal(res.redirect.firstCall.args[0], `/auth/login`); +test(`should require authentication`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: {}, + session: {} + }; + const res = { + redirect: sinon.stub() + }; + const next = sinon.stub(); + oauth2.required(req, res, next); + t.true(next.calledOnce); - it(`should add template variables`, () => { - const oauth2 = proxyquire(`../lib/oauth2`, { - passport: passportMock - }); - const req = { - originalUrl: `/some/path`, - user: { - id: 1, - displayName: `Joe Developer`, - image: `image.jpg` - } - }; - const res = { - locals: {} - }; - const next = sinon.stub(); - oauth2.template(req, res, next); - assert(next.calledOnce); - assert.strictEqual(res.locals.profile, req.user); - assert.equal( - res.locals.login, - `/auth/login?return=${encodeURIComponent(req.originalUrl)}` - ); - assert.equal( - res.locals.logout, - `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` - ); + req.user = undefined; + oauth2.required(req, res, next); + t.true(next.calledOnce); + t.is(req.session.oauth2return, req.originalUrl); + t.true(res.redirect.calledOnce); + t.is(res.redirect.firstCall.args[0], `/auth/login`); +}); + +test(`should add template variables`, (t) => { + const passportMock = getPassportMock(); + const oauth2 = proxyquire(`../lib/oauth2`, { + passport: passportMock }); + const req = { + originalUrl: `/some/path`, + user: { + id: 1, + displayName: `Joe Developer`, + image: `image.jpg` + } + }; + const res = { + locals: {} + }; + const next = sinon.stub(); + oauth2.template(req, res, next); + t.true(next.calledOnce); + t.is(res.locals.profile, req.user); + t.is( + res.locals.login, `/auth/login?return=${encodeURIComponent(req.originalUrl)}` + ); + t.is( + res.locals.logout, `/auth/logout?return=${encodeURIComponent(req.originalUrl)}` + ); }); diff --git a/optional-container-engine/test/worker.test.js b/optional-container-engine/test/worker.test.js index dc98593ccc..60c65c8fe9 100644 --- a/optional-container-engine/test/worker.test.js +++ b/optional-container-engine/test/worker.test.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 @@ -13,18 +13,18 @@ 'use strict'; -const assert = require(`assert`); -const config = require(`./config.worker`); +const testConfig = require(`./_test-config.worker`); const path = require(`path`); const proxyquire = require(`proxyquire`); const sinon = require(`sinon`); const supertest = require(`supertest`); +const test = require(`ava`); const utils = require(`nodejs-repo-tools`); const projectId = process.env.GCLOUD_PROJECT; function getUrl () { - return `http://${config.test}-dot-worker-dot-${projectId}.appspot-preview.com`; + return `http://${testConfig.test}-dot-worker-dot-${projectId}.appspot-preview.com`; } function getRequest () { @@ -34,68 +34,66 @@ function getRequest () { return supertest(proxyquire(path.join(__dirname, `../worker`), {}).app); } -describe(`worker.js`, () => { - if (!process.env.E2E_TESTS) { - it(`should run`, (done) => { - utils.testLocalApp(config, done); - }); - } - - it(`should return number of processed books`, (done) => { - getRequest(config) - .get(`/`) - .expect(200) - .expect((response) => { - assert.equal(response.text.includes(`This worker has processed`), true); - }) - .end(done); +if (!process.env.E2E_TESTS) { + test.serial.cb(`should run`, (t) => { + utils.testLocalApp(testConfig, t.end); }); +} - it(`should do a health check`, (done) => { - getRequest(config) - .get(`/_ah/health`) - .expect(200) - .expect((response) => { - assert.equal(response.text, `ok`); - }) - .end(done); - }); +test.serial.cb(`should return number of processed books`, (t) => { + getRequest(testConfig) + .get(`/`) + .expect(200) + .expect((response) => { + t.regex(response.text, /This worker has processed/); + }) + .end(t.end); +}); + +test.serial.cb(`should do a health check`, (t) => { + getRequest(testConfig) + .get(`/_ah/health`) + .expect(200) + .expect((response) => { + t.is(response.text, `ok`); + }) + .end(t.end); +}); - it(`should process a book`, (done) => { - const appConfig = require(`../config`); - const loggingStub = { - error: sinon.stub(), - info: sinon.stub(), - warn: sinon.stub() - }; - const stubs = { - './lib/logging': loggingStub, - '@google/cloud-trace': { - start: sinon.stub(), - '@noCallThru': true - }, - '@google/cloud-debug': { - '@noCallThru': true - } - }; - stubs[`./books/model-${appConfig.get('DATA_BACKEND')}`] = { - read: (bookId, cb) => { - cb(null, {}); - }, - update: (bookId, book, queueBook, cb) => { - cb(); - } - }; - const worker = proxyquire(path.join(__dirname, `../worker`), stubs); - const processBook = worker.processBook; +test.serial.cb(`should process a book`, (t) => { + const appConfig = require(`../config`); + const loggingStub = { + error: sinon.stub(), + info: sinon.stub(), + warn: sinon.stub() + }; + const stubs = { + './lib/logging': loggingStub, + '@google/cloud-trace': { + start: sinon.stub(), + '@noCallThru': true + }, + '@google/cloud-debug': { + '@noCallThru': true + } + }; + stubs[`./books/model-${appConfig.get('DATA_BACKEND')}`] = { + read: (bookId, cb) => { + cb(null, {}); + }, + update: (bookId, book, queueBook, cb) => { + cb(); + } + }; + const worker = proxyquire(path.join(__dirname, `../worker`), stubs); + const processBook = worker.processBook; - processBook(1, (err, bookId) => { - if (err) { - return done(err); - } - assert(loggingStub.info.calledOnce); - assert.equal(loggingStub.info.firstCall.args[0], `Updated book 1`); - done(); - }); + processBook(1, (err, bookId) => { + if (err) { + return t.end(err); + } + t.true(loggingStub.info.calledOnce); + t.is(loggingStub.info.firstCall.args[0], `Updated book 1`); + t.end(); }); }); diff --git a/optional-container-engine/views/base.jade b/optional-container-engine/views/base.jade index 4d1c80d45d..72a8e8a7c1 100644 --- a/optional-container-engine/views/base.jade +++ b/optional-container-engine/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/optional-container-engine/views/books/form.jade b/optional-container-engine/views/books/form.jade index 8368619eeb..2a96f6d50e 100644 --- a/optional-container-engine/views/books/form.jade +++ b/optional-container-engine/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/optional-container-engine/views/books/list.jade b/optional-container-engine/views/books/list.jade index dd453386ba..f7e901c3b3 100644 --- a/optional-container-engine/views/books/list.jade +++ b/optional-container-engine/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/optional-container-engine/views/books/view.jade b/optional-container-engine/views/books/view.jade index fb0123b05c..92776d30d1 100644 --- a/optional-container-engine/views/books/view.jade +++ b/optional-container-engine/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015-2016, Google, Inc. +//- Copyright 2017, Google, Inc. 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 diff --git a/optional-container-engine/worker.js b/optional-container-engine/worker.js index e32adb3f59..66f429abf2 100644 --- a/optional-container-engine/worker.js +++ b/optional-container-engine/worker.js @@ -1,4 +1,4 @@ -// Copyright 2015-2016, Google, Inc. +// Copyright 2017, Google, Inc. // 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 diff --git a/package.json b/package.json index c59ad6dda8..8623fd542f 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,14 @@ { "name": "nodejs-getting-started", "version": "1.0.0", - "description": "End to end sample for running Node.JS applications on Google Cloud Platform", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", "private": true, "scripts": { "lint": "semistandard \"**/*.js\"", - "mocha": "mocha test/index.js -t 30000", - "cover": "istanbul cover --hook-run-in-context node_modules/mocha/bin/_mocha -- test/index.js -t 30000", - "test": "npm run lint && npm run cover", - "e2e": "mocha test/e2e.js -t 1800000", - "codecov": "cat ./coverage/lcov.info | codecov", - "unify": "node scripts/unify" + "test": "ava -s -t 30s --tap **/*.test.js | tap-dot", + "cover": "nyc --cache npm test; nyc report --reporter=html", + "e2e": "mocha test/e2e.js -t 1800000" }, "author": "Google Inc.", "contributors": [ @@ -26,9 +23,13 @@ { "name": "Jason Dobry", "email": "jdobry@google.com" + }, + { + "name": "Ace Nassri", + "email": "anassri@google.com" } ], - "license": "Apache Version 2.0", + "license": "Apache-2.0", "semistandard": { "globals": [ "after", @@ -39,46 +40,42 @@ "it" ] }, + "engines": { + "node": ">=4.3.2" + }, "dependencies": { - "@google-cloud/datastore": "^0.1.1", - "@google-cloud/pubsub": "^0.2.0", - "@google-cloud/storage": "^0.1.1", - "@google/cloud-debug": "^0.8.3", - "@google/cloud-trace": "^0.5.5", - "async": "^2.0.1", - "body-parser": "^1.15.2", - "connect-memcached": "^0.2.0", - "cookie-session": "^2.0.0-alpha.1", - "express": "^4.14.0", - "express-session": "^1.14.0", - "express-winston": "^2.0.0", - "gcloud": "~0.37.0", - "jade": "^1.11.0", - "kerberos": "^0.0.21", - "lodash": "^4.14.2", - "mongodb": "^2.2.5", - "multer": "^1.2.0", - "mysql": "^2.11.1", - "nconf": "^0.8.4", - "passport": "^0.3.2", - "passport-google-oauth20": "^1.0.0", - "prompt": "^1.0.0", - "request": "^2.74.0", - "winston": "^2.2.0" + "@google-cloud/debug-agent": "~1.0.0", + "@google-cloud/trace-agent": "~1.0.4", + "@google-cloud/datastore": "~1.0.0", + "@google-cloud/storage": "~1.1.0", + "@google-cloud/pubsub": "~0.10.0", + "async": "~2.3.0", + "body-parser": "~1.17.1", + "connect-memcached": "~0.2.0", + "express": "~4.15.2", + "express-session": "~1.15.2", + "express-winston": "~2.3.0", + "jade": "~1.11.0", + "lodash": "~4.17.4", + "mongodb": "~2.2.25", + "multer": "~1.3.0", + "mysql": "~2.13.0", + "nconf": "~0.8.4", + "passport": "~0.3.2", + "passport-google-oauth20": "~1.0.0", + "prompt": "~1.0.0", + "request": "~2.81.0", + "winston": "~2.3.1" }, "devDependencies": { - "codecov": "^1.0.1", - "istanbul": "^0.4.4", - "mocha": "^3.0.2", + "ava": "~0.19.1", "nodejs-repo-tools": "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git", - "proxyquire": "^1.7.10", - "request": "^2.74.0", - "semistandard": "^9.1.0", - "shelljs": "^0.7.3", - "sinon": "^1.17.5", - "supertest": "^2.0.0" + "proxyquire": "~1.7.11", + "sinon": "~2.1.0", + "supertest": "~3.0.0", + "tap-dot": "~1.0.5" }, "engines": { - "node": ">=0.12.7" + "node": ">=4.3.2" } } diff --git a/scripts/unify b/scripts/unify index cfb25ede87..871f554c66 100755 --- a/scripts/unify +++ b/scripts/unify @@ -1,36 +1,59 @@ -'use strict'; +#!/usr/bin/env node + +/** + * Copyright 2017, Google, Inc. + * 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. + */ -// Copyright 2016, Google, Inc. -// 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. +'use strict'; const fs = require(`fs`); const path = require(`path`); const PROJECT_ROOT = path.join(__dirname, `..`); -// Get subdirectories with a `package.json` file -const directories = fs.readdirSync(PROJECT_ROOT) - .filter((dir) => fs.existsSync(path.join(PROJECT_ROOT, dir, `package.json`))); - // Dedupe package.json dependencies // WARNING: This will fail if two different versions of the same package are required. -let pkgSet = {}; -let pkgJson; -directories.forEach((dir) => { - pkgJson = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, dir, `package.json`))); - Object.assign(pkgSet, pkgJson.dependencies); -}); +const pkgSet = {}; +const pkgDevSet = {}; + +function getDeps (directory) { + // Get subdirectories with a `package.json` file + const directories = fs.readdirSync(directory) + .filter((dir) => fs.existsSync(path.join(directory, dir, `package.json`))); -// Update root-level package.json (by shelling to yarn) -const spawn = require('child_process').spawn; -spawn(`yarn`, [`add`].concat(Object.keys(pkgSet).map(pkg => `${pkg}@${pkgSet[pkg]}`))); + let pkgJson; + directories.forEach((dir) => { + pkgJson = JSON.parse(fs.readFileSync(path.join(directory, dir, `package.json`))); + Object.assign(pkgSet, pkgJson.dependencies); + Object.assign(pkgDevSet, pkgJson.devDependencies); + }); +} + +getDeps(PROJECT_ROOT); + +console.log(JSON.stringify(pkgSet, null, 2)); +console.log(JSON.stringify(pkgDevSet, null, 2)); + +// Update root-level package.json (by shelling to npm) +const spawnSync = require('child_process').spawnSync; +let args = [`add`].concat(Object.keys(pkgSet).map(pkg => `${pkg}@${pkgSet[pkg]}`)); +console.log(`yarn ${args.join(' ')}`); +spawnSync(`yarn`, args, { + cwd: PROJECT_ROOT +}); +args = [`add`, `-D`].concat(Object.keys(pkgDevSet).map(pkg => `${pkg}@${pkgDevSet[pkg]}`)); +console.log(`yarn ${args.join(' ')}`); +spawnSync(`yarn`, args, { + cwd: PROJECT_ROOT +}); diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 14ead80c8f..0000000000 --- a/test/index.js +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015-2016, Google, Inc. -// 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. - -'use strict'; - -const MongoClient = require(`mongodb`).MongoClient; - -describe(`Bookshelf app`, () => { - require(`../1-hello-world/test`); - require(`../2-structured-data/test`); - require(`../3-binary-data/test`); - require(`../4-auth/test`); - require(`../5-logging/test`); - require(`../6-pubsub/test`); - require(`../7-gce/test`); - after((done) => { - const config = require(`../7-gce/config`); - if (config.get(`DATA_BACKEND`) !== `mongodb`) { - return done(); - } - MongoClient.connect(config.get(`MONGO_URL`), (err, db) => { - if (err) { - return done(err); - } - db.collection(config.get(`MONGO_COLLECTION`)).remove(done); - }); - }); -}); diff --git a/yarn.lock b/yarn.lock index d02ec31048..2a7179ac61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,63 +2,85 @@ # yarn lockfile v1 -"@google-cloud/common@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.1.1.tgz#7267fa35437885dccd02e2ba72a43958ae012ee2" +"@ava/babel-preset-stage-4@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-stage-4/-/babel-preset-stage-4-1.0.0.tgz#a613b5e152f529305422546b072d47facfb26291" + dependencies: + babel-plugin-check-es2015-constants "^6.8.0" + babel-plugin-syntax-trailing-function-commas "^6.20.0" + babel-plugin-transform-async-to-generator "^6.16.0" + babel-plugin-transform-es2015-destructuring "^6.19.0" + babel-plugin-transform-es2015-function-name "^6.9.0" + babel-plugin-transform-es2015-modules-commonjs "^6.18.0" + babel-plugin-transform-es2015-parameters "^6.21.0" + babel-plugin-transform-es2015-spread "^6.8.0" + babel-plugin-transform-es2015-sticky-regex "^6.8.0" + babel-plugin-transform-es2015-unicode-regex "^6.11.0" + babel-plugin-transform-exponentiation-operator "^6.8.0" + package-hash "^1.2.0" + +"@ava/babel-preset-transform-test-files@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-2.0.1.tgz#d75232cc6d71dc9c7eae4b76a9004fd81501d0c1" dependencies: - array-uniq "^1.0.2" - arrify "^1.0.0" - concat-stream "^1.5.0" - create-error-class "^2.0.1" + babel-plugin-ava-throws-helper "^1.0.0" + babel-plugin-espower "^2.3.2" + package-hash "^1.2.0" + +"@ava/pretty-format@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@ava/pretty-format/-/pretty-format-1.0.2.tgz#e3a90465cc014bf542036555e5ec9796031db858" + dependencies: + ansi-styles "^2.2.1" + +"@google-cloud/common-grpc@^0.1.1": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@google-cloud/common-grpc/-/common-grpc-0.1.3.tgz#fe692a1df214b0b8dc25ae96ba96990dc9c18b98" + dependencies: + "@google-cloud/common" "^0.12.0" dot-prop "^2.4.0" - duplexify "^3.2.0" - ent "^2.2.0" + duplexify "^3.5.0" extend "^3.0.0" - google-auto-auth "^0.2.4" - google-proto-files "^0.2.1" - grpc "^0.14.1" - is "^3.0.1" - methmeth "^1.0.0" - request "^2.70.0" - retry-request "^1.3.0" - split-array-stream "^1.0.0" - stream-events "^1.0.1" - string-format-obj "^1.0.0" - through2 "^2.0.0" + google-proto-files "^0.8.6" + grpc "^1.1.1" + is "^3.2.0" + retry-request "^1.3.2" + through2 "^2.0.3" -"@google-cloud/common@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.4.0.tgz#2742140c5130e10571578148ee0c035383a7f2c7" +"@google-cloud/common@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-0.12.0.tgz#b1cb0922c70f7e5273e402b0a6ae71de2f32abc3" dependencies: - array-uniq "^1.0.2" - arrify "^1.0.0" - concat-stream "^1.5.0" - create-error-class "^2.0.1" + array-uniq "^1.0.3" + arrify "^1.0.1" + concat-stream "^1.6.0" + create-error-class "^3.0.2" dot-prop "^2.4.0" - duplexify "^3.2.0" + duplexify "^3.5.0" ent "^2.2.0" extend "^3.0.0" - google-auto-auth "^0.2.4" - google-proto-files "^0.7.0" - grpc "^1.0.0" - is "^3.0.1" - methmeth "^1.0.0" + google-auto-auth "^0.5.2" + google-proto-files "^0.8.6" + is "^3.2.0" + log-driver "^1.2.5" + methmeth "^1.1.0" modelo "^4.2.0" - request "^2.70.0" - retry-request "^1.3.0" + request "^2.79.0" + retry-request "^1.3.2" split-array-stream "^1.0.0" stream-events "^1.0.1" - string-format-obj "^1.0.0" - through2 "^2.0.0" + string-format-obj "^1.1.0" + through2 "^2.0.3" -"@google-cloud/datastore@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-0.1.1.tgz#fbff4a2694bff5f802ee872ebe8837deff8cf681" +"@google-cloud/datastore@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-0.7.0.tgz#d7f7888e16cd3903bec6e8b5a2b0aefdfa82a154" dependencies: - "@google-cloud/common" "^0.1.0" + "@google-cloud/common" "^0.12.0" + "@google-cloud/common-grpc" "^0.1.1" arrify "^1.0.0" concat-stream "^1.5.0" - create-error-class "^2.0.1" + create-error-class "^3.0.2" extend "^3.0.0" is "^3.0.1" lodash.flatten "^4.2.0" @@ -67,28 +89,30 @@ propprop "^0.3.0" split-array-stream "^1.0.0" -"@google-cloud/pubsub@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-0.2.0.tgz#48d9ea2969f81f44ad0b878238e351c1bf5bce6a" +"@google-cloud/pubsub@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-0.8.0.tgz#257309fa3a184df25208da743454dd8dbcc96056" dependencies: - "@google-cloud/common" "^0.4.0" - arguejs "^0.2.3" + "@google-cloud/common" "^0.12.0" + "@google-cloud/common-grpc" "^0.1.1" arrify "^1.0.0" extend "^3.0.0" - google-gax "^0.6.0" + google-gax "^0.10.0" + google-proto-files "^0.8.0" is "^3.0.1" modelo "^4.2.0" propprop "^0.3.0" + uuid "^3.0.1" -"@google-cloud/storage@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-0.1.1.tgz#3f977c5254b86e9822bf67f8eb144747a05a382b" +"@google-cloud/storage@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-0.7.0.tgz#87eb7bde35a1597511cba7b651e666178ac90fe1" dependencies: - "@google-cloud/common" "^0.1.0" + "@google-cloud/common" "^0.12.0" arrify "^1.0.0" - async "^1.4.2" + async "^2.0.1" concat-stream "^1.5.0" - create-error-class "^2.0.1" + create-error-class "^3.0.2" duplexify "^3.2.0" extend "^3.0.0" gcs-resumable-upload "^0.7.1" @@ -101,12 +125,13 @@ string-format-obj "^1.0.0" through2 "^2.0.0" -"@google/cloud-debug@^0.8.3": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@google/cloud-debug/-/cloud-debug-0.8.5.tgz#85b00ed761b6acd562273cb1a8259cad86806cfe" +"@google/cloud-debug@0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@google/cloud-debug/-/cloud-debug-0.9.1.tgz#b4d8f475042eef604cdef0e2213df04d10c81042" dependencies: - "@google/cloud-diagnostics-common" "0.2.5" - acorn "^3.3.0" + "@google/cloud-diagnostics-common" "0.3.0" + acorn "^4.0.3" + async "^2.1.2" coffee-script "^1.9.3" findit "^2.0.0" google-auth-library "^0.9.5" @@ -116,14 +141,6 @@ source-map "^0.5.1" split "^1.0.0" -"@google/cloud-diagnostics-common@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@google/cloud-diagnostics-common/-/cloud-diagnostics-common-0.2.5.tgz#5325a41911e2cbda4a7320aa2dc271bdcbb483f1" - dependencies: - google-auth-library "git://github.com/google/google-auth-library-nodejs.git#02f587472ecdc2582f8d5fc754996aa6ef9388a0" - moment "^2.15.0" - request "^2.75.0" - "@google/cloud-diagnostics-common@0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@google/cloud-diagnostics-common/-/cloud-diagnostics-common-0.3.0.tgz#bf37ccc5effc8a518a599036739914a3b1e6fb2e" @@ -132,27 +149,22 @@ moment "^2.15.0" request "^2.75.0" -"@google/cloud-trace@^0.5.5": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@google/cloud-trace/-/cloud-trace-0.5.10.tgz#287564b2dff8b7e9e66fe89376eb1a3bcee55767" +"@google/cloud-trace@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@google/cloud-trace/-/cloud-trace-0.6.2.tgz#278ef115deff916bd89429ab89863283273fd788" dependencies: - "@google/cloud-diagnostics-common" "0.3.0" + "@google-cloud/common" "^0.12.0" continuation-local-storage "^3.1.4" + gcp-metadata "^0.1.0" + is "^3.2.0" lodash.findindex "^4.4.0" lodash.isequal "^4.0.0" methods "^1.1.1" semver "^5.0.1" shimmer "^1.0.0" - uuid "^2.0.1" - -JSONStream@^1.0.7: - version "1.3.0" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.0.tgz#680ab9ac6572a8a1a207e0b38721db1c77b215e5" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" + uuid "^3.0.1" -abbrev@1, abbrev@1.0.x: +abbrev@1: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -169,12 +181,6 @@ acorn-globals@^1.0.3: dependencies: acorn "^2.1.0" -acorn-jsx@^3.0.0, acorn-jsx@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - dependencies: - acorn "^3.0.4" - acorn@^1.0.1: version "1.2.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" @@ -183,44 +189,23 @@ acorn@^2.1.0: version "2.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" -acorn@^3.0.4, acorn@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - -acorn@^4.0.1: +acorn@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a" -ajv-keywords@^1.0.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.0.tgz#c11e6859eafff83e0dafc416929472eca946aa2c" - -ajv@^4.7.0: - version "4.10.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.10.4.tgz#c0974dd00b3464984892d6010aa9c2c945933254" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" +ansi-align@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" + dependencies: + string-width "^1.0.1" ansi-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107" + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" ansi-styles@^2.2.1: version "2.2.1" @@ -230,13 +215,20 @@ ansi-styles@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" +anymatch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + dependencies: + arrify "^1.0.0" + micromatch "^2.1.5" + append-field@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-0.1.0.tgz#6ddc58fa083c7bc545d3c5995b2830cc2366d44a" aproba@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.0.4.tgz#2713680775e7614c8ba186c065d4e2e52d1072c0" + version "1.1.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.0.tgz#4d8f047a318604e18e3c06a0e52230d3d19f147b" are-we-there-yet@~1.1.2: version "1.1.2" @@ -245,19 +237,31 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.0 || ^1.1.13" -argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" - dependencies: - sprintf-js "~1.0.2" - arguejs@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/arguejs/-/arguejs-0.2.3.tgz#b6f939f5fe0e3cd1f3f93e2aa9262424bf312af7" -argv@>=0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-exclude@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/arr-exclude/-/arr-exclude-1.0.0.tgz#dfc7c2e552a270723ccda04cf3128c8cbfe5c631" + +arr-flatten@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" array-flatten@1.1.1: version "1.1.1" @@ -269,11 +273,15 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.1, array-uniq@^1.0.2: +array-uniq@^1.0.1, array-uniq@^1.0.2, array-uniq@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" -arrify@^1.0.0: +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -300,9 +308,9 @@ assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" async-listener@^0.6.0: version "0.6.3" @@ -310,19 +318,15 @@ async-listener@^0.6.0: dependencies: shimmer "1.0.0" -async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - -async@^2.0.1: +async@2.1.4, async@^2.0.1, async@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: lodash "^4.14.0" -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" +async@^1.4.0, async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@~0.9.0: version "0.9.2" @@ -340,21 +344,401 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" +auto-bind@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-1.1.0.tgz#93b864dc7ee01a326281775d5c75ca0a751e5961" + +ava-init@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ava-init/-/ava-init-0.2.0.tgz#9304c8b4c357d66e3dfdae1fbff47b1199d5c55d" + dependencies: + arr-exclude "^1.0.0" + execa "^0.5.0" + has-yarn "^1.0.0" + read-pkg-up "^2.0.0" + write-pkg "^2.0.0" + +ava@0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/ava/-/ava-0.18.1.tgz#5f47e09642b397aba471e09ffc1e5a59c670a52d" + dependencies: + "@ava/babel-preset-stage-4" "^1.0.0" + "@ava/babel-preset-transform-test-files" "^2.0.0" + "@ava/pretty-format" "^1.0.0" + arr-flatten "^1.0.1" + array-union "^1.0.1" + array-uniq "^1.0.2" + arrify "^1.0.0" + auto-bind "^1.1.0" + ava-init "^0.2.0" + babel-code-frame "^6.16.0" + babel-core "^6.17.0" + bluebird "^3.0.0" + caching-transform "^1.0.0" + chalk "^1.0.0" + chokidar "^1.4.2" + clean-stack "^1.1.1" + clean-yaml-object "^0.1.0" + cli-cursor "^2.1.0" + cli-spinners "^1.0.0" + cli-truncate "^0.2.0" + co-with-promise "^4.6.0" + code-excerpt "^2.1.0" + common-path-prefix "^1.0.0" + convert-source-map "^1.2.0" + core-assert "^0.2.0" + currently-unhandled "^0.4.1" + debug "^2.2.0" + diff "^3.0.1" + dot-prop "^4.1.0" + empower-core "^0.6.1" + equal-length "^1.0.0" + figures "^2.0.0" + find-cache-dir "^0.1.1" + fn-name "^2.0.0" + get-port "^2.1.0" + globby "^6.0.0" + has-flag "^2.0.0" + ignore-by-default "^1.0.0" + indent-string "^3.0.0" + is-ci "^1.0.7" + is-generator-fn "^1.0.0" + is-obj "^1.0.0" + is-observable "^0.2.0" + is-promise "^2.1.0" + jest-snapshot "^18.1.0" + last-line-stream "^1.0.0" + lodash.debounce "^4.0.3" + lodash.difference "^4.3.0" + lodash.flatten "^4.2.0" + lodash.isequal "^4.5.0" + loud-rejection "^1.2.0" + matcher "^0.1.1" + max-timeout "^1.0.0" + md5-hex "^2.0.0" + meow "^3.7.0" + ms "^0.7.1" + multimatch "^2.1.0" + observable-to-promise "^0.4.0" + option-chain "^0.1.0" + package-hash "^1.2.0" + pkg-conf "^2.0.0" + plur "^2.0.0" + pretty-ms "^2.0.0" + require-precompiled "^0.1.0" + resolve-cwd "^1.0.0" + slash "^1.0.0" + source-map-support "^0.4.0" + stack-utils "^0.4.0" + strip-ansi "^3.0.1" + strip-bom-buf "^1.0.0" + time-require "^0.1.2" + unique-temp-dir "^1.0.0" + update-notifier "^1.0.0" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" aws4@^1.2.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.16.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.20.0.tgz#b968f839090f9a8bc6d41938fb96cb84f7387b26" +babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" dependencies: chalk "^1.1.0" esutils "^2.0.2" - js-tokens "^2.0.0" + js-tokens "^3.0.0" + +babel-core@^6.17.0, babel-core@^6.22.0: + version "6.22.1" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.22.1.tgz#9c5fd658ba1772d28d721f6d25d968fc7ae21648" + dependencies: + babel-code-frame "^6.22.0" + babel-generator "^6.22.0" + babel-helpers "^6.22.0" + babel-messages "^6.22.0" + babel-register "^6.22.0" + babel-runtime "^6.22.0" + babel-template "^6.22.0" + babel-traverse "^6.22.1" + babel-types "^6.22.0" + babylon "^6.11.0" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-is-absolute "^1.0.0" + private "^0.1.6" + slash "^1.0.0" + source-map "^0.5.0" + +babel-generator@^6.1.0, babel-generator@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.22.0.tgz#d642bf4961911a8adc7c692b0c9297f325cda805" + dependencies: + babel-messages "^6.22.0" + babel-runtime "^6.22.0" + babel-types "^6.22.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.2.0" + source-map "^0.5.0" + +babel-helper-builder-binary-assignment-operator-visitor@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.22.0.tgz#29df56be144d81bdeac08262bfa41d2c5e91cdcd" + dependencies: + babel-helper-explode-assignable-expression "^6.22.0" + babel-runtime "^6.22.0" + babel-types "^6.22.0" + +babel-helper-call-delegate@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.22.0.tgz#119921b56120f17e9dae3f74b4f5cc7bcc1b37ef" + dependencies: + babel-helper-hoist-variables "^6.22.0" + babel-runtime "^6.22.0" + babel-traverse "^6.22.0" + babel-types "^6.22.0" + +babel-helper-explode-assignable-expression@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.22.0.tgz#c97bf76eed3e0bae4048121f2b9dae1a4e7d0478" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.22.0" + babel-types "^6.22.0" + +babel-helper-function-name@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.22.0.tgz#51f1bdc4bb89b15f57a9b249f33d742816dcbefc" + dependencies: + babel-helper-get-function-arity "^6.22.0" + babel-runtime "^6.22.0" + babel-template "^6.22.0" + babel-traverse "^6.22.0" + babel-types "^6.22.0" + +babel-helper-get-function-arity@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.22.0.tgz#0beb464ad69dc7347410ac6ade9f03a50634f5ce" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.22.0" + +babel-helper-hoist-variables@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.22.0.tgz#3eacbf731d80705845dd2e9718f600cfb9b4ba72" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.22.0" + +babel-helper-regex@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.22.0.tgz#79f532be1647b1f0ee3474b5f5c3da58001d247d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.22.0" + lodash "^4.2.0" + +babel-helper-remap-async-to-generator@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.22.0.tgz#2186ae73278ed03b8b15ced089609da981053383" + dependencies: + babel-helper-function-name "^6.22.0" + babel-runtime "^6.22.0" + babel-template "^6.22.0" + babel-traverse "^6.22.0" + babel-types "^6.22.0" + +babel-helpers@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.22.0.tgz#d275f55f2252b8101bff07bc0c556deda657392c" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.22.0" + +babel-messages@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.22.0.tgz#36066a214f1217e4ed4164867669ecb39e3ea575" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-ava-throws-helper@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-ava-throws-helper/-/babel-plugin-ava-throws-helper-1.0.0.tgz#8fe6e79d2fd19838b5c3649f89cfb03fd563e241" + dependencies: + babel-template "^6.7.0" + babel-types "^6.7.2" + +babel-plugin-check-es2015-constants@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-espower@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz#5516b8fcdb26c9f0e1d8160749f6e4c65e71271e" + dependencies: + babel-generator "^6.1.0" + babylon "^6.1.0" + call-matcher "^1.0.0" + core-js "^2.0.0" + espower-location-detector "^1.0.0" + espurify "^1.6.0" + estraverse "^4.1.1" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-trailing-function-commas@^6.20.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.16.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.22.0.tgz#194b6938ec195ad36efc4c33a971acf00d8cd35e" + dependencies: + babel-helper-remap-async-to-generator "^6.22.0" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-destructuring@^6.19.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.22.0.tgz#8e0af2f885a0b2cf999d47c4c1dd23ce88cfa4c6" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.9.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.22.0.tgz#f5fcc8b09093f9a23c76ac3d9e392c3ec4b77104" + dependencies: + babel-helper-function-name "^6.22.0" + babel-runtime "^6.22.0" + babel-types "^6.22.0" + +babel-plugin-transform-es2015-modules-commonjs@^6.18.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.22.0.tgz#6ca04e22b8e214fb50169730657e7a07dc941145" + dependencies: + babel-plugin-transform-strict-mode "^6.22.0" + babel-runtime "^6.22.0" + babel-template "^6.22.0" + babel-types "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.21.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.22.0.tgz#57076069232019094f27da8c68bb7162fe208dbb" + dependencies: + babel-helper-call-delegate "^6.22.0" + babel-helper-get-function-arity "^6.22.0" + babel-runtime "^6.22.0" + babel-template "^6.22.0" + babel-traverse "^6.22.0" + babel-types "^6.22.0" + +babel-plugin-transform-es2015-spread@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.22.0.tgz#ab316829e866ee3f4b9eb96939757d19a5bc4593" + dependencies: + babel-helper-regex "^6.22.0" + babel-runtime "^6.22.0" + babel-types "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.11.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.22.0.tgz#8d9cc27e7ee1decfe65454fb986452a04a613d20" + dependencies: + babel-helper-regex "^6.22.0" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.22.0.tgz#d57c8335281918e54ef053118ce6eb108468084d" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.22.0" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-strict-mode@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.22.0.tgz#e008df01340fdc87e959da65991b7e05970c8c7c" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.22.0" + +babel-register@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.22.0.tgz#a61dd83975f9ca4a9e7d6eff3059494cd5ea4c63" + dependencies: + babel-core "^6.22.0" + babel-runtime "^6.22.0" + core-js "^2.4.0" + home-or-tmp "^2.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + source-map-support "^0.4.2" + +babel-runtime@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.22.0.tgz#1cf8b4ac67c77a4ddb0db2ae1f74de52ac4ca611" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-template@^6.22.0, babel-template@^6.7.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.22.0.tgz#403d110905a4626b317a2a1fcb8f3b73204b2edb" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.22.0" + babel-types "^6.22.0" + babylon "^6.11.0" + lodash "^4.2.0" + +babel-traverse@^6.22.0, babel-traverse@^6.22.1: + version "6.22.1" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.22.1.tgz#3b95cd6b7427d6f1f757704908f2fc9748a5f59f" + dependencies: + babel-code-frame "^6.22.0" + babel-messages "^6.22.0" + babel-runtime "^6.22.0" + babel-types "^6.22.0" + babylon "^6.15.0" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-types@^6.22.0, babel-types@^6.7.2: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.22.0.tgz#2a447e8d0ea25d2512409e4175479fd78cc8b1db" + dependencies: + babel-runtime "^6.22.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.1.0, babylon@^6.11.0, babylon@^6.15.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" balanced-match@^0.4.1: version "0.4.2" @@ -380,14 +764,18 @@ base64url@~1.0.4: meow "~2.0.0" bcrypt-pbkdf@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4" + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" dependencies: tweetnacl "^0.14.3" -bignumber.js@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8" +bignumber.js@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-3.1.2.tgz#f3bdb99ad5268a15fc1f0bed2fb018e2693fe236" + +binary-extensions@^1.0.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" bl@~1.1.2: version "1.1.2" @@ -401,20 +789,24 @@ block-stream@*: dependencies: inherits "~2.0.0" -body-parser@^1.15.2: - version "1.15.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.15.2.tgz#d7578cf4f1d11d5f6ea804cef35dc7a7ff6dae67" +bluebird@^3.0.0: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + +body-parser@1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.0.tgz#924a5e472c6229fb9d69b85a20d5f2532dec788b" dependencies: bytes "2.4.0" content-type "~1.0.2" - debug "~2.2.0" + debug "2.6.0" depd "~1.1.0" - http-errors "~1.5.0" - iconv-lite "0.4.13" + http-errors "~1.5.1" + iconv-lite "0.4.15" on-finished "~2.3.0" - qs "6.2.0" - raw-body "~2.1.7" - type-is "~1.6.13" + qs "6.2.1" + raw-body "~2.2.0" + type-is "~1.6.14" boom@2.x.x: version "2.10.1" @@ -422,6 +814,20 @@ boom@2.x.x: dependencies: hoek "2.x.x" +boxen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" + dependencies: + ansi-align "^1.1.0" + camelcase "^2.1.0" + chalk "^1.1.1" + cli-boxes "^1.0.0" + filled-array "^1.0.0" + object-assign "^4.0.1" + repeating "^2.0.0" + string-width "^1.0.1" + widest-line "^1.0.0" + brace-expansion@^1.0.0: version "1.1.6" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" @@ -429,13 +835,21 @@ brace-expansion@^1.0.0: balanced-match "^0.4.1" concat-map "0.0.1" -browser-stdout@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" -bson@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.3.tgz#a3b66f724b1673067efc32249e1151205d3ba854" +bson@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c" + +buf-compare@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buf-compare/-/buf-compare-1.0.1.tgz#fef28da8b8113a0a0db4430b0b6467b69730b34a" buffer-equal-constant-time@^1.0.1: version "1.0.1" @@ -449,24 +863,17 @@ buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" -bufferview@~1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bufferview/-/bufferview-1.0.1.tgz#7afd74a45f937fa422a1d338c08bbfdc76cd725d" +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" busboy@^0.2.11: - version "0.2.13" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.13.tgz#90fc4f6a3967d815616fc976bfa8e56aed0c58b6" + version "0.2.14" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" dependencies: dicer "0.2.5" readable-stream "1.1.x" -"bytebuffer@~4 >=4.1": - version "4.1.0" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-4.1.0.tgz#4c58269e052ab1e4b1f7ff364fdfb3ce882906aa" - dependencies: - bufferview "~1" - long "~2 >=2.3.0" - bytebuffer@~5: version "5.0.1" resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" @@ -477,15 +884,26 @@ bytes@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" +caching-transform@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-1.0.1.tgz#6dbdb2f20f8d8fbce79f3e94e9d1742dcdf5c0a1" dependencies: - callsites "^0.2.0" + md5-hex "^1.2.0" + mkdirp "^0.5.1" + write-file-atomic "^1.1.4" -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +call-matcher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-matcher/-/call-matcher-1.0.1.tgz#5134d077984f712a54dad3cbf62de28dce416ca8" + dependencies: + core-js "^2.0.0" + deep-equal "^1.0.0" + espurify "^1.6.0" + estraverse "^4.0.0" + +call-signature@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996" camelcase-keys@^1.0.0: version "1.0.0" @@ -494,11 +912,18 @@ camelcase-keys@^1.0.0: camelcase "^1.0.1" map-obj "^1.0.0" -camelcase@^1.0.1, camelcase@^1.0.2: +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^1.0.1: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" -camelcase@^2.0.1: +camelcase@^2.0.0, camelcase@^2.0.1, camelcase@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -510,20 +935,13 @@ caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chai@*: - version "3.5.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" +chalk@^0.4.0, chalk@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" dependencies: - assertion-error "^1.0.1" - deep-eql "^0.1.3" - type-detect "^1.0.0" + ansi-styles "~1.0.0" + has-color "~0.1.0" + strip-ansi "~0.1.0" chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" @@ -535,46 +953,64 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" - dependencies: - ansi-styles "~1.0.0" - has-color "~0.1.0" - strip-ansi "~0.1.0" - character-parser@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-1.2.1.tgz#c0dde4ab182713b919b970959a123ecc1a30fcd6" -circular-json@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" +chokidar@^1.4.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" clean-css@^3.1.9: - version "3.4.23" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.23.tgz#604fbbca24c12feb59b02f00b84f1fb7ded6d001" + version "3.4.24" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.24.tgz#89f5a5e9da37ae02394fe049a41388abbe72c3b5" dependencies: commander "2.8.x" source-map "0.4.x" -cli-cursor@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - dependencies: - restore-cursor "^1.0.1" +clean-stack@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.1.1.tgz#a1b3711122df162df7c7cb9b3c0470f28cb58adb" -cli-width@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" +clean-yaml-object@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz#63fb110dc2ce1a84dc21f6d9334876d010ae8b68" + +cli-boxes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" -cliui@^2.1.0: +cli-cursor@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.0.0.tgz#ef987ed3d48391ac3dab9180b406a742180d6e6a" + +cli-truncate@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" + slice-ansi "0.0.4" + string-width "^1.0.1" cliui@^3.0.3: version "3.2.0" @@ -584,26 +1020,25 @@ cliui@^3.0.3: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -co@^4.6.0: +co-with-promise@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.yarnpkg.com/co-with-promise/-/co-with-promise-4.6.0.tgz#413e7db6f5893a60b942cf492c4bec93db415ab7" + dependencies: + pinkie-promise "^1.0.0" + +code-excerpt@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/code-excerpt/-/code-excerpt-2.1.0.tgz#5dcc081e88f4a7e3b554e9e35d7ef232d47f8147" + dependencies: + convert-to-spaces "^1.0.1" code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -codecov@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-1.0.1.tgz#97260ceac0e96b8eda8d562006558a53a139dffd" - dependencies: - argv ">=0.0.2" - execSync "1.0.2" - request ">=2.42.0" - urlgrey ">=0.4.0" - coffee-script@^1.9.3: - version "1.12.2" - resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.2.tgz#0d4cbdee183f650da95419570c4929d08ef91376" + version "1.12.3" + resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.3.tgz#de5f4b1b934a4e9f915c57acd7ad323f68f715db" colors@1.0.x: version "1.0.3" @@ -629,7 +1064,7 @@ commander@2.8.x: dependencies: graceful-readlink ">= 1.0.0" -commander@2.9.0, commander@^2.9.0: +commander@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: @@ -639,6 +1074,14 @@ commander@~2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" +common-path-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-1.0.0.tgz#cd52f6f0712e0baab97d6f9732874f22f47752c0" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + component-emitter@^1.2.0, component-emitter@~1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -647,7 +1090,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.6, concat-stream@^1.5.0: +concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -676,7 +1119,21 @@ configstore@^1.2.1: write-file-atomic "^1.1.2" xdg-basedir "^2.0.0" -connect-memcached@^0.2.0: +configstore@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" + dependencies: + dot-prop "^3.0.0" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + object-assign "^4.0.1" + os-tmpdir "^1.0.0" + osenv "^0.1.0" + uuid "^2.0.1" + write-file-atomic "^1.1.2" + xdg-basedir "^2.0.0" + +connect-memcached@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/connect-memcached/-/connect-memcached-0.2.0.tgz#64907180324e117d9e8f299c994c0cb03d5020cf" dependencies: @@ -696,9 +1153,9 @@ constantinople@~3.0.1: dependencies: acorn "^2.1.0" -content-disposition@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b" +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" content-type@~1.0.2: version "1.0.2" @@ -711,15 +1168,15 @@ continuation-local-storage@^3.1.4: async-listener "^0.6.0" emitter-listener "^1.0.1" -cookie-session@^2.0.0-alpha.1: - version "2.0.0-alpha.2" - resolved "https://registry.yarnpkg.com/cookie-session/-/cookie-session-2.0.0-alpha.2.tgz#f4e565c570cd7e07d2ec5656869ed429fac2e631" - dependencies: - cookies "0.6.1" - debug "2.3.2" - on-headers "~1.0.1" +convert-source-map@^1.1.0, convert-source-map@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.3.0.tgz#e9f3e9c6e2728efc2676696a70eb382f73106a67" -cookie-signature@1.0.6: +convert-to-spaces@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.1.tgz#df97c15b6d061375cc4f3efe01bfc1f4d2a83ad6" + +cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -727,35 +1184,41 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -cookiejar@2.0.6: +cookiejar@2.0.6, cookiejar@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.6.tgz#0abf356ad00d1c5a219d88d44518046dd026acfe" -cookiejar@^2.0.6: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.0.tgz#86549689539b6d0e269b6637a304be508194d898" - -cookies@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.6.1.tgz#ef693b1bc6f01f567d46e2f504e9c15fb70cba90" +core-assert@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/core-assert/-/core-assert-0.2.1.tgz#f85e2cf9bfed28f773cc8b3fa5c5b69bdc02fe3f" dependencies: - depd "~1.1.0" - keygrip "~1.0.0" + buf-compare "^1.0.0" + is-error "^2.2.0" + +core-js@^2.0.0, core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -crc@3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.1.tgz#65d5830b1a2569557cfb324c0e679998521473ee" +crc@3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" -create-error-class@^2.0.0, create-error-class@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-2.0.1.tgz#a8759ed5c8d214a461e81d18e70aacb33dd63c9c" +create-error-class@^3.0.1, create-error-class@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" dependencies: capture-stack-trace "^1.0.0" - inherits "^2.0.1" + +cross-spawn@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" cryptiles@2.x.x: version "2.0.5" @@ -778,47 +1241,45 @@ css@~1.0.8: css-parse "1.0.4" css-stringify "1.0.5" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + cycle@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" -d@^0.1.1, d@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" - dependencies: - es5-ext "~0.10.2" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" dependencies: assert-plus "^1.0.0" -debug-log@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" +date-time@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/date-time/-/date-time-0.1.1.tgz#ed2f6d93d9790ce2fd66d5b5ff3edd5bbcbf3b07" -debug@2, debug@2.3.2, debug@^2.1.1, debug@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.2.tgz#94cb466ef7d6d2c7e5245cdd6e4104f2d0d70d30" +debug@2, debug@2.6.0, debug@^2.1.1, debug@^2.2.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" dependencies: ms "0.7.2" -debug@2.2.0, debug@~2.2.0: +debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: ms "0.7.1" -decamelize@^1.0.0, decamelize@^1.1.1: +decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -deep-eql@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" - dependencies: - type-detect "0.1.1" +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" deep-equal@~0.2.1: version "0.2.2" @@ -828,33 +1289,6 @@ deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - -deglob@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/deglob/-/deglob-2.1.0.tgz#4d44abe16ef32c779b4972bd141a80325029a14a" - dependencies: - find-root "^1.0.0" - glob "^7.0.5" - ignore "^3.0.9" - pkg-config "^1.1.0" - run-parallel "^1.1.2" - uniq "^1.0.1" - -del@^2.0.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" - dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -871,6 +1305,12 @@ destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + dicer@0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" @@ -878,20 +1318,9 @@ dicer@0.2.5: readable-stream "1.1.x" streamsearch "0.1.2" -diff@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" - -dns-zonefile@0.1.18: - version "0.1.18" - resolved "https://registry.yarnpkg.com/dns-zonefile/-/dns-zonefile-0.1.18.tgz#7569c7371dbdfd50bf8e53e2da0740679292299e" - -doctrine@^1.2.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" +diff@^3.0.0, diff@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" dot-prop@^2.4.0: version "2.4.0" @@ -899,7 +1328,25 @@ dot-prop@^2.4.0: dependencies: is-obj "^1.0.0" -duplexify@^3.1.2, duplexify@^3.2.0: +dot-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" + dependencies: + is-obj "^1.0.0" + +dot-prop@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.0.tgz#eb29eac57dfa31fda1edef50ea462ee3d38ff3ab" + dependencies: + is-obj "^1.0.0" + +duplexer2@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + dependencies: + readable-stream "^2.0.2" + +duplexify@^3.1.2, duplexify@^3.2.0, duplexify@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604" dependencies: @@ -931,6 +1378,13 @@ emitter-listener@^1.0.1: dependencies: shimmer "1.0.0" +empower-core@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/empower-core/-/empower-core-0.6.1.tgz#6c187f502fcef7554d57933396aac655483772b1" + dependencies: + call-signature "0.0.2" + core-js "^2.0.0" + encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" @@ -951,192 +1405,47 @@ ent@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" +equal-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/equal-length/-/equal-length-1.0.1.tgz#21ca112d48ab24b4e1e7ffc0e5339d31fdfc274c" + error-ex@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.0.tgz#e67b43f3e82c96ea3a584ffee0b9fc3325d802d9" dependencies: is-arrayish "^0.2.1" -es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: - version "0.10.12" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" - dependencies: - es6-iterator "2" - es6-symbol "~3.1" - -es6-iterator@2: - version "2.0.0" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac" - dependencies: - d "^0.1.1" - es5-ext "^0.10.7" - es6-symbol "3" - -es6-map@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897" - dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - es6-iterator "2" - es6-set "~0.1.3" - es6-symbol "~3.1.0" - event-emitter "~0.3.4" - es6-promise@3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" -es6-set@~0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" - dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - es6-iterator "2" - es6-symbol "3" - event-emitter "~0.3.4" - -es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" - dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - -es6-weak-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.1.tgz#0d2bbd8827eb5fb4ba8f97fbfea50d43db21ea81" - dependencies: - d "^0.1.1" - es5-ext "^0.10.8" - es6-iterator "2" - es6-symbol "3" - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -escodegen@1.8.x: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" - dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-config-semistandard@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-semistandard/-/eslint-config-semistandard-7.0.0.tgz#f803493f56a5172f7f59c35ae648360b41f2ff71" - -eslint-config-standard-jsx@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-3.2.0.tgz#c240e26ed919a11a42aa4de8059472b38268d620" - -eslint-config-standard@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-6.2.1.tgz#d3a68aafc7191639e7ee441e7348739026354292" - -eslint-plugin-promise@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.4.0.tgz#6ba9048c2df57be77d036e0c68918bc9b4fc4195" - -eslint-plugin-react@~6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.8.0.tgz#741ab5438a094532e5ce1bbb935d6832356f492d" - dependencies: - doctrine "^1.2.2" - jsx-ast-utils "^1.3.4" - -eslint-plugin-standard@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-2.0.1.tgz#3589699ff9c917f2c25f76a916687f641c369ff3" - -eslint@~3.11.1: - version "3.11.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.11.1.tgz#408be581041385cba947cd8d1cd2227782b55dbf" - dependencies: - babel-code-frame "^6.16.0" - chalk "^1.1.3" - concat-stream "^1.4.6" - debug "^2.1.1" - doctrine "^1.2.2" - escope "^3.6.0" - espree "^3.3.1" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - glob "^7.0.3" - globals "^9.2.0" - ignore "^3.2.0" - imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" - is-resolvable "^1.0.0" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" - levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.7.5" - strip-bom "^3.0.0" - strip-json-comments "~1.0.1" - table "^3.7.8" - text-table "~0.2.0" - user-home "^2.0.0" - -espree@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.3.2.tgz#dbf3fadeb4ecb4d4778303e50103b3d36c88b89c" +espower-location-detector@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/espower-location-detector/-/espower-location-detector-1.0.0.tgz#a17b7ecc59d30e179e2bef73fb4137704cb331b5" dependencies: - acorn "^4.0.1" - acorn-jsx "^3.0.0" - -esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + is-url "^1.2.1" + path-is-absolute "^1.0.0" + source-map "^0.5.0" + xtend "^4.0.0" -esrecurse@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" +espurify@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/espurify/-/espurify-1.6.0.tgz#6cb993582d9422bd6f2d4b258aadb14833f394f0" dependencies: - estraverse "~4.1.0" - object-assign "^4.0.1" - -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + core-js "^2.0.0" -estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.0.0, estraverse@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" -estraverse@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" - esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -1145,55 +1454,58 @@ etag@~1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" -event-emitter@~0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5" +execa@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" dependencies: - d "~0.1.1" - es5-ext "~0.10.7" - -eventemitter2@~1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-1.0.5.tgz#f983610517b1737c0b9dc643beca93893c04df18" + cross-spawn "^4.0.0" + get-stream "^2.2.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" -execSync@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/execSync/-/execSync-1.0.2.tgz#1f42eda582225180053224ecdd3fd1960fdb3139" +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" dependencies: - temp "~0.5.1" + is-posix-bracket "^0.1.0" -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" -express-session@^1.14.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.14.2.tgz#6bcf586ed6d1dc37b02570087756c9de7b80b275" +express-session@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.0.tgz#67131dd5b78a42bc57b50af0a14880265c03f919" dependencies: cookie "0.3.1" cookie-signature "1.0.6" - crc "3.4.1" - debug "~2.2.0" + crc "3.4.4" + debug "2.6.0" depd "~1.1.0" on-headers "~1.0.1" parseurl "~1.3.1" uid-safe "~2.1.3" utils-merge "1.0.0" -express-winston@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/express-winston/-/express-winston-2.1.2.tgz#8661075253166cdf2fc82f0f4675472b5f02a089" +express-winston@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/express-winston/-/express-winston-2.2.0.tgz#47a08d117f3915beba6bc89271af0087c4c96a30" dependencies: chalk "~0.4.0" lodash "~4.11.1" -express@^4.14.0: - version "4.14.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.14.0.tgz#c1ee3f42cdc891fb3dc650a8922d51ec847d0d66" +express@4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33" dependencies: accepts "~1.3.3" array-flatten "1.1.1" - content-disposition "0.5.1" + content-disposition "0.5.2" content-type "~1.0.2" cookie "0.3.1" cookie-signature "1.0.6" @@ -1202,19 +1514,19 @@ express@^4.14.0: encodeurl "~1.0.1" escape-html "~1.0.3" etag "~1.7.0" - finalhandler "0.5.0" + finalhandler "0.5.1" fresh "0.3.0" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.1" path-to-regexp "0.1.7" - proxy-addr "~1.1.2" + proxy-addr "~1.1.3" qs "6.2.0" range-parser "~1.2.0" - send "0.14.1" - serve-static "~1.11.1" - type-is "~1.6.13" + send "0.14.2" + serve-static "~1.11.2" + type-is "~1.6.14" utils-merge "1.0.0" vary "~1.1.0" @@ -1222,6 +1534,12 @@ extend@3.0.0, extend@^3.0.0, extend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" @@ -1230,23 +1548,15 @@ eyes@0.1.x: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - -figures@^1.3.5: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" dependencies: escape-string-regexp "^1.0.5" - object-assign "^4.1.0" -file-entry-cache@^2.0.0: +filename-regex@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775" fill-keys@^1.0.2: version "1.0.2" @@ -1255,32 +1565,68 @@ fill-keys@^1.0.2: is-object "~1.0.1" merge-descriptors "~1.0.0" -finalhandler@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +filled-array@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" + +finalhandler@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd" dependencies: debug "~2.2.0" escape-html "~1.0.3" on-finished "~2.3.0" - statuses "~1.3.0" + statuses "~1.3.1" unpipe "~1.0.0" -find-root@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.0.0.tgz#962ff211aab25c6520feeeb8d6287f8f6e95807a" +find-cache-dir@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" + dependencies: + commondir "^1.0.1" + mkdirp "^0.5.1" + pkg-dir "^1.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" findit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findit/-/findit-2.0.0.tgz#6509f0126af4c178551cfa99394e032e13a4d56e" -flat-cache@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" +fn-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" + +for-in@^0.1.5: + version "0.1.6" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8" + +for-own@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.4.tgz#0149b41a39088c7515f51ebe1c1386d45f935072" dependencies: - circular-json "^0.3.1" - del "^2.0.2" - graceful-fs "^4.1.2" - write "^0.2.1" + for-in "^0.1.5" forever-agent@~0.6.1: version "0.6.1" @@ -1294,15 +1640,7 @@ form-data@1.0.0-rc3: combined-stream "^1.0.5" mime-types "^2.1.3" -form-data@1.0.0-rc4, form-data@~1.0.0-rc4: - version "1.0.0-rc4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" - dependencies: - async "^1.5.2" - combined-stream "^1.0.5" - mime-types "^2.1.10" - -form-data@~2.1.1: +form-data@^2.1.1, form-data@~2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" dependencies: @@ -1310,13 +1648,25 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" +form-data@~1.0.0-rc4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" + dependencies: + async "^2.0.1" + combined-stream "^1.0.5" + mime-types "^2.1.11" + formatio@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" dependencies: samsam "~1.1" -formidable@^1.0.17, formidable@~1.0.14: +formidable@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" + +formidable@~1.0.14: version "1.0.17" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.0.17.tgz#ef5491490f9433b705faa77249c99029ae348559" @@ -1332,6 +1682,13 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" +fsevents@^1.0.0: + version "1.0.17" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.0.17.tgz#8537f3f12272678765b4fd6528c0f1f66f8f4558" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.29" + fstream-ignore@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" @@ -1363,54 +1720,12 @@ gauge@~2.7.1: supports-color "^0.2.0" wide-align "^1.1.0" -gce-images@^0.2.0: - version "0.2.3" - resolved "https://registry.yarnpkg.com/gce-images/-/gce-images-0.2.3.tgz#9297119fa9800404481222ed95f4f99843bb19c7" - dependencies: - arrify "^1.0.0" - async "^1.4.2" - google-auto-auth "^0.1.0" - got "^4.1.1" - object-assign "^3.0.0" - -gcloud@~0.37.0: - version "0.37.0" - resolved "https://registry.yarnpkg.com/gcloud/-/gcloud-0.37.0.tgz#fc0b31c7ad2e5c06c08d6e4e08d2732c4a490e6a" +gcp-metadata@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.1.0.tgz#abe21f1ea324dd0b34a3f06ca81763fb1eee37d9" dependencies: - JSONStream "^1.0.7" - array-uniq "^1.0.2" - arrify "^1.0.0" - async "^1.4.2" - concat-stream "^1.5.0" - create-error-class "^2.0.1" - dns-zonefile "0.1.18" - dot-prop "^2.4.0" - duplexify "^3.2.0" - ent "^2.2.0" extend "^3.0.0" - gce-images "^0.2.0" - gcs-resumable-upload "^0.7.1" - google-auto-auth "^0.2.4" - google-proto-files "^0.2.1" - grpc "^0.14.1" - hash-stream-validation "^0.2.1" - is "^3.0.1" - lodash.flatten "^4.2.0" - methmeth "^1.0.0" - mime-types "^2.0.8" - modelo "^4.2.0" - node-int64 "^0.4.0" - once "^1.3.1" - prop-assign "^1.0.0" - propprop "^0.3.0" - pumpify "^1.3.3" - request "^2.70.0" - retry-request "^1.3.0" - rgb-hex "^1.0.0" - split-array-stream "^1.0.0" - stream-events "^1.0.1" - string-format-obj "^1.0.0" - through2 "^2.0.0" + retry-request "^1.3.2" gcs-resumable-upload@^0.7.1: version "0.7.4" @@ -1434,13 +1749,22 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" +get-port@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-2.1.0.tgz#8783f9dcebd1eea495a334e1a6a251e78887ab1a" + dependencies: + pinkie-promise "^2.0.0" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" -get-stdin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" getpass@^0.1.1: version "0.1.6" @@ -1448,43 +1772,45 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@7.0.5, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95" +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" -glob@^5.0.10, glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" +glob@^7.0.3, glob@^7.0.5: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: + fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "2 || 3" + minimatch "^3.0.2" once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.2.0: +globals@^9.0.0: version "9.14.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" +globby@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" dependencies: array-union "^1.0.1" - arrify "^1.0.0" glob "^7.0.3" object-assign "^4.0.1" pify "^2.0.0" pinkie-promise "^2.0.0" -google-auth-library@^0.9.5, google-auth-library@^0.9.6, google-auth-library@^0.9.9: +google-auth-library@^0.9.10, google-auth-library@^0.9.5, google-auth-library@^0.9.6, google-auth-library@^0.9.9: version "0.9.10" resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.9.10.tgz#4993dc07bb4834b8ca0350213a6873a32c6051b9" dependencies: @@ -1495,41 +1821,33 @@ google-auth-library@^0.9.5, google-auth-library@^0.9.6, google-auth-library@^0.9 request "~2.74.0" string-template "~0.2.0" -"google-auth-library@git://github.com/google/google-auth-library-nodejs.git#02f587472ecdc2582f8d5fc754996aa6ef9388a0": - version "0.9.7" - resolved "git://github.com/google/google-auth-library-nodejs.git#02f587472ecdc2582f8d5fc754996aa6ef9388a0" - dependencies: - async "~1.4.2" - gtoken "^1.1.0" - jws "~3.0.0" - lodash.noop "~3.0.0" - request "~2.74.0" - string-template "~0.2.0" - -google-auto-auth@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.1.1.tgz#69e061ac54484c11e8a971a5e76d4239dbee6910" +google-auto-auth@^0.2.1: + version "0.2.4" + resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.2.4.tgz#16dafbf150d353a42190979c6803ffc75f6455fa" dependencies: google-auth-library "^0.9.6" object-assign "^3.0.0" -google-auto-auth@^0.2.1, google-auto-auth@^0.2.4, google-auto-auth@~0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.2.4.tgz#16dafbf150d353a42190979c6803ffc75f6455fa" +google-auto-auth@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/google-auto-auth/-/google-auto-auth-0.5.2.tgz#4c9f38574e69fb55a3c516ab0415e9fa33e67602" dependencies: - google-auth-library "^0.9.6" + async "^2.1.2" + google-auth-library "^0.9.10" object-assign "^3.0.0" + request "^2.79.0" -google-gax@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-0.6.0.tgz#c7a7bc7f2acf23ce685888dd81485b7924841b73" +google-gax@^0.10.0: + version "0.10.7" + resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-0.10.7.tgz#073e413dca4a508899b4c345f82042e5d1290009" dependencies: - chai "*" - eventemitter2 "~1.0.2" - google-auto-auth "~0.2.4" - grpc "~0.15.0" - lodash "~4.11.1" - through2 "~2.0.1" + extend "^3.0.0" + google-auto-auth "^0.5.2" + google-proto-files "^0.8.3" + grpc "~1.1" + lodash "^4.17.2" + process-nextick-args "^1.0.7" + readable-stream "^2.2.2" google-p12-pem@^0.1.0: version "0.1.1" @@ -1537,76 +1855,47 @@ google-p12-pem@^0.1.0: dependencies: node-forge "^0.6.46" -google-proto-files@^0.2.1: - version "0.2.4" - resolved "https://registry.yarnpkg.com/google-proto-files/-/google-proto-files-0.2.4.tgz#5ef818e62cd345e9f9215a2b78c4a4ed7193adf7" +google-proto-files@^0.8.0, google-proto-files@^0.8.3, google-proto-files@^0.8.6: + version "0.8.6" + resolved "https://registry.yarnpkg.com/google-proto-files/-/google-proto-files-0.8.6.tgz#a7c8ddccd2179690d270b0ebfc42994d56da0ee6" -google-proto-files@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/google-proto-files/-/google-proto-files-0.7.0.tgz#8fac0c23ace78e6bf4aab63ad2209c506569d710" - -got@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/got/-/got-4.2.0.tgz#af59f461834bfafd722cba01acf4c14a9dd5da06" +got@^5.0.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" dependencies: - create-error-class "^2.0.0" - duplexify "^3.2.0" - is-plain-obj "^1.0.0" + create-error-class "^3.0.1" + duplexer2 "^0.1.4" is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" is-stream "^1.0.0" lowercase-keys "^1.0.0" node-status-codes "^1.0.0" - object-assign "^3.0.0" + object-assign "^4.0.1" parse-json "^2.1.0" - pinkie-promise "^1.0.0" - prepend-http "^1.0.0" + pinkie-promise "^2.0.0" read-all-stream "^3.0.0" - timed-out "^2.0.0" - unzip-response "^1.0.0" + readable-stream "^2.0.5" + timed-out "^3.0.0" + unzip-response "^1.0.2" + url-parse-lax "^1.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -graceful-fs@~1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -growl@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" - -grpc@^0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-0.14.1.tgz#9a5ad23f5038eea324a33c67aa634c2cd3d77193" - dependencies: - arguejs "^0.2.3" - lodash "^3.9.3" - nan "^2.0.0" - protobufjs "^4.0.0" - -grpc@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.0.1.tgz#e965544b5e56c998058102184e2ab1f27f123afd" +grpc@^1.1.1, grpc@~1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.1.1.tgz#f8fe5fc984e7d4d6ab0c19259a7d232c95e142bc" dependencies: arguejs "^0.2.3" lodash "^4.15.0" nan "^2.0.0" - node-pre-gyp "^0.6.0" - protobufjs "^5.0.0" - -grpc@~0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-0.15.0.tgz#734e3c4ad1b6df208f2d0647c1f6402fb141f9dd" - dependencies: - arguejs "^0.2.3" - lodash "^3.9.3" - nan "^2.0.0" - protobufjs "^4.0.0" + node-pre-gyp "^0.6.0" + protobufjs "^5.0.0" gtoken@^1.1.0: version "1.2.1" @@ -1617,16 +1906,6 @@ gtoken@^1.1.0: mime "^1.2.11" request "^2.72.0" -handlebars@^4.0.1: - version "4.0.6" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7" - dependencies: - async "^1.4.0" - optimist "^0.6.1" - source-map "^0.4.4" - optionalDependencies: - uglify-js "^2.6" - har-validator@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" @@ -1646,14 +1925,18 @@ has-color@~0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" +has-yarn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-1.0.0.tgz#89e25db604b725c8f5976fff0addc921b828a5a7" + hash-stream-validation@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.1.tgz#ecc9b997b218be5bb31298628bb807869b73dcd1" @@ -1687,7 +1970,11 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -http-errors@~1.5.0: +hosted-git-info@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.2.0.tgz#7a0d097863d886c0fabbdcd37bf1758d8becf8a5" + +http-errors@~1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" dependencies: @@ -1707,13 +1994,13 @@ i@0.3.x: version "0.3.5" resolved "https://registry.yarnpkg.com/i/-/i-0.3.5.tgz#1d2b854158ec8169113c6cb7f6b6801e99e211d5" -iconv-lite@0.4.13: - version "0.4.13" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" +iconv-lite@0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" -ignore@^3.0.9, ignore@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435" +ignore-by-default@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" imurmurhash@^0.1.4: version "0.1.4" @@ -1727,6 +2014,16 @@ indent-string@^1.1.0: minimist "^1.1.0" repeating "^1.1.0" +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indent-string@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.1.0.tgz#08ff4334603388399b329e6b9538dc7a3cf5de7d" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1746,45 +2043,73 @@ ini@^1.3.0, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" -inquirer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" +invariant@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - figures "^1.3.5" - lodash "^4.3.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - -interpret@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" + loose-envify "^1.0.0" invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ipaddr.js@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.1.1.tgz#c791d95f52b29c1247d5df80ada39b8a73647230" +ipaddr.js@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4" + +irregular-plurals@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.2.0.tgz#38f299834ba8c00c30be9c554e137269752ff3ac" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + is-buffer@^1.0.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" -is-finite@^1.0.0: +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + +is-dotfile@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-error@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-error/-/is-error-2.2.1.tgz#684a96d84076577c98f4cdb40c6d26a5123bf19c" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0, is-finite@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" dependencies: @@ -1796,11 +2121,17 @@ is-fullwidth-code-point@^1.0.0: dependencies: number-is-nan "^1.0.0" -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-generator-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" -is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: +is-my-json-valid@^2.12.4: version "2.15.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" dependencies: @@ -1809,6 +2140,16 @@ is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: jsonpointer "^4.0.0" xtend "^4.0.0" +is-npm@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + +is-number@^2.0.2, is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" @@ -1817,27 +2158,25 @@ is-object@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - -is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" +is-observable@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2" dependencies: - path-is-inside "^1.0.1" + symbol-observable "^0.2.2" is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" -is-promise@^2.0.0: +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.0.0, is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" @@ -1853,17 +2192,15 @@ is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" -is-resolvable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" - dependencies: - tryit "^1.0.1" +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" is-stream-ended@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.0.tgz#40f058df6b044ee598fee4df7dc1ec2bcdd8df60" -is-stream@^1.0.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -1871,7 +2208,15 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -is@^3.0.1: +is-url@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.2.tgz#498905a593bf47cc2d9e7f738372bbf7696c7f26" + +is-utf8@^0.2.0, is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is@^3.0.1, is@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/is/-/is-3.2.0.tgz#a362e3daf7df3fd8b7114115d624c5b7e1cb90f7" @@ -1879,7 +2224,7 @@ isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@^1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1887,36 +2232,23 @@ isexe@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + isstream@0.1.x, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -istanbul@^0.4.4: - version "0.4.5" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" - dependencies: - abbrev "1.0.x" - async "1.x" - escodegen "1.8.x" - esprima "2.7.x" - glob "^5.0.15" - handlebars "^4.0.1" - js-yaml "3.x" - mkdirp "0.5.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" - jackpot@>=0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/jackpot/-/jackpot-0.0.6.tgz#3cff064285cbf66f4eab2593c90bce816a821849" dependencies: retry "0.6.0" -jade@^1.11.0: +jade@1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/jade/-/jade-1.11.0.tgz#9c80e538c12d3fb95c8d9bb9559fa0cc040405fd" dependencies: @@ -1930,52 +2262,85 @@ jade@^1.11.0: void-elements "~2.0.1" with "~4.0.0" +jest-diff@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-18.1.0.tgz#4ff79e74dd988c139195b365dc65d87f606f4803" + dependencies: + chalk "^1.1.3" + diff "^3.0.0" + jest-matcher-utils "^18.1.0" + pretty-format "^18.1.0" + +jest-file-exists@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-17.0.0.tgz#7f63eb73a1c43a13f461be261768b45af2cdd169" + +jest-matcher-utils@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-18.1.0.tgz#1ac4651955ee2a60cef1e7fcc98cdfd773c0f932" + dependencies: + chalk "^1.1.3" + pretty-format "^18.1.0" + +jest-mock@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-18.0.0.tgz#5c248846ea33fa558b526f5312ab4a6765e489b3" + +jest-snapshot@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-18.1.0.tgz#55b96d2ee639c9bce76f87f2a3fd40b71c7a5916" + dependencies: + jest-diff "^18.1.0" + jest-file-exists "^17.0.0" + jest-matcher-utils "^18.1.0" + jest-util "^18.1.0" + natural-compare "^1.4.0" + pretty-format "^18.1.0" + +jest-util@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-18.1.0.tgz#3a99c32114ab17f84be094382527006e6d4bfc6a" + dependencies: + chalk "^1.1.1" + diff "^3.0.0" + graceful-fs "^4.1.6" + jest-file-exists "^17.0.0" + jest-mock "^18.0.0" + mkdirp "^0.5.1" + jodid25519@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" dependencies: jsbn "~0.1.0" -js-tokens@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5" - -js-yaml@3.x, js-yaml@^3.5.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" jsbn@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd" +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" -json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json3@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -jsonparse@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" +json5@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" jsonpointer@^4.0.0: version "4.0.1" @@ -1996,13 +2361,6 @@ jstransformer@0.0.2: is-promise "^2.0.0" promise "^6.0.1" -jsx-ast-utils@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.3.5.tgz#9ba6297198d9f754594d62e59496ffb923778dd4" - dependencies: - acorn-jsx "^3.0.1" - object-assign "^4.1.0" - jwa@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.0.2.tgz#fd79609f1e772e299dce8ddb76d00659dd83511f" @@ -2018,25 +2376,27 @@ jws@^3.0.0, jws@~3.0.0: base64url "~1.0.4" jwa "~1.0.0" -kerberos@^0.0.21: - version "0.0.21" - resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-0.0.21.tgz#414f7f947d45afff10406018f233bf471a1d1195" - dependencies: - nan "~2.3" - -keygrip@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.1.tgz#b02fa4816eef21a8c4b35ca9e52921ffc89a30e9" - kind-of@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" dependencies: is-buffer "^1.0.2" -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" +last-line-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/last-line-stream/-/last-line-stream-1.0.0.tgz#d1b64d69f86ff24af2d04883a2ceee14520a5600" + dependencies: + through2 "^2.0.0" + +latest-version@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" + dependencies: + package-json "^2.0.0" + +lazy-req@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" lcid@^1.0.0: version "1.0.0" @@ -2044,43 +2404,39 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" -lodash._baseassign@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" dependencies: - lodash._basecopy "^3.0.0" - lodash.keys "^3.0.0" - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - -lodash._basecreate@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" +lodash.debounce@^4.0.3: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" -lodash.create@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" - dependencies: - lodash._baseassign "^3.0.0" - lodash._basecreate "^3.0.0" - lodash._isiterateecall "^3.0.0" +lodash.difference@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" lodash.findindex@^4.4.0: version "4.6.0" @@ -2090,35 +2446,15 @@ lodash.flatten@^4.2.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - -lodash.isequal@^4.0.0: +lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - lodash.noop@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" -lodash@^3.9.3: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - -lodash@^4.0.0, lodash@^4.12.0, lodash@^4.14.0, lodash@^4.14.2, lodash@^4.15.0, lodash@^4.3.0: +lodash@4.17.4, lodash@^4.12.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.2.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -2126,30 +2462,72 @@ lodash@~4.11.1: version "4.11.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.11.2.tgz#d6b4338b110a58e21dae5cebcfdbbfd2bc4cdb3b" +log-driver@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" + lolex@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" -"long@~2 >=2.3.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f" - long@~3: version "3.2.0" resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +loud-rejection@^1.0.0, loud-rejection@^1.2.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -map-obj@^1.0.0: +lru-cache@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" + dependencies: + pseudomap "^1.0.1" + yallist "^2.0.0" + +map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" +matcher@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-0.1.2.tgz#ef20cbde64c24c50cc61af5b83ee0b1b8ff00101" + dependencies: + escape-string-regexp "^1.0.4" + +max-timeout@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/max-timeout/-/max-timeout-1.0.0.tgz#b68f69a2f99e0b476fd4cb23e2059ca750715e1f" + +md5-hex@^1.2.0, md5-hex@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-1.3.0.tgz#d2c4afe983c4370662179b8cad145219135046c4" + dependencies: + md5-o-matic "^0.1.1" + +md5-hex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-2.0.0.tgz#d0588e9f1c74954492ecd24ac0ac6ce997d92e33" + dependencies: + md5-o-matic "^0.1.1" + +md5-o-matic@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -2161,6 +2539,21 @@ memcached@2.2.x: hashring "3.2.x" jackpot ">=0.0.6" +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + meow@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-2.0.0.tgz#8f530a8ecf5d40d3f4b4df93c3472900fba2a8f1" @@ -2174,7 +2567,7 @@ merge-descriptors@1.0.1, merge-descriptors@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" -methmeth@^1.0.0: +methmeth@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/methmeth/-/methmeth-1.1.0.tgz#e80a26618e52f5c4222861bb748510bd10e29089" @@ -2182,56 +2575,62 @@ methods@1.x, methods@^1.1.1, methods@~1.1.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -mime-db@~1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392" +micromatch@^2.1.5: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@~1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" -mime-types@^2.0.8, mime-types@^2.1.10, mime-types@^2.1.12, mime-types@^2.1.3, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7: - version "2.1.13" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88" +mime-types@^2.0.8, mime-types@^2.1.11, mime-types@^2.1.12, mime-types@^2.1.3, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7: + version "2.1.14" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee" dependencies: - mime-db "~1.25.0" + mime-db "~1.26.0" mime@1.3.4, mime@^1.2.11, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2: +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +minimatch@^3.0.0, minimatch@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: brace-expansion "^1.0.0" -minimist@0.0.8, minimist@~0.0.1: +minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0, minimist@^1.2.0: +minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" -mocha@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.2.0.tgz#7dc4f45e5088075171a68896814e6ae9eb7a85e3" - dependencies: - browser-stdout "1.3.0" - commander "2.9.0" - debug "2.2.0" - diff "1.4.0" - escape-string-regexp "1.0.5" - glob "7.0.5" - growl "1.9.2" - json3 "3.3.2" - lodash.create "3.1.1" - mkdirp "0.5.1" - supports-color "3.1.2" - modelo@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/modelo/-/modelo-4.2.0.tgz#3b4b420023a66ca7e32bdba16e710937e14d1b0b" @@ -2244,32 +2643,32 @@ moment@^2.15.0: version "2.17.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" -mongodb-core@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.4.tgz#97f84cb126606c72585e66066ea3a05e9c20dc9b" +mongodb-core@2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.7.tgz#6a27909b98142ef2508d924c274969008954fa29" dependencies: - bson "~1.0.3" + bson "~1.0.4" require_optional "~1.0.0" -mongodb@^2.2.5: - version "2.2.19" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.19.tgz#9d84745f9369ff301df5a1790cf485375f1df7d7" +mongodb@2.2.22: + version "2.2.22" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.22.tgz#d67c588fc08f922db19754b1d2e03e2d7d1319fb" dependencies: es6-promise "3.2.1" - mongodb-core "2.1.4" + mongodb-core "2.1.7" readable-stream "2.1.5" ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" -ms@0.7.2: +ms@0.7.2, ms@^0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" -multer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.2.1.tgz#759959c6c19f7fb03d0239f1df0b1ecec88473a3" +multer@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.3.0.tgz#092b2670f6846fa4914965efc8cf94c20fec6cd2" dependencies: append-field "^0.1.0" busboy "^0.2.11" @@ -2280,31 +2679,36 @@ multer@^1.2.0: type-is "^1.6.4" xtend "^4.0.0" -mute-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" +multimatch@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" + dependencies: + array-differ "^1.0.0" + array-union "^1.0.1" + arrify "^1.0.0" + minimatch "^3.0.0" mute-stream@~0.0.4: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -mysql@^2.11.1: - version "2.12.0" - resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.12.0.tgz#18f7af74555f3f55b7c33d67fc2ca48ccd490e4d" +mysql@2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.13.0.tgz#998f1f8ca46e2e3dd7149ce982413653986aae47" dependencies: - bignumber.js "2.4.0" + bignumber.js "3.1.2" readable-stream "1.1.14" sqlstring "2.2.0" -nan@^2.0.0, nan@~2.3: - version "2.3.5" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.3.5.tgz#822a0dc266290ce4cd3a12282ca3e7e364668a08" +nan@^2.0.0, nan@^2.3.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" -nconf@^0.8.4: +nconf@0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.8.4.tgz#9502234f7ad6238cab7f92d7c068c20434d3ff93" dependencies: @@ -2322,16 +2726,12 @@ negotiator@0.6.1: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" node-forge@^0.6.46: - version "0.6.46" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.46.tgz#04a8a1c336eb72ef6f434ba7c854d608916c328d" + version "0.6.49" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.49.tgz#f1ee95d5d74623938fe19d698aa5a26d54d2f60f" -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - -node-pre-gyp@^0.6.0: - version "0.6.32" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.32.tgz#fc452b376e7319b3d255f5f34853ef6fd8fe1fd5" +node-pre-gyp@^0.6.0, node-pre-gyp@^0.6.29: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz#640ac55198f6a925972e0c16c4ac26a034d5ecc9" dependencies: mkdirp "~0.5.1" nopt "~3.0.6" @@ -2351,21 +2751,40 @@ node-uuid@~1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f" -"nodejs-repo-tools@git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git": +"nodejs-repo-tools@git+https://git@github.com/ace-n/nodejs-repo-tools.git": version "1.0.0" - resolved "git+https://git@github.com/GoogleCloudPlatform/nodejs-repo-tools.git#6f3a29782c457976e6ec1551d0b3996db975ee4e" + resolved "git+https://git@github.com/ace-n/nodejs-repo-tools.git#6f3a29782c457976e6ec1551d0b3996db975ee4e" dependencies: async "^1.5.2" proxyquire "^1.7.4" request "^2.72.0" supertest "^1.2.0" -nopt@3.x, nopt@~3.0.6: +nopt@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" dependencies: abbrev "1" +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + npmlog@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" @@ -2396,8 +2815,22 @@ object-assign@^3.0.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" object-assign@^4.0.1, object-assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +observable-to-promise@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/observable-to-promise/-/observable-to-promise-0.4.0.tgz#28afe71645308f2d41d71f47ad3fece1a377e52b" + dependencies: + is-observable "^0.2.0" + symbol-observable "^0.2.2" on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" @@ -2409,7 +2842,7 @@ on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" -once@1.x, once@^1.3.0, once@^1.3.1: +once@^1.3.0, once@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -2421,16 +2854,11 @@ once@~1.3.0, once@~1.3.3: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" +onetime@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.0.tgz#52aa8110e52fc5126ffc667bd8ec21c2ed209ce6" dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" + mimic-fn "^1.0.0" optimist@~0.3.5: version "0.3.7" @@ -2438,16 +2866,11 @@ optimist@~0.3.5: dependencies: wordwrap "~0.0.2" -optionator@^0.8.1, optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" +option-chain@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/option-chain/-/option-chain-0.1.1.tgz#e9b811e006f1c0f54802f28295bfc8970f8dcfbd" dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" + object-assign "^4.0.1" optjs@~3.2.2: version "3.2.2" @@ -2474,17 +2897,63 @@ osenv@^0.1.0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -parse-json@^2.1.0: +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +package-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44" + dependencies: + md5-hex "^1.3.0" + +package-json@^2.0.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" + dependencies: + got "^5.0.0" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.1.0, parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" dependencies: error-ex "^1.2.0" +parse-ms@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-0.1.2.tgz#dd3fa25ed6c2efc7bdde12ad9b46c163aa29224e" + +parse-ms@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" + parseurl@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" -passport-google-oauth20@^1.0.0: +passport-google-oauth20@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-1.0.0.tgz#3b960e8a1d70d1dbe794615c827c68c40392a5d0" dependencies: @@ -2503,25 +2972,49 @@ passport-strategy@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" -passport@^0.3.2: +passport@0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/passport/-/passport-0.3.2.tgz#9dd009f915e8fe095b0124a01b8f82da07510102" dependencies: passport-strategy "1.x.x" pause "0.0.1" +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + pause@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" @@ -2550,13 +3043,18 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" -pkg-config@^1.0.1, pkg-config@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pkg-config/-/pkg-config-1.1.1.tgz#557ef22d73da3c8837107766c52eadabde298fe4" +pkg-conf@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.0.0.tgz#071c87650403bccfb9c627f58751bfe47c067279" + dependencies: + find-up "^2.0.0" + load-json-file "^2.0.0" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" dependencies: - debug-log "^1.0.0" - find-root "^1.0.0" - xtend "^4.0.1" + find-up "^1.0.0" pkginfo@0.3.x: version "0.3.1" @@ -2566,26 +3064,52 @@ pkginfo@0.x.x: version "0.4.0" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.0.tgz#349dbb7ffd38081fcadc0853df687f0c7744cd65" -pluralize@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +plur@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-1.0.0.tgz#db85c6814f5e5e5a3b49efc28d604fec62975156" -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +plur@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" + dependencies: + irregular-plurals "^1.0.0" -prepend-http@^1.0.0: +prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" -process-nextick-args@~1.0.6: +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +pretty-format@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-18.1.0.tgz#fb65a86f7a7f9194963eee91865c1bcf1039e284" + dependencies: + ansi-styles "^2.2.1" + +pretty-ms@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-0.2.2.tgz#da879a682ff33a37011046f13d627f67c73b84f6" + dependencies: + parse-ms "^0.1.0" + +pretty-ms@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-2.1.0.tgz#4257c256df3fb0b451d6affaab021884126981dc" + dependencies: + is-finite "^1.0.1" + parse-ms "^1.0.0" + plur "^1.0.0" + +private@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + +process-nextick-args@^1.0.7, process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - promise@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-6.1.0.tgz#2ce729f6b94b45c26891ad0602c5c90e04c6eef6" @@ -2598,7 +3122,7 @@ promise@~2.0: dependencies: is-promise "~1" -prompt@^1.0.0: +prompt@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prompt/-/prompt-1.0.0.tgz#8e57123c396ab988897fb327fd3aedc3e735e4fe" dependencies: @@ -2617,39 +3141,34 @@ propprop@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/propprop/-/propprop-0.3.1.tgz#a049a3568b896440067d15d8ec9f33735e570178" -protobufjs@^4.0.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-4.1.3.tgz#8e36d1b0226cbb68d647e4b44c2a144f37f2779e" - dependencies: - ascli "~1" - bytebuffer "~4 >=4.1" - glob "^5.0.10" - yargs "^3.10.0" - protobufjs@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.1.tgz#589ecdda1a555fd69df4699adc142d36f133aa0b" + version "5.0.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.2.tgz#59748d7dcf03d2db22c13da9feb024e16ab80c91" dependencies: ascli "~1" bytebuffer "~5" - glob "^5.0.10" + glob "^7.0.5" yargs "^3.10.0" -proxy-addr@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.2.tgz#b4cc5f22610d9535824c123aef9d3cf73c40ba37" +proxy-addr@~1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074" dependencies: forwarded "~0.1.0" - ipaddr.js "1.1.1" + ipaddr.js "1.2.0" -proxyquire@^1.7.10, proxyquire@^1.7.4: - version "1.7.10" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.7.10.tgz#75be0770a81188f4d08bd01dfc30b767365aa20d" +proxyquire@1.7.11, proxyquire@^1.7.4: + version "1.7.11" + resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.7.11.tgz#13b494eb1e71fb21cc3ebe3699e637d3bec1af9e" dependencies: fill-keys "^1.0.2" module-not-found-error "^1.0.0" resolve "~1.1.7" +pseudomap@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + pump@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51" @@ -2673,10 +3192,14 @@ qs@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404" -qs@6.2.0, qs@~6.2.0: +qs@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" +qs@6.2.1, qs@~6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" + qs@^6.1.0, qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" @@ -2685,19 +3208,26 @@ random-bytes@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" +randomatic@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb" + dependencies: + is-number "^2.0.2" + kind-of "^3.0.2" + range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raw-body@~2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774" +raw-body@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" dependencies: bytes "2.4.0" - iconv-lite "0.4.13" + iconv-lite "0.4.15" unpipe "1.0.0" -rc@~1.1.6: +rc@^1.0.1, rc@^1.1.6, rc@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9" dependencies: @@ -2706,6 +3236,10 @@ rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~1.0.4" +re-emitter@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/re-emitter/-/re-emitter-1.1.3.tgz#fa9e319ffdeeeb35b27296ef0f3d374dac2f52a7" + read-all-stream@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" @@ -2713,6 +3247,36 @@ read-all-stream@^3.0.0: pinkie-promise "^2.0.0" readable-stream "^2.0.0" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read@1.0.x: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -2749,7 +3313,7 @@ readable-stream@2.1.5, readable-stream@~2.1.4: string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -2772,24 +3336,75 @@ readable-stream@~2.0.5: string_decoder "~0.10.x" util-deprecate "~1.0.1" -readline2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" dependencies: - resolve "^1.1.6" + indent-string "^2.1.0" + strip-indent "^1.0.1" reduce-component@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/reduce-component/-/reduce-component-1.0.1.tgz#e0c93542c574521bea13df0f9488ed82ab77c5da" +regenerate@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" + +regenerator-runtime@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz#257f41961ce44558b18f7814af48c17559f9faeb" + +regex-cache@^0.4.2: + version "0.4.3" + resolved "http://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + dependencies: + is-equal-shallow "^0.1.3" + is-primitive "^2.0.0" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +registry-auth-token@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.1.0.tgz#997c08256e0c7999837b90e944db39d8a790276b" + dependencies: + rc "^1.1.6" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + dependencies: + rc "^1.0.1" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" @@ -2800,6 +3415,12 @@ repeating@^1.1.0: dependencies: is-finite "^1.0.0" +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + request@2.76.0: version "2.76.0" resolved "https://registry.yarnpkg.com/request/-/request-2.76.0.tgz#be44505afef70360a0436955106be3945d95560e" @@ -2825,7 +3446,7 @@ request@2.76.0: tough-cookie "~2.3.0" tunnel-agent "~0.4.1" -request@>=2.42.0, request@^2.61.0, request@^2.70.0, request@^2.72.0, request@^2.74.0, request@^2.75.0, request@^2.79.0: +request@2.79.0, request@^2.61.0, request@^2.72.0, request@^2.75.0, request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -2876,12 +3497,9 @@ request@~2.74.0: tough-cookie "~2.3.0" tunnel-agent "~0.4.1" -require-uncached@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" +require-precompiled@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/require-precompiled/-/require-precompiled-0.1.0.tgz#5a1b52eb70ebed43eb982e974c85ab59571e56fa" require_optional@~1.0.0: version "1.0.0" @@ -2890,26 +3508,28 @@ require_optional@~1.0.0: resolve-from "^2.0.0" semver "^5.1.0" -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f" + dependencies: + resolve-from "^2.0.0" resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" -resolve@1.1.x, resolve@^1.1.6, resolve@~1.1.7: +resolve@~1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" + onetime "^2.0.0" + signal-exit "^3.0.2" -retry-request@^1.3.0: +retry-request@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-1.3.2.tgz#59ad24e71f8ae3f312d5f7b4bcf467a5e5a57bd6" dependencies: @@ -2924,42 +3544,12 @@ revalidator@0.1.x: version "0.1.8" resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" -rgb-hex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-1.0.0.tgz#bfaf8cd9cd9164b5a26d71eb4f15a0965324b3c1" - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - dependencies: - align-text "^0.1.1" - -rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@~2.5.1, rimraf@~2.5.4: +rimraf@2, rimraf@2.x.x, rimraf@~2.5.1, rimraf@~2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: glob "^7.0.5" -rimraf@~2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.1.4.tgz#5a6eb62eeda068f51ede50f29b3e5cd22f3d9bb2" - optionalDependencies: - graceful-fs "~1" - -run-async@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" - dependencies: - once "^1.3.0" - -run-parallel@^1.1.2: - version "1.1.6" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.6.tgz#29003c9a2163e01e2d2dfc90575f2c6c1d61a039" - -rx-lite@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" - safe-buffer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" @@ -2972,26 +3562,19 @@ secure-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca" -semistandard@^9.1.0: - version "9.2.1" - resolved "https://registry.yarnpkg.com/semistandard/-/semistandard-9.2.1.tgz#65d0e99deb63225250b8a993cec8174b54593a9d" +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" dependencies: - eslint "~3.11.1" - eslint-config-semistandard "7.0.0" - eslint-config-standard "6.2.1" - eslint-config-standard-jsx "3.2.0" - eslint-plugin-promise "~3.4.0" - eslint-plugin-react "~6.8.0" - eslint-plugin-standard "~2.0.1" - standard-engine "~5.3.0" + semver "^5.0.3" -semver@^5.0.1, semver@^5.1.0, semver@~5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" -send@0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a" +send@0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" dependencies: debug "~2.2.0" depd "~1.1.0" @@ -3000,38 +3583,34 @@ send@0.14.1: escape-html "~1.0.3" etag "~1.7.0" fresh "0.3.0" - http-errors "~1.5.0" + http-errors "~1.5.1" mime "1.3.4" - ms "0.7.1" + ms "0.7.2" on-finished "~2.3.0" range-parser "~1.2.0" - statuses "~1.3.0" + statuses "~1.3.1" -serve-static@~1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.1.tgz#d6cce7693505f733c759de57befc1af76c0f0805" +serve-static@~1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7" dependencies: encodeurl "~1.0.1" escape-html "~1.0.3" parseurl "~1.3.1" - send "0.14.1" + send "0.14.2" set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + setprototypeof@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" -shelljs@^0.7.3, shelljs@^0.7.5: - version "0.7.6" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad" - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shimmer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.0.0.tgz#49c2d71c678360b802be18b278382d1cbb805c39" @@ -3040,7 +3619,7 @@ shimmer@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.1.0.tgz#97d7377137ffbbab425522e429fe0aa89a488b35" -signal-exit@^3.0.0: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -3048,7 +3627,7 @@ simple-lru-cache@0.0.x: version "0.0.2" resolved "https://registry.yarnpkg.com/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz#d59cc3a193c1a5d0320f84ee732f6e4713e511dd" -sinon@^1.17.5: +sinon@1.17.7: version "1.17.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" dependencies: @@ -3057,6 +3636,10 @@ sinon@^1.17.5: samsam "1.1.2" util ">=0.10.3 <1" +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" @@ -3071,13 +3654,25 @@ sntp@1.x.x: dependencies: hoek "2.x.x" -source-map@0.4.x, source-map@^0.4.4: +sort-keys@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-map-support@^0.4.0, source-map-support@^0.4.2: + version "0.4.11" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.11.tgz#647f939978b38535909530885303daf23279f322" + dependencies: + source-map "^0.5.3" + +source-map@0.4.x: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: amdefine ">=0.0.4" -source-map@^0.5.1, source-map@~0.5.1: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -3087,11 +3682,19 @@ source-map@~0.1.7: dependencies: amdefine ">=0.0.4" -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" dependencies: - amdefine ">=0.0.4" + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" split-array-stream@^1.0.0: version "1.0.0" @@ -3106,17 +3709,13 @@ split@^1.0.0: dependencies: through "2" -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - sqlstring@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.2.0.tgz#c3135c4ea8abcd7e7ee741a4966a891d86a4f191" sshpk@^1.7.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.1.tgz#30e1a5d329244974a1af61511339d595af6638b0" + version "1.10.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.2.tgz#d5a804ce22695515638e798dbe23273de070a5fa" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -3133,18 +3732,11 @@ stack-trace@0.0.x: version "0.0.9" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" -standard-engine@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/standard-engine/-/standard-engine-5.3.0.tgz#fa254d7e068d92de8019d9945d420286d1ce04c9" - dependencies: - deglob "^2.1.0" - find-root "^1.0.0" - get-stdin "^5.0.1" - home-or-tmp "^2.0.0" - minimist "^1.1.0" - pkg-config "^1.0.1" +stack-utils@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-0.4.0.tgz#940cb82fccfa84e8ff2f3fdf293fe78016beccd1" -"statuses@>= 1.3.1 < 2", statuses@~1.3.0: +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -3162,7 +3754,7 @@ streamsearch@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" -string-format-obj@^1.0.0: +string-format-obj@^1.0.0, string-format-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/string-format-obj/-/string-format-obj-1.1.0.tgz#7635610b1ef397013e8478be98a170e04983d068" @@ -3178,13 +3770,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^3.0.0" - string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -3203,11 +3788,33 @@ strip-ansi@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" +strip-bom-buf@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572" + dependencies: + is-utf8 "^0.2.1" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" -strip-json-comments@~1.0.1, strip-json-comments@~1.0.4: +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" @@ -3231,21 +3838,28 @@ superagent@^1.7.2: readable-stream "1.0.27-1" reduce-component "1.0.1" -superagent@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-2.3.0.tgz#703529a0714e57e123959ddefbce193b2e50d115" +superagent@^3.0.0: + version "3.4.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.4.2.tgz#aa9a00f21720e230a6e7e9d7f07e0ab84509b833" dependencies: component-emitter "^1.2.0" cookiejar "^2.0.6" debug "^2.2.0" extend "^3.0.0" - form-data "1.0.0-rc4" - formidable "^1.0.17" + form-data "^2.1.1" + formidable "^1.1.1" methods "^1.1.1" mime "^1.3.4" qs "^6.1.0" readable-stream "^2.0.5" +supertest@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.0.0.tgz#8d4bb68fd1830ee07033b1c5a5a9a4021c965296" + dependencies: + methods "~1.1.2" + superagent "^3.0.0" + supertest@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/supertest/-/supertest-1.2.0.tgz#850a795f9068d2faf19e01799ff09962e0ce43be" @@ -3253,19 +3867,6 @@ supertest@^1.2.0: methods "1.x" superagent "^1.7.2" -supertest@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/supertest/-/supertest-2.0.1.tgz#a058081d788f1515d4700d7502881e6b759e44cd" - dependencies: - methods "1.x" - superagent "^2.0.0" - -supports-color@3.1.2, supports-color@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" - dependencies: - has-flag "^1.0.0" - supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -3274,16 +3875,26 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" +symbol-observable@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" + +tap-dot@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tap-dot/-/tap-dot-1.0.5.tgz#541703b784977508ee4f86f98e71d993beda23b4" dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" + tap-out "^1.3.2" + through2 "^2.0.0" + +tap-out@^1.3.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/tap-out/-/tap-out-1.4.2.tgz#c907ec1bf9405111d088263e92f5608b88cbb37a" + dependencies: + re-emitter "^1.0.0" + readable-stream "^2.0.0" + split "^1.0.0" + trim "0.0.1" tar-pack@~3.3.0: version "3.3.0" @@ -3306,30 +3917,37 @@ tar@~2.2.1: fstream "^1.0.2" inherits "2" -temp@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/temp/-/temp-0.5.1.tgz#77ab19c79aa7b593cbe4fac2441768cad987b8df" - dependencies: - rimraf "~2.1.4" - -text-table@~0.2.0: +text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -through2@^2.0.0, through2@~2.0.1: +through2@^2.0.0, through2@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" dependencies: readable-stream "^2.1.5" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.6: +through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -timed-out@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" +time-require@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/time-require/-/time-require-0.1.2.tgz#f9e12cb370fc2605e11404582ba54ef5ca2b2d98" + dependencies: + chalk "^0.4.0" + date-time "^0.1.1" + pretty-ms "^0.2.1" + text-table "^0.2.0" + +timed-out@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" + +to-fast-properties@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320" tough-cookie@~2.3.0: version "2.3.2" @@ -3345,9 +3963,13 @@ transformers@2.1.0: promise "~2.0" uglify-js "~2.2.5" -tryit@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" tunnel-agent@~0.4.1: version "0.4.3" @@ -3357,21 +3979,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - dependencies: - prelude-ls "~1.1.2" - -type-detect@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" - -type-detect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" - -type-is@^1.6.4, type-is@~1.6.13: +type-is@^1.6.4, type-is@~1.6.14: version "1.6.14" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2" dependencies: @@ -3382,15 +3990,6 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -uglify-js@^2.6: - version "2.7.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - uglify-js@~2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.2.5.tgz#a6e02a70d839792b9780488b7b8b184c095c99c7" @@ -3398,10 +3997,6 @@ uglify-js@~2.2.5: optimist "~0.3.5" source-map "~0.1.7" -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -3413,31 +4008,44 @@ uid-safe@~2.1.3: base64-url "1.3.3" random-bytes "~1.0.0" -uid2@0.0.x: +uid2@0.0.3, uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" +unique-temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz#6dce95b2681ca003eebfb304a415f9cbabcc5385" + dependencies: + mkdirp "^0.5.1" + os-tmpdir "^1.0.1" + uid2 "0.0.3" unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -unzip-response@^1.0.0: +unzip-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" -urlgrey@>=0.4.0: - version "0.4.4" - resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" +update-notifier@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" + dependencies: + boxen "^0.6.0" + chalk "^1.0.0" + configstore "^2.0.0" + is-npm "^1.0.0" + latest-version "^2.0.0" + lazy-req "^1.1.0" + semver-diff "^2.0.0" + xdg-basedir "^2.0.0" -user-home@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" dependencies: - os-homedir "^1.0.0" + prepend-http "^1.0.1" util-deprecate@~1.0.1: version "1.0.2" @@ -3468,10 +4076,17 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0: +uuid@^3.0.0, uuid@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + vary@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140" @@ -3486,7 +4101,7 @@ void-elements@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" -which@^1.1.1: +which@^1.2.9: version "1.2.12" resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" dependencies: @@ -3498,9 +4113,11 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.1" -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +widest-line@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" + dependencies: + string-width "^1.0.1" window-size@^0.1.4: version "0.1.4" @@ -3518,9 +4135,9 @@ winston@2.1.x: pkginfo "0.3.x" stack-trace "0.0.x" -winston@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.3.0.tgz#207faaab6fccf3fe493743dd2b03dbafc7ceb78c" +winston@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/winston/-/winston-2.3.1.tgz#0b48420d978c01804cf0230b648861598225a119" dependencies: async "~1.0.0" colors "1.0.x" @@ -3536,13 +4153,9 @@ with@~4.0.0: acorn "^1.0.1" acorn-globals "^1.0.3" -wordwrap@0.0.2, wordwrap@~0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - -wordwrap@^1.0.0, wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" wrap-ansi@^2.0.0: version "2.1.0" @@ -3555,7 +4168,7 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^1.1.2: +write-file-atomic@^1.1.2, write-file-atomic@^1.1.4: version "1.3.1" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.1.tgz#7d45ba32316328dd1ec7d90f60ebc0d845bb759a" dependencies: @@ -3563,11 +4176,21 @@ write-file-atomic@^1.1.2: imurmurhash "^0.1.4" slide "^1.1.5" -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" +write-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.0.0.tgz#0eaec981fcf9288dbc2806cbd26e06ab9bdca4ed" dependencies: + graceful-fs "^4.1.2" mkdirp "^0.5.1" + pify "^2.0.0" + sort-keys "^1.1.1" + write-file-atomic "^1.1.2" + +write-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-2.0.0.tgz#93b922ee9a429f9bd74cdc69e549733c9e468156" + dependencies: + write-json-file "^2.0.0" xdg-basedir@^2.0.0: version "2.0.0" @@ -3575,7 +4198,7 @@ xdg-basedir@^2.0.0: dependencies: os-homedir "^1.0.0" -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -3583,6 +4206,10 @@ y18n@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" +yallist@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.0.0.tgz#306c543835f09ee1a4cb23b7bce9ab341c91cdd4" + yargs@^3.10.0, yargs@^3.19.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" @@ -3594,12 +4221,3 @@ yargs@^3.10.0, yargs@^3.19.0: string-width "^1.0.1" window-size "^0.1.4" y18n "^3.2.0" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0"