diff --git a/functions/datastore/README.md b/functions/datastore/README.md index 774d1ca634..aadbae89ee 100644 --- a/functions/datastore/README.md +++ b/functions/datastore/README.md @@ -10,7 +10,8 @@ View the [source code][code]. ## Deploy and Test -1. Follow the [Cloud Functions quickstart guide](https://cloud.google.com/functions/quickstart) to setup Cloud Functions for your project. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. 1. Clone this repository: @@ -19,7 +20,9 @@ View the [source code][code]. 1. Create a Cloud Storage Bucket to stage our deployment: - gsutil mb gs:// + gsutil mb gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 1. Ensure the Cloud Datastore API is enabled: @@ -27,19 +30,25 @@ View the [source code][code]. 1. Deploy the "ds-get" function with an HTTP trigger: - gcloud alpha functions deploy ds-get --bucket --trigger-http --entry-point get + gcloud alpha functions deploy ds-get --bucket [YOUR_BUCKET_NAME] --trigger-http --entry-point get + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 1. Deploy the "ds-set" function with an HTTP trigger: - gcloud alpha functions deploy ds-set --bucket --trigger-http --entry-point set + gcloud alpha functions deploy ds-set --bucket [YOUR_BUCKET_NAME] --trigger-http --entry-point set + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 1. Deploy the "ds-del" function with an HTTP trigger: - gcloud alpha functions deploy ds-del --bucket --trigger-http --entry-point del + gcloud alpha functions deploy ds-del --bucket [YOUR_BUCKET_NAME] --trigger-http --entry-point del + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 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!"}}' + 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: @@ -52,3 +61,5 @@ View the [source code][code]. 1. Call the "ds-get" function again to verify it was deleted: gcloud alpha functions call ds-get --data '{"kind":"gcf-test","key":"foobar"}' + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/gcs/README.md b/functions/gcs/README.md index 0d4b0e8567..ada491d689 100644 --- a/functions/gcs/README.md +++ b/functions/gcs/README.md @@ -10,7 +10,8 @@ View the [source code][code]. ## Deploy and Test -1. Follow the [Cloud Functions quickstart guide](https://cloud.google.com/functions/quickstart) to setup Cloud Functions for your project. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. 1. Clone this repository: @@ -19,20 +20,30 @@ View the [source code][code]. 1. Create a Cloud Storage Bucket to stage our deployment: - gsutil mb gs:// + gsutil mb gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 1. Upload the sample file to the bucket: - gsutil cp sample.txt gs:// + gsutil cp sample.txt gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 1. Deploy the "wordCount" function with an HTTP trigger: - gcloud alpha functions deploy wordCount --bucket --trigger-http --entry-point map + gcloud alpha functions deploy wordCount --bucket [YOUR_BUCKET_NAME] --trigger-http --entry-point map + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. 1. Call the "wordCount" function using the sample file: - gcloud alpha functions call wordCount --data '{"bucket":"","file":"sample.txt"}' + gcloud alpha functions call wordCount --data '{"bucket":"[YOUR_BUCKET_NAME]","file":"sample.txt"}' + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. You should see something like this in your console The file sample.txt has 114 words + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/helloworld/README.md b/functions/helloworld/README.md index 72c3f42a04..fced1e12d4 100644 --- a/functions/helloworld/README.md +++ b/functions/helloworld/README.md @@ -9,18 +9,35 @@ View the [documentation][docs] or the [source code][code]. [docs]: https://cloud.google.com/functions/writing [code]: index.js -## Deploy +## Deploy and Test -This example deploys the function with an HTTP trigger. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. - gcloud alpha functions deploy helloworld --bucket --trigger-http +1. Clone this repository: -## Test + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/module - gcloud alpha functions call helloworld +1. Create a Cloud Storage Bucket to stage our deployment: -Running the above command should print "Hello World!". + gsutil mb gs://[YOUR_BUCKET_NAME] -You can also use `curl` to trigger the function: + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. - curl -X POST https://..cloudfunctions.net/helloworld +1. Deploy the `helloworld` function with an HTTP trigger: + + gcloud alpha functions deploy helloworld --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `helloworld` function: + + gcloud alpha functions call helloworld + + You should see something like this in your console: + + executionId: abcd1234-0 + result: Hello World! + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/log/README.md b/functions/log/README.md index b4d20f6889..c087d9f1f8 100644 --- a/functions/log/README.md +++ b/functions/log/README.md @@ -9,16 +9,40 @@ View the [documentation][docs] or the [source code][code]. [docs]: https://cloud.google.com/functions/walkthroughs [code]: index.js -## Deploy +## Deploy and Test -This example deploys the function with an HTTP trigger. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. - gcloud alpha functions deploy helloworld --bucket --trigger-http +1. Clone this repository: -## Test + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/module - gcloud alpha functions call helloworld +1. Create a Cloud Storage Bucket to stage our deployment: -You can also use `curl` to trigger the function: + gsutil mb gs://[YOUR_BUCKET_NAME] - curl -X POST https://..cloudfunctions.net/helloworld + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Deploy the `helloworld` function with an HTTP trigger: + + gcloud alpha functions deploy helloworld --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `helloworld` function: + + gcloud alpha functions call helloworld + +1. Check the logs for the `helloworld` function: + + gcloud alpha functions get-logs helloworld + + You should see something like this in your console: + + D ... User function triggered, starting execution + I ... I am a log entry! + D ... Execution took 1 ms, user function completed successfully + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/log2/README.md b/functions/log2/README.md index 1e44dd8df1..697e23adf0 100644 --- a/functions/log2/README.md +++ b/functions/log2/README.md @@ -9,16 +9,41 @@ View the [documentation][docs] or the [source code][code]. [docs]: https://cloud.google.com/functions/walkthroughs [code]: index.js -## Deploy +## Deploy and Test -This example deploys the function with an HTTP trigger. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. - gcloud alpha functions deploy helloworld --bucket --trigger-http +1. Clone this repository: -## Test + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/module - gcloud alpha functions call helloworld +1. Create a Cloud Storage Bucket to stage our deployment: -You can also use `curl` to trigger the function: + gsutil mb gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Deploy the `helloworld` function with an HTTP trigger: + + gcloud alpha functions deploy helloworld --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `helloworld` function: + + gcloud alpha functions call helloworld --data '{"message":"Hello World!"}' + +1. Check the logs for the `helloworld` function: + + gcloud alpha functions get-logs helloworld + + You should see something like this in your console: + + D ... User function triggered, starting execution + I ... My GCF Function: Hello World! + D ... Execution took 1 ms, user function completed successfully + +[quickstart]: https://cloud.google.com/functions/quickstart - curl -X POST https://..cloudfunctions.net/helloworld diff --git a/functions/message/README.md b/functions/message/README.md index d932439e93..13949855b3 100644 --- a/functions/message/README.md +++ b/functions/message/README.md @@ -9,16 +9,50 @@ View the [documentation][docs] or the [source code][code]. [docs]: https://cloud.google.com/functions/writing [code]: index.js -## Deploy +## Deploy and Test -This example deploys the function with an HTTP trigger. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. - gcloud alpha functions deploy helloworld --bucket --trigger-http +1. Clone this repository: -## Test + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/module - gcloud alpha functions call helloworld +1. Create a Cloud Storage Bucket to stage our deployment: -You can also use `curl` to trigger the function: + gsutil mb gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Deploy the `helloworld` function with an HTTP trigger: + + gcloud alpha functions deploy helloworld --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `helloworld` function with some data: + + gcloud alpha functions call helloworld --data '{"message":"keyboard cat"}' + +1. Call the `helloworld` function without any data: + + gcloud alpha functions call helloworld + + You should see something like this in your console: + + executionId: abcd1234-0 + result: Hello World! + +1. Check the logs for the `helloworld` function: + + gcloud alpha functions get-logs helloworld + + You should see something like this in your console: + + D ... User function triggered, starting execution + I ... keyboard cat + D ... Execution took 1 ms, user function completed successfully + +[quickstart]: https://cloud.google.com/functions/quickstart - curl -X POST https://..cloudfunctions.net/helloworld --data '{"message":"cat"}' diff --git a/functions/module/README.md b/functions/module/README.md index 5fab254533..e19e49bcbc 100644 --- a/functions/module/README.md +++ b/functions/module/README.md @@ -10,18 +10,35 @@ View the [documentation][docs] or the [source code][code]. [docs]: https://cloud.google.com/functions/writing [code]: index.js -## Deploy +## Deploy and Test -This example deploys the function with an HTTP trigger. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. - gcloud alpha functions deploy helloworld --bucket --trigger-http +1. Clone this repository: -## Test + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/module - gcloud alpha functions call helloworld +1. Create a Cloud Storage Bucket to stage our deployment: -Running the above command should print "Hello World!". + gsutil mb gs://[YOUR_BUCKET_NAME] -You can also use `curl` to trigger the function: + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. - curl -X POST https://..cloudfunctions.net/helloworld +1. Deploy the `helloworld` function with an HTTP trigger: + + gcloud alpha functions deploy helloworld --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `helloworld` function: + + gcloud alpha functions call helloworld + + You should see something like this in your console: + + executionId: abcd1234-0 + result: Hello World! + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/ocr/.gitignore b/functions/ocr/.gitignore new file mode 100644 index 0000000000..36420af85d --- /dev/null +++ b/functions/ocr/.gitignore @@ -0,0 +1,2 @@ +node_modules +config.json \ No newline at end of file diff --git a/functions/ocr/README.md b/functions/ocr/README.md new file mode 100644 index 0000000000..c0a1032a0c --- /dev/null +++ b/functions/ocr/README.md @@ -0,0 +1,102 @@ +Google Cloud Platform logo + +# Google Cloud Functions OCR sample + +This recipe shows you how to use the Cloud Vision API together with the Google +Translate API using Cloud Pub/Sub as a message bus. + +View the [source code][code]. + +[code]: index.js + +## Overview + +![OCR](readme.png "OCR") + +1. Image is uploaded to Cloud Storage with text in any language (text in the +image itself). +1. Cloud Function is triggered, uses the Vision API to extract the text, and the +Translate API to detect the language. +1. For all languages we're translating into (except the language of the text), +publish a message to ther *translate* topic. +1. For the language that matches the language of the text, bypass translation +and publish to the *save* topic. +1. Cloud Function is triggered and uses the Translate API to translate the +message into various languages, then publishes each translation to the *save* +topic. +1. Cloud Function is triggered and saves text to Cloud Storage. +1. Translated text from the original source image is downloaded. + +## Deploy and Test + +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. + +1. Enable the [Vision API][enable_vision] and the [Translate API][enable_translate]. + +1. Clone this repository: + + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/ocr + +1. [Create a Google Translate API Key][translate_api_key]. + +1. Create a file in the `app` folder called `config.json` with the following +contents: + + { + "TRANSLATE_API_KEY": "[YOUR_API_KEY]", + "RESULT_TOPIC": "[RESULT_TOPIC_NAME]", + "RESULT_BUCKET": "[RESULT_BUCKET_NAME]", + "TRANSLATE_TOPIC": "[TRANSLATE_TOPIC_NAME]", + "TRANSLATE": true, + "TO_LANG": ["en", "fr", "es", "ja", "ru"] + } + + * Replace `[YOUR_API_KEY]` with your Translate API key. + * Replace `[RESULT_TOPIC_NAME]` with a topic name used for saving results. + * Replace `[RESULT_BUCKET_NAME]` with a bucket name used for saving results. + * Replace `[TRANSLATE_TOPIC_NAME]` with a topic name used for translating results. + +1. Deploy the `processImage` function with a GCS trigger: + + gcloud alpha functions deploy ocr-extract --bucket [YOUR_BUCKET_NAME] --trigger-gs-uri [YOUR_UPLOAD_BUCKET_NAME] --source app --entry-point processImage + + * Replace `[YOUR_BUCKET_NAME]` with the name of your staging Cloud Storage Bucket. + * Replace `[YOUR_UPLOAD_BUCKET_NAME]` with the name of your Cloud Storage Bucket where you will be uploading images. + +1. Deploy the `translateText` function with a Pub/Sub trigger: + + gcloud alpha functions deploy ocr-translate --bucket [YOUR_BUCKET_NAME] --trigger-topic [YOUR_TRANSLATE_TOPIC_NAME] --source app --entry-point translateText + + * Replace `[YOUR_BUCKET_NAME]` with the name of your staging Cloud Storage Bucket. + * Replace `[YOUR_TRANSLATE_TOPIC_NAME]` with the name of your Pub/Sub topic with which translations will be triggered. + +1. Deploy the `saveResult` function with a Pub/Sub trigger: + + gcloud alpha functions deploy ocr-save --bucket [YOUR_BUCKET_NAME] --trigger-topic [YOUR_RESULT_TOPIC_NAME] --source app --entry-point saveResult + + * Replace `[YOUR_BUCKET_NAME]` with the name of your staging Cloud Storage Bucket. + * Replace `[YOUR_RESULT_TOPIC_NAME]` with the name of your Pub/Sub topic with which saving of results will be triggered. + +1. Upload a sample image: + + gsutil cp images/menu.jpg gs://[YOUR_UPLOAD_BUCKET_NAME] + + * Replace `[YOUR_UPLOAD_BUCKET_NAME]` with the name of your Cloud Storage Bucket where you will be uploading images. + +1. Watch the logs to make sure the executions have completed + + gcloud alpha functions get-logs --limit 100 + +1. Pull the extracted text from the bucket and pipe to standard out + + gsutil cat gs://[YOUR_RESULT_BUCKET_NAME]/menu_to_en.txt + + * Replace `[YOUR_RESULT_BUCKET_NAME]` with the name of your Cloud Storage Bucket where translated text will be saved. + +[quickstart]: https://cloud.google.com/functions/quickstart +[enable_vision]: https://console.cloud.google.com/flows/enableapi?apiid=vision.googleapis.com&redirect=https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/functions/ocr +[enable_translate]: https://console.cloud.google.com/flows/enableapi?apiid=translate&redirect=https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/functions/ocr +[translate_api_key]: https://cloud.google.com/translate/v2/translating-text-with-rest#auth + diff --git a/functions/ocr/app/config.default.json b/functions/ocr/app/config.default.json new file mode 100644 index 0000000000..9766ceb121 --- /dev/null +++ b/functions/ocr/app/config.default.json @@ -0,0 +1,8 @@ +{ + "TRANSLATE_API_KEY": "[YOUR_API_KEY]", + "RESULT_TOPIC": "[RESULT_TOPIC_NAME]", + "RESULT_BUCKET": "[RESULT_BUCKET_NAME]", + "TRANSLATE_TOPIC": "[TRANSLATE_TOPIC_NAME]", + "TRANSLATE": true, + "TO_LANG": ["en", "fr", "es", "ja", "ru"] +} \ No newline at end of file diff --git a/functions/ocr/app/index.js b/functions/ocr/app/index.js new file mode 100644 index 0000000000..409b74ad4d --- /dev/null +++ b/functions/ocr/app/index.js @@ -0,0 +1,276 @@ +// 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 ocr_setup] +var async = require('async'); +var config = require('./config.json'); +var gcloud = require('gcloud'); + +// Get a reference to the Pub/Sub component +var pubsub = gcloud.pubsub(); +// Get a reference to the Cloud Storage component +var storage = gcloud.storage(); +// Get a reference to the Cloud Vision API component +var vision = gcloud.vision(); +// Get a reference to the Translate API component +var translate = gcloud.translate({ + key: config.TRANSLATE_API_KEY +}); +// [END ocr_setup] + +// [START ocr_publish] +/** + * Publishes the result to the given pubsub topic and returns a Promise. + * + * @param {string} topicName Name of the topic on which to publish. + * @param {Object} data The data to publish. + * @param {Function} callback Callback function. + */ +function publishResult (topicName, data, callback) { + return pubsub.topic(topicName).get({ + autoCreate: true + }, function (err, topic) { + if (err) { + return callback(err); + } + // Pub/Sub messages must be valid JSON objects with a data property. + return topic.publish({ + data: data + }, callback); + }); +} +// [END ocr_publish] + +// [START ocr_detect] +/** + * Detects the text in an image using the Google Vision API. + * + * @param {string} filename Name of the file to scan. + * @param {Object} image Cloud Storage File instance. + */ +function detectText (filename, image, callback) { + var text; + + return async.waterfall([ + // Read the text from the image. + function (cb) { + console.log('Looking for text in file ' + filename); + vision.detectText(image, cb); + }, + // Detect the language to avoid unnecessary translations + function (result, apiResponse, cb) { + text = result[0]; + console.log('Extracted text from image (' + text.length + ' chars)'); + translate.detect(text, cb); + }, + // Publish results + function (result, cb) { + console.log('Detected language "' + result.language + '" for ' + filename); + // Submit a message to the bus for each language we're going to translate to + var tasks = config.TO_LANG.map(function (lang) { + var topicName = config.TRANSLATE_TOPIC; + if (result.language === lang) { + topicName = config.RESULT_TOPIC; + } + var payload = { + text: text, + filename: filename, + lang: lang, + from: result.language + }; + return function (cb) { + publishResult(topicName, payload, cb); + }; + }); + async.parallel(tasks, cb); + } + ], callback); +} +// [END ocr_detect] + +// [START ocr_rename] +/** + * Appends a .txt suffix to the image name. + * + * @param {string} filename Name of a file. + * @param {string} lang Language to append. + * @returns {string} The new filename. + */ +function renameImageForSave (filename, lang) { + var dotIndex = filename.indexOf('.'); + var suffix = '_to_' + lang + '.txt'; + if (dotIndex !== -1) { + filename = filename.replace(/\.[^/.]+$/, suffix); + } else { + filename += suffix; + } + return filename; +} +// [END ocr_rename] + +// [START ocr_process] +/** + * Cloud Function triggered by Cloud Storage when a file is uploaded. + * + * @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 Cloud Storage. + * @param {string} data.bucket Name of the Cloud Storage bucket. + * @param {string} data.name Name of the file. + * @param {string} [data.timeDeleted] Time the file was deleted if this is a deletion event. + * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource + */ +exports.processImage = function processImage (context, data) { + try { + if (data.hasOwnProperty('timeDeleted')) { + // This was a deletion event, we don't want to process this + return context.done(); + } + + if (!data.bucket) { + throw new Error('Bucket not provided. Make sure you have a ' + + '"bucket" property in your request'); + } + if (!data.name) { + throw new Error('Filename not provided. Make sure you have a ' + + '"name" property in your request'); + } + + var bucket = storage.bucket(data.bucket); + var file = bucket.file(data.name); + detectText(data.name, file, function (err) { + if (err) { + console.error(err); + return context.failure(err); + } + console.log('Processed ' + data.name); + return context.success(); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +}; +// [END ocr_process] + +// [START ocr_translate] +/** + * Translates text using the Google Translate API. 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.text Text to be translated. + * @param {Object} data.filename Name of the filename that contained the text. + * @param {Object} data.lang Language to translate to. + */ +exports.translateText = function translateText (context, data) { + try { + if (!data.text) { + throw new Error('Text not provided. Make sure you have a ' + + '"text" property in your request'); + } + if (!data.filename) { + throw new Error('Filename not provided. Make sure you have a ' + + '"filename" property in your request'); + } + if (!data.lang) { + throw new Error('Language not provided. Make sure you have a ' + + '"lang" property in your request'); + } + + console.log('Translating text into ' + data.lang); + return translate.translate(data.text, { + from: data.from, + to: data.lang + }, function (err, translation) { + if (err) { + console.error(err); + return context.failure(err); + } + + return publishResult(config.RESULT_TOPIC, { + text: translation, + filename: data.filename, + lang: data.lang + }, function (err) { + if (err) { + console.error(err); + return context.failure(err); + } + console.log('Text translated to ' + data.lang); + return context.success(); + }); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +}; +// [END ocr_translate] + +// [START ocr_save] +/** + * Saves the data packet to a file in GCS. 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.text Text to save. + * @param {Object} data.filename Name of the filename that contained the text. + * @param {Object} data.lang Language of the text. + */ +exports.saveResult = function saveResult (context, data) { + try { + if (!data.text) { + throw new Error('Text not provided. Make sure you have a ' + + '"text" property in your request'); + } + if (!data.filename) { + throw new Error('Filename not provided. Make sure you have a ' + + '"filename" property in your request'); + } + if (!data.lang) { + throw new Error('Language not provided. Make sure you have a ' + + '"lang" property in your request'); + } + + console.log('Received request to save file ' + data.filename); + + var bucketName = config.RESULT_BUCKET; + var filename = renameImageForSave(data.filename, data.lang); + var file = storage.bucket(bucketName).file(filename); + + console.log('Saving result to ' + filename + ' in bucket ' + bucketName); + + file.save(data.text, function (err) { + if (err) { + console.error(err); + return context.failure(err); + } + console.log('Text written to ' + filename); + return context.success(); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +}; +// [END ocr_save] diff --git a/functions/ocr/app/package.json b/functions/ocr/app/package.json new file mode 100644 index 0000000000..eee690afdd --- /dev/null +++ b/functions/ocr/app/package.json @@ -0,0 +1,16 @@ +{ + "name": "nodejs-docs-samples-functions-ocr", + "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": { + "async": "^1.5.2", + "gcloud": "^0.35.0" + } +} diff --git a/functions/ocr/images/menu.jpg b/functions/ocr/images/menu.jpg new file mode 100644 index 0000000000..e53d61222e Binary files /dev/null and b/functions/ocr/images/menu.jpg differ diff --git a/functions/ocr/images/sign.png b/functions/ocr/images/sign.png new file mode 100644 index 0000000000..588320438c Binary files /dev/null and b/functions/ocr/images/sign.png differ diff --git a/functions/ocr/readme.png b/functions/ocr/readme.png new file mode 100644 index 0000000000..20f05abb94 Binary files /dev/null and b/functions/ocr/readme.png differ diff --git a/functions/pubsub/README.md b/functions/pubsub/README.md index 276f7b07bc..034b6b4623 100644 --- a/functions/pubsub/README.md +++ b/functions/pubsub/README.md @@ -11,8 +11,8 @@ View the [source code][code]. ## Deploy and Test -1. Follow the [Cloud Functions quickstart guide](https://cloud.google.com/functions/quickstart) -to setup Cloud Functions for your project. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. 1. Clone this repository: @@ -22,31 +22,43 @@ to setup Cloud Functions for your project. 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 + gcloud alpha pubsub topics create [YOUR_TOPIC_NAME] + + * Replace `[YOUR_TOPIC_NAME]` with the name of your Pub/Sub Topic. 1. Create a Cloud Storage Bucket to stage our deployment: - gsutil mb gs:// + gsutil mb gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Deploy the `publish` function with an HTTP trigger: + + gcloud alpha functions deploy publish --bucket [YOUR_BUCKET_NAME] --trigger-http -1. Deploy the "publish" function with an HTTP trigger + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. - gcloud alpha functions deploy publish --bucket --trigger-http +1. Deploy the `subscribe` function with the Pub/Sub topic as a trigger: -1. Deploy the "subscribe" function with the Pub/Sub topic as a trigger + gcloud alpha functions deploy subscribe --bucket [YOUR_BUCKET_NAME] --trigger-topic [YOUR_TOPIC_NAME] - gcloud alpha functions deploy subscribe --bucket --trigger-topic + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + * Replace `[YOUR_TOPIC_NAME]` with the name of your Pub/Sub Topic. -1. Call the "publish" function: +1. Call the `publish` function: - gcloud alpha functions call publish --data '{"topic":"","message":"Hello World!"}' + gcloud alpha functions call publish --data '{"topic":"[YOUR_TOPIC_NAME]","message":"Hello World!"}' -1. Check the logs for the "subscribe" function: + * Replace `[YOUR_TOPIC_NAME]` with the name of your Pub/Sub Topic. + +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 -``` + 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 + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/sendgrid/README.md b/functions/sendgrid/README.md new file mode 100644 index 0000000000..45a2bfce7a --- /dev/null +++ b/functions/sendgrid/README.md @@ -0,0 +1,65 @@ +Google Cloud Platform logo + +# Google Cloud Functions SendGrid sample + +This recipe shows you how to send an email from a Cloud Function using SendGrid. + +View the [source code][code]. + +[code]: index.js + +## Deploy and Test + +1. Follow the [Cloud Functions quickstart guide][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/sendgrid + +1. Create a Sendgrid account. You can either do this manually via the [SendGrid website](https://sendgrid.com/free), +or you can use the [Google Cloud Launcher](https://cloud.google.com/launcher) +which will create an account for you and integrate billing. + + See [Creating a SendGrid account using Cloud Launcher](https://cloud.google.com/launcher/solution/sendgrid-app/sendgrid-email). + +1. Create a SendGrid API key: + + 1. Log in to your SendGrid account at [https://app.sendgrid.com](https://app.sendgrid.com). + 1. Navigate to "Settings" => "API Keys". + 1. Create a new "General API Key". + 1. Ensure you select (at least) the "Mail Send" permission when you create the API key. + 1. Copy the API Key when it is displayed (you will only see this once, make sure you paste it somewhere!). + +1. Create a Cloud Storage Bucket to stage our deployment: + + gsutil mb gs://[YOUR_BUCKET_NAME] + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Deploy the `sendEmail` function with an HTTP trigger: + + gcloud alpha functions deploy sendEmail --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `sendEmail` function: + + gcloud alpha functions call sendEmail --data '{"sg_key":"[YOUR_SENDGRID_KEY]","to":"[YOUR_RECIPIENT_ADDR]","from":"[YOUR_SENDER_ADDR]","subject":"Hello from Sendgrid!","body":"Hello World!"}' + + * Replace `[YOUR_SENDGRID_KEY]` with your SendGrid API KEY. + * Replace `[YOUR_RECIPIENT_ADDR]` with the recipient's email address. + * Replace `[YOUR_SENDER_ADDR]` with your SendGrid account's email address. + +1. Check the logs for the `subscribe` function: + + gcloud alpha functions get-logs sendEmail + + You should see something like this in your console: + + D ... User function triggered, starting execution + I ... Sending email to: [YOUR_RECIPIENT_ADDR] + D ... Execution took 1 ms, user function completed successfully + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/functions/sendgrid/index.js b/functions/sendgrid/index.js new file mode 100644 index 0000000000..7ed8908e35 --- /dev/null +++ b/functions/sendgrid/index.js @@ -0,0 +1,113 @@ +// 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 sendgrid = require('sendgrid'); + +/** + * Returns a configured SendGrid client. + * + * @param {Object} requestData Cloud Function request data. + * @param {string} data.sg_key Your SendGrid API key. + * @returns {Object} SendGrid client. + */ +function getClient (requestData) { + if (!requestData.sg_key) { + throw new Error('SendGrid API key not provided. Make sure you have a ' + + '"sg_key" property in your request'); + } + + // Using SendGrid's Node.js Library https://github.com/sendgrid/sendgrid-nodejs + return sendgrid.SendGrid(requestData.sg_key); +} + +/** + * Constructs the payload object from the request data. + * + * @param {Object} requestData Cloud Function request data. + * @param {string} data.to Email address of the recipient. + * @param {string} data.from Email address of the sender. + * @param {string} data.subject Email subject line. + * @param {string} data.body Body of the email subject line. + * @returns {Object} Payload object. + */ +function getPayload (requestData) { + if (!requestData.to) { + throw new Error('To email address not provided. Make sure you have a ' + + '"to" property in your request'); + } + + if (!requestData.from) { + throw new Error('From email address not provided. Make sure you have a ' + + '"from" property in your request'); + } + + if (!requestData.subject) { + throw new Error('Email subject line not provided. Make sure you have a ' + + '"subject" property in your request'); + } + + if (!requestData.body) { + throw new Error('Email content not provided. Make sure you have a ' + + '"body" property in your request'); + } + + var helper = sendgrid.mail; + return new helper.Mail( + new helper.Email(requestData.from), + requestData.subject, + new helper.Email(requestData.to), + new helper.Content('text/plain', requestData.body) + ); +} + +/** + * Send an email using SendGrid. + * + * @example + * gcloud alpha functions call sendEmail --data '{"sg_key":"[YOUR_SENDGRID_KEY]","to":"[YOUR_RECIPIENT_ADDR]","from":"[YOUR_SENDER_ADDR]","subject":"Hello from Sendgrid!","body":"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.to Email address of the recipient. + * @param {string} data.from Email address of the sender. + * @param {string} data.sg_key Your SendGrid API key. + * @param {string} data.subject Email subject line. + * @param {string} data.body Body of the email subject line. + */ +exports.sendEmail = function sendEmail (context, data) { + try { + var client = getClient(data); + var mail = getPayload(data); + + var requestBody = mail.toJSON(); + console.log('Sending email...'); + var request = client.emptyRequest(); + request.method = 'POST'; + request.path = '/v3/mail/send'; + request.body = requestBody; + client.API(request, function (response) { + if (response.statusCode >= 200 && response.statusCode < 400) { + return context.success('Email sent!'); + } + console.error(response); + return context.failure('Failed to send email'); + }); + } catch (err) { + console.error(err); + return context.failure(err.message); + } +}; diff --git a/functions/sendgrid/package.json b/functions/sendgrid/package.json new file mode 100644 index 0000000000..f9aecec361 --- /dev/null +++ b/functions/sendgrid/package.json @@ -0,0 +1,15 @@ +{ + "name": "nodejs-docs-samples-functions-sendgrid", + "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": { + "sendgrid": "^3.0.5" + } +} diff --git a/functions/uuid/README.md b/functions/uuid/README.md index e39fc85c5e..4c62270131 100644 --- a/functions/uuid/README.md +++ b/functions/uuid/README.md @@ -10,18 +10,35 @@ View the [documentation][docs] or the [source code][code]. [docs]: https://cloud.google.com/functions/writing [code]: index.js -## Deploy +## Deploy and Test -This example deploys the function with an HTTP trigger. +1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud +Functions for your project. - gcloud alpha functions deploy uuid --bucket --trigger-http +1. Clone this repository: -## Test + git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git + cd nodejs-docs-samples/functions/uuid - gcloud alpha functions call uuid +1. Create a Cloud Storage Bucket to stage our deployment: -Running the above command should print a generated UUID. + gsutil mb gs://[YOUR_BUCKET_NAME] -You can also use `curl` to trigger the function: + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. - curl -X POST https://..cloudfunctions.net/uuid +1. Deploy the `uuid` function with an HTTP trigger: + + gcloud alpha functions deploy uuid --bucket [YOUR_BUCKET_NAME] --trigger-http + + * Replace `[YOUR_BUCKET_NAME]` with the name of your Cloud Storage Bucket. + +1. Call the `uuid` function: + + gcloud alpha functions call uuid + + You should see something like this in your console: + + executionId: abcd1234-0 + result: 0ef22088-07f2-44ca-9cef-cea8ff666d69 + +[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/test/computeengine/mailjet.test.js b/test/computeengine/mailjet.test.js index 048940da1b..5a84b87ab6 100644 --- a/test/computeengine/mailjet.test.js +++ b/test/computeengine/mailjet.test.js @@ -39,7 +39,8 @@ test.cb('should send an email', function (t) { }, 'nodemailer-smtp-transport': function (options) { t.deepEqual(options, { - service: 'Mailjet', + host: 'in.mailjet.com', + port: 2525, auth: { user: 'foo', pass: 'bar' diff --git a/test/functions/ocr.test.js b/test/functions/ocr.test.js new file mode 100644 index 0000000000..2dffb9f411 --- /dev/null +++ b/test/functions/ocr.test.js @@ -0,0 +1,446 @@ +// 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 bucket = 'bucket'; +var name = 'name'; +var text = 'text'; +var filename = 'filename'; +var lang = 'lang'; +var translation = 'translation'; + +function getSample () { + var config = { + TRANSLATE_API_KEY: 'key', + RESULT_TOPIC: 'result-topic', + RESULT_BUCKET: 'result-bucket', + TRANSLATE_TOPIC: 'translate-topic', + TRANSLATE: true, + TO_LANG: ['en', 'fr', 'es', 'ja', 'ru'] + }; + var topic = { + publish: sinon.stub().callsArg(1) + }; + topic.get = sinon.stub().callsArgWith(1, null, topic); + var file = { + save: sinon.stub().callsArg(1) + }; + var bucket = { + file: sinon.stub().returns(file) + }; + var pubsub = { + topic: sinon.stub().returns(topic) + }; + var storage = { + bucket: sinon.stub().returns(bucket) + }; + var vision = { + detectText: sinon.stub().callsArg(1) + }; + var translate = { + detect: sinon.stub().callsArg(1) + }; + var gcloud = { + pubsub: sinon.stub().returns(pubsub), + storage: sinon.stub().returns(storage), + vision: sinon.stub().returns(vision), + translate: sinon.stub().returns(translate) + }; + return { + sample: proxyquire('../../functions/ocr/app', { + gcloud: gcloud, + './config.json': config + }), + mocks: { + gcloud: gcloud, + pubsub: pubsub, + storage: storage, + bucket: bucket, + file: file, + vision: vision, + translate: translate, + topic: topic + } + }; +} + +function getMockContext () { + return { + done: sinon.stub(), + success: sinon.stub(), + failure: sinon.stub() + }; +} + +test.before(function () { + sinon.stub(console, 'error'); + sinon.stub(console, 'log'); +}); + +test('processImage does nothing on delete', function (t) { + var context = getMockContext(); + + getSample().sample.processImage(context, { + timeDeleted: 1234 + }); + + t.is(context.done.calledOnce, true); + t.is(context.failure.called, false); + t.is(context.success.called, false); +}); + +test('processImage 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.processImage(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('processImage fails without a name', function (t) { + var expectedMsg = 'Filename not provided. Make sure you have a ' + + '"name" property in your request'; + var context = getMockContext(); + + getSample().sample.processImage(context, { + bucket: bucket + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test.cb('processImage handles detectText error', function (t) { + var expectedMsg = 'error'; + var context = { + success: t.fail, + failure: function () { + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.calledWith(expectedMsg), true); + t.end(); + } + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.vision.detectText = sinon.stub().callsArgWith(1, expectedMsg); + ocrSample.sample.processImage(context, { + bucket: bucket, + name: name + }); +}); + +test.cb('processImage processes an image', function (t) { + var context = { + success: function () { + t.is(context.success.calledOnce, true); + t.is(context.failure.called, false); + t.is(console.log.calledWith('Processed ' + name), true); + t.end(); + }, + failure: t.fail + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.vision.detectText = sinon.stub().callsArgWith(1, null, [ + text + ], {}); + ocrSample.mocks.translate.detect = sinon.stub().callsArgWith(1, null, { + language: 'ja' + }); + ocrSample.sample.processImage(context, { + bucket: bucket, + name: name + }); +}); + +test('translateText fails without text', function (t) { + var expectedMsg = 'Text not provided. Make sure you have a ' + + '"text" property in your request'; + var context = getMockContext(); + + getSample().sample.translateText(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('translateText fails without a filename', function (t) { + var expectedMsg = 'Filename not provided. Make sure you have a ' + + '"filename" property in your request'; + var context = getMockContext(); + + getSample().sample.translateText(context, { + text: text + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('translateText fails without a lang', function (t) { + var expectedMsg = 'Language not provided. Make sure you have a ' + + '"lang" property in your request'; + var context = getMockContext(); + + getSample().sample.translateText(context, { + text: text, + filename: filename + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test.cb('translateText handles translation error', function (t) { + var expectedMsg = 'error'; + var context = { + success: t.fail, + failure: function () { + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.calledWith(expectedMsg), true); + t.end(); + } + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.translate.translate = sinon.stub().callsArgWith(2, expectedMsg); + ocrSample.sample.translateText(context, { + text: text, + filename: filename, + lang: lang + }); +}); + +test.cb('translateText handles get topic error', function (t) { + var expectedMsg = 'error'; + var context = { + success: t.fail, + failure: function () { + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.calledWith(expectedMsg), true); + t.end(); + } + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.translate.translate = sinon.stub().callsArgWith(2, null, translation); + ocrSample.mocks.topic.get = sinon.stub().callsArgWith(1, expectedMsg); + ocrSample.sample.translateText(context, { + text: text, + filename: filename, + lang: lang + }); +}); + +test.cb('translateText handles publish error', function (t) { + var expectedMsg = 'error'; + var context = { + success: t.fail, + failure: function () { + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.calledWith(expectedMsg), true); + t.end(); + } + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.translate.translate = sinon.stub().callsArgWith(2, null, translation); + ocrSample.mocks.topic.publish = sinon.stub().callsArgWith(1, expectedMsg); + ocrSample.sample.translateText(context, { + text: text, + filename: filename, + lang: lang + }); +}); + +test.cb('translateText translates and publishes text', function (t) { + var context = { + success: function () { + t.is(context.success.called, true); + t.is(context.failure.called, false); + t.is(console.log.calledWith('Text translated to ' + lang), true); + t.end(); + }, + failure: t.fail + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.translate.translate = sinon.stub().callsArgWith(2, null, translation); + ocrSample.sample.translateText(context, { + text: text, + filename: filename, + lang: lang + }); +}); + +test('saveResult fails without text', function (t) { + var expectedMsg = 'Text not provided. Make sure you have a ' + + '"text" property in your request'; + var context = getMockContext(); + + getSample().sample.saveResult(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('saveResult fails without a filename', function (t) { + var expectedMsg = 'Filename not provided. Make sure you have a ' + + '"filename" property in your request'; + var context = getMockContext(); + + getSample().sample.saveResult(context, { + text: text + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('saveResult fails without a lang', function (t) { + var expectedMsg = 'Language not provided. Make sure you have a ' + + '"lang" property in your request'; + var context = getMockContext(); + + getSample().sample.saveResult(context, { + text: text, + filename: filename + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test.cb('saveResult handles save error', function (t) { + var expectedMsg = 'error'; + var context = { + success: t.fail, + failure: function () { + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.calledWith(expectedMsg), true); + t.end(); + } + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.mocks.file.save = sinon.stub().callsArgWith(1, expectedMsg); + ocrSample.sample.saveResult(context, { + text: text, + filename: filename, + lang: lang + }); +}); + +test.cb('saveResult translates and publishes text', function (t) { + var context = { + success: function () { + t.is(context.success.called, true); + t.is(context.failure.called, false); + t.is(console.log.calledWith('Text written to ' + filename + '_to_lang.txt'), true); + t.end(); + }, + failure: t.fail + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.sample.saveResult(context, { + text: text, + filename: filename, + lang: lang + }); +}); + +test.cb('saveResult translates and publishes text with dot in filename', function (t) { + var context = { + success: function () { + t.is(context.success.called, true); + t.is(context.failure.called, false); + t.is(console.log.calledWith('Text written to ' + filename + '_to_lang.txt'), true); + t.end(); + }, + failure: t.fail + }; + + sinon.spy(context, 'success'); + sinon.spy(context, 'failure'); + + var ocrSample = getSample(); + ocrSample.sample.saveResult(context, { + text: text, + filename: filename + '.jpg', + lang: lang + }); +}); + +test.after(function () { + console.error.restore(); + console.log.restore(); +}); diff --git a/test/functions/sendgrid.test.js b/test/functions/sendgrid.test.js new file mode 100644 index 0000000000..b1a1313fcc --- /dev/null +++ b/test/functions/sendgrid.test.js @@ -0,0 +1,210 @@ +// 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 = 'sengrid_key'; +var to = 'receiver@email.com'; +var from = 'sender@email.com'; +var subject = 'subject'; +var body = 'body'; + +function getSample () { + var request = {}; + var client = { + API: sinon.stub().callsArgWith(1, { + statusCode: 200 + }), + emptyRequest: sinon.stub().returns(request) + }; + var mail = { + toJSON: sinon.stub() + }; + var sendgrid = { + SendGrid: sinon.stub().returns(client), + mail: { + Mail: sinon.stub().returns(mail), + Email: sinon.stub(), + Content: sinon.stub() + } + }; + return { + sample: proxyquire('../../functions/sendgrid', { + sendgrid: sendgrid + }), + mocks: { + sendgrid: sendgrid, + client: client, + mail: mail, + request: request + } + }; +} + +function getMockContext () { + return { + success: sinon.stub(), + failure: sinon.stub() + }; +} + +test.beforeEach(function () { + sinon.stub(console, 'error'); + sinon.stub(console, 'log'); +}); + +test('Send fails without an API key', function (t) { + var expectedMsg = 'SendGrid API key not provided. Make sure you have a ' + + '"sg_key" property in your request'; + var context = getMockContext(); + + getSample().sample.sendEmail(context, {}); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('Send fails without a "to"', function (t) { + var expectedMsg = 'To email address not provided. Make sure you have a ' + + '"to" property in your request'; + var context = getMockContext(); + + getSample().sample.sendEmail(context, { + sg_key: key + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('Send fails without a "from"', function (t) { + var expectedMsg = 'From email address not provided. Make sure you have a ' + + '"from" property in your request'; + var context = getMockContext(); + + getSample().sample.sendEmail(context, { + sg_key: key, + to: to + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('Send fails without a "subject"', function (t) { + var expectedMsg = 'Email subject line not provided. Make sure you have a ' + + '"subject" property in your request'; + var context = getMockContext(); + + getSample().sample.sendEmail(context, { + sg_key: key, + to: to, + from: from + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('Send fails without a "body"', function (t) { + var expectedMsg = 'Email content not provided. Make sure you have a ' + + '"body" property in your request'; + var context = getMockContext(); + + getSample().sample.sendEmail(context, { + sg_key: key, + to: to, + from: from, + subject: subject + }); + + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(context.success.called, false); + t.is(console.error.called, true); +}); + +test('Sends the email and calls success', function (t) { + var expectedMsg = 'Email sent!'; + var data = { + sg_key: key, + to: to, + from: from, + subject: subject, + body: body + }; + var payload = { + method: 'POST', + path: '/v3/mail/send', + body: null + }; + var context = getMockContext(); + + var sendgridSample = getSample(); + sendgridSample.sample.sendEmail(context, data); + + t.is(context.success.calledOnce, true); + t.is(context.success.firstCall.args[0], expectedMsg); + t.is(context.failure.called, false); + t.is(sendgridSample.mocks.client.API.calledOnce, true); + t.deepEqual(sendgridSample.mocks.client.API.firstCall.args[0], payload); + t.is(console.error.called, false); +}); + +test('Fails to send the email and calls failure', function (t) { + var expectedMsg = 'Failed to send email'; + var data = { + sg_key: key, + to: to, + from: from, + subject: subject, + body: body + }; + var payload = { + method: 'POST', + path: '/v3/mail/send', + body: null + }; + var context = getMockContext(); + + var sendgridSample = getSample(); + sendgridSample.mocks.client.API = sinon.stub().callsArgWith(1, { + statusCode: 400 + }); + + sendgridSample.sample.sendEmail(context, data); + + t.is(context.success.called, false); + t.is(context.failure.calledOnce, true); + t.is(context.failure.firstCall.args[0], expectedMsg); + t.is(sendgridSample.mocks.client.API.calledOnce, true); + t.deepEqual(sendgridSample.mocks.client.API.firstCall.args[0], payload); + t.is(console.error.called, true); +}); + +test.afterEach(function () { + console.error.restore(); + console.log.restore(); +});