From 45f38b57b5bce16056bb65db10f0450c7e8970ea Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Mon, 1 May 2017 15:35:18 -0700 Subject: [PATCH 1/7] Add spanner sample + test to GitHub --- functions/spanner/index.js | 61 ++++++++++++++++++++ functions/spanner/test/index.test.js | 84 ++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 functions/spanner/index.js create mode 100644 functions/spanner/test/index.test.js diff --git a/functions/spanner/index.js b/functions/spanner/index.js new file mode 100644 index 0000000000..d156cfddee --- /dev/null +++ b/functions/spanner/index.js @@ -0,0 +1,61 @@ +/** + * Copyright 2017, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// [START spanner_functions_quickstart] +// Imports the Google Cloud client library +const Spanner = require('@google-cloud/spanner'); + +// Your Google Cloud Platform project ID +const projectId = 'YOUR_PROJECT_ID'; + +// Instantiates a client +const spanner = Spanner({ + projectId: projectId +}); + +// Your Cloud Spanner instance ID +const instanceId = 'my-instance'; + +// Your Cloud Spanner database ID +const databaseId = 'my-database'; + +/** + * HTTP Cloud Function. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.get = function helloGET (req, res) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // The query to execute + const query = { + sql: 'SELECT * FROM Albums' + }; + + // Execute the query + database.run(query) + .then((results) => { + const rows = results[0]; + var data = []; + rows.forEach((row) => data.push(row.toJSON())); + res.send(data); + }); +}; +// [END spanner_functions_quickstart] diff --git a/functions/spanner/test/index.test.js b/functions/spanner/test/index.test.js new file mode 100644 index 0000000000..78490ae619 --- /dev/null +++ b/functions/spanner/test/index.test.js @@ -0,0 +1,84 @@ +/** + * Copyright 2017, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const proxyquire = require(`proxyquire`).noCallThru(); +const sinon = require(`sinon`); +const test = require(`ava`); + +const entities = [ + { + SingerId: { value: 1 }, + AlbumId: { value: 1 }, + AlbumTitle: 'Go, Go, Go' + }, + { + SingerId: { value: 1 }, + AlbumId: { value: 2 }, + AlbumTitle: 'Total Junk' + } +]; + +const query = { + sql: 'SELECT * FROM Albums' +}; + +function getSample () { + const resultsMock = entities.map((row) => { + return { toJSON: sinon.stub().returns(row) }; + }); + const databaseMock = { + run: sinon.stub().returns(Promise.resolve([resultsMock])) + }; + const instanceMock = { + database: sinon.stub().returns(databaseMock) + }; + const spannerMock = { + instance: sinon.stub().returns(instanceMock) + }; + + const SpannerMock = sinon.stub().returns(spannerMock); + + return { + program: proxyquire(`../`, { + '@google-cloud/spanner': SpannerMock + }), + mocks: { + spanner: spannerMock, + database: databaseMock, + instance: instanceMock, + results: resultsMock, + res: { + status: sinon.stub().returnsThis(), + send: sinon.stub().returnsThis() + } + } + }; +} + +test(`get: Gets albums`, async (t) => { + const sample = getSample(); + const mocks = sample.mocks; + + const err = await sample.program.get(mocks.req, mocks.res); + t.falsy(err, null); + t.true(mocks.spanner.instance.called); + t.true(mocks.instance.database.called); + t.true(mocks.database.run.calledWith(query)); + t.true(mocks.results[0].toJSON.called); + t.true(mocks.res.send.called); + t.true(mocks.res.send.calledWith(entities)); +}); From 3f33b21aea73c78fa10ddbcbfbfd32a7fb2fb983 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Mon, 1 May 2017 15:52:58 -0700 Subject: [PATCH 2/7] Switch to arrow syntax --- functions/spanner/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/spanner/index.js b/functions/spanner/index.js index d156cfddee..86e6395241 100644 --- a/functions/spanner/index.js +++ b/functions/spanner/index.js @@ -39,7 +39,7 @@ const databaseId = 'my-database'; * @param {Object} req Cloud Function request context. * @param {Object} res Cloud Function response context. */ -exports.get = function helloGET (req, res) { +exports.get = (req, res) => { // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId); From 678af97cd1a51470c2375fabf08ee80fced95024 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Tue, 2 May 2017 14:43:16 -0700 Subject: [PATCH 3/7] Address comments --- functions/spanner/index.js | 15 ++++++--------- functions/spanner/test/index.test.js | 5 ++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/functions/spanner/index.js b/functions/spanner/index.js index 86e6395241..48c80db124 100644 --- a/functions/spanner/index.js +++ b/functions/spanner/index.js @@ -19,13 +19,8 @@ // Imports the Google Cloud client library const Spanner = require('@google-cloud/spanner'); -// Your Google Cloud Platform project ID -const projectId = 'YOUR_PROJECT_ID'; - // Instantiates a client -const spanner = Spanner({ - projectId: projectId -}); +const spanner = Spanner(); // Your Cloud Spanner instance ID const instanceId = 'my-instance'; @@ -53,9 +48,11 @@ exports.get = (req, res) => { database.run(query) .then((results) => { const rows = results[0]; - var data = []; - rows.forEach((row) => data.push(row.toJSON())); - res.send(data); + res.send(rows.map((row) => row.toJSON())); + }) + .catch((err) => { + res.status(500); + res.send(`Error querying Spanner: ${err}`); }); }; // [END spanner_functions_quickstart] diff --git a/functions/spanner/test/index.test.js b/functions/spanner/test/index.test.js index 78490ae619..bc89b66d0d 100644 --- a/functions/spanner/test/index.test.js +++ b/functions/spanner/test/index.test.js @@ -69,12 +69,11 @@ function getSample () { }; } -test(`get: Gets albums`, async (t) => { +test(`get: Gets albums`, (t) => { const sample = getSample(); const mocks = sample.mocks; - const err = await sample.program.get(mocks.req, mocks.res); - t.falsy(err, null); + const err = sample.program.get(mocks.req, mocks.res); t.true(mocks.spanner.instance.called); t.true(mocks.instance.database.called); t.true(mocks.database.run.calledWith(query)); From f74cd88bc6e19d09a6fc7a1a27aa727df45bca88 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 3 May 2017 10:49:24 -0700 Subject: [PATCH 4/7] Address further comments (from jdobry + vikask) --- functions/spanner/index.js | 14 ++++++++++---- functions/spanner/test/index.test.js | 12 +++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/functions/spanner/index.js b/functions/spanner/index.js index 48c80db124..9ee02d4a3e 100644 --- a/functions/spanner/index.js +++ b/functions/spanner/index.js @@ -47,12 +47,18 @@ exports.get = (req, res) => { // Execute the query database.run(query) .then((results) => { - const rows = results[0]; - res.send(rows.map((row) => row.toJSON())); + const rows = results[0].map((row) => row.toJSON()); + rows.forEach((row) => { + res.write(`SingerId: ${row.SingerId.value}, AlbumId: ${row.AlbumId.value}, AlbumTitle: ${row.AlbumTitle}
`); + }); + res + .status(200) + .end(); }) .catch((err) => { - res.status(500); - res.send(`Error querying Spanner: ${err}`); + res + .status(500) + .send(`Error querying Spanner: ${err}`); }); }; // [END spanner_functions_quickstart] diff --git a/functions/spanner/test/index.test.js b/functions/spanner/test/index.test.js index bc89b66d0d..d8bf8231af 100644 --- a/functions/spanner/test/index.test.js +++ b/functions/spanner/test/index.test.js @@ -63,21 +63,23 @@ function getSample () { results: resultsMock, res: { status: sinon.stub().returnsThis(), - send: sinon.stub().returnsThis() + send: sinon.stub().returnsThis(), + end: sinon.stub().returnsThis(), + write: sinon.stub().returnsThis() } } }; } -test(`get: Gets albums`, (t) => { +test(`get: Gets albums`, async (t) => { const sample = getSample(); const mocks = sample.mocks; - const err = sample.program.get(mocks.req, mocks.res); + await sample.program.get(mocks.req, mocks.res); t.true(mocks.spanner.instance.called); t.true(mocks.instance.database.called); t.true(mocks.database.run.calledWith(query)); t.true(mocks.results[0].toJSON.called); - t.true(mocks.res.send.called); - t.true(mocks.res.send.calledWith(entities)); + t.true(mocks.res.write.calledWith(`SingerId: 1, AlbumId: 2, AlbumTitle: Total Junk
`)); + t.true(mocks.res.end.called); }); From a62010fb2d138d7f7fd13e06209b2dd36449ef8a Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 3 May 2017 11:20:05 -0700 Subject: [PATCH 5/7] Fix nit --- functions/spanner/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functions/spanner/index.js b/functions/spanner/index.js index 9ee02d4a3e..f637360027 100644 --- a/functions/spanner/index.js +++ b/functions/spanner/index.js @@ -58,7 +58,8 @@ exports.get = (req, res) => { .catch((err) => { res .status(500) - .send(`Error querying Spanner: ${err}`); + .send(`Error querying Spanner: ${err}`) + .end(); }); }; // [END spanner_functions_quickstart] From 624de84c1cc7aa15d94ee4d5f7e55a27bb007374 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 3 May 2017 12:44:19 -0700 Subject: [PATCH 6/7] Add package.json + convert html to plaintext --- functions/spanner/index.js | 2 +- functions/spanner/package.json | 32 ++++++++++++++++++++++++++++ functions/spanner/test/index.test.js | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 functions/spanner/package.json diff --git a/functions/spanner/index.js b/functions/spanner/index.js index f637360027..d6ebb40be4 100644 --- a/functions/spanner/index.js +++ b/functions/spanner/index.js @@ -49,7 +49,7 @@ exports.get = (req, res) => { .then((results) => { const rows = results[0].map((row) => row.toJSON()); rows.forEach((row) => { - res.write(`SingerId: ${row.SingerId.value}, AlbumId: ${row.AlbumId.value}, AlbumTitle: ${row.AlbumTitle}
`); + res.write(`SingerId: ${row.SingerId.value}, AlbumId: ${row.AlbumId.value}, AlbumTitle: ${row.AlbumTitle}\n`); }); res .status(200) diff --git a/functions/spanner/package.json b/functions/spanner/package.json new file mode 100644 index 0000000000..587f035ed7 --- /dev/null +++ b/functions/spanner/package.json @@ -0,0 +1,32 @@ +{ + "name": "nodejs-docs-samples-functions-spanner", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "engines": { + "node": ">=4.3.2" + }, + "scripts": { + "lint": "samples lint", + "pretest": "npm run lint", + "test": "ava -T 20s --verbose test/*.test.js" + }, + "dependencies": { + "@google-cloud/spanner": "0.4.2" + }, + "devDependencies": { + "@google-cloud/nodejs-repo-tools": "1.4.7", + "ava": "0.19.1", + "proxyquire": "1.7.11", + "sinon": "2.1.0" + }, + "cloud-repo-tools": { + "requiresKeyFile": true, + "requiresProjectId": true + } +} diff --git a/functions/spanner/test/index.test.js b/functions/spanner/test/index.test.js index d8bf8231af..46892cbe3a 100644 --- a/functions/spanner/test/index.test.js +++ b/functions/spanner/test/index.test.js @@ -80,6 +80,6 @@ test(`get: Gets albums`, async (t) => { t.true(mocks.instance.database.called); t.true(mocks.database.run.calledWith(query)); t.true(mocks.results[0].toJSON.called); - t.true(mocks.res.write.calledWith(`SingerId: 1, AlbumId: 2, AlbumTitle: Total Junk
`)); + t.true(mocks.res.write.calledWith(`SingerId: 1, AlbumId: 2, AlbumTitle: Total Junk\n`)); t.true(mocks.res.end.called); }); From afc6a6d1d3ba85245c275f51b59d8efb35e4bb0f Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Thu, 4 May 2017 10:24:51 -0700 Subject: [PATCH 7/7] Address comments --- functions/spanner/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/spanner/index.js b/functions/spanner/index.js index d6ebb40be4..ae708d1599 100644 --- a/functions/spanner/index.js +++ b/functions/spanner/index.js @@ -45,7 +45,7 @@ exports.get = (req, res) => { }; // Execute the query - database.run(query) + return database.run(query) .then((results) => { const rows = results[0].map((row) => row.toJSON()); rows.forEach((row) => {