mirror of
https://github.com/zhigang1992/firebase-tools.git
synced 2026-01-12 22:47:24 +08:00
Add integration test for deployment of functions (#111)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
/.vscode
|
||||
/node_modules
|
||||
node_modules
|
||||
/coverage
|
||||
firebase-debug.log
|
||||
npm-debug.log
|
||||
|
||||
@@ -25,6 +25,10 @@ var paths = {
|
||||
|
||||
tests: [
|
||||
'test/**/*.spec.js'
|
||||
],
|
||||
|
||||
scripts: [
|
||||
'scripts/*.js'
|
||||
]
|
||||
};
|
||||
|
||||
@@ -34,7 +38,7 @@ var paths = {
|
||||
/***********/
|
||||
// Lints the JavaScript files
|
||||
gulp.task('lint', function() {
|
||||
var filesToLint = _.union(paths.js, paths.tests);
|
||||
var filesToLint = _.union(paths.js, paths.tests, paths.scripts);
|
||||
return gulp.src(filesToLint)
|
||||
.pipe(eslint())
|
||||
.pipe(eslint.format())
|
||||
|
||||
28
scripts/assets/functions_to_test.js
Normal file
28
scripts/assets/functions_to_test.js
Normal file
@@ -0,0 +1,28 @@
|
||||
var functions = require('firebase-functions');
|
||||
|
||||
exports.dbAction = functions.database().path('/input/{uuid}').onWrite(function(event) {
|
||||
return event.data.ref.root.child('output/' + event.params.uuid).set(event.data.val());
|
||||
});
|
||||
|
||||
exports.nested = {
|
||||
dbAction: functions.database().path('/inputNested/{uuid}').onWrite(function(event) {
|
||||
return event.data.ref.root.child('output/' + event.params.uuid).set(event.data.val());
|
||||
})
|
||||
};
|
||||
|
||||
exports.httpsAction = functions.cloud.https().onRequest(function(req, res) {
|
||||
res.send(req.body);
|
||||
});
|
||||
|
||||
exports.pubsubAction = functions.cloud.pubsub('topic1').onPublish(function(event) {
|
||||
var uuid = event.data.json;
|
||||
var app = functions.app;
|
||||
return app.database().ref('output/' + uuid).set(uuid);
|
||||
});
|
||||
|
||||
exports.gcsAction = functions.cloud.storage('functions-integration-test.appspot.com')
|
||||
.onChange(function(event) {
|
||||
var uuid = event.data.data.name;
|
||||
var app = functions.app;
|
||||
return app.database().ref('output/' + uuid).set(uuid);
|
||||
});
|
||||
14
scripts/package.json
Normal file
14
scripts/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "firebase-functions-integration-test",
|
||||
"version": "1.0.0",
|
||||
"description": "Integration test for deploying Firebase functions",
|
||||
"main": "test-functions-deploy.js",
|
||||
"scripts": {
|
||||
"test": "node test-functions-deploy.js"
|
||||
},
|
||||
"author": "Firebase",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"firebase": "^3.5.0"
|
||||
}
|
||||
}
|
||||
203
scripts/test-functions-deploy.js
Normal file
203
scripts/test-functions-deploy.js
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
var expect = require('chai').expect;
|
||||
var execSync = require('child_process').execSync;
|
||||
var exec = require('child_process').exec;
|
||||
var tmp = require('tmp');
|
||||
var _ = require('lodash');
|
||||
var fs = require('fs-extra');
|
||||
var cloudfunctions = require('../lib/gcp/cloudfunctions');
|
||||
var api = require('../lib/api');
|
||||
var scopes = require('../lib/scopes');
|
||||
var configstore = require('../lib/configstore');
|
||||
var extractTriggers = require('../lib/extractTriggers');
|
||||
var RSVP = require('rsvp');
|
||||
var chalk = require('chalk');
|
||||
var firebase = require('firebase');
|
||||
|
||||
var functionsSource = __dirname + '/assets/functions_to_test.js';
|
||||
var projectDir = __dirname + '/test-project';
|
||||
var projectId = 'functions-integration-test';
|
||||
var httpsTrigger = 'https://us-central1-functions-integration-test.cloudfunctions.net/httpsAction';
|
||||
var region = 'us-central1';
|
||||
var localFirebase = __dirname + '/../bin/firebase';
|
||||
var TIMEOUT = 40000;
|
||||
var tmpDir;
|
||||
var app;
|
||||
|
||||
var parseFunctionsList = function() {
|
||||
var triggers = [];
|
||||
extractTriggers(require(tmpDir + '/functions'), triggers);
|
||||
return _.map(triggers, 'name');
|
||||
};
|
||||
|
||||
var getUuid = function() {
|
||||
return Math.floor(Math.random() * 100000000000).toString();
|
||||
};
|
||||
|
||||
var preTest = function() {
|
||||
var dir = tmp.dirSync({prefix: 'fntest_'});
|
||||
tmpDir = dir.name;
|
||||
fs.copySync(projectDir, tmpDir);
|
||||
execSync('npm install', {'cwd': tmpDir + '/functions'});
|
||||
api.setToken(configstore.get('tokens').refresh_token);
|
||||
api.setScopes(scopes.CLOUD_PLATFORM);
|
||||
var config = {
|
||||
apiKey: 'AIzaSyCLgng7Qgzf-2UKRPLz--LtLLxUsMK8oco',
|
||||
authDomain: 'functions-integration-test.firebaseapp.com',
|
||||
databaseURL: 'https://functions-integration-test.firebaseio.com',
|
||||
storageBucket: 'functions-integration-test.appspot.com'
|
||||
};
|
||||
app = firebase.initializeApp(config);
|
||||
console.log('Done pretest prep.');
|
||||
};
|
||||
|
||||
var postTest = function() {
|
||||
fs.remove(tmpDir);
|
||||
execSync(localFirebase + ' database:remove / -y', {'cwd': tmpDir});
|
||||
console.log('Done post-test cleanup.');
|
||||
process.exit();
|
||||
};
|
||||
|
||||
var checkFunctionsListMatch = function(expectedFunctions) {
|
||||
return cloudfunctions.list(projectId, region).then(function(result) {
|
||||
var deployedFunctions = _.map(result, 'functionName');
|
||||
expect(_.isEmpty(_.xor(expectedFunctions, deployedFunctions))).to.be.true;
|
||||
return true;
|
||||
}).catch(function(err) {
|
||||
expect(err).to.be.null;
|
||||
});
|
||||
};
|
||||
|
||||
var testCreateUpdate = function() {
|
||||
fs.copySync(functionsSource, tmpDir + '/functions/index.js');
|
||||
return new RSVP.Promise(function(resolve) {
|
||||
exec(localFirebase + ' deploy', {'cwd': tmpDir}, function(err, stdout) {
|
||||
console.log(stdout);
|
||||
expect(err).to.be.null;
|
||||
resolve(checkFunctionsListMatch(parseFunctionsList()));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var testDelete = function() {
|
||||
return new RSVP.Promise(function(resolve) {
|
||||
exec('> functions/index.js &&' + localFirebase + ' deploy', {'cwd': tmpDir}, function(err, stdout) {
|
||||
console.log(stdout);
|
||||
expect(err).to.be.null;
|
||||
resolve(checkFunctionsListMatch([]));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var waitForAck = function(uuid, testDescription) {
|
||||
return Promise.race([
|
||||
new Promise(function(resolve) {
|
||||
var ref = firebase.database().ref('output').child(uuid);
|
||||
var listener = ref.on('value', function(snap) {
|
||||
if (snap.exists()) {
|
||||
ref.off('value', listener);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}),
|
||||
new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {
|
||||
reject('Timed out while waiting for output from ' + testDescription);
|
||||
}, TIMEOUT);
|
||||
})
|
||||
]);
|
||||
};
|
||||
|
||||
var writeToDB = function(path) {
|
||||
var uuid = getUuid();
|
||||
return app.database().ref(path).child(uuid).set({'foo': 'bar'}).then(function() {
|
||||
return RSVP.resolve(uuid);
|
||||
});
|
||||
};
|
||||
|
||||
var sendHttpRequest = function(message) {
|
||||
return api.request('POST', httpsTrigger, {
|
||||
data: message,
|
||||
origin: ''
|
||||
}).then(function(resp) {
|
||||
expect(resp.status).to.equal(200);
|
||||
expect(resp.body).to.deep.equal(message);
|
||||
});
|
||||
};
|
||||
|
||||
var publishPubsub = function(topic) {
|
||||
var uuid = getUuid();
|
||||
var message = new Buffer(uuid).toString('base64');
|
||||
return api.request('POST', '/v1/projects/functions-integration-test/topics/' + topic + ':publish', {
|
||||
auth: true,
|
||||
data: {'messages': [
|
||||
{'data': message}
|
||||
]},
|
||||
origin: 'https://pubsub.googleapis.com'
|
||||
}).then(function(resp) {
|
||||
expect(resp.status).to.equal(200);
|
||||
return RSVP.resolve(uuid);
|
||||
});
|
||||
};
|
||||
|
||||
var saveToStorage = function() {
|
||||
var uuid = getUuid();
|
||||
var contentLength = Buffer.byteLength(uuid, 'utf8');
|
||||
var resource = ['b', projectId + '.appspot.com', 'o'].join('/');
|
||||
var endpoint = '/upload/storage/v1/' + resource + '?uploadType=media&name=' + uuid;
|
||||
return api.request('POST', endpoint, {
|
||||
auth: true,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Length': contentLength
|
||||
},
|
||||
data: uuid,
|
||||
json: false,
|
||||
origin: api.googleOrigin
|
||||
}).then(function(resp) {
|
||||
expect(resp.status).to.equal(200);
|
||||
return RSVP.resolve(uuid);
|
||||
});
|
||||
};
|
||||
|
||||
var testFunctionsTrigger = function() {
|
||||
var checkDbAction = writeToDB('input').then(function(uuid) {
|
||||
return waitForAck(uuid, 'database triggered function');
|
||||
});
|
||||
var checkNestedDbAction = writeToDB('inputNested').then(function(uuid) {
|
||||
return waitForAck(uuid, 'nested database triggered function');
|
||||
});
|
||||
var checkHttpsAction = sendHttpRequest({'message': 'hello'});
|
||||
var checkPubsubAction = publishPubsub('topic1').then(function(uuid) {
|
||||
return waitForAck(uuid, 'pubsub triggered function');
|
||||
});
|
||||
var checkGcsAction = saveToStorage().then(function(uuid) {
|
||||
return waitForAck(uuid, 'storage triggered function');
|
||||
});
|
||||
return RSVP.all([checkDbAction, checkNestedDbAction, checkHttpsAction, checkPubsubAction, checkGcsAction]);
|
||||
};
|
||||
|
||||
var main = function() {
|
||||
preTest();
|
||||
testCreateUpdate().then(function() {
|
||||
console.log(chalk.green('\u2713 Test passed: creating functions'));
|
||||
return testCreateUpdate();
|
||||
}).then(function() {
|
||||
console.log(chalk.green('\u2713 Test passed: updating functions'));
|
||||
return testFunctionsTrigger();
|
||||
}).then(function() {
|
||||
console.log(chalk.green('\u2713 Test passed: triggering functions'));
|
||||
return testDelete();
|
||||
}).then(function() {
|
||||
console.log(chalk.green('\u2713 Test passed: deleting functions'));
|
||||
}).catch(function(err) {
|
||||
console.log(chalk.red('Error while running tests: '), err);
|
||||
return RSVP.resolve();
|
||||
}).then(postTest);
|
||||
};
|
||||
|
||||
main();
|
||||
|
||||
|
||||
5
scripts/test-project/.firebaserc
Normal file
5
scripts/test-project/.firebaserc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"projects": {
|
||||
"default": "functions-integration-test"
|
||||
}
|
||||
}
|
||||
6
scripts/test-project/database.rules.json
Normal file
6
scripts/test-project/database.rules.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"rules": {
|
||||
".read": true,
|
||||
".write": true
|
||||
}
|
||||
}
|
||||
8
scripts/test-project/firebase.json
Normal file
8
scripts/test-project/firebase.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"database": {
|
||||
"rules": "database.rules.json"
|
||||
},
|
||||
"hosting": {
|
||||
"public": "public"
|
||||
}
|
||||
}
|
||||
8
scripts/test-project/functions/package.json
Normal file
8
scripts/test-project/functions/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "functions",
|
||||
"description": "Firebase Functions",
|
||||
"dependencies": {
|
||||
"firebase": "^3.1",
|
||||
"firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz"
|
||||
}
|
||||
}
|
||||
4
scripts/test-project/public/404.html
Normal file
4
scripts/test-project/public/404.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
</html>
|
||||
7
scripts/test-project/public/index.html
Normal file
7
scripts/test-project/public/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user