diff --git a/.gitignore b/.gitignore index 37e0121c38..2b572d900c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ .DS_Store node_modules +**/node_modules/** *.log +coverage/ +test/encrypted/nodejs-docs-samples.json +*.iml +.idea/ diff --git a/.jshintrc b/.jshintrc index a8e226dd4b..d06dbfe00a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -2,14 +2,21 @@ "camelcase" : true, "curly": true, "eqeqeq": true, - "globalstrict": true, "indent": 2, "newcap": true, "maxlen": 80, "node": true, "quotmark": "single", - "strict": true, + "strict": "global", "trailing": true, "undef": true, - "unused": true + "unused": true, + "globals": { + "after": false, + "afterEach": false, + "before": false, + "beforeEach": false, + "describe": false, + "it": false + } } diff --git a/.travis.yml b/.travis.yml index 2c1de29a3d..26cc89a6b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,51 @@ +# 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. + sudo: false language: node_js node_js: - "stable" - "0.12" - "0.10" + +cache: + directories: + - $HOME/gcloud/ + - 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/ + +env: + - PATH=$PATH:$HOME/gcloud/google-cloud-sdk/bin GOOGLE_APPLICATION_CREDENTIALS=$TRAVIS_BUILD_DIR/test/encrypted/nodejs-docs-samples.json TEST_BUCKET_NAME=nodejs-docs-samples GCLOUD_PROJECT=nodejs-docs-samples #Other environment variables on same line + +before_install: + - if [ ! -d $HOME/gcloud/google-cloud-sdk ]; then + mkdir -p $HOME/gcloud && + wget https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz --directory-prefix=$HOME/gcloud && + cd $HOME/gcloud && + tar xzf google-cloud-sdk.tar.gz && + printf '\ny\n\ny\ny\n' | ./google-cloud-sdk/install.sh && + source $HOME/.bashrc && + cd $TRAVIS_BUILD_DIR; + fi + - openssl aes-256-cbc -K $encrypted_06352980ac5c_key -iv $encrypted_06352980ac5c_iv -in test/encrypted/nodejs-docs-samples.json.enc -out test/encrypted/nodejs-docs-samples.json -d + - if [ -a test/encrypted/nodejs-docs-samples.json ]; then + gcloud auth activate-service-account --key-file test/encrypted/nodejs-docs-samples.json; + fi + +after_success: +- npm run coveralls diff --git a/1-hello-world/app.js b/1-hello-world/app.js index b6b8401613..4d99aead3e 100644 --- a/1-hello-world/app.js +++ b/1-hello-world/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -25,13 +25,16 @@ app.get('/', function(req, res) { }); // [END hello_world] +if (module === require.main) { + // [START server] + // Start the server + var server = app.listen(process.env.PORT || 8080, function () { + var host = server.address().address; + var port = server.address().port; -// [START server] -// Start the server -var server = app.listen(process.env.PORT || 8080, function () { - var host = server.address().address; - var port = server.address().port; + console.log('App listening at http://%s:%s', host, port); + }); + // [END server] +} - console.log('App listening at http://%s:%s', host, port); -}); -// [END server] +module.exports = app; diff --git a/1-hello-world/app.yaml b/1-hello-world/app.yaml index b4e5381ed7..f90ff8a1f2 100644 --- a/1-hello-world/app.yaml +++ b/1-hello-world/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/1-hello-world/package.json b/1-hello-world/package.json index 25ff574f58..f43fbcf0e4 100644 --- a/1-hello-world/package.json +++ b/1-hello-world/package.json @@ -8,8 +8,9 @@ "start": "node app.js", "monitor": "nodemon app.js", "deploy": "gcloud preview app deploy app.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint" + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha" }, "author": "Google Inc.", "contributors": [ @@ -28,10 +29,12 @@ ], "license": "Apache Version 2.0", "dependencies": { - "express": "^4.13.3" + "express": "^4.13.4" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/1-hello-world/test/1-hello-world.test.js b/1-hello-world/test/1-hello-world.test.js new file mode 100644 index 0000000000..ae6cce107f --- /dev/null +++ b/1-hello-world/test/1-hello-world.test.js @@ -0,0 +1,49 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '1-hello-world', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'Hello, world!' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should create an express app', function (done) { + request(require('../app')) + .get('/') + .expect(200) + .expect(function (response) { + assert.equal(response.text, config.msg); + }) + .end(done); + }); + + it('should run', function (done) { + utils.testLocalApp(config, done); + }); +}); diff --git a/2-structured-data/app.js b/2-structured-data/app.js index 8784fbaec1..dc8394b456 100644 --- a/2-structured-data/app.js +++ b/2-structured-data/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -44,11 +44,14 @@ app.use(function(err, req, res, next) { res.status(500).send('Something broke!'); }); +if (module === require.main) { + // Start the server + var server = app.listen(config.port, function () { + var host = server.address().address; + var port = server.address().port; -// Start the server -var server = app.listen(config.port, function () { - var host = server.address().address; - var port = server.address().port; + console.log('App listening at http://%s:%s', host, port); + }); +} - console.log('App listening at http://%s:%s', host, port); -}); +module.exports = app; diff --git a/2-structured-data/app.yaml b/2-structured-data/app.yaml index 3e5c27cc72..13a38d297f 100644 --- a/2-structured-data/app.yaml +++ b/2-structured-data/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/2-structured-data/books/api.js b/2-structured-data/books/api.js index dafd1c18f1..0d195c2391 100644 --- a/2-structured-data/books/api.js +++ b/2-structured-data/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/2-structured-data/books/crud.js b/2-structured-data/books/crud.js index 7ef21a028f..644322343d 100644 --- a/2-structured-data/books/crud.js +++ b/2-structured-data/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/2-structured-data/books/model-cloudsql.js b/2-structured-data/books/model-cloudsql.js index 987c2ebc96..f96cfa9724 100644 --- a/2-structured-data/books/model-cloudsql.js +++ b/2-structured-data/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/2-structured-data/books/model-datastore.js b/2-structured-data/books/model-datastore.js index 5a8c84d33c..5dc682cf5c 100644 --- a/2-structured-data/books/model-datastore.js +++ b/2-structured-data/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -124,7 +124,8 @@ module.exports = function(config) { ds.save( entity, function(err) { - cb(err, err ? null : fromDatastore(entity)); + data.id = entity.key.id; + cb(err, err ? null : data); } ); } diff --git a/2-structured-data/books/model-mongodb.js b/2-structured-data/books/model-mongodb.js index d2798f061b..a0978a7e5f 100644 --- a/2-structured-data/books/model-mongodb.js +++ b/2-structured-data/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/2-structured-data/config.js b/2-structured-data/config.js index e83d8290b4..7a511674f7 100644 --- a/2-structured-data/config.js +++ b/2-structured-data/config.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -25,7 +25,7 @@ module.exports = { // This is the id of your project in the Google Developers Console. gcloud: { - projectId: 'your-project-id' + projectId: process.env.GCLOUD_PROJECT || 'your-project-id' }, mysql: { diff --git a/2-structured-data/package.json b/2-structured-data/package.json index dba7c891ab..7cd2ca8455 100644 --- a/2-structured-data/package.json +++ b/2-structured-data/package.json @@ -8,8 +8,9 @@ "start": "node app.js", "monitor": "nodemon app.js", "deploy": "gcloud preview app deploy app.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint", + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -30,17 +31,19 @@ "license": "Apache Version 2.0", "dependencies": { "body-parser": "^1.14.2", - "express": "^4.13.3", + "express": "^4.13.4", "gcloud": "^0.27.0", "jade": "^1.11.0", "kerberos": "^0.0.18", - "lodash": "^4.0.0", + "lodash": "^4.2.1", "mongodb": "^2.1.4", "mysql": "^2.10.2", "prompt": "^0.2.14" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/2-structured-data/test/2-structured-data.test.js b/2-structured-data/test/2-structured-data.test.js new file mode 100644 index 0000000000..eb510d06f6 --- /dev/null +++ b/2-structured-data/test/2-structured-data.test.js @@ -0,0 +1,96 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '2-structured-data', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'No books found.' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should redirect / to /books', function (done) { + request(require('../app')) + .get('/') + .expect(302) + .expect(function (response) { + assert.ok(response.text.indexOf('Redirecting to /books') !== -1); + }) + .end(done); + }); + + var id; + + it('should create a book', function (done) { + request(require('../app')) + .post('/api/books') + .send({ foo: 'bar', title: 'beep' }) + .expect(200) + .expect(function (response) { + id = response.body.id; + assert.ok(response.body.id); + assert.equal(response.body.foo, 'bar'); + assert.equal(response.body.title, 'beep'); + }) + .end(done); + }); + + it('should list books', function (done) { + request(require('../app')) + .get('/api/books') + .expect(200) + .expect(function (response) { + assert.ok(Array.isArray(response.body.items)); + assert.ok(response.body.items.length >= 1); + }) + .end(done); + }); + + it('should delete a book', function (done) { + request(require('../app')) + .delete('/api/books/' + id) + .expect(200) + .expect(function (response) { + assert.equal(response.text, 'OK'); + }) + .end(done); + }); + + it('should show add book form', function (done) { + request(require('../app')) + .get('/books/add') + .expect(200) + .expect(function (response) { + assert.ok(response.text.indexOf('Add book') !== -1); + }) + .end(done); + }); + + it('should run', function (done) { + utils.testLocalApp(config, done); + }); +}); diff --git a/2-structured-data/views/base.jade b/2-structured-data/views/base.jade index 3d6bbe3afa..7a93bf7156 100644 --- a/2-structured-data/views/base.jade +++ b/2-structured-data/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/2-structured-data/views/books/form.jade b/2-structured-data/views/books/form.jade index afac36b0a8..24fcc7c0bc 100644 --- a/2-structured-data/views/books/form.jade +++ b/2-structured-data/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/2-structured-data/views/books/list.jade b/2-structured-data/views/books/list.jade index ab2fce0fe0..3ecd5b5f05 100644 --- a/2-structured-data/views/books/list.jade +++ b/2-structured-data/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/2-structured-data/views/books/view.jade b/2-structured-data/views/books/view.jade index ad808d406f..760730fed6 100644 --- a/2-structured-data/views/books/view.jade +++ b/2-structured-data/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/3-binary-data/app.js b/3-binary-data/app.js index 58140cad8a..13a306adda 100644 --- a/3-binary-data/app.js +++ b/3-binary-data/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -48,10 +48,14 @@ app.use(function(err, req, res, next) { }); -// Start the server -var server = app.listen(config.port, function () { - var host = server.address().address; - var port = server.address().port; +if (module === require.main) { + // Start the server + var server = app.listen(config.port, function () { + var host = server.address().address; + var port = server.address().port; - console.log('App listening at http://%s:%s', host, port); -}); + console.log('App listening at http://%s:%s', host, port); + }); +} + +module.exports = app; diff --git a/3-binary-data/app.yaml b/3-binary-data/app.yaml index 3e5c27cc72..13a38d297f 100644 --- a/3-binary-data/app.yaml +++ b/3-binary-data/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/3-binary-data/books/api.js b/3-binary-data/books/api.js index dafd1c18f1..0d195c2391 100644 --- a/3-binary-data/books/api.js +++ b/3-binary-data/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/3-binary-data/books/crud.js b/3-binary-data/books/crud.js index e89bf659eb..e51c6e6e8b 100644 --- a/3-binary-data/books/crud.js +++ b/3-binary-data/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/3-binary-data/books/model-cloudsql.js b/3-binary-data/books/model-cloudsql.js index a988be9adb..e0278ac8c6 100644 --- a/3-binary-data/books/model-cloudsql.js +++ b/3-binary-data/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/3-binary-data/books/model-datastore.js b/3-binary-data/books/model-datastore.js index a96b12fbc7..1979693049 100644 --- a/3-binary-data/books/model-datastore.js +++ b/3-binary-data/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -119,7 +119,8 @@ module.exports = function(config) { ds.save( entity, function(err) { - cb(err, err ? null : fromDatastore(entity)); + data.id = entity.key.id; + cb(err, err ? null : data); } ); } diff --git a/3-binary-data/books/model-mongodb.js b/3-binary-data/books/model-mongodb.js index b224cffc15..fd811e45db 100644 --- a/3-binary-data/books/model-mongodb.js +++ b/3-binary-data/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/3-binary-data/config.js b/3-binary-data/config.js index e8cbf7eeb7..66b422da27 100644 --- a/3-binary-data/config.js +++ b/3-binary-data/config.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -25,7 +25,7 @@ module.exports = { // This is the id of your project in the Google Developers Console. gcloud: { - projectId: 'your-project-id' + projectId: process.env.GCLOUD_PROJECT || 'your-project-id' }, // Typically, you will create a bucket with the same name as your project ID. diff --git a/3-binary-data/lib/images.js b/3-binary-data/lib/images.js index 00e4cafd95..4aa78eb828 100644 --- a/3-binary-data/lib/images.js +++ b/3-binary-data/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/3-binary-data/package.json b/3-binary-data/package.json index d85315349d..173132dff7 100644 --- a/3-binary-data/package.json +++ b/3-binary-data/package.json @@ -8,8 +8,9 @@ "start": "node app.js", "monitor": "nodemon app.js", "deploy": "gcloud preview app deploy app.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint", + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -30,18 +31,20 @@ "license": "Apache Version 2.0", "dependencies": { "body-parser": "^1.14.2", - "express": "^4.13.3", + "express": "^4.13.4", "gcloud": "^0.27.0", "jade": "^1.11.0", "kerberos": "^0.0.18", - "lodash": "^4.0.0", + "lodash": "^4.2.1", "mongodb": "^2.1.4", "multer": "^1.1.0", "mysql": "^2.10.2", "prompt": "^0.2.14" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/3-binary-data/test/3-binary-data.test.js b/3-binary-data/test/3-binary-data.test.js new file mode 100644 index 0000000000..9b22688a2d --- /dev/null +++ b/3-binary-data/test/3-binary-data.test.js @@ -0,0 +1,96 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '3-binary-data', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'No books found.' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should redirect / to /books', function (done) { + request(require('../app')) + .get('/') + .expect(302) + .expect(function (response) { + assert.ok(response.text.indexOf('Redirecting to /books') !== -1); + }) + .end(done); + }); + + var id; + + it('should create a book', function (done) { + request(require('../app')) + .post('/api/books') + .send({ foo: 'bar', title: 'beep' }) + .expect(200) + .expect(function (response) { + id = response.body.id; + assert.ok(response.body.id); + assert.equal(response.body.foo, 'bar'); + assert.equal(response.body.title, 'beep'); + }) + .end(done); + }); + + it('should list books', function (done) { + request(require('../app')) + .get('/api/books') + .expect(200) + .expect(function (response) { + assert.ok(Array.isArray(response.body.items)); + assert.ok(response.body.items.length >= 1); + }) + .end(done); + }); + + it('should delete a book', function (done) { + request(require('../app')) + .delete('/api/books/' + id) + .expect(200) + .expect(function (response) { + assert.equal(response.text, 'OK'); + }) + .end(done); + }); + + it('should show add book form', function (done) { + request(require('../app')) + .get('/books/add') + .expect(200) + .expect(function (response) { + assert.ok(response.text.indexOf('Add book') !== -1); + }) + .end(done); + }); + + it('should run', function (done) { + utils.testLocalApp(config, done); + }); +}); diff --git a/3-binary-data/views/base.jade b/3-binary-data/views/base.jade index 3d6bbe3afa..7a93bf7156 100644 --- a/3-binary-data/views/base.jade +++ b/3-binary-data/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/3-binary-data/views/books/form.jade b/3-binary-data/views/books/form.jade index abf50cbfca..36a6308a9d 100644 --- a/3-binary-data/views/books/form.jade +++ b/3-binary-data/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/3-binary-data/views/books/list.jade b/3-binary-data/views/books/list.jade index 38977aa211..dd453386ba 100644 --- a/3-binary-data/views/books/list.jade +++ b/3-binary-data/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/3-binary-data/views/books/view.jade b/3-binary-data/views/books/view.jade index 7b0efe5696..55e7a4532c 100644 --- a/3-binary-data/views/books/view.jade +++ b/3-binary-data/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/4-auth/app.js b/4-auth/app.js index f614d655c7..4d4f5883dd 100644 --- a/4-auth/app.js +++ b/4-auth/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -67,10 +67,14 @@ app.use(function(err, req, res, next) { }); -// Start the server -var server = app.listen(config.port, function () { - var host = server.address().address; - var port = server.address().port; +if (module === require.main) { + // Start the server + var server = app.listen(config.port, function () { + var host = server.address().address; + var port = server.address().port; - console.log('App listening at http://%s:%s', host, port); -}); + console.log('App listening at http://%s:%s', host, port); + }); +} + +module.exports = app; diff --git a/4-auth/app.yaml b/4-auth/app.yaml index cf826b98f5..069f5f04c6 100644 --- a/4-auth/app.yaml +++ b/4-auth/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/4-auth/books/api.js b/4-auth/books/api.js index dafd1c18f1..0d195c2391 100644 --- a/4-auth/books/api.js +++ b/4-auth/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/4-auth/books/crud.js b/4-auth/books/crud.js index bbddf25590..9c93d99495 100644 --- a/4-auth/books/crud.js +++ b/4-auth/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/4-auth/books/model-cloudsql.js b/4-auth/books/model-cloudsql.js index b64a9ebbc1..2df73df4a6 100644 --- a/4-auth/books/model-cloudsql.js +++ b/4-auth/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/4-auth/books/model-datastore.js b/4-auth/books/model-datastore.js index 07fda19768..2fac0d88de 100644 --- a/4-auth/books/model-datastore.js +++ b/4-auth/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -136,7 +136,8 @@ module.exports = function(config) { ds.save( entity, function(err) { - cb(err, err ? null : fromDatastore(entity)); + data.id = entity.key.id; + cb(err, err ? null : data); } ); } diff --git a/4-auth/books/model-mongodb.js b/4-auth/books/model-mongodb.js index bee809d9eb..216def4058 100644 --- a/4-auth/books/model-mongodb.js +++ b/4-auth/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/4-auth/config.js b/4-auth/config.js index f87988db02..7f8611d575 100644 --- a/4-auth/config.js +++ b/4-auth/config.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -28,7 +28,7 @@ module.exports = { // This is the id of your project in the Google Developers Console. gcloud: { - projectId: 'your-project-id' + projectId: process.env.GCLOUD_PROJECT || 'your-project-id' }, // Typically, you will create a bucket with the same name as your project ID. diff --git a/4-auth/lib/images.js b/4-auth/lib/images.js index 3dc199441b..f2d17de98e 100644 --- a/4-auth/lib/images.js +++ b/4-auth/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/4-auth/lib/oauth2.js b/4-auth/lib/oauth2.js index b2b85066b7..b514ebc52b 100644 --- a/4-auth/lib/oauth2.js +++ b/4-auth/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/4-auth/package.json b/4-auth/package.json index f0bbb79683..4e083b1759 100644 --- a/4-auth/package.json +++ b/4-auth/package.json @@ -8,8 +8,9 @@ "start": "node app.js", "monitor": "nodemon app.js", "deploy": "gcloud preview app deploy app.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint", + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -32,19 +33,21 @@ "async": "^1.5.2", "body-parser": "^1.14.2", "cookie-session": "^2.0.0-alpha.1", - "express": "^4.13.3", + "express": "^4.13.4", "gcloud": "^0.27.0", "googleapis": "^2.1.7", "jade": "^1.11.0", "kerberos": "^0.0.18", - "lodash": "^4.0.0", + "lodash": "^4.2.1", "mongodb": "^2.1.4", "multer": "^1.1.0", "mysql": "^2.10.2", "prompt": "^0.2.14" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/4-auth/test/4-auth.test.js b/4-auth/test/4-auth.test.js new file mode 100644 index 0000000000..e5be96076f --- /dev/null +++ b/4-auth/test/4-auth.test.js @@ -0,0 +1,97 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '4-auth', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'No books found.' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should redirect / to /books', function (done) { + request(require('../app')) + .get('/') + .expect(302) + .expect(function (response) { + assert.ok(response.text.indexOf('Redirecting to /books') !== -1); + }) + .end(done); + }); + + var id; + + it('should create a book', function (done) { + request(require('../app')) + .post('/api/books') + .send({ foo: 'bar', title: 'beep' }) + .expect(200) + .expect(function (response) { + id = response.body.id; + assert.ok(response.body.id); + assert.equal(response.body.foo, 'bar'); + assert.equal(response.body.title, 'beep'); + }) + .end(done); + }); + + it('should list books', function (done) { + request(require('../app')) + .get('/api/books') + .expect(200) + .expect(function (response) { + assert.ok(Array.isArray(response.body.items)); + assert.ok(response.body.items.length >= 1); + }) + .end(done); + }); + + it('should delete a book', function (done) { + request(require('../app')) + .delete('/api/books/' + id) + .expect(200) + .expect(function (response) { + assert.equal(response.text, 'OK'); + }) + .end(done); + }); + + it('should show add book form', function (done) { + request(require('../app')) + .get('/books/add') + .expect(200) + .expect(function (response) { + assert.ok(response.text.indexOf('Add book') !== -1); + }) + .end(done); + }); + + it('should run', function (done) { + this.timeout(15 * 1000); // Allow 15 seconds to test app + utils.testLocalApp(config, done); + }); +}); diff --git a/4-auth/views/base.jade b/4-auth/views/base.jade index fdba4df175..4aada5913e 100644 --- a/4-auth/views/base.jade +++ b/4-auth/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/4-auth/views/books/form.jade b/4-auth/views/books/form.jade index dd8b680871..8368619eeb 100644 --- a/4-auth/views/books/form.jade +++ b/4-auth/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/4-auth/views/books/list.jade b/4-auth/views/books/list.jade index 38977aa211..dd453386ba 100644 --- a/4-auth/views/books/list.jade +++ b/4-auth/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/4-auth/views/books/view.jade b/4-auth/views/books/view.jade index 0921825cce..fb0123b05c 100644 --- a/4-auth/views/books/view.jade +++ b/4-auth/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/5-logging/app.js b/5-logging/app.js index e5ac5c42bc..e64eef0b32 100644 --- a/5-logging/app.js +++ b/5-logging/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -78,10 +78,14 @@ app.use(function(err, req, res, next) { // [END errors] -// Start the server -var server = app.listen(config.port, function () { - var host = server.address().address; - var port = server.address().port; +if (module === require.main) { + // Start the server + var server = app.listen(config.port, function () { + var host = server.address().address; + var port = server.address().port; - console.log('App listening at http://%s:%s', host, port); -}); + console.log('App listening at http://%s:%s', host, port); + }); +} + +module.exports = app; diff --git a/5-logging/app.yaml b/5-logging/app.yaml index cf826b98f5..069f5f04c6 100644 --- a/5-logging/app.yaml +++ b/5-logging/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/5-logging/books/api.js b/5-logging/books/api.js index dafd1c18f1..0d195c2391 100644 --- a/5-logging/books/api.js +++ b/5-logging/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/books/crud.js b/5-logging/books/crud.js index 63514bf5d5..8fc7865b41 100644 --- a/5-logging/books/crud.js +++ b/5-logging/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/books/model-cloudsql.js b/5-logging/books/model-cloudsql.js index ad266e5020..f4bb94ae8b 100644 --- a/5-logging/books/model-cloudsql.js +++ b/5-logging/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/books/model-datastore.js b/5-logging/books/model-datastore.js index 155a0db17e..98487b2331 100644 --- a/5-logging/books/model-datastore.js +++ b/5-logging/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -135,7 +135,8 @@ module.exports = function(config) { ds.save( entity, function(err) { - cb(err, err ? null : fromDatastore(entity)); + data.id = entity.key.id; + cb(err, err ? null : data); } ); } diff --git a/5-logging/books/model-mongodb.js b/5-logging/books/model-mongodb.js index ea6e6711ae..a0a8a98754 100644 --- a/5-logging/books/model-mongodb.js +++ b/5-logging/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/config.js b/5-logging/config.js index f87988db02..7f8611d575 100644 --- a/5-logging/config.js +++ b/5-logging/config.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -28,7 +28,7 @@ module.exports = { // This is the id of your project in the Google Developers Console. gcloud: { - projectId: 'your-project-id' + projectId: process.env.GCLOUD_PROJECT || 'your-project-id' }, // Typically, you will create a bucket with the same name as your project ID. diff --git a/5-logging/lib/images.js b/5-logging/lib/images.js index 3dc199441b..f2d17de98e 100644 --- a/5-logging/lib/images.js +++ b/5-logging/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/lib/logging.js b/5-logging/lib/logging.js index e0bbc01dbd..1d45af6af1 100644 --- a/5-logging/lib/logging.js +++ b/5-logging/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/lib/oauth2.js b/5-logging/lib/oauth2.js index 21a9291a39..dcbce38ebe 100644 --- a/5-logging/lib/oauth2.js +++ b/5-logging/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/5-logging/package.json b/5-logging/package.json index 62b42bd3b1..18e1585b13 100644 --- a/5-logging/package.json +++ b/5-logging/package.json @@ -8,8 +8,9 @@ "start": "node app.js", "monitor": "nodemon app.js", "deploy": "gcloud preview app deploy app.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint", + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -32,13 +33,13 @@ "async": "^1.5.2", "body-parser": "^1.14.2", "cookie-session": "^2.0.0-alpha.1", - "express": "^4.13.3", + "express": "^4.13.4", "express-winston": "^1.2.0", "gcloud": "^0.27.0", "googleapis": "^2.1.7", "jade": "^1.11.0", "kerberos": "^0.0.18", - "lodash": "^4.0.0", + "lodash": "^4.2.1", "mongodb": "^2.1.4", "multer": "^1.1.0", "mysql": "^2.10.2", @@ -46,7 +47,9 @@ "winston": "^2.1.1" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/5-logging/test/5-logging.test.js b/5-logging/test/5-logging.test.js new file mode 100644 index 0000000000..b9c5e0f53a --- /dev/null +++ b/5-logging/test/5-logging.test.js @@ -0,0 +1,96 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '5-logging', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'No books found.' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should redirect / to /books', function (done) { + request(require('../app')) + .get('/') + .expect(302) + .expect(function (response) { + assert.ok(response.text.indexOf('Redirecting to /books') !== -1); + }) + .end(done); + }); + + var id; + + it('should create a book', function (done) { + request(require('../app')) + .post('/api/books') + .send({ foo: 'bar', title: 'beep' }) + .expect(200) + .expect(function (response) { + id = response.body.id; + assert.ok(response.body.id); + assert.equal(response.body.foo, 'bar'); + assert.equal(response.body.title, 'beep'); + }) + .end(done); + }); + + it('should list books', function (done) { + request(require('../app')) + .get('/api/books') + .expect(200) + .expect(function (response) { + assert.ok(Array.isArray(response.body.items)); + assert.ok(response.body.items.length >= 1); + }) + .end(done); + }); + + it('should delete a book', function (done) { + request(require('../app')) + .delete('/api/books/' + id) + .expect(200) + .expect(function (response) { + assert.equal(response.text, 'OK'); + }) + .end(done); + }); + + it('should show add book form', function (done) { + request(require('../app')) + .get('/books/add') + .expect(200) + .expect(function (response) { + assert.ok(response.text.indexOf('Add book') !== -1); + }) + .end(done); + }); + + it('should run', function (done) { + utils.testLocalApp(config, done); + }); +}); diff --git a/5-logging/views/base.jade b/5-logging/views/base.jade index bdb991feeb..ed8e70329c 100644 --- a/5-logging/views/base.jade +++ b/5-logging/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/5-logging/views/books/form.jade b/5-logging/views/books/form.jade index dd8b680871..8368619eeb 100644 --- a/5-logging/views/books/form.jade +++ b/5-logging/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/5-logging/views/books/list.jade b/5-logging/views/books/list.jade index 38977aa211..dd453386ba 100644 --- a/5-logging/views/books/list.jade +++ b/5-logging/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/5-logging/views/books/view.jade b/5-logging/views/books/view.jade index 0921825cce..fb0123b05c 100644 --- a/5-logging/views/books/view.jade +++ b/5-logging/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/6-pubsub/app.js b/6-pubsub/app.js index 7e798b6c25..6bab05f31c 100644 --- a/6-pubsub/app.js +++ b/6-pubsub/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -76,10 +76,14 @@ app.use(function(err, req, res, next) { }); -// Start the server -var server = app.listen(config.port, function () { - var host = server.address().address; - var port = server.address().port; +if (module === require.main) { + // Start the server + var server = app.listen(config.port, function () { + var host = server.address().address; + var port = server.address().port; - console.log('App listening at http://%s:%s', host, port); -}); + console.log('App listening at http://%s:%s', host, port); + }); +} + +module.exports = app; diff --git a/6-pubsub/app.yaml b/6-pubsub/app.yaml index cf826b98f5..069f5f04c6 100644 --- a/6-pubsub/app.yaml +++ b/6-pubsub/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/6-pubsub/books/api.js b/6-pubsub/books/api.js index dafd1c18f1..0d195c2391 100644 --- a/6-pubsub/books/api.js +++ b/6-pubsub/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/books/crud.js b/6-pubsub/books/crud.js index 63514bf5d5..8fc7865b41 100644 --- a/6-pubsub/books/crud.js +++ b/6-pubsub/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/books/model-cloudsql.js b/6-pubsub/books/model-cloudsql.js index 3d7da3ccc3..a6a5af4a9a 100644 --- a/6-pubsub/books/model-cloudsql.js +++ b/6-pubsub/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/books/model-datastore.js b/6-pubsub/books/model-datastore.js index b5ef29247a..491fbe91cc 100644 --- a/6-pubsub/books/model-datastore.js +++ b/6-pubsub/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -137,9 +137,9 @@ module.exports = function(config, background) { entity, function(err) { if(err) { return cb(err); } - var book = fromDatastore(entity); - background.queueBook(book.id); - cb(null, book); + data.id = entity.key.id; + background.queueBook(data.id); + cb(null, data); } ); } diff --git a/6-pubsub/books/model-mongodb.js b/6-pubsub/books/model-mongodb.js index 0e3d942b28..b2d646c768 100644 --- a/6-pubsub/books/model-mongodb.js +++ b/6-pubsub/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/config.js b/6-pubsub/config.js index f87988db02..7f8611d575 100644 --- a/6-pubsub/config.js +++ b/6-pubsub/config.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -28,7 +28,7 @@ module.exports = { // This is the id of your project in the Google Developers Console. gcloud: { - projectId: 'your-project-id' + projectId: process.env.GCLOUD_PROJECT || 'your-project-id' }, // Typically, you will create a bucket with the same name as your project ID. diff --git a/6-pubsub/lib/background.js b/6-pubsub/lib/background.js index e0059867c7..c5ed4fd9a9 100644 --- a/6-pubsub/lib/background.js +++ b/6-pubsub/lib/background.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/lib/images.js b/6-pubsub/lib/images.js index ddadce4a6c..e1bbde18b5 100644 --- a/6-pubsub/lib/images.js +++ b/6-pubsub/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/lib/logging.js b/6-pubsub/lib/logging.js index 123e11f293..133411900b 100644 --- a/6-pubsub/lib/logging.js +++ b/6-pubsub/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/lib/oauth2.js b/6-pubsub/lib/oauth2.js index 21a9291a39..dcbce38ebe 100644 --- a/6-pubsub/lib/oauth2.js +++ b/6-pubsub/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/6-pubsub/package.json b/6-pubsub/package.json index 02e9640704..9def4a9e38 100644 --- a/6-pubsub/package.json +++ b/6-pubsub/package.json @@ -8,8 +8,9 @@ "start": "node ${SCRIPT:-app.js}", "monitor": "nodemon ${SCRIPT:-app.js}", "deploy": "gcloud preview app deploy app.yaml worker.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint", + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -32,22 +33,26 @@ "async": "^1.5.2", "body-parser": "^1.14.2", "cookie-session": "^2.0.0-alpha.1", - "express": "^4.13.3", + "express": "^4.13.4", "express-winston": "^1.2.0", "gcloud": "^0.27.0", "googleapis": "^2.1.7", "jade": "^1.11.0", "kerberos": "^0.0.18", - "lodash": "^4.0.0", + "lodash": "^4.2.1", "mongodb": "^2.1.4", "multer": "^1.1.0", "mysql": "^2.10.2", "prompt": "^0.2.14", - "request": "^2.67.0", + "request": "^2.69.0", "winston": "^2.1.1" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "proxyquire": "^1.7.4", + "sinon": "^1.17.3", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/6-pubsub/test/6-pubsub.test.js b/6-pubsub/test/6-pubsub.test.js new file mode 100644 index 0000000000..e1c39beb7e --- /dev/null +++ b/6-pubsub/test/6-pubsub.test.js @@ -0,0 +1,96 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '6-pubsub', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'No books found.' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should redirect / to /books', function (done) { + request(require('../app')) + .get('/') + .expect(302) + .expect(function (response) { + assert.ok(response.text.indexOf('Redirecting to /books') !== -1); + }) + .end(done); + }); + + var id; + + it('should create a book', function (done) { + request(require('../app')) + .post('/api/books') + .send({ foo: 'bar', title: 'beep' }) + .expect(200) + .expect(function (response) { + id = response.body.id; + assert.ok(response.body.id); + assert.equal(response.body.foo, 'bar'); + assert.equal(response.body.title, 'beep'); + }) + .end(done); + }); + + it('should list books', function (done) { + request(require('../app')) + .get('/api/books') + .expect(200) + .expect(function (response) { + assert.ok(Array.isArray(response.body.items)); + assert.ok(response.body.items.length >= 1); + }) + .end(done); + }); + + it('should delete a book', function (done) { + request(require('../app')) + .delete('/api/books/' + id) + .expect(200) + .expect(function (response) { + assert.equal(response.text, 'OK'); + }) + .end(done); + }); + + it('should show add book form', function (done) { + request(require('../app')) + .get('/books/add') + .expect(200) + .expect(function (response) { + assert.ok(response.text.indexOf('Add book') !== -1); + }) + .end(done); + }); + + it('should run', function (done) { + utils.testLocalApp(config, done); + }); +}); diff --git a/6-pubsub/test/background.test.js b/6-pubsub/test/background.test.js new file mode 100644 index 0000000000..c80cf2f65b --- /dev/null +++ b/6-pubsub/test/background.test.js @@ -0,0 +1,423 @@ +// 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'; + +var assert = require('assert'); +var sinon = require('sinon'); +var proxyquire = require('proxyquire').noPreserveCache(); +var background; +var mocks = {}; + +describe('background.js', function () { + + beforeEach(function () { + // Mock dependencies used by background.js + mocks.gcloudConfig = sinon.stub(); + mocks.config = { + gcloud: sinon.stub() + }; + Object.defineProperty(mocks.config, 'gcloud', { + get: mocks.gcloudConfig + }); + 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.gcloud = { + pubsub: sinon.stub().returns(mocks.pubsub) + }; + mocks.logging = { + info: sinon.stub(), + error: sinon.stub() + }; + // Load background.js with provided mocks + background = proxyquire('../lib/background', { + gcloud: mocks.gcloud, + '../config': mocks.config + })(null, mocks.logging); + + assert.ok( + mocks.gcloudConfig.calledOnce, + 'config.gcloud should have been accessed' + ); + assert.ok( + mocks.gcloud.pubsub.calledOnce, + 'gcloud.pubsub() should have been called once' + ); + }); + + describe('subscribe()', function () { + it('should subscribe and log message', function (done) { + // Setup + var testMessage = 'test message'; + + // Run target functionality + background.subscribe(function (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.calledOnce, + 'subscription.on should have been called once' + ); + 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' + ); + done(); + }); + + // Trigger a message + setTimeout(function () { + mocks.subscription.on.firstCall.args[1]({ + data: testMessage + }); + }, 10); + }); + it('should return topic error, if any', function (done) { + // Setup + var testErrorMsg = 'test error'; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + + // Run target functionality + background.subscribe(function (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', function (done) { + // Setup + var testErrorMsg = 'test error'; + mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); + + // Run target functionality + background.subscribe(function (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(); + }); + }); + }); + + describe('queueBook()', function () { + it('should queue a book and log message', function () { + // Setup + var testBookId = 1; + + // 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', function () { + // Setup + var testBookId = 1; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { + code: 409 + }); + + // 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.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', function () { + // Setup + var testBookId = 1; + var testErrorMsg = 'test error'; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + + // 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.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', function () { + // Setup + var testBookId = 1; + var testErrorMsg = 'test error'; + mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); + + // 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' + ); + }); + }); +}); diff --git a/6-pubsub/views/base.jade b/6-pubsub/views/base.jade index bdb991feeb..ed8e70329c 100644 --- a/6-pubsub/views/base.jade +++ b/6-pubsub/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/6-pubsub/views/books/form.jade b/6-pubsub/views/books/form.jade index dd8b680871..8368619eeb 100644 --- a/6-pubsub/views/books/form.jade +++ b/6-pubsub/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/6-pubsub/views/books/list.jade b/6-pubsub/views/books/list.jade index 38977aa211..dd453386ba 100644 --- a/6-pubsub/views/books/list.jade +++ b/6-pubsub/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/6-pubsub/views/books/view.jade b/6-pubsub/views/books/view.jade index 0921825cce..fb0123b05c 100644 --- a/6-pubsub/views/books/view.jade +++ b/6-pubsub/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/6-pubsub/worker.js b/6-pubsub/worker.js index 87eb94958b..15c3c589a8 100644 --- a/6-pubsub/worker.js +++ b/6-pubsub/worker.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -122,7 +122,7 @@ function findBookInfo(book, cb) { var top = r.items[0]; book.title = top.volumeInfo.title; - book.author = top.volumeInfo.authors.join(', '); + book.author = (top.volumeInfo.authors || []).join(', '); book.publishedDate = top.volumeInfo.publishedDate; book.description = book.description || top.volumeInfo.description; diff --git a/6-pubsub/worker.yaml b/6-pubsub/worker.yaml index 46a6a05b69..a6bae0d26b 100644 --- a/6-pubsub/worker.yaml +++ b/6-pubsub/worker.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/7-gce/app.js b/7-gce/app.js index d59a90b043..5684f2d324 100644 --- a/7-gce/app.js +++ b/7-gce/app.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -84,10 +84,14 @@ app.use(function(err, req, res, next) { }); -// Start the server -var server = app.listen(config.port, function () { - var host = server.address().address; - var port = server.address().port; +if (module === require.main) { + // Start the server + var server = app.listen(config.port, function () { + var host = server.address().address; + var port = server.address().port; - console.log('App listening at http://%s:%s', host, port); -}); + console.log('App listening at http://%s:%s', host, port); + }); +} + +module.exports = app; diff --git a/7-gce/app.yaml b/7-gce/app.yaml index cf826b98f5..069f5f04c6 100644 --- a/7-gce/app.yaml +++ b/7-gce/app.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/7-gce/books/api.js b/7-gce/books/api.js index dafd1c18f1..0d195c2391 100644 --- a/7-gce/books/api.js +++ b/7-gce/books/api.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/books/crud.js b/7-gce/books/crud.js index 63514bf5d5..8fc7865b41 100644 --- a/7-gce/books/crud.js +++ b/7-gce/books/crud.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/books/model-cloudsql.js b/7-gce/books/model-cloudsql.js index a4116f28d5..62268a713f 100644 --- a/7-gce/books/model-cloudsql.js +++ b/7-gce/books/model-cloudsql.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/books/model-datastore.js b/7-gce/books/model-datastore.js index 401a8b3fa0..1d31f05d46 100644 --- a/7-gce/books/model-datastore.js +++ b/7-gce/books/model-datastore.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -136,9 +136,9 @@ module.exports = function(config, background) { entity, function(err) { if(err) { return cb(err); } - var book = fromDatastore(entity); - background.queueBook(book.id); - cb(null, book); + data.id = entity.key.id; + background.queueBook(data.id); + cb(null, data); } ); } diff --git a/7-gce/books/model-mongodb.js b/7-gce/books/model-mongodb.js index cc08e35da8..3b1cc7eeed 100644 --- a/7-gce/books/model-mongodb.js +++ b/7-gce/books/model-mongodb.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/config.js b/7-gce/config.js index f87988db02..7f8611d575 100644 --- a/7-gce/config.js +++ b/7-gce/config.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -28,7 +28,7 @@ module.exports = { // This is the id of your project in the Google Developers Console. gcloud: { - projectId: 'your-project-id' + projectId: process.env.GCLOUD_PROJECT || 'your-project-id' }, // Typically, you will create a bucket with the same name as your project ID. diff --git a/7-gce/gce/startup-script.sh b/7-gce/gce/startup-script.sh index 5b6adff7f0..0d1e11d33b 100644 --- a/7-gce/gce/startup-script.sh +++ b/7-gce/gce/startup-script.sh @@ -1,6 +1,6 @@ #! /bin/bash -# Copyright 2015, Google, Inc. +# 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 diff --git a/7-gce/lib/background.js b/7-gce/lib/background.js index 4eb609bdb5..c596f02535 100644 --- a/7-gce/lib/background.js +++ b/7-gce/lib/background.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/lib/images.js b/7-gce/lib/images.js index c81e66dac5..e6cc36e0fe 100644 --- a/7-gce/lib/images.js +++ b/7-gce/lib/images.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/lib/logging.js b/7-gce/lib/logging.js index 123e11f293..133411900b 100644 --- a/7-gce/lib/logging.js +++ b/7-gce/lib/logging.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/lib/oauth2.js b/7-gce/lib/oauth2.js index 21a9291a39..dcbce38ebe 100644 --- a/7-gce/lib/oauth2.js +++ b/7-gce/lib/oauth2.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 diff --git a/7-gce/package.json b/7-gce/package.json index 02e9640704..9def4a9e38 100644 --- a/7-gce/package.json +++ b/7-gce/package.json @@ -8,8 +8,9 @@ "start": "node ${SCRIPT:-app.js}", "monitor": "nodemon ${SCRIPT:-app.js}", "deploy": "gcloud preview app deploy app.yaml worker.yaml", - "lint": "jshint --exclude-path=.gitignore .", - "test": "npm run lint", + "lint": "jshint --exclude-path=../.gitignore .", + "mocha": "mocha test/*.test.js -t 30000", + "test": "npm run lint && npm run mocha", "init-cloudsql": "node books/model-cloudsql.js" }, "author": "Google Inc.", @@ -32,22 +33,26 @@ "async": "^1.5.2", "body-parser": "^1.14.2", "cookie-session": "^2.0.0-alpha.1", - "express": "^4.13.3", + "express": "^4.13.4", "express-winston": "^1.2.0", "gcloud": "^0.27.0", "googleapis": "^2.1.7", "jade": "^1.11.0", "kerberos": "^0.0.18", - "lodash": "^4.0.0", + "lodash": "^4.2.1", "mongodb": "^2.1.4", "multer": "^1.1.0", "mysql": "^2.10.2", "prompt": "^0.2.14", - "request": "^2.67.0", + "request": "^2.69.0", "winston": "^2.1.1" }, "devDependencies": { - "jshint": "^2.9.1" + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "proxyquire": "^1.7.4", + "sinon": "^1.17.3", + "supertest": "^1.1.0" }, "engines": { "node": ">=0.12.7" diff --git a/7-gce/test/7-gce.test.js b/7-gce/test/7-gce.test.js new file mode 100644 index 0000000000..4015ef2f3c --- /dev/null +++ b/7-gce/test/7-gce.test.js @@ -0,0 +1,96 @@ +// 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'; + +var assert = require('assert'); +var path = require('path'); +var request = require('supertest'); +var utils = require('../../test/utils'); + +var config = { + test: '7-gce', + path: path.resolve(path.join(__dirname, '../')), + cmd: 'node', + args: ['app.js'], + msg: 'No books found.' +}; + +describe(config.test, function () { + + it('should install dependencies', function (done) { + this.timeout(60 * 1000); // Allow 1 minute to test installation + utils.testInstallation(config, done); + }); + + it('should redirect / to /books', function (done) { + request(require('../app')) + .get('/') + .expect(302) + .expect(function (response) { + assert.ok(response.text.indexOf('Redirecting to /books') !== -1); + }) + .end(done); + }); + + var id; + + it('should create a book', function (done) { + request(require('../app')) + .post('/api/books') + .send({ foo: 'bar', title: 'beep' }) + .expect(200) + .expect(function (response) { + id = response.body.id; + assert.ok(response.body.id); + assert.equal(response.body.foo, 'bar'); + assert.equal(response.body.title, 'beep'); + }) + .end(done); + }); + + it('should list books', function (done) { + request(require('../app')) + .get('/api/books') + .expect(200) + .expect(function (response) { + assert.ok(Array.isArray(response.body.items)); + assert.ok(response.body.items.length >= 1); + }) + .end(done); + }); + + it('should delete a book', function (done) { + request(require('../app')) + .delete('/api/books/' + id) + .expect(200) + .expect(function (response) { + assert.equal(response.text, 'OK'); + }) + .end(done); + }); + + it('should show add book form', function (done) { + request(require('../app')) + .get('/books/add') + .expect(200) + .expect(function (response) { + assert.ok(response.text.indexOf('Add book') !== -1); + }) + .end(done); + }); + + it('should run', function (done) { + utils.testLocalApp(config, done); + }); +}); diff --git a/7-gce/test/background.test.js b/7-gce/test/background.test.js new file mode 100644 index 0000000000..c80cf2f65b --- /dev/null +++ b/7-gce/test/background.test.js @@ -0,0 +1,423 @@ +// 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'; + +var assert = require('assert'); +var sinon = require('sinon'); +var proxyquire = require('proxyquire').noPreserveCache(); +var background; +var mocks = {}; + +describe('background.js', function () { + + beforeEach(function () { + // Mock dependencies used by background.js + mocks.gcloudConfig = sinon.stub(); + mocks.config = { + gcloud: sinon.stub() + }; + Object.defineProperty(mocks.config, 'gcloud', { + get: mocks.gcloudConfig + }); + 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.gcloud = { + pubsub: sinon.stub().returns(mocks.pubsub) + }; + mocks.logging = { + info: sinon.stub(), + error: sinon.stub() + }; + // Load background.js with provided mocks + background = proxyquire('../lib/background', { + gcloud: mocks.gcloud, + '../config': mocks.config + })(null, mocks.logging); + + assert.ok( + mocks.gcloudConfig.calledOnce, + 'config.gcloud should have been accessed' + ); + assert.ok( + mocks.gcloud.pubsub.calledOnce, + 'gcloud.pubsub() should have been called once' + ); + }); + + describe('subscribe()', function () { + it('should subscribe and log message', function (done) { + // Setup + var testMessage = 'test message'; + + // Run target functionality + background.subscribe(function (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.calledOnce, + 'subscription.on should have been called once' + ); + 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' + ); + done(); + }); + + // Trigger a message + setTimeout(function () { + mocks.subscription.on.firstCall.args[1]({ + data: testMessage + }); + }, 10); + }); + it('should return topic error, if any', function (done) { + // Setup + var testErrorMsg = 'test error'; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + + // Run target functionality + background.subscribe(function (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', function (done) { + // Setup + var testErrorMsg = 'test error'; + mocks.topic.subscribe = sinon.stub().callsArgWith(2, testErrorMsg); + + // Run target functionality + background.subscribe(function (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(); + }); + }); + }); + + describe('queueBook()', function () { + it('should queue a book and log message', function () { + // Setup + var testBookId = 1; + + // 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', function () { + // Setup + var testBookId = 1; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, { + code: 409 + }); + + // 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.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', function () { + // Setup + var testBookId = 1; + var testErrorMsg = 'test error'; + mocks.pubsub.createTopic = sinon.stub().callsArgWith(1, testErrorMsg); + + // 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.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', function () { + // Setup + var testBookId = 1; + var testErrorMsg = 'test error'; + mocks.topic.publish = sinon.stub().callsArgWith(1, testErrorMsg); + + // 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' + ); + }); + }); +}); diff --git a/7-gce/views/base.jade b/7-gce/views/base.jade index bdb991feeb..ed8e70329c 100644 --- a/7-gce/views/base.jade +++ b/7-gce/views/base.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/7-gce/views/books/form.jade b/7-gce/views/books/form.jade index dd8b680871..8368619eeb 100644 --- a/7-gce/views/books/form.jade +++ b/7-gce/views/books/form.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/7-gce/views/books/list.jade b/7-gce/views/books/list.jade index 38977aa211..dd453386ba 100644 --- a/7-gce/views/books/list.jade +++ b/7-gce/views/books/list.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/7-gce/views/books/view.jade b/7-gce/views/books/view.jade index 0921825cce..fb0123b05c 100644 --- a/7-gce/views/books/view.jade +++ b/7-gce/views/books/view.jade @@ -1,4 +1,4 @@ -//- Copyright 2015, Google, Inc. +//- 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 diff --git a/7-gce/worker.js b/7-gce/worker.js index 3083309bb6..c9dc32e95d 100644 --- a/7-gce/worker.js +++ b/7-gce/worker.js @@ -1,4 +1,4 @@ -// Copyright 2015, Google, Inc. +// 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 @@ -115,7 +115,7 @@ function findBookInfo(book, cb) { var top = r.items[0]; book.title = top.volumeInfo.title; - book.author = top.volumeInfo.authors.join(', '); + book.author = (top.volumeInfo.authors || []).join(', '); book.publishedDate = top.volumeInfo.publishedDate; book.description = book.description || top.volumeInfo.description; diff --git a/7-gce/worker.yaml b/7-gce/worker.yaml index 46a6a05b69..a6bae0d26b 100644 --- a/7-gce/worker.yaml +++ b/7-gce/worker.yaml @@ -1,4 +1,4 @@ -# Copyright 2015, Google, Inc. +# 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 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..388604a2e5 --- /dev/null +++ b/package.json @@ -0,0 +1,60 @@ +{ + "name": "nodejs-getting-started", + "version": "1.0.0", + "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": "jshint --exclude-path=.gitignore .", + "mocha": "mocha 1-hello-world/test/* 2-structured-data/test/* 3-binary-data/test/* 4-auth/test/* 5-logging/test/* 6-pubsub/test/* 7-gce/test/* -t 30000", + "cover": "istanbul cover --hook-run-in-context node_modules/mocha/bin/_mocha -- 1-hello-world/test/* 2-structured-data/test/* 3-binary-data/test/* 4-auth/test/* 5-logging/test/* 6-pubsub/test/* 7-gce/test/* -t 30000", + "test": "npm run lint && npm run cover", + "coveralls": "cat ./coverage/lcov.info | node_modules/coveralls/bin/coveralls.js" + }, + "author": "Google Inc.", + "contributors": [ + { + "name": "Jon Wayne Parrott", + "email": "jonwayne@google.com" + }, + { + "name": "Jonathan Simon", + "email": "jbsimon@google.com" + }, + { + "name": "Jason Dobry", + "email": "jdobry@google.com" + } + ], + "license": "Apache Version 2.0", + "dependencies": { + "async": "^1.5.2", + "body-parser": "^1.14.2", + "cookie-session": "^2.0.0-alpha.1", + "express": "^4.13.4", + "express-winston": "^1.2.0", + "gcloud": "^0.27.0", + "googleapis": "^2.1.7", + "jade": "^1.11.0", + "kerberos": "^0.0.18", + "lodash": "^4.2.1", + "mongodb": "^2.1.4", + "multer": "^1.1.0", + "mysql": "^2.10.2", + "prompt": "^0.2.14", + "request": "^2.69.0", + "winston": "^2.1.1" + }, + "devDependencies": { + "coveralls": "^2.11.6", + "istanbul": "^0.4.2", + "jshint": "^2.9.1", + "mocha": "^2.4.5", + "proxyquire": "^1.7.4", + "sinon": "^1.17.3", + "supertest": "^1.1.0" + }, + "engines": { + "node": ">=0.12.7" + } +} diff --git a/test/encrypted/nodejs-docs-samples.json.enc b/test/encrypted/nodejs-docs-samples.json.enc new file mode 100644 index 0000000000..cadf165cf6 Binary files /dev/null and b/test/encrypted/nodejs-docs-samples.json.enc differ diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000000..080421fcd6 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,117 @@ +// 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'; + +var spawn = require('child_process').spawn; +var request = require('request'); + +// Send a request to the given url and test that the response body has the +// expected value +function testRequest(url, config, cb) { + request(url, function (err, res, body) { + if (err) { + // Request error + return cb(err); + } else { + if (body && body.indexOf(config.msg) !== -1 && + (res.statusCode === 200 || res.statusCode === config.code)) { + // Success + return cb(null, true); + } else { + // Short-circuit app test + var message = config.test + ': failed verification!\n' + + 'Expected: ' + config.msg + '\n' + + 'Actual: ' + body; + + // Response body did not match expected + return cb(new Error(message)); + } + } + }); +} + +exports.testInstallation = function testInstallation(config, done) { + + // Keep track off whether "done" has been called yet + var calledDone = false; + + var proc = spawn('npm', ['install'], { + cwd: config.path + }); + + proc.on('error', finish); + + proc.stderr.on('data', function (data) { + console.log('stderr: ' + data); + }); + + proc.on('exit', function (code) { + if (code !== 0) { + finish(new Error(config.test + ': failed to install dependencies!')); + } else { + finish(); + } + }); + + // Exit helper so we don't call "cb" more than once + function finish(err) { + if (!calledDone) { + calledDone = true; + done(err); + } + } +}; + +exports.testLocalApp = function testLocalApp(config, done) { + var calledDone = false; + + var proc = spawn(config.cmd, config.args, { + cwd: config.path + }); + + proc.on('error', finish); + + if (!process.env.TRAVIS) { + proc.stderr.on('data', function (data) { + console.log('stderr: ' + data); + }); + } + + proc.on('exit', function (code, signal) { + if (code !== 0 && signal !== 'SIGKILL') { + return finish(new Error(config.test + ': failed to run!')); + } else { + return finish(); + } + }); + + // Give the server time to start up + setTimeout(function () { + // Test that the app is working + testRequest('http://localhost:8080', config, function (err) { + proc.kill('SIGKILL'); + setTimeout(function () { + return finish(err); + }, 1000); + }); + }, 3000); + + // Exit helper so we don't call "cb" more than once + function finish(err) { + if (!calledDone) { + calledDone = true; + done(err); + } + } +};