From ed745a3b0fade12045dab135b359085866bc7ee6 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Tue, 24 Apr 2018 16:50:26 -0700 Subject: [PATCH 01/17] Add inline samples --- functions/helloworld/.gcloudignore | 16 ++++ functions/helloworld/index.js | 41 ++++++++++ functions/http/index.js | 72 +++++++++++++++++ functions/http/package.json | 1 + functions/tips/index.js | 124 +++++++++++++++++++++++++++++ functions/tips/package.json | 36 +++++++++ 6 files changed, 290 insertions(+) create mode 100644 functions/helloworld/.gcloudignore create mode 100644 functions/tips/index.js create mode 100644 functions/tips/package.json diff --git a/functions/helloworld/.gcloudignore b/functions/helloworld/.gcloudignore new file mode 100644 index 0000000000..ccc4eb240e --- /dev/null +++ b/functions/helloworld/.gcloudignore @@ -0,0 +1,16 @@ +# This file specifies files that are *not* uploaded to Google Cloud Platform +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +node_modules diff --git a/functions/helloworld/index.js b/functions/helloworld/index.js index 3eda309167..8aeda8654c 100644 --- a/functions/helloworld/index.js +++ b/functions/helloworld/index.js @@ -170,3 +170,44 @@ exports.helloTemplate = (req, res) => { res.send(html).end(); }; // [END functions_helloworld_template] + +/** + * HTTP Cloud Function that demonstrates + * how to catch errors of different types. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.helloErrorTypes = (req, res) => { + // [START functions_helloworld_error_object] + try { + // Throw an Error object (to simulate a GCP API failure) + throw new Error('Error object!'); + } catch (err) { + // err is already an Error object + console.error(err); + } + // [END functions_helloworld_error_object] + + /* eslint-disable no-throw-literal */ + // [START functions_helloworld_error_unknown] + try { + // Throw an unknown error type + if (Date.now() % 2 === 0) { + throw 'Error string!'; + } else { + throw new Error('Error object!'); + } + } catch (err) { + // Determine the error type + if (err instanceof Error) { + console.error(err); + } else { + console.error(new Error(err)); + } + } + // [END functions_helloworld_error_unknown] + /* eslint-enable no-throw-literal */ + + res.end(); +}; diff --git a/functions/http/index.js b/functions/http/index.js index 317994e015..db0c941bfa 100644 --- a/functions/http/index.js +++ b/functions/http/index.js @@ -105,3 +105,75 @@ exports.helloHttp = (req, res) => { } }; // [END functions_http_method] + +// [START functions_http_xml] +/** + * Parses a document of type 'text/xml' + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.parseXML = (req, res) => { + // Convert the request to a Buffer and a string + // Use whichever one is accepted by your XML parser + let data = req.rawBody; + let xmlData = data.toString(); + + const parseString = require('xml2js').parseString; + + parseString(xmlData, (err, result) => { + if (err) { + console.error(err); + res.status(500).end(); + return; + } + res.send(result); + }); +}; +// [END functions_http_xml] + +// [START functions_http_form_data] +/** + * Parses a 'multipart/form-data' upload request + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +const path = require('path'); +const os = require('os'); +const fs = require('fs'); +const Busboy = require('busboy'); + +exports.uploadFile = (req, res) => { + if (req.method === 'POST') { + const busboy = new Busboy({ headers: req.headers }); + + // This object will accumulate all the uploaded files, keyed by their name. + const uploads = {}; + const tmpdir = os.tmpdir(); + + // This callback will be invoked for each file uploaded. + busboy.on('file', (fieldname, file, filename) => { + // Note: os.tmpdir() - and any files in it - must fit in memory. + const filepath = path.join(tmpdir, filename); + uploads[fieldname] = filepath; + file.pipe(fs.createWriteStream(filepath)); + }); + + // This callback will be invoked after all uploaded files are saved. + busboy.on('finish', () => { + // TODO(developer): Process uploaded files here + for (const name in uploads) { + const file = uploads[name]; + fs.unlinkSync(file); + } + res.send(); + }); + + req.pipe(busboy); + } else { + // Ignore non-POST requests + res.status(405).send(); + } +}; +// [END functions_http_form_data] diff --git a/functions/http/package.json b/functions/http/package.json index 18638d347b..5a82ee97e2 100644 --- a/functions/http/package.json +++ b/functions/http/package.json @@ -23,6 +23,7 @@ "sinon": "4.4.2" }, "dependencies": { + "busboy": "^0.2.14", "safe-buffer": "5.1.1" }, "cloud-repo-tools": { diff --git a/functions/tips/index.js b/functions/tips/index.js new file mode 100644 index 0000000000..1e70c80745 --- /dev/null +++ b/functions/tips/index.js @@ -0,0 +1,124 @@ +/** + * Copyright 2016, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const Buffer = require('safe-buffer').Buffer; + +const lightComputation = () => { + const numbers = [1,2,3,4,5,6,7,8,9]; + return numbers.reduce((t, x) => t + x) +} + +const heavyComputation = () => { + // Multiplication is more computationally expensive than addition + const numbers = [1,2,3,4,5,6,7,8,9]; + return numbers.reduce((t, x) => t * x) +} + +const functionSpecificComputation = heavyComputation; +const fileWideComputation = lightweightComputation; + +// [START functions_scopes] +// Global (instance-wide) scope +// This computation runs at instance cold-start +const instanceVar = heavyComputation(); + +/** + * Sample HTTP Cloud Function. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.scopeDemo = (req, res) => { + // Per-function scope + // This computation runs every time this function is called + const functionVar = lightweightComputation(); + + res.end(`Per instance: ${instanceVar}, per function: ${functionVar}`); +} +// [END functions_scopes] + +// [START functions_lazy_globals] +// This value is always initialized, which happens at cold-start +const nonLazyGlobal = fileWideComputation(); +let lazyGlobal; + +/** + * Sample HTTP Cloud Function. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.lazyGlobals = (req, res) => { + // This value is initialized only if (and when) the function is called + lazyGlobal = lazyGlobal || functionSpecificComputation(); + + res.end(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`); +} +// [END functions_lazy_globals] + +// [START functions_ephemeral_connection] +// [START functions_cached_connection] +const http = require('http'); +// [END functions_ephemeral_connection] +const agent = new http.Agent({keepAlive: true}); +// [END functions_cached_connection] + +// TODO(ace-n) make sure this import works as intended +// [START functions_ephemeral_connection] +exports.ephemeralConnection = (req, res) => { + req = http.request({ + host: '', + port: 80, + path: '', + method: 'GET', + }, res => { + let rawData = ''; + res.setEncoding('utf8'); + res.on('data', chunk => { rawData += chunk; }); + res.on('end', () => { + response.status(200).send(`Data: ${rawData}`); + }); + }); + req.on('error', (e) => { + response.status(500).send(`Error: ${e.message}`); + }); + req.end(); +}; +// [END functions_ephemeral_connection] + +// [START functions_cached_connection] +exports.cachedConnection = (request, response) => { + req = http.request({ + host: '', + port: 80, + path: '', + method: 'GET', + agent: agent, + }, res => { + let rawData = ''; + res.setEncoding('utf8'); + res.on('data', chunk => { rawData += chunk; }); + res.on('end', () => { + response.status(200).send(`Data: ${rawData}`); + }); + }); + req.on('error', e => { + response.status(500).send(`Error: ${e.message}`); + }); + req.end(); +}; +// [END functions_cached_connection] diff --git a/functions/tips/package.json b/functions/tips/package.json new file mode 100644 index 0000000000..a3cfe24660 --- /dev/null +++ b/functions/tips/package.json @@ -0,0 +1,36 @@ +{ + "name": "nodejs-docs-samples-functions-tips", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "engines": { + "node": ">=6.14.0" + }, + "scripts": { + "lint": "repo-tools lint", + "pretest": "npm run lint", + }, + "dependencies": { + "safe-buffer": "5.1.1" + }, + "devDependencies": { + "@google-cloud/nodejs-repo-tools": "2.2.5", + "ava": "0.25.0", + }, + "cloud-repo-tools": { + "requiresKeyFile": true, + "requiresProjectId": true, + "requiredEnvVars": [ + "BASE_URL", + "GCF_REGION", + "TOPIC", + "BUCKET", + "FUNCTIONS_CMD" + ] + } +} From 3ec59a13c2dd276c161e2b909da76c269fd2e76b Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Tue, 24 Apr 2018 17:17:22 -0700 Subject: [PATCH 02/17] Add getSignedUrl sample --- functions/http/index.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/functions/http/index.js b/functions/http/index.js index db0c941bfa..2d806c6053 100644 --- a/functions/http/index.js +++ b/functions/http/index.js @@ -177,3 +177,42 @@ exports.uploadFile = (req, res) => { } }; // [END functions_http_form_data] + +// [START functions_http_signed_url] +const storage = require('@google-cloud/storage')(); + +/** + * HTTP function that generates a signed URL + * The signed URL can be used to upload files to Google Cloud Storage (GCS) + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.getSignedUrl = (req, res) => { + if(req.method === 'POST') { + // TODO(developer) check that the user is authorized to upload + + // Get a reference to the destination file in GCS + const file = storage.bucket('my-bucket').file(req.body.filename); + + // Create a temporary upload URL + const expiresAtMs = Date.now() + 300000; // Link expires in 5 minutes + const config = { + action: 'write', + expires: expiresAtMs, + contentType: req.body.contentType + }; + + file.getSignedUrl(config, function(err, url) { + if (err) { + console.error(err); + res.status(500).end(); + return; + } + res.send(url); + }); + } else { + res.status(405).end(); + } +} +// [END functions_http_signed_url] From 0f31a808d2176036b19f8f8df6dbf2a665457f05 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Tue, 24 Apr 2018 17:22:57 -0700 Subject: [PATCH 03/17] Update tips region tags --- functions/tips/index.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/functions/tips/index.js b/functions/tips/index.js index 1e70c80745..2015fe9a8c 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -31,7 +31,7 @@ const heavyComputation = () => { const functionSpecificComputation = heavyComputation; const fileWideComputation = lightweightComputation; -// [START functions_scopes] +// [START functions_tips_scopes] // Global (instance-wide) scope // This computation runs at instance cold-start const instanceVar = heavyComputation(); @@ -49,9 +49,9 @@ exports.scopeDemo = (req, res) => { res.end(`Per instance: ${instanceVar}, per function: ${functionVar}`); } -// [END functions_scopes] +// [END functions_tips_scopes] -// [START functions_lazy_globals] +// [START functions_tips_lazy_globals] // This value is always initialized, which happens at cold-start const nonLazyGlobal = fileWideComputation(); let lazyGlobal; @@ -68,17 +68,17 @@ exports.lazyGlobals = (req, res) => { res.end(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`); } -// [END functions_lazy_globals] +// [END functions_tips_lazy_globals] -// [START functions_ephemeral_connection] -// [START functions_cached_connection] +// [START functions_tips_ephemeral_connection] +// [START functions_tips_cached_connection] const http = require('http'); -// [END functions_ephemeral_connection] +// [END functions_tips_ephemeral_connection] const agent = new http.Agent({keepAlive: true}); -// [END functions_cached_connection] +// [END functions_tips_cached_connection] // TODO(ace-n) make sure this import works as intended -// [START functions_ephemeral_connection] +// [START functions_tips_ephemeral_connection] exports.ephemeralConnection = (req, res) => { req = http.request({ host: '', @@ -98,9 +98,9 @@ exports.ephemeralConnection = (req, res) => { }); req.end(); }; -// [END functions_ephemeral_connection] +// [END functions_tips_ephemeral_connection] -// [START functions_cached_connection] +// [START functions_tips_cached_connection] exports.cachedConnection = (request, response) => { req = http.request({ host: '', @@ -121,4 +121,4 @@ exports.cachedConnection = (request, response) => { }); req.end(); }; -// [END functions_cached_connection] +// [END functions_tips_cached_connection] From fa3bd7c0eaa5bcf6ed2a103f6b157f02bcdb45d1 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 25 Apr 2018 15:15:41 -0700 Subject: [PATCH 04/17] Add retry samples --- functions/tips/index.js | 150 ++++++++++++++++++++++++++++++---------- 1 file changed, 112 insertions(+), 38 deletions(-) diff --git a/functions/tips/index.js b/functions/tips/index.js index 2015fe9a8c..88f690bff3 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -15,21 +15,19 @@ 'use strict'; -const Buffer = require('safe-buffer').Buffer; - const lightComputation = () => { - const numbers = [1,2,3,4,5,6,7,8,9]; - return numbers.reduce((t, x) => t + x) -} + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + return numbers.reduce((t, x) => t + x); +}; const heavyComputation = () => { // Multiplication is more computationally expensive than addition - const numbers = [1,2,3,4,5,6,7,8,9]; - return numbers.reduce((t, x) => t * x) -} + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + return numbers.reduce((t, x) => t * x); +}; const functionSpecificComputation = heavyComputation; -const fileWideComputation = lightweightComputation; +const fileWideComputation = lightComputation; // [START functions_tips_scopes] // Global (instance-wide) scope @@ -43,12 +41,12 @@ const instanceVar = heavyComputation(); * @param {Object} res Cloud Function response context. */ exports.scopeDemo = (req, res) => { - // Per-function scope - // This computation runs every time this function is called - const functionVar = lightweightComputation(); + // Per-function scope + // This computation runs every time this function is called + const functionVar = lightComputation(); - res.end(`Per instance: ${instanceVar}, per function: ${functionVar}`); -} + res.end(`Per instance: ${instanceVar}, per function: ${functionVar}`); +}; // [END functions_tips_scopes] // [START functions_tips_lazy_globals] @@ -67,58 +65,134 @@ exports.lazyGlobals = (req, res) => { lazyGlobal = lazyGlobal || functionSpecificComputation(); res.end(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`); -} +}; // [END functions_tips_lazy_globals] -// [START functions_tips_ephemeral_connection] -// [START functions_tips_cached_connection] +// [START functions_tips_ephemeral_agent] +// [START functions_tips_cached_agent] const http = require('http'); -// [END functions_tips_ephemeral_connection] +// [END functions_tips_ephemeral_agent] const agent = new http.Agent({keepAlive: true}); // [END functions_tips_cached_connection] // TODO(ace-n) make sure this import works as intended -// [START functions_tips_ephemeral_connection] -exports.ephemeralConnection = (req, res) => { +// [START functions_tips_ephemeral_agent] +/** + * HTTP Cloud Function that uses an ephemeral HTTP agent + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.ephemeralAgent = (req, res) => { req = http.request({ host: '', port: 80, path: '', - method: 'GET', - }, res => { + method: 'GET' + }, resInner => { let rawData = ''; - res.setEncoding('utf8'); - res.on('data', chunk => { rawData += chunk; }); - res.on('end', () => { - response.status(200).send(`Data: ${rawData}`); + resInner.setEncoding('utf8'); + resInner.on('data', chunk => { rawData += chunk; }); + resInner.on('end', () => { + res.status(200).send(`Data: ${rawData}`); }); }); req.on('error', (e) => { - response.status(500).send(`Error: ${e.message}`); + res.status(500).send(`Error: ${e.message}`); }); req.end(); }; -// [END functions_tips_ephemeral_connection] +// [END functions_tips_ephemeral_agent] -// [START functions_tips_cached_connection] -exports.cachedConnection = (request, response) => { +// [START functions_tips_cached_agent] +/** + * HTTP Cloud Function that uses a cached HTTP agent + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.cachedAgent = (req, res) => { req = http.request({ host: '', port: 80, path: '', method: 'GET', - agent: agent, - }, res => { + agent: agent + }, resInner => { let rawData = ''; - res.setEncoding('utf8'); - res.on('data', chunk => { rawData += chunk; }); - res.on('end', () => { - response.status(200).send(`Data: ${rawData}`); + resInner.setEncoding('utf8'); + resInner.on('data', chunk => { rawData += chunk; }); + resInner.on('end', () => { + res.status(200).send(`Data: ${rawData}`); }); }); req.on('error', e => { - response.status(500).send(`Error: ${e.message}`); + res.status(500).send(`Error: ${e.message}`); }); req.end(); }; -// [END functions_tips_cached_connection] +// [END functions_tips_cached_agent] + +// [START functions_tips_infinite_retries] +/** + * Background Cloud Function that only executes within + * a certain time period after the triggering event + * + * @param {object} event The Cloud Functions event. + * @param {function} callback The callback function. + */ +exports.avoidInfiniteRetries = (event, callback) => { + const eventAge = Date.now() - Date.parse(event.timestamp); + const eventMaxAge = 10000; + + // Ignore events that are too old + if (eventAge > eventMaxAge) { + console.log(`Dropping event ${event} with age ${eventAge} ms.`); + callback(); + return; + } + + // Do what the function is supposed to do + console.log(`Processing event ${event} with age ${eventAge} ms.`); +}; +// [END functions_tips_infinite_retries] + +// [START functions_tips_retry_promise] +/** + * Background Cloud Function that demonstrates + * how to toggle retries using a callback + * + * @param {object} event The Cloud Functions event. + */ +exports.retryPromise = (event) => { + const tryAgain = false; + + if (tryAgain) { + throw new Error(`Retrying...`); + } else { + return Promise.reject(new Error('Not retrying...')); + } +}; +// [END functions_tips_retry_promise] + +// [START functions_tips_retry_callback] +/** + * Background Cloud Function that demonstrates + * how to toggle retries using a promise + * + * @param {object} event The Cloud Functions event. + * @param {function} callback The callback function. + */ +exports.retryCallback = (event, callback) => { + const tryAgain = false; + const err = new Error('Error!'); + + if (tryAgain) { + console.error(`Retrying: ${err}`); + callback(err); + } else { + console.error(`Not retrying: ${err}`); + callback(); + } +}; +// [END functions_tips_retry_callback] From 14e9ee6a21d58a71bef2346bae1e04c56eba5401 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 25 Apr 2018 15:19:36 -0700 Subject: [PATCH 05/17] Fix comments --- functions/tips/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/tips/index.js b/functions/tips/index.js index 88f690bff3..7bb63bbd84 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -160,7 +160,7 @@ exports.avoidInfiniteRetries = (event, callback) => { // [START functions_tips_retry_promise] /** * Background Cloud Function that demonstrates - * how to toggle retries using a callback + * how to toggle retries using a promise * * @param {object} event The Cloud Functions event. */ @@ -178,7 +178,7 @@ exports.retryPromise = (event) => { // [START functions_tips_retry_callback] /** * Background Cloud Function that demonstrates - * how to toggle retries using a promise + * how to toggle retries using a callback * * @param {object} event The Cloud Functions event. * @param {function} callback The callback function. From b023e7934586765516815b55208801790bed3ae3 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 25 Apr 2018 16:04:25 -0700 Subject: [PATCH 06/17] Add cached client library instance sample --- functions/tips/index.js | 26 ++++++++++++++++++++++++++ functions/tips/package.json | 5 +++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/functions/tips/index.js b/functions/tips/index.js index 7bb63bbd84..38575e5884 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -196,3 +196,29 @@ exports.retryCallback = (event, callback) => { } }; // [END functions_tips_retry_callback] + +// [START functions_tips_gcp_apis] +const Pubsub = require('@google-cloud/pubsub'); +const pubsub = Pubsub(); + +/** + * HTTP Cloud Function that uses a cached client library instance to + * reduce the number of connections required per function invocation. + * + * @param {Object} req Cloud Function request context. + * @param {Object} req.body Cloud Function request context body. + * @param {String} req.body.topic The Cloud Pub/Sub topic to publish to. + * @param {Object} res Cloud Function response context. + */ +exports.gcpApiCall = (req, res) => { + const topic = pubsub.topic(req.body.topic); + + topic.publish('Test message', err => { + if (err) { + res.status(500).send(`Error publishing the message: ${err}`); + } else { + res.status(200).send('1 message published'); + } + }); +}; +// [END functions_tips_gcp_apis] diff --git a/functions/tips/package.json b/functions/tips/package.json index a3cfe24660..5668e4a942 100644 --- a/functions/tips/package.json +++ b/functions/tips/package.json @@ -13,14 +13,15 @@ }, "scripts": { "lint": "repo-tools lint", - "pretest": "npm run lint", + "pretest": "npm run lint" }, "dependencies": { + "@google-cloud/pubsub": "^0.18.0", "safe-buffer": "5.1.1" }, "devDependencies": { "@google-cloud/nodejs-repo-tools": "2.2.5", - "ava": "0.25.0", + "ava": "0.25.0" }, "cloud-repo-tools": { "requiresKeyFile": true, From 5f2d2c148421abf80b7999ee1708a7359dd3efd0 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 25 Apr 2018 16:32:49 -0700 Subject: [PATCH 07/17] Move existing + add new conceptual samples --- functions/concepts/index.js | 96 +++++++++++++++++++++++++++++++++ functions/concepts/package.json | 23 ++++++++ 2 files changed, 119 insertions(+) create mode 100644 functions/concepts/index.js create mode 100644 functions/concepts/package.json diff --git a/functions/concepts/index.js b/functions/concepts/index.js new file mode 100644 index 0000000000..28cfc4d411 --- /dev/null +++ b/functions/concepts/index.js @@ -0,0 +1,96 @@ +/** + * 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'; + +/** + * HTTP Cloud Function that demonstrates + * how to catch errors of different types. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.errorTypes = (req, res) => { + // [START functions_concepts_error_object] + try { + // Throw an Error object (to simulate a GCP API failure) + throw new Error('Error object!'); + } catch (err) { + // err is already an Error object + console.error(err); + } + // [END functions_concepts_error_object] + + /* eslint-disable no-throw-literal */ + // [START functions_concepts_error_unknown] + try { + // Throw an unknown error type + if (Date.now() % 2 === 0) { + throw 'Error string!'; + } else { + throw new Error('Error object!'); + } + } catch (err) { + // Determine the error type + if (err instanceof Error) { + console.error(err); + } else { + console.error(new Error(err)); + } + } + // [END functions_concepts_error_unknown] + /* eslint-enable no-throw-literal */ + + res.end(); +}; + +// [START functions_concepts_stateless] +// Global variable, but only shared within function instance. +let count = 0; + +/** + * HTTP Cloud Function that counts how many times + * it is executed within a specific instance. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.executionCount = (req, res) => { + count++; + + // Note: the total function invocation count across + // all instances may not be equal to this value! + res.send(`Instance execution count: ${count}`); +}; +// [END functions_concepts_stateless] + +// [START functions_after_response] +exports.afterResponse = (req, res) => { + res.end(); + + // This statement may not execute + console.log('Function complete!'); +}; +// [END functions_after_response] + +// [START functions_after_timeout] +exports.afterTimeout = (req, res) => { + setTimeout(() => { + // May not execute if function's timeout is <2 minutes + console.log('Function running...'); + res.end(); + }, 120000); // 2 minute delay +}; +// [END functions_after_timeout] diff --git a/functions/concepts/package.json b/functions/concepts/package.json new file mode 100644 index 0000000000..a424b413cc --- /dev/null +++ b/functions/concepts/package.json @@ -0,0 +1,23 @@ +{ + "name": "nodejs-docs-samples-functions-concepts", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "engines": { + "node": ">=4.3.2" + }, + "scripts": { + "lint": "repo-tools lint" + }, + "dependencies": {}, + "devDependencies": { + "@google-cloud/nodejs-repo-tools": "2.2.5", + "ava": "0.25.0", + "supertest": "^3.0.0" + } +} From 925ed9a1defaefa789513db4f279e5a02fecca8b Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Wed, 25 Apr 2018 17:06:09 -0700 Subject: [PATCH 08/17] Add conceptual samples, round 3 --- functions/concepts/index.js | 85 +++++++++++++++++++++++++++++++-- functions/concepts/loadable.js | 22 +++++++++ functions/concepts/package.json | 4 +- 3 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 functions/concepts/loadable.js diff --git a/functions/concepts/index.js b/functions/concepts/index.js index 28cfc4d411..0792d252c1 100644 --- a/functions/concepts/index.js +++ b/functions/concepts/index.js @@ -1,5 +1,5 @@ /** - * Copyright 2016, Google, Inc. + * Copyright 2018, Google, Inc. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -76,16 +76,30 @@ exports.executionCount = (req, res) => { }; // [END functions_concepts_stateless] -// [START functions_after_response] +// [START functions_concepts_after_response] +/** + * HTTP Cloud Function that may not completely + * execute due to early HTTP response + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ exports.afterResponse = (req, res) => { res.end(); // This statement may not execute console.log('Function complete!'); }; -// [END functions_after_response] +// [END functions_concepts_after_response] -// [START functions_after_timeout] +// [START functions_concepts_after_timeout] +/** + * HTTP Cloud Function that may not completely + * execute due to function execution timeout + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ exports.afterTimeout = (req, res) => { setTimeout(() => { // May not execute if function's timeout is <2 minutes @@ -93,4 +107,65 @@ exports.afterTimeout = (req, res) => { res.end(); }, 120000); // 2 minute delay }; -// [END functions_after_timeout] +// [END functions_concepts_after_timeout] + +// [START functions_concepts_filesystem] +const fs = require('fs'); + +/** + * HTTP Cloud Function that lists files in the function directory + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.listFiles = (req, res) => { + fs.readdir(__dirname, (err, files) => { + if (err) { + console.error(err); + res.sendStatus(500); + } else { + console.log('Files', files); + res.sendStatus(200); + } + }); +}; +// [END functions_concepts_filesystem] + +// [START functions_concepts_modules] +const path = require('path'); +const loadedModule = require(path.join(__dirname, 'loadable.js')); + +/** + * HTTP Cloud Function that runs a function loaded from another Node.js file + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.runLoadedModule = (req, res) => { + console.log(`Loaded function from file ${loadedModule.getFileName()}`); + res.end(); +}; +// [END functions_concepts_modules] + +// [START functions_concepts_requests] +const request = require('request'); + +/** + * HTTP Cloud Function that makes an HTTP request + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.makeRequest = (req, res) => { + // The URL to send the request to + const url = 'https://example.com'; + + request(url, (err, response) => { + if (!err && response.statusCode === 200) { + res.sendStatus(200); + } else { + res.sendStatus(500); + } + }); +}; +// [END functions_concepts_requests] diff --git a/functions/concepts/loadable.js b/functions/concepts/loadable.js new file mode 100644 index 0000000000..822b444d20 --- /dev/null +++ b/functions/concepts/loadable.js @@ -0,0 +1,22 @@ +/** + * Copyright 2018, Google, Inc. + * Licensed under the Apache License, Version 2.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 functions_sample_module] +exports.getFileName = () => { + return __filename; +}; +// [END functions_sample_module] diff --git a/functions/concepts/package.json b/functions/concepts/package.json index a424b413cc..b561e48927 100644 --- a/functions/concepts/package.json +++ b/functions/concepts/package.json @@ -14,7 +14,9 @@ "scripts": { "lint": "repo-tools lint" }, - "dependencies": {}, + "dependencies": { + "request": "^2.85.0" + }, "devDependencies": { "@google-cloud/nodejs-repo-tools": "2.2.5", "ava": "0.25.0", From bae2b5b0684018617186e436e3c8e8afbc1baccb Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Thu, 26 Apr 2018 11:50:49 -0700 Subject: [PATCH 09/17] Address comments --- functions/helloworld/index.js | 43 +---------------------------------- functions/http/index.js | 13 ++++++----- functions/tips/index.js | 11 +++++---- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/functions/helloworld/index.js b/functions/helloworld/index.js index 8aeda8654c..32ceeb4263 100644 --- a/functions/helloworld/index.js +++ b/functions/helloworld/index.js @@ -1,5 +1,5 @@ /** - * Copyright 2016, Google, Inc. + * Copyright 2018, Google, Inc. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -170,44 +170,3 @@ exports.helloTemplate = (req, res) => { res.send(html).end(); }; // [END functions_helloworld_template] - -/** - * HTTP Cloud Function that demonstrates - * how to catch errors of different types. - * - * @param {Object} req Cloud Function request context. - * @param {Object} res Cloud Function response context. - */ -exports.helloErrorTypes = (req, res) => { - // [START functions_helloworld_error_object] - try { - // Throw an Error object (to simulate a GCP API failure) - throw new Error('Error object!'); - } catch (err) { - // err is already an Error object - console.error(err); - } - // [END functions_helloworld_error_object] - - /* eslint-disable no-throw-literal */ - // [START functions_helloworld_error_unknown] - try { - // Throw an unknown error type - if (Date.now() % 2 === 0) { - throw 'Error string!'; - } else { - throw new Error('Error object!'); - } - } catch (err) { - // Determine the error type - if (err instanceof Error) { - console.error(err); - } else { - console.error(new Error(err)); - } - } - // [END functions_helloworld_error_unknown] - /* eslint-enable no-throw-literal */ - - res.end(); -}; diff --git a/functions/http/index.js b/functions/http/index.js index 2d806c6053..fab17b21a5 100644 --- a/functions/http/index.js +++ b/functions/http/index.js @@ -154,7 +154,8 @@ exports.uploadFile = (req, res) => { // This callback will be invoked for each file uploaded. busboy.on('file', (fieldname, file, filename) => { - // Note: os.tmpdir() - and any files in it - must fit in memory. + // Note: os.tmpdir() points to an in-memory file system on GCF + // Thus, any files in it must fit in the instance's memory. const filepath = path.join(tmpdir, filename); uploads[fieldname] = filepath; file.pipe(fs.createWriteStream(filepath)); @@ -173,7 +174,7 @@ exports.uploadFile = (req, res) => { req.pipe(busboy); } else { // Ignore non-POST requests - res.status(405).send(); + res.status(405).end(); } }; // [END functions_http_form_data] @@ -189,7 +190,7 @@ const storage = require('@google-cloud/storage')(); * @param {Object} res Cloud Function response context. */ exports.getSignedUrl = (req, res) => { - if(req.method === 'POST') { + if (req.method === 'POST') { // TODO(developer) check that the user is authorized to upload // Get a reference to the destination file in GCS @@ -203,7 +204,7 @@ exports.getSignedUrl = (req, res) => { contentType: req.body.contentType }; - file.getSignedUrl(config, function(err, url) { + file.getSignedUrl(config, function (err, url) { if (err) { console.error(err); res.status(500).end(); @@ -212,7 +213,7 @@ exports.getSignedUrl = (req, res) => { res.send(url); }); } else { - res.status(405).end(); + res.status(405).end(); } -} +}; // [END functions_http_signed_url] diff --git a/functions/tips/index.js b/functions/tips/index.js index 38575e5884..eaa1715beb 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -45,13 +45,15 @@ exports.scopeDemo = (req, res) => { // This computation runs every time this function is called const functionVar = lightComputation(); - res.end(`Per instance: ${instanceVar}, per function: ${functionVar}`); + res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`); }; // [END functions_tips_scopes] // [START functions_tips_lazy_globals] -// This value is always initialized, which happens at cold-start +// Always initialized (at cold-start) const nonLazyGlobal = fileWideComputation(); + +// Declared at cold-start, but only initialized if/when the function executes let lazyGlobal; /** @@ -64,7 +66,7 @@ exports.lazyGlobals = (req, res) => { // This value is initialized only if (and when) the function is called lazyGlobal = lazyGlobal || functionSpecificComputation(); - res.end(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`); + res.send(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`); }; // [END functions_tips_lazy_globals] @@ -73,9 +75,8 @@ exports.lazyGlobals = (req, res) => { const http = require('http'); // [END functions_tips_ephemeral_agent] const agent = new http.Agent({keepAlive: true}); -// [END functions_tips_cached_connection] +// [END functions_tips_cached_agent] -// TODO(ace-n) make sure this import works as intended // [START functions_tips_ephemeral_agent] /** * HTTP Cloud Function that uses an ephemeral HTTP agent From ce54daa3addd4ec9b794f60691bd2bedd7252323 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Thu, 26 Apr 2018 12:27:39 -0700 Subject: [PATCH 10/17] Address comments, round 2 --- functions/tips/.gcloudignore | 16 +++++++++ functions/tips/index.js | 16 ++++++--- functions/tips/package.json | 3 +- functions/tips/test/index.test.js | 55 +++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 functions/tips/.gcloudignore create mode 100644 functions/tips/test/index.test.js diff --git a/functions/tips/.gcloudignore b/functions/tips/.gcloudignore new file mode 100644 index 0000000000..ccc4eb240e --- /dev/null +++ b/functions/tips/.gcloudignore @@ -0,0 +1,16 @@ +# This file specifies files that are *not* uploaded to Google Cloud Platform +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +node_modules diff --git a/functions/tips/index.js b/functions/tips/index.js index eaa1715beb..b2d4f7201c 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -1,5 +1,5 @@ /** - * Copyright 2016, Google, Inc. + * Copyright 2018, Google, Inc. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -78,6 +78,7 @@ const agent = new http.Agent({keepAlive: true}); // [END functions_tips_cached_agent] // [START functions_tips_ephemeral_agent] + /** * HTTP Cloud Function that uses an ephemeral HTTP agent * @@ -155,6 +156,7 @@ exports.avoidInfiniteRetries = (event, callback) => { // Do what the function is supposed to do console.log(`Processing event ${event} with age ${eventAge} ms.`); + callback(); }; // [END functions_tips_infinite_retries] @@ -164,9 +166,11 @@ exports.avoidInfiniteRetries = (event, callback) => { * how to toggle retries using a promise * * @param {object} event The Cloud Functions event. + * @param {object} event.data Data included with the event. + * @param {object} event.data.retry Whether or not to retry the function. */ exports.retryPromise = (event) => { - const tryAgain = false; + const tryAgain = !!event.data.retry; if (tryAgain) { throw new Error(`Retrying...`); @@ -182,17 +186,19 @@ exports.retryPromise = (event) => { * how to toggle retries using a callback * * @param {object} event The Cloud Functions event. + * @param {object} event.data Data included with the event. + * @param {object} event.data.retry Whether or not to retry the function. * @param {function} callback The callback function. */ exports.retryCallback = (event, callback) => { - const tryAgain = false; + const tryAgain = !!event.data.retry; const err = new Error('Error!'); if (tryAgain) { - console.error(`Retrying: ${err}`); + console.error('Retrying:', err); callback(err); } else { - console.error(`Not retrying: ${err}`); + console.error('Not retrying:', err); callback(); } }; diff --git a/functions/tips/package.json b/functions/tips/package.json index 5668e4a942..e29698ada7 100644 --- a/functions/tips/package.json +++ b/functions/tips/package.json @@ -21,7 +21,8 @@ }, "devDependencies": { "@google-cloud/nodejs-repo-tools": "2.2.5", - "ava": "0.25.0" + "ava": "0.25.0", + "sinon": "^4.5.0" }, "cloud-repo-tools": { "requiresKeyFile": true, diff --git a/functions/tips/test/index.test.js b/functions/tips/test/index.test.js new file mode 100644 index 0000000000..4bca0df209 --- /dev/null +++ b/functions/tips/test/index.test.js @@ -0,0 +1,55 @@ +/** + * Copyright 2018, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const sinon = require(`sinon`); +const test = require(`ava`); +const tools = require(`@google-cloud/nodejs-repo-tools`); + +const sample = require(`../`); + +test.beforeEach(tools.stubConsole); +test.afterEach.always(tools.restoreConsole); + +test(`should demonstrate retry behavior for a promise`, async (t) => { + // Retry by throwing an error + t.throws(() => { + sample.retryPromise({ data: { + retry: true } + }); + }, 'Retrying...'); + + // Terminate by returning a rejected promise + await t.throws( + sample.retryPromise({ data: {} }), + 'Not retrying...' + ); +}); + +test(`should demonstrate retry behavior for a callback`, (t) => { + const cb = sinon.stub(); + const err = new Error('Error!'); + + // Retry by passing an error to the callback + sample.retryCallback({ data: { + retry: true } + }, cb); + t.deepEqual(cb.firstCall.args, [err]); + + // Terminate by passing nothing to the callback + sample.retryCallback({ data: {} }, cb); + t.deepEqual(cb.secondCall.args, []); +}); From 5639b38cb56039357df94e3930bcc2ecaf3cc8b1 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Thu, 26 Apr 2018 13:10:58 -0700 Subject: [PATCH 11/17] Add unit test for concepts samples --- functions/concepts/index.js | 6 +++- functions/concepts/package.json | 5 +-- functions/concepts/test/index.test.js | 46 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 functions/concepts/test/index.test.js diff --git a/functions/concepts/index.js b/functions/concepts/index.js index 0792d252c1..6800aff3fb 100644 --- a/functions/concepts/index.js +++ b/functions/concepts/index.js @@ -20,6 +20,8 @@ * how to catch errors of different types. * * @param {Object} req Cloud Function request context. + * @param {Object} req.body Cloud Function request context body. + * @param {String} req.body.topic The Cloud Pub/Sub topic to publish to. * @param {Object} res Cloud Function response context. */ exports.errorTypes = (req, res) => { @@ -33,11 +35,13 @@ exports.errorTypes = (req, res) => { } // [END functions_concepts_error_object] + const someCondition = !!req.body.throwAsString; + /* eslint-disable no-throw-literal */ // [START functions_concepts_error_unknown] try { // Throw an unknown error type - if (Date.now() % 2 === 0) { + if (someCondition) { throw 'Error string!'; } else { throw new Error('Error object!'); diff --git a/functions/concepts/package.json b/functions/concepts/package.json index b561e48927..298ba762f5 100644 --- a/functions/concepts/package.json +++ b/functions/concepts/package.json @@ -18,8 +18,9 @@ "request": "^2.85.0" }, "devDependencies": { - "@google-cloud/nodejs-repo-tools": "2.2.5", - "ava": "0.25.0", + "@google-cloud/nodejs-repo-tools": "^2.2.5", + "ava": "^0.25.0", + "sinon": "^4.5.0", "supertest": "^3.0.0" } } diff --git a/functions/concepts/test/index.test.js b/functions/concepts/test/index.test.js new file mode 100644 index 0000000000..2652c3e52d --- /dev/null +++ b/functions/concepts/test/index.test.js @@ -0,0 +1,46 @@ +/** + * Copyright 2018, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const sinon = require(`sinon`); +const test = require(`ava`); +const tools = require(`@google-cloud/nodejs-repo-tools`); + +const sample = require(`../`); + +test.beforeEach(tools.stubConsole); +test.afterEach.always(tools.restoreConsole); + +test(`should demonstrate error type behavior`, (t) => { + const objError = new Error('Error object!'); + const strError = new Error('Error string!'); + + const req = { body: + { throwAsString: true } + }; + const res = { end: sinon.stub() }; + + // Test throwing both objects and strings + sample.errorTypes(req, res); + t.deepEqual(console.error.getCall(0).args, [objError]); + t.deepEqual(console.error.getCall(1).args, [strError]); + + // Test throwing objects only + req.body.throwAsString = false; + sample.errorTypes(req, res); + t.deepEqual(console.error.getCall(2).args, [objError]); + t.deepEqual(console.error.getCall(3).args, [objError]); +}) \ No newline at end of file From 09f9efeb73a82c68d67bc7555c829522b5767042 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Thu, 26 Apr 2018 13:22:54 -0700 Subject: [PATCH 12/17] Clarify comments --- functions/tips/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/tips/index.js b/functions/tips/index.js index b2d4f7201c..7616ebf844 100644 --- a/functions/tips/index.js +++ b/functions/tips/index.js @@ -35,7 +35,7 @@ const fileWideComputation = lightComputation; const instanceVar = heavyComputation(); /** - * Sample HTTP Cloud Function. + * HTTP Cloud Function that declares a variable. * * @param {Object} req Cloud Function request context. * @param {Object} res Cloud Function response context. @@ -57,7 +57,7 @@ const nonLazyGlobal = fileWideComputation(); let lazyGlobal; /** - * Sample HTTP Cloud Function. + * HTTP Cloud Function that uses lazy-initialized globals * * @param {Object} req Cloud Function request context. * @param {Object} res Cloud Function response context. From ea3bb0d381cd4b0dd77807bf268caa04459c38c9 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Thu, 26 Apr 2018 13:59:59 -0700 Subject: [PATCH 13/17] Fix lint --- functions/concepts/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/concepts/test/index.test.js b/functions/concepts/test/index.test.js index 2652c3e52d..f0ffaf4129 100644 --- a/functions/concepts/test/index.test.js +++ b/functions/concepts/test/index.test.js @@ -43,4 +43,4 @@ test(`should demonstrate error type behavior`, (t) => { sample.errorTypes(req, res); t.deepEqual(console.error.getCall(2).args, [objError]); t.deepEqual(console.error.getCall(3).args, [objError]); -}) \ No newline at end of file +}); From fe260b22d684be544d1bfe8285dee09d4914355f Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Fri, 27 Apr 2018 11:37:40 -0700 Subject: [PATCH 14/17] Make comments consistent --- functions/http/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functions/http/index.js b/functions/http/index.js index fab17b21a5..13a92e9b13 100644 --- a/functions/http/index.js +++ b/functions/http/index.js @@ -173,7 +173,7 @@ exports.uploadFile = (req, res) => { req.pipe(busboy); } else { - // Ignore non-POST requests + // Return a "method not allowed" error res.status(405).end(); } }; @@ -213,6 +213,7 @@ exports.getSignedUrl = (req, res) => { res.send(url); }); } else { + // Return a "method not allowed" error res.status(405).end(); } }; From edd196e2ff8b49673b316857d63a7824c06f8574 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Mon, 30 Apr 2018 13:33:35 -0700 Subject: [PATCH 15/17] Fix failing + add new tests --- circle.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index e98ee2eea5..3737695447 100644 --- a/circle.yml +++ b/circle.yml @@ -35,10 +35,13 @@ dependencies: - yarn install - yarn run lint - samples test install -l=functions/background + - samples test install -l=functions/concepts + - samples test install -l=functions/datastore + - samples test install -l=functions/debug - samples test install -l=functions/errorreporting - samples test install -l=functions/gcs - - samples test install -l=functions/datastore - samples test install -l=functions/helloworld + - samples test install -l=functions/http - samples test install -l=functions/imagemagick - samples test install -l=functions/log - samples test install -l=functions/ocr/app @@ -46,11 +49,14 @@ dependencies: - samples test install -l=functions/sendgrid - samples test install -l=functions/slack - samples test install -l=functions/spanner + - samples test install -l=functions/tips - samples test install -l=functions/uuid cache_directories: - ~/.cache/yarn - functions/background/node_modules + - functions/concepts/node_modules - functions/datastore/node_modules + - functions/debug/node_modules - functions/errorreporting/node_modules - functions/gcs/node_modules - functions/helloworld/node_modules @@ -62,6 +68,7 @@ dependencies: - functions/sendgrid/node_modules - functions/slack/node_modules - functions/spanner/node_modules + - functions/tips/node_modules - functions/uuid/node_modules # Run your tests @@ -70,6 +77,7 @@ test: - functions-emulator config set projectId $GCLOUD_PROJECT && functions-emulator start && cd functions/datastore && npm run system-test && functions-emulator stop - functions-emulator config set projectId $GCLOUD_PROJECT && functions-emulator start && cd functions/helloworld && npm run test && functions-emulator stop - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/background/test/**/*.test.js' + - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/concepts/test/**/*.test.js' - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/gcs/test/**/*.test.js' - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/http/test/**/*.test.js' - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/imagemagick/test/**/*.test.js' @@ -79,6 +87,7 @@ test: - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/slack/test/**/*.test.js' - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/spanner/test/**/*.test.js' - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/uuid/test/**/*.test.js' + - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/tips/test/**/*.test.js' post: - nyc report --reporter=lcov > coverage.lcov && codecov || true deployment: From b53bd1ce31193ed807c7f06d2ca21dfb414e09dc Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Mon, 30 Apr 2018 13:52:40 -0700 Subject: [PATCH 16/17] Don't run debug tests --- circle.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/circle.yml b/circle.yml index 3737695447..624ed6bee5 100644 --- a/circle.yml +++ b/circle.yml @@ -37,7 +37,6 @@ dependencies: - samples test install -l=functions/background - samples test install -l=functions/concepts - samples test install -l=functions/datastore - - samples test install -l=functions/debug - samples test install -l=functions/errorreporting - samples test install -l=functions/gcs - samples test install -l=functions/helloworld @@ -56,7 +55,6 @@ dependencies: - functions/background/node_modules - functions/concepts/node_modules - functions/datastore/node_modules - - functions/debug/node_modules - functions/errorreporting/node_modules - functions/gcs/node_modules - functions/helloworld/node_modules From db5b186b56d1475d646646e5379502110a502f01 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Mon, 30 Apr 2018 14:06:52 -0700 Subject: [PATCH 17/17] Increment Node version --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 624ed6bee5..93790d7600 100644 --- a/circle.yml +++ b/circle.yml @@ -18,7 +18,7 @@ machine: node: - version: 6.12.3 + version: 6.14.1 # Use for broader build-related configuration general: