diff --git a/functions/datastore/README.md b/functions/datastore/README.md new file mode 100644 index 0000000000..a3715be0de --- /dev/null +++ b/functions/datastore/README.md @@ -0,0 +1,54 @@ +Google Cloud Platform logo + +# Google Cloud Functions Cloud Datastore sample + +This recipe shows you how to read and write an entity in Datastore from a Cloud Function. + +View the [source code][code]. + +[code]: index.js + +## Deploy and Test + +1. Follow the [Cloud Functions quickstart guide](https://cloud.google.com/functions/quickstart) to setup Cloud Functions for your project. + +1. Clone this repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/datastore + +1. Create a Cloud Storage Bucket to stage our deployment: + + gsutil mb gs:// + +1. Ensure the Cloud Datastore API is enabled: + + [Click here to enable the Cloud Datastore API](https://console.cloud.google.com/flows/enableapi?apiid=datastore.googleapis.com&redirect=https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/functions/datastore) + +1. Deploy the "ds-get" function with an HTTP trigger: + + gcloud alpha functions deploy ds-get --bucket --trigger-http --entry-point get + +1. Deploy the "ds-set" function with an HTTP trigger: + + gcloud alpha functions deploy ds-set --bucket --trigger-http --entry-point set + +1. Deploy the "ds-del" function with an HTTP trigger: + + gcloud alpha functions deploy ds-del --bucket --trigger-http --entry-point del + +1. Call the "ds-set" function to create a new entity: + + gcloud alpha functions call ds-set --data '{"kind":"gcf-test","key":"foobar","value":{"message": "Hello World!"}}' + +1. Call the "ds-get" function to read the newly created entity: + + gcloud alpha functions call ds-get --data '{"kind":"gcf-test","key":"foobar"}' + +1. Call the "ds-del" function to delete the entity: + + gcloud alpha functions call ds-del --data '{"kind":"gcf-test","key":"foobar"}' + +1. Call the "ds-get" function again to verify it was deleted: + + gcloud alpha functions call ds-get --data '{"kind":"gcf-test","key":"foobar"}' diff --git a/functions/datastore/index.js b/functions/datastore/index.js new file mode 100644 index 0000000000..91df305df6 --- /dev/null +++ b/functions/datastore/index.js @@ -0,0 +1,154 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var gcloud = require('gcloud'); + +// Create a datastore client. +var datastore = gcloud.datastore(); + +/** + * Gets a Datastore key from the kind/key pair in the request. + * + * @param {Object} requestData Cloud Function request data. + * @param {string} requestData.key Datastore key string. + * @param {string} requestData.kind Datastore kind. + * @returns {Object} Datastore key object. + */ +function getKeyFromRequestData (requestData) { + if (!requestData.key) { + throw new Error('Key not provided. Make sure you have a "key" property ' + + 'in your request'); + } + + if (!requestData.kind) { + throw new Error('Kind not provided. Make sure you have a "kind" property ' + + 'in your request'); + } + + return datastore.key([requestData.kind, requestData.key]); +} + +/** + * Creates and/or updates a record. + * + * @example + * gcloud alpha functions call ds-set --data '{"kind":"gcf-test","key":"foobar","value":{"message": "Hello World!"}}' + * + * @param {Object} context Cloud Function context. + * @param {Function} context.success Success callback. + * @param {Function} context.failure Failure callback. + * @param {Object} data Request data, in this case an object provided by the user. + * @param {string} data.kind The Datastore kind of the data to save, e.g. "user". + * @param {string} data.key Key at which to save the data, e.g. 5075192766267392. + * @param {Object} data.value Value to save to Cloud Datastore, e.g. {"name":"John"} + */ +function set (context, data) { + try { + // The value contains a JSON document representing the entity we want to save + if (!data.value) { + throw new Error('Value not provided. Make sure you have a "value" ' + + 'property in your request'); + } + + var key = getKeyFromRequestData(data); + + return datastore.save({ + key: key, + data: data.value + }, function (err) { + if (err) { + console.error(err); + return context.failure(err); + } + + return context.success('Entity saved'); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +} + +/** + * Retrieves a record. + * + * @example + * gcloud alpha functions call ds-get --data '{"kind":"gcf-test","key":"foobar"}' + * + * @param {Object} context Cloud Function context. + * @param {Function} context.success Success callback. + * @param {Function} context.failure Failure callback. + * @param {Object} data Request data, in this case an object provided by the user. + * @param {string} data.kind The Datastore kind of the data to retrieve, e.g. "user". + * @param {string} data.key Key at which to retrieve the data, e.g. 5075192766267392. + */ +function get (context, data) { + try { + var key = getKeyFromRequestData(data); + + return datastore.get(key, function (err, entity) { + if (err) { + console.error(err); + return context.failure(err); + } + + // The get operation will not fail for a non-existent entity, it just + // returns null. + if (!entity) { + return context.failure('No entity found for key ' + key.path); + } + + return context.success(entity); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +} + +/** + * Deletes a record. + * + * @example + * gcloud alpha functions call ds-del --data '{"kind":"gcf-test","key":"foobar"}' + * + * @param {Object} context Cloud Function context. + * @param {Function} context.success Success callback. + * @param {Function} context.failure Failure callback. + * @param {Object} data Request data, in this case an object provided by the user. + * @param {string} data.kind The Datastore kind of the data to delete, e.g. "user". + * @param {string} data.key Key at which to delete data, e.g. 5075192766267392. + */ +function del (context, data) { + try { + var key = getKeyFromRequestData(data); + + return datastore.delete(key, function (err) { + if (err) { + console.error(err); + return context.failure(err); + } + + return context.success('Entity deleted'); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +} + +exports.set = set; +exports.get = get; +exports.del = del; diff --git a/functions/datastore/package.json b/functions/datastore/package.json new file mode 100644 index 0000000000..62b1f2f0e5 --- /dev/null +++ b/functions/datastore/package.json @@ -0,0 +1,15 @@ +{ + "name": "nodejs-docs-samples-functions-datastore", + "description": "Node.js samples found on https://cloud.google.com", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "dependencies": { + "gcloud": "^0.35.0" + } +} diff --git a/functions/gcs/README.md b/functions/gcs/README.md new file mode 100644 index 0000000000..5b6150fba3 --- /dev/null +++ b/functions/gcs/README.md @@ -0,0 +1,38 @@ +Google Cloud Platform logo + +# Google Cloud Functions Cloud Storage sample + +This recipe demonstrates how to load a file from Cloud Storage. + +View the [source code][code]. + +[code]: index.js + +## Deploy and Test + +1. Follow the [Cloud Functions quickstart guide](https://cloud.google.com/functions/quickstart) to setup Cloud Functions for your project. + +1. Clone this repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/gcs + +1. Create a Cloud Storage Bucket to stage our deployment: + + gsutil mb gs:// + +1. Upload the sample file to the bucket: + + gsutil cp sample.txt gs:// + +1. Deploy the "wordCount" function with an HTTP trigger: + + gcloud alpha functions deploy wordCount --bucket --trigger-http --entry-point map + +1. Call the "wordCount" function using the sample file: + + gcloud alpha functions call wordCount --data '{"bucket":"","file":"sample.txt"}' + + You should see something like this in your console + + The file sample.txt has 114 words diff --git a/functions/gcs/index.js b/functions/gcs/index.js new file mode 100644 index 0000000000..3b3517bb61 --- /dev/null +++ b/functions/gcs/index.js @@ -0,0 +1,69 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var gcloud = require('gcloud'); +var readline = require('readline'); + +function getFileStream (bucketName, fileName) { + if (!bucketName) { + throw new Error('Bucket not provided. Make sure you have a ' + + '"bucket" property in your request'); + } + if (!fileName) { + throw new Error('Filename not provided. Make sure you have a ' + + '"file" property in your request'); + } + + // Create a gcs client. + var gcs = gcloud.storage(); + var bucket = gcs.bucket(bucketName); + return bucket.file(fileName).createReadStream(); +} + +/** + * Reads file and responds with the number of words in the file. + * + * @example + * gcloud alpha functions call wordCount --data '{"bucket":"","file":"sample.txt"}' + * + * @param {Object} context Cloud Function context. + * @param {Function} context.success Success callback. + * @param {Function} context.failure Failure callback. + * @param {Object} data Request data, in this case an object provided by the user. + * @param {Object} data.bucket Name of a Cloud Storage bucket. + * @param {Object} data.file Name of a file in the Cloud Storage bucket. + */ +function wordCount (context, data) { + try { + var count = 0; + + // Use the linebyline module to read the stream line by line. + var lineReader = readline.createInterface({ + input: getFileStream(data.bucket, data.file) + }); + + lineReader.on('line', function (line) { + count += line.trim().split(/\s+/).length; + }); + + lineReader.on('close', function () { + context.success('The file ' + data.file + ' has ' + count + ' words'); + }); + } catch (err) { + context.failure(err.message); + } +} + +exports.wordCount = wordCount; diff --git a/functions/gcs/package.json b/functions/gcs/package.json new file mode 100644 index 0000000000..e2c8c45c19 --- /dev/null +++ b/functions/gcs/package.json @@ -0,0 +1,15 @@ +{ + "name": "nodejs-docs-samples-functions-cloud-storage", + "description": "Node.js samples found on https://cloud.google.com", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "dependencies": { + "gcloud": "^0.35.0" + } +} diff --git a/functions/gcs/sample.txt b/functions/gcs/sample.txt new file mode 100644 index 0000000000..fa128797ee --- /dev/null +++ b/functions/gcs/sample.txt @@ -0,0 +1,14 @@ +Shall I compare thee to a summer's day? +Thou art more lovely and more temperate: +Rough winds do shake the darling buds of May, +And summer's lease hath all too short a date: +Sometime too hot the eye of heaven shines, +And often is his gold complexion dimm'd; +And every fair from fair sometime declines, +By chance, or nature's changing course, untrimm'd; +But thy eternal summer shall not fade +Nor lose possession of that fair thou ow'st; +Nor shall Death brag thou wander'st in his shade, +When in eternal lines to time thou grow'st; +So long as men can breathe or eyes can see, +So long lives this, and this gives life to thee. diff --git a/functions/log/index.js b/functions/log/index.js index 573699750a..35ed2a25ef 100644 --- a/functions/log/index.js +++ b/functions/log/index.js @@ -19,19 +19,3 @@ exports.helloworld = function (context, data) { context.success(); }; // [END log] - -exports.log = exports.helloworld; - -// [START walkthrough_pubsub] -exports.helloworld = function (context, data) { - console.log('My GCF Function: ' + data.message); - context.success(); -}; -// [END walkthrough_pubsub] - -// [START walkthrough_http] -exports.hellohttp = function (context, data) { - // Use the success argument to send data back to the caller - context.success('My GCF Function: ' + data.message); -}; -// [END walkthrough_http] diff --git a/functions/log2/README.md b/functions/log2/README.md new file mode 100644 index 0000000000..1e44dd8df1 --- /dev/null +++ b/functions/log2/README.md @@ -0,0 +1,24 @@ +Google Cloud Platform logo + +# Google Cloud Functions message sample #2 + +This sample shows writing to logs in a Cloud Function. + +View the [documentation][docs] or the [source code][code]. + +[docs]: https://cloud.google.com/functions/walkthroughs +[code]: index.js + +## Deploy + +This example deploys the function with an HTTP trigger. + + gcloud alpha functions deploy helloworld --bucket --trigger-http + +## Test + + gcloud alpha functions call helloworld + +You can also use `curl` to trigger the function: + + curl -X POST https://..cloudfunctions.net/helloworld diff --git a/functions/log2/index.js b/functions/log2/index.js new file mode 100644 index 0000000000..a1f7b20439 --- /dev/null +++ b/functions/log2/index.js @@ -0,0 +1,28 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START walkthrough_pubsub] +exports.helloworld = function (context, data) { + console.log('My GCF Function: ' + data.message); + context.success(); +}; +// [END walkthrough_pubsub] + +// [START walkthrough_http] +exports.hellohttp = function (context, data) { + // Use the success argument to send data back to the caller + context.success('My GCF Function: ' + data.message); +}; +// [END walkthrough_http] diff --git a/functions/pubsub/README.md b/functions/pubsub/README.md new file mode 100644 index 0000000000..74020076b5 --- /dev/null +++ b/functions/pubsub/README.md @@ -0,0 +1,52 @@ +Google Cloud Platform logo + +# Google Cloud Functions Pub/Sub sample + +This recipe shows you how to publish messages to a Cloud Pub/Sub topic from a +Cloud Function. + +View the [source code][code]. + +[code]: index.js + +## Deploy and Test + +1. Follow the [Cloud Functions quickstart guide](https://cloud.google.com/functions/quickstart) +to setup Cloud Functions for your project. + +1. Clone this repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/pubsub + +1. Create a Cloud Pub/Sub topic (if you already have one you want to use, you +can skip this step): + + gcloud alpha pubsub topics create + +1. Create a Cloud Storage Bucket to stage our deployment: + + gsutil mb gs:// + +1. Deploy the "publish" function with an HTTP trigger + + gcloud alpha functions deploy publish --bucket --trigger-http + +1. Deploy the "subscribe" function with the Pub/Sub topic as a trigger + + gcloud alpha functions deploy subscribe --bucket --trigger-topic + +1. Call the "publish" function: + + gcloud alpha functions call publish --data '{"topic":"","message":"Hello World!"}' + +1. Check the logs for the "subscribe" function: + + gcloud alpha functions get-logs subscribe + +You should see something like this in your console +``` +D ... User function triggered, starting execution +I ... Hello World! +D ... Execution took 1 ms, user function completed successfully +``` diff --git a/functions/pubsub/index.js b/functions/pubsub/index.js new file mode 100644 index 0000000000..da73061ed3 --- /dev/null +++ b/functions/pubsub/index.js @@ -0,0 +1,86 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var gcloud = require('gcloud'); + +// Create a pubsub client. +var pubsub = gcloud.pubsub(); + +/** + * Publishes a message to a Cloud Pub/Sub Topic. + * + * @example + * gcloud alpha functions call publish --data '{"topic":"","message":"Hello World!"}' + * + * @param {Object} context Cloud Function context. + * @param {Function} context.success Success callback. + * @param {Function} context.failure Failure callback. + * @param {Object} data Request data, in this case an object provided by the user. + * @param {string} data.topic Topic name on which to publish. + * @param {string} data.message Message to publish. + */ +function publish (context, data) { + try { + if (!data.topic) { + throw new Error('Topic not provided. Make sure you have a ' + + '"topic" property in your request'); + } + if (!data.message) { + throw new Error('Message not provided. Make sure you have a ' + + '"message" property in your request'); + } + + console.log('Publishing message to topic ' + data.topic); + + // The Pub/Sub topic must already exist. + var topic = pubsub.topic(data.topic); + + // Pub/Sub messages must be valid JSON objects. + return topic.publish({ + data: { + message: data.message + } + }, function (err) { + if (err) { + console.error(err); + return context.failure(err); + } + return context.success('Message published'); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +} + +/** + * Triggered from a message on a Pub/Sub topic. + * + * @param {Object} context Cloud Function context. + * @param {Function} context.success Success callback. + * @param {Function} context.failure Failure callback. + * @param {Object} data Request data, in this case an object provided by the Pub/Sub trigger. + * @param {Object} data.message Message that was published via Pub/Sub. + */ +function subscribe (context, data) { + // We're just going to log the message to prove that it worked! + console.log(data.message); + + // Don't forget to call success! + context.success(); +} + +exports.publish = publish; +exports.subscribe = subscribe; diff --git a/functions/pubsub/package.json b/functions/pubsub/package.json new file mode 100644 index 0000000000..1f489c2950 --- /dev/null +++ b/functions/pubsub/package.json @@ -0,0 +1,15 @@ +{ + "name": "nodejs-docs-samples-functions-pubsub", + "description": "Node.js samples found on https://cloud.google.com", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "dependencies": { + "gcloud": "^0.35.0" + } +} diff --git a/package.json b/package.json index 1357ca8e84..d1542c8ccc 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "proxyquire": "^1.7.9", "request": "^2.72.0", "semistandard": "^8.0.0", + "sinon": "^1.17.4", "supertest": "^1.2.0" } } diff --git a/test/functions/datastore.test.js b/test/functions/datastore.test.js new file mode 100644 index 0000000000..4436fe3483 --- /dev/null +++ b/test/functions/datastore.test.js @@ -0,0 +1,312 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var test = require('ava'); +var sinon = require('sinon'); +var proxyquire = require('proxyquire').noCallThru(); + +var KEY = 'key'; +var KIND = 'user'; + +function getSample () { + var datastore = { + delete: sinon.stub().callsArg(1), + get: sinon.stub().callsArg(1), + key: sinon.stub().returns({ + kind: KIND, + path: KEY + }), + save: sinon.stub().callsArg(1) + }; + var gcloud = { + datastore: sinon.stub().returns(datastore) + }; + return { + sample: proxyquire('../../functions/datastore', { + gcloud: gcloud + }), + mocks: { + gcloud: gcloud, + datastore: datastore + } + }; +} + +function getMockContext () { + return { + success: sinon.stub(), + failure: sinon.stub() + }; +} + +test('set: Set fails without a value', function (t) { + var expectedMsg = 'Value not provided. Make sure you have a "value" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.set(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('set: Set fails without a key', function (t) { + var expectedMsg = 'Key not provided. Make sure you have a "key" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.set(context, { + value: {} + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('set: Set fails without a kind', function (t) { + var expectedMsg = 'Kind not provided. Make sure you have a "kind" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.set(context, { + value: {}, + key: KEY + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('set: Handles save error', function (t) { + var expectedMsg = 'test error'; + var context = getMockContext(); + var datastoreSample = getSample(); + + datastoreSample.mocks.datastore.save = sinon.stub().callsArgWith( + 1, + expectedMsg + ); + + datastoreSample.sample.set(context, { + value: {}, + key: KEY, + kind: KIND + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(datastoreSample.mocks.datastore.save.calledOnce, true); +}); + +test('set: Set saves an entity', function (t) { + var expectedMsg = 'Entity saved'; + var context = getMockContext(); + var datastoreSample = getSample(); + + var data = { + value: { + name: 'John' + }, + key: KEY, + kind: KIND + }; + + datastoreSample.sample.set(context, data); + + t.is(context.success.calledOnce, true); + t.is(context.success.firstCall.args[0], expectedMsg); + t.is(context.failure.called, false); + t.is(datastoreSample.mocks.datastore.key.calledOnce, true); + t.deepEqual( + datastoreSample.mocks.datastore.key.firstCall.args[0], + [data.kind, data.key] + ); + t.is(datastoreSample.mocks.datastore.save.calledOnce, true); + t.deepEqual(datastoreSample.mocks.datastore.save.firstCall.args[0], { + key: { + kind: data.kind, + path: data.key + }, + data: data.value + }); +}); + +test('get: Get fails without a key', function (t) { + var expectedMsg = 'Key not provided. Make sure you have a "key" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.get(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('get: Get fails without a kind', function (t) { + var expectedMsg = 'Kind not provided. Make sure you have a "kind" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.get(context, { + key: KEY + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('get: Handles get error', function (t) { + var expectedMsg = 'test error'; + var context = getMockContext(); + var datastoreSample = getSample(); + + datastoreSample.mocks.datastore.get = sinon.stub().callsArgWith( + 1, + expectedMsg + ); + + datastoreSample.sample.get(context, { + key: KEY, + kind: KIND + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(datastoreSample.mocks.datastore.get.calledOnce, true); +}); + +test('get: Fails when entity does not exist', function (t) { + var expectedMsg = 'No entity found for key key'; + var context = getMockContext(); + var datastoreSample = getSample(); + + datastoreSample.sample.get(context, { + key: KEY, + kind: KIND + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(datastoreSample.mocks.datastore.get.calledOnce, true); +}); + +test('get: Finds an entity', function (t) { + var expectedResult = { + name: 'John' + }; + var context = getMockContext(); + var datastoreSample = getSample(); + + datastoreSample.mocks.datastore.get = sinon.stub().callsArgWith( + 1, + null, + expectedResult + ); + datastoreSample.sample.get(context, { + key: KEY, + kind: KIND + }); + + t.is(context.success.calledOnce, true); + t.is(context.success.firstCall.args[0], expectedResult); + t.is(context.failure.called, false); + t.is(datastoreSample.mocks.datastore.get.calledOnce, true); + t.deepEqual( + datastoreSample.mocks.datastore.get.firstCall.args[0], + { + path: KEY, + kind: KIND + } + ); +}); + +test('del: Delete fails without a key', function (t) { + var expectedMsg = 'Key not provided. Make sure you have a "key" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.del(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('del: Delete fails without a kind', function (t) { + var expectedMsg = 'Kind not provided. Make sure you have a "kind" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.del(context, { + key: KEY + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('del: Handles delete error', function (t) { + var expectedMsg = 'Kind not provided. Make sure you have a "kind" ' + + 'property in your request'; + var context = getMockContext(); + var datastoreSample = getSample(); + + datastoreSample.mocks.datastore.delete = sinon.stub().callsArgWith( + 1, + expectedMsg + ); + + datastoreSample.sample.del(context, { + key: KEY, + kind: KIND + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(datastoreSample.mocks.datastore.delete.calledOnce, true); +}); + +test('del: Deletes an entity', function (t) { + var expectedMsg = 'Entity deleted'; + var context = getMockContext(); + var datastoreSample = getSample(); + + datastoreSample.sample.del(context, { + key: KEY, + kind: KIND + }); + + t.is(context.success.calledOnce, true); + t.is(context.success.firstCall.args[0], expectedMsg); + t.is(context.failure.called, false); + t.is(datastoreSample.mocks.datastore.delete.calledOnce, true); + t.deepEqual( + datastoreSample.mocks.datastore.delete.firstCall.args[0], + { + path: KEY, + kind: KIND + } + ); +}); diff --git a/test/functions/gcs.test.js b/test/functions/gcs.test.js new file mode 100644 index 0000000000..704001f9f3 --- /dev/null +++ b/test/functions/gcs.test.js @@ -0,0 +1,109 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var test = require('ava'); +var sinon = require('sinon'); +var proxyquire = require('proxyquire').noCallThru(); + +function getSample () { + var file = { + createReadStream: function () { + var filepath = path.join(__dirname, '../../functions/gcs/sample.txt'); + return fs.createReadStream(filepath, { encoding: 'utf8' }); + } + }; + var bucket = { + file: sinon.stub().returns(file) + }; + var storage = { + bucket: sinon.stub().returns(bucket) + }; + var gcloud = { + storage: sinon.stub().returns(storage) + }; + return { + sample: proxyquire('../../functions/gcs', { + gcloud: gcloud + }), + mocks: { + gcloud: gcloud, + storage: storage, + bucket: bucket, + file: file + } + }; +} + +function getMockContext () { + return { + success: sinon.stub(), + failure: sinon.stub() + }; +} + +test('Fails without a bucket', function (t) { + var expectedMsg = 'Bucket not provided. Make sure you have a "bucket" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.wordCount(context, { + file: 'file' + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('Fails without a file', function (t) { + var expectedMsg = 'Filename not provided. Make sure you have a "file" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.wordCount(context, { + bucket: 'bucket' + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test.cb('Reads the file line by line', function (t) { + var expectedMsg = 'The file sample.txt has 114 words'; + var data = { + bucket: 'bucket', + file: 'sample.txt' + }; + var context = { + success: function (message) { + t.is(message, expectedMsg); + t.end(); + }, + failure: function () { + t.end('Should have succeeded!'); + } + }; + + var gcsSample = getSample(); + gcsSample.sample.wordCount(context, data); + + t.is(gcsSample.mocks.storage.bucket.calledOnce, true); + t.is(gcsSample.mocks.storage.bucket.firstCall.args[0], data.bucket); + t.is(gcsSample.mocks.bucket.file.calledOnce, true); + t.is(gcsSample.mocks.bucket.file.firstCall.args[0], data.file); +}); diff --git a/test/functions/log.test.js b/test/functions/log.test.js index a72a0be532..cb17ec710b 100644 --- a/test/functions/log.test.js +++ b/test/functions/log.test.js @@ -14,60 +14,21 @@ 'use strict'; var test = require('ava'); +var sinon = require('sinon'); var logSample = require('../../functions/log'); -test.cb('should write to log', function (t) { - var logMessage = 'I am a log entry!'; - var messageWasPrinted = false; - - console.log = function (data) { - if (data === logMessage) { - messageWasPrinted = true; - } - }; - - logSample.log({ - success: function (result) { - t.is(result, undefined); - if (messageWasPrinted) { - t.end(); - } else { - t.end('message was not printed!'); - } - } - }); -}); -test.cb('should write to log 2', function (t) { - var logMessage = 'My GCF Function: foo'; - var messageWasPrinted = false; - - console.log = function (data) { - if (data === logMessage) { - messageWasPrinted = true; - } - }; +test('should write to log', function (t) { + var expectedMsg = 'I am a log entry!'; + sinon.spy(console, 'log'); logSample.helloworld({ success: function (result) { t.is(result, undefined); - if (messageWasPrinted) { - t.end(); - } else { - t.end('message was not printed!'); - } - } - }, { - message: 'foo' - }); -}); -test.cb('should write to log 3', function (t) { - var logMessage = 'My GCF Function: foo'; - logSample.hellohttp({ - success: function (result) { - t.is(result, logMessage); - t.end(); - } - }, { - message: 'foo' + t.is(console.log.calledOnce, true); + t.is(console.log.calledWith(expectedMsg), true); + }, + failure: t.fail }); + + console.log.restore(); }); diff --git a/test/functions/log2.test.js b/test/functions/log2.test.js new file mode 100644 index 0000000000..4457758e01 --- /dev/null +++ b/test/functions/log2.test.js @@ -0,0 +1,47 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var test = require('ava'); +var sinon = require('sinon'); +var log2Sample = require('../../functions/log2'); + +test('should write to log 2', function (t) { + var expectedMsg = 'My GCF Function: foo'; + sinon.spy(console, 'log'); + + log2Sample.helloworld({ + success: function (result) { + t.is(result, undefined); + t.is(console.log.calledOnce, true); + t.is(console.log.calledWith(expectedMsg), true); + }, + failure: t.fail + }, { + message: 'foo' + }); + + console.log.restore(); +}); +test('should write to log 3', function (t) { + var logMessage = 'My GCF Function: foo'; + log2Sample.hellohttp({ + success: function (result) { + t.is(result, logMessage); + }, + failure: t.fail + }, { + message: 'foo' + }); +}); diff --git a/test/functions/pubsub.test.js b/test/functions/pubsub.test.js new file mode 100644 index 0000000000..90d32a7c51 --- /dev/null +++ b/test/functions/pubsub.test.js @@ -0,0 +1,146 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var test = require('ava'); +var sinon = require('sinon'); +var proxyquire = require('proxyquire').noCallThru(); + +function getSample () { + var topic = { + publish: sinon.stub().callsArg(1) + }; + var pubsub = { + topic: sinon.stub().returns(topic) + }; + var gcloud = { + pubsub: sinon.stub().returns(pubsub) + }; + return { + sample: proxyquire('../../functions/pubsub', { + gcloud: gcloud + }), + mocks: { + gcloud: gcloud, + pubsub: pubsub, + topic: topic + } + }; +} + +function getMockContext () { + return { + success: sinon.stub(), + failure: sinon.stub() + }; +} + +test('Publish fails without a topic', function (t) { + var expectedMsg = 'Topic not provided. Make sure you have a "topic" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.publish(context, { + message: 'message' + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('Publish fails without a message', function (t) { + var expectedMsg = 'Message not provided. Make sure you have a "message" ' + + 'property in your request'; + var context = getMockContext(); + + getSample().sample.publish(context, { + topic: 'topic' + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); +}); + +test('Publishes the message to the topic and calls success', function (t) { + var expectedMsg = 'Message published'; + var data = { + topic: 'topic', + message: 'message' + }; + var context = getMockContext(); + + var pubsubSample = getSample(); + pubsubSample.sample.publish(context, data); + + t.is(context.success.calledOnce, true); + t.is(context.success.firstCall.args[0], expectedMsg); + t.is(context.failure.called, false); + t.is(pubsubSample.mocks.pubsub.topic.calledOnce, true); + t.deepEqual(pubsubSample.mocks.pubsub.topic.firstCall.args[0], data.topic); + t.is(pubsubSample.mocks.topic.publish.calledOnce, true); + t.deepEqual(pubsubSample.mocks.topic.publish.firstCall.args[0], { + data: { + message: data.message + } + }); +}); + +test('Fails to publish the message and calls failure', function (t) { + var expectedMsg = 'error'; + var data = { + topic: 'topic', + message: 'message' + }; + var context = getMockContext(); + + var pubsubSample = getSample(); + pubsubSample.mocks.topic.publish = sinon.stub().callsArgWith(1, expectedMsg); + + pubsubSample.sample.publish(context, data); + + t.is(context.success.called, false); + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(pubsubSample.mocks.pubsub.topic.calledOnce, true); + t.deepEqual(pubsubSample.mocks.pubsub.topic.firstCall.args[0], data.topic); + t.is(pubsubSample.mocks.topic.publish.calledOnce, true); + t.deepEqual(pubsubSample.mocks.topic.publish.firstCall.args[0], { + data: { + message: data.message + } + }); +}); + +test('Subscribes to a message', function (t) { + var expectedMsg = 'message'; + var data = { + topic: 'topic', + message: expectedMsg + }; + var context = getMockContext(); + + var pubsubSample = getSample(); + sinon.spy(console, 'log'); + + pubsubSample.sample.subscribe(context, data); + + t.is(console.log.calledOnce, true); + t.is(console.log.firstCall.args[0], expectedMsg); + t.is(context.success.calledOnce, true); + t.is(context.failure.called, false); + + console.log.restore(); +});