diff --git a/circle.yml b/circle.yml index 0b1266544a..b4341ee6bd 100644 --- a/circle.yml +++ b/circle.yml @@ -67,8 +67,8 @@ dependencies: # Run your tests test: override: - - functions start && cd functions/datastore && npm run system-test - - functions start && cd functions/helloworld && npm run system-test + - functions start && cd functions/datastore && npm run system-test && functions stop + - functions start && cd functions/helloworld && npm run test && functions 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/gcs/test/**/*.test.js' - samples test run --cmd nyc -- --cache ava --verbose -T 30s 'functions/http/test/**/*.test.js' diff --git a/functions/ci_cd/cloudbuild.yaml b/functions/ci_cd/cloudbuild.yaml new file mode 100644 index 0000000000..41de4c6c77 --- /dev/null +++ b/functions/ci_cd/cloudbuild.yaml @@ -0,0 +1,10 @@ +steps: +- name: 'gcr.io/cloud-builders/yarn' + args: ['install'] + dir: 'functions/autodeploy' +- name: 'gcr.io/cloud-builders/npm' + args: ['test'] + dir: 'functions/autodeploy' +- name: 'gcr.io/cloud-builders/gcloud' + args: ['beta', 'functions', 'deploy', '[YOUR_FUNCTION_NAME]', '[YOUR_FUNCTION_TRIGGER]'] + dir: 'functions/autodeploy' \ No newline at end of file diff --git a/functions/helloworld/package.json b/functions/helloworld/package.json index 42ffb0c51c..fd3c51cd1e 100644 --- a/functions/helloworld/package.json +++ b/functions/helloworld/package.json @@ -15,8 +15,8 @@ "lint": "repo-tools lint", "pretest": "npm run lint", "e2e-test": "export FUNCTIONS_CMD='gcloud beta functions' && sh test/updateFunctions.sh && BASE_URL=\"https://$GCF_REGION-$GCLOUD_PROJECT.cloudfunctions.net/\" ava -T 20s --verbose test/*.test.js", - "system-test": "export FUNCTIONS_CMD='functions' && sh test/updateFunctions.sh && export BASE_URL=\"http://localhost:8010/$GCLOUD_PROJECT/$GCF_REGION\" && ava -T 20s --verbose test/*.test.js", - "test": "npm run system-test" + "test": "export FUNCTIONS_CMD='functions' && sh test/updateFunctions.sh && export BASE_URL=\"http://localhost:8010/$GCLOUD_PROJECT/$GCF_REGION\" && ava -T 20s --verbose test/index.test.js test/*unit*test.js test/*integration*test.js", + "system-test": "export FUNCTIONS_CMD='functions' && sh test/updateFunctions.sh && export BASE_URL=\"http://localhost:8010/$GCLOUD_PROJECT/$GCF_REGION\" && ava -T 20s --verbose test/*.test.js" }, "dependencies": { "@google-cloud/debug-agent": "2.3.0", @@ -24,7 +24,6 @@ "safe-buffer": "5.1.1" }, "devDependencies": { - "@google-cloud/functions-emulator": "^1.0.0-alpha.29", "@google-cloud/nodejs-repo-tools": "2.1.3", "@google-cloud/pubsub": "^0.15.0", "@google-cloud/storage": "^1.5.0", diff --git a/functions/helloworld/test/sample.integration.http.test.js b/functions/helloworld/test/sample.integration.http.test.js new file mode 100644 index 0000000000..5e23086b3e --- /dev/null +++ b/functions/helloworld/test/sample.integration.http.test.js @@ -0,0 +1,41 @@ +/** + * 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. + */ + +// [START functions_http_integration_test] +const test = require(`ava`); +const Supertest = require(`supertest`); +const supertest = Supertest(process.env.BASE_URL); + +test.cb(`helloHttp: should print a name`, (t) => { + supertest + .post(`/helloHttp`) + .send({ name: 'John' }) + .expect(200) + .expect((response) => { + t.is(response.text, 'Hello John!'); + }) + .end(t.end); +}); + +test.cb(`helloHttp: should print hello world`, (t) => { + supertest + .get(`/helloHttp`) + .expect(200) + .expect((response) => { + t.is(response.text, `Hello World!`); + }) + .end(t.end); +}); +// [END functions_http_integration_test] diff --git a/functions/helloworld/test/sample.integration.pubsub.test.js b/functions/helloworld/test/sample.integration.pubsub.test.js new file mode 100644 index 0000000000..10442445ab --- /dev/null +++ b/functions/helloworld/test/sample.integration.pubsub.test.js @@ -0,0 +1,47 @@ +/** + * 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. + */ + +// [START functions_pubsub_integration_test] +const childProcess = require(`child_process`); +const test = require(`ava`); +const uuid = require(`uuid`); + +test(`helloPubSub: should print a name`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + const name = uuid.v4(); + + // Mock Pub/Sub call, as the emulator doesn't listen to Pub/Sub topics + const encodedName = Buffer.from(name).toString(`base64`); + const data = JSON.stringify({ data: encodedName }); + childProcess.execSync(`functions call helloPubSub --data '${data}'`); + + // Check the emulator's logs + const logs = childProcess.execSync(`functions logs read helloPubSub --start-time ${startTime}`).toString(); + t.true(logs.includes(`Hello, ${name}!`)); +}); + +test(`helloPubSub: should print hello world`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + + // Mock Pub/Sub call, as the emulator doesn't listen to Pub/Sub topics + childProcess.execSync(`functions call helloPubSub --data {}`); + + // Check the emulator's logs + const logs = childProcess.execSync(`functions logs read helloPubSub --start-time ${startTime}`).toString(); + t.true(logs.includes(`Hello, World!`)); +}); +// [END functions_pubsub_integration_test] diff --git a/functions/helloworld/test/sample.integration.storage.test.js b/functions/helloworld/test/sample.integration.storage.test.js new file mode 100644 index 0000000000..c7361e2e64 --- /dev/null +++ b/functions/helloworld/test/sample.integration.storage.test.js @@ -0,0 +1,77 @@ +/** + * 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. + */ + +// [START functions_storage_integration_test] +const childProcess = require(`child_process`); +const test = require(`ava`); +const uuid = require(`uuid`); + +test(`helloGCS: should print uploaded message`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + const filename = uuid.v4(); // Use a unique filename to avoid conflicts + + // Mock GCS call, as the emulator doesn't listen to GCS buckets + const data = JSON.stringify({ + name: filename, + resourceState: 'exists', + metageneration: '1' + }); + + childProcess.execSync(`functions call helloGCS --data '${data}'`); + + // Check the emulator's logs + const logs = childProcess.execSync(`functions logs read helloGCS --start-time ${startTime}`).toString(); + t.true(logs.includes(`File ${filename} uploaded.`)); +}); + +test(`helloGCS: should print metadata updated message`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + const filename = uuid.v4(); // Use a unique filename to avoid conflicts + + // Mock GCS call, as the emulator doesn't listen to GCS buckets + const data = JSON.stringify({ + name: filename, + resourceState: 'exists', + metageneration: '2' + }); + + childProcess.execSync(`functions call helloGCS --data '${data}'`); + + // Check the emulator's logs + const logs = childProcess.execSync(`functions logs read helloGCS --start-time ${startTime}`).toString(); + t.true(logs.includes(`File ${filename} metadata updated.`)); +}); + +test(`helloGCS: should print deleted message`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + const filename = uuid.v4(); // Use a unique filename to avoid conflicts + + // Mock GCS call, as the emulator doesn't listen to GCS buckets + const data = JSON.stringify({ + name: filename, + resourceState: 'not_exists', + metageneration: '3' + }); + + childProcess.execSync(`functions call helloGCS --data '${data}'`); + + // Check the emulator's logs + const logs = childProcess.execSync(`functions logs read helloGCS --start-time ${startTime}`).toString(); + t.true(logs.includes(`File ${filename} deleted.`)); +}); +// [END functions_storage_integration_test] diff --git a/functions/helloworld/test/sample.system.http.test.js b/functions/helloworld/test/sample.system.http.test.js new file mode 100644 index 0000000000..b4a750b937 --- /dev/null +++ b/functions/helloworld/test/sample.system.http.test.js @@ -0,0 +1,41 @@ +/** + * 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. + */ + +// [START functions_http_system_test] +const test = require(`ava`); +const Supertest = require(`supertest`); +const supertest = Supertest(process.env.BASE_URL); + +test.cb(`helloHttp: should print a name`, (t) => { + supertest + .post(`/helloHttp`) + .send({ name: 'John' }) + .expect(200) + .expect((response) => { + t.is(response.text, 'Hello John!'); + }) + .end(t.end); +}); + +test.cb(`helloHttp: should print hello world`, (t) => { + supertest + .get(`/helloHttp`) + .expect(200) + .expect((response) => { + t.is(response.text, `Hello World!`); + }) + .end(t.end); +}); +// [END functions_http_system_test] diff --git a/functions/helloworld/test/sample.system.pubsub.test.js b/functions/helloworld/test/sample.system.pubsub.test.js new file mode 100644 index 0000000000..0fa7b25370 --- /dev/null +++ b/functions/helloworld/test/sample.system.pubsub.test.js @@ -0,0 +1,60 @@ +/** + * 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. + */ + +// [START functions_pubsub_system_test] +const childProcess = require(`child_process`); +const test = require(`ava`); +const uuid = require(`uuid`); +const Pubsub = require(`@google-cloud/pubsub`); +const pubsub = Pubsub(); + +const topicName = process.env.FUNCTIONS_TOPIC; +const baseCmd = `gcloud beta functions`; + +test(`helloPubSub: should print a name`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + const name = uuid.v4(); + + // Publish to pub/sub topic + const topic = pubsub.topic(topicName); + const publisher = topic.publisher(); + await publisher.publish(Buffer.from(name)); + + // Wait for logs to become consistent + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check logs after a delay + const logs = childProcess.execSync(`${baseCmd} logs read helloPubSub --start-time ${startTime}`).toString(); + t.true(logs.includes(`Hello, ${name}!`)); +}); + +test(`helloPubSub: should print hello world`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + + // Publish to pub/sub topic + const topic = pubsub.topic(topicName); + const publisher = topic.publisher(); + await publisher.publish(Buffer.from(''), { a: 'b' }); + + // Wait for logs to become consistent + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check logs after a delay + const logs = childProcess.execSync(`${baseCmd} logs read helloPubSub --start-time ${startTime}`).toString(); + t.true(logs.includes('Hello, World!')); +}); +// [END functions_pubsub_system_test] diff --git a/functions/helloworld/test/sample.system.storage.test.js b/functions/helloworld/test/sample.system.storage.test.js new file mode 100644 index 0000000000..37f2e93743 --- /dev/null +++ b/functions/helloworld/test/sample.system.storage.test.js @@ -0,0 +1,80 @@ +/** + * 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. + */ + +// [START functions_storage_system_test] +const Storage = require(`@google-cloud/storage`); +const storage = Storage(); +const uuid = require(`uuid`); +const test = require(`ava`); +const path = require(`path`); +const childProcess = require(`child_process`); +const localFileName = `test.txt`; + +// Use unique GCS filename to avoid conflicts between concurrent test runs +const gcsFileName = `test-${uuid.v4()}.txt`; + +const bucketName = process.env.BUCKET_NAME; +const bucket = storage.bucket(bucketName); +const baseCmd = `gcloud beta functions`; + +test.serial(`helloGCS: should print uploaded message`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + + // Upload file + const filepath = path.join(__dirname, localFileName); + await bucket.upload(filepath, { + destination: gcsFileName + }); + + // Wait for consistency + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check logs + const logs = childProcess.execSync(`${baseCmd} logs read helloGCS --start-time ${startTime}`).toString(); + t.true(logs.includes(`File ${gcsFileName} uploaded`)); +}); + +test.serial(`helloGCS: should print metadata updated message`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + + // Update file metadata + const file = bucket.file(gcsFileName); + await file.setMetadata(gcsFileName, { foo: `bar` }); + + // Wait for consistency + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check logs + const logs = childProcess.execSync(`${baseCmd} logs read helloGCS --start-time ${startTime}`).toString(); + t.true(logs.includes(`File ${gcsFileName} metadata updated`)); +}); + +test.serial(`helloGCS: should print deleted message`, async (t) => { + t.plan(1); + const startTime = new Date(Date.now()).toISOString(); + + // Delete file + bucket.deleteFiles(); + + // Wait for consistency + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check logs + const logs = childProcess.execSync(`${baseCmd} logs read helloGCS --start-time ${startTime}`).toString(); + t.true(logs.includes(`File ${gcsFileName} deleted`)); +}); +// [START functions_storage_system_test] diff --git a/functions/helloworld/test/sample.unit.http.test.js b/functions/helloworld/test/sample.unit.http.test.js new file mode 100644 index 0000000000..e08d1d6ec8 --- /dev/null +++ b/functions/helloworld/test/sample.unit.http.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. + */ + +// [START functions_http_unit_test] +const test = require(`ava`); +const sinon = require(`sinon`); +const uuid = require(`uuid`); + +const helloHttp = require(`..`).helloHttp; + +test(`helloHttp: should print a name`, t => { + // Initialize mocks + const name = uuid.v4(); + const req = { + body: { + name: name + } + }; + const res = { send: sinon.stub() }; + + // Call tested function + helloHttp(req, res); + + // Verify behavior of tested function + t.true(res.send.calledOnce); + t.deepEqual(res.send.firstCall.args, [`Hello ${name}!`]); +}); + +test(`helloHttp: should print hello world`, t => { + // Initialize mocks + const req = { + body: {} + }; + const res = { send: sinon.stub() }; + + // Call tested function + helloHttp(req, res); + + // Verify behavior of tested function + t.true(res.send.calledOnce); + t.deepEqual(res.send.firstCall.args, [`Hello World!`]); +}); +// [END functions_http_unit_test] diff --git a/functions/helloworld/test/sample.unit.pubsub.test.js b/functions/helloworld/test/sample.unit.pubsub.test.js new file mode 100644 index 0000000000..85910bad07 --- /dev/null +++ b/functions/helloworld/test/sample.unit.pubsub.test.js @@ -0,0 +1,56 @@ +/** + * 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. + */ + +// [START functions_pubsub_unit_test] +const test = require(`ava`); +const uuid = require(`uuid`); +const sinon = require(`sinon`); + +const helloPubSub = require(`..`).helloPubSub; +const consoleLog = sinon.stub(console, 'log'); + +test.cb(`helloPubSub: should print a name`, t => { + t.plan(1); + + // Initialize mocks + const name = uuid.v4(); + const event = { + data: { + data: Buffer.from(name).toString(`base64`) + } + }; + + // Call tested function and verify its behavior + helloPubSub(event, () => { + t.true(consoleLog.calledWith(`Hello, ${name}!`)); + t.end(); + }); +}); + +test.cb(`helloPubSub: should print hello world`, t => { + t.plan(1); + + // Initialize mocks + const event = { + data: {} + }; + + // Call tested function and verify its behavior + helloPubSub(event, () => { + t.true(consoleLog.calledWith(`Hello, World!`)); + t.end(); + }); +}); +// [END functions_pubsub_unit_test] diff --git a/functions/helloworld/test/sample.unit.storage.test.js b/functions/helloworld/test/sample.unit.storage.test.js new file mode 100644 index 0000000000..a13a1b5250 --- /dev/null +++ b/functions/helloworld/test/sample.unit.storage.test.js @@ -0,0 +1,83 @@ +/** + * 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. + */ + +// [START functions_storage_unit_test] +const test = require(`ava`); +const uuid = require(`uuid`); +const sinon = require(`sinon`); + +const helloGCS = require(`..`).helloGCS; +const consoleLog = sinon.stub(console, 'log'); + +test.cb(`helloGCS: should print uploaded message`, t => { + t.plan(1); + + // Initialize mocks + const filename = uuid.v4(); + const event = { + data: { + name: filename, + resourceState: 'exists', + metageneration: '1' + } + }; + + // Call tested function and verify its behavior + helloGCS(event, () => { + t.true(consoleLog.calledWith(`File ${filename} uploaded.`)); + t.end(); + }); +}); + +test.cb(`helloGCS: should print metadata updated message`, t => { + t.plan(1); + + // Initialize mocks + const filename = uuid.v4(); + const event = { + data: { + name: filename, + resourceState: 'exists', + metageneration: '2' + } + }; + + // Call tested function and verify its behavior + helloGCS(event, () => { + t.true(consoleLog.calledWith(`File ${filename} metadata updated.`)); + t.end(); + }); +}); + +test.cb(`helloGCS: should print deleted message`, t => { + t.plan(1); + + // Initialize mocks + const filename = uuid.v4(); + const event = { + data: { + name: filename, + resourceState: 'not_exists', + metageneration: '3' + } + }; + + // Call tested function and verify its behavior + helloGCS(event, () => { + t.true(consoleLog.calledWith(`File ${filename} deleted.`)); + t.end(); + }); +}); +// [END functions_storage_unit_test]