Skip to content

Commit

Permalink
v0.4.6 (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
prescottprue authored Jun 13, 2018
1 parent 0cf98bf commit 7d842df
Show file tree
Hide file tree
Showing 99 changed files with 1,027 additions and 727 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ env:
es6: true
node: true

ecmaFeatures:
jsx: true
modules: true

globals:
__DEV__: false
__COVERAGE__: false
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sudo: false
language: node_js

node_js:
- 6.11.5 # runtime used within Firebase functions
- 6.14.0 # runtime used within Firebase functions

notifications:
email:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Interested in adding a feature or contributing? Open an issue or [reach out over
If you are just getting started with Fireadmin, it is probably best to checkout the [hosted version at fireadmin.io](http://fireadmin.io). After you become more familiar, feel free to run your own by pulling this source and proceeding to the [run your own section](#run-your-own).

### Requirements
* node `^6.0.0` (`6.11.5` suggested to match [Cloud Functions Runtime][functions-runtime-url])
* node `^6.14.0` (`6.14.0` suggested for function to match [Cloud Functions Runtime][functions-runtime-url])

## Running Your Own

Expand Down
5 changes: 3 additions & 2 deletions build/scripts/start.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const logger = require('../lib/logger')
const config = require('../../project.config')

logger.info('Starting server...')
require('../../server/main').listen(3000, () => {
logger.success('Server is running at http://localhost:3000')
require('../../server/main').listen(config.port, () => {
logger.success(`Server is running at http://localhost:${config.port}`)
})
9 changes: 8 additions & 1 deletion build/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,19 @@ const config = {
// ------------------------------------
config.module.rules.push({
test: /\.(js|jsx)$/,
exclude: [/node_modules/, /redux-firestore/, /react-redux-firebase/],
exclude: [
/node_modules/,
/redux-firestore\/es/,
/react-redux-firebase\/es/
// Add other packages that you are npm linking here
],
use: [
{
loader: 'babel-loader',
query: {
cacheDirectory: true,
// ignore root .babelrc (Check issue #59 for more details)
babelrc: false,
plugins: [
'lodash',
'transform-decorators-legacy',
Expand Down
27 changes: 21 additions & 6 deletions functions/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
const admin = require('firebase-admin')
const functions = require('firebase-functions')
const glob = require('glob')
const path = require('path')
const admin = require('firebase-admin')
const functions = require('firebase-functions')

// Initialize Firebase so it is available within functions
try {
admin.initializeApp(functions.config().firebase)
} catch (err) {}
} catch (e) {
/* istanbul ignore next: not called in tests */
console.error(
'Caught error initializing app with functions.config():',
e.message || e
)
}

const codeFolder = process.env.NODE_ENV === 'test' ? './src' : './dist'

// Load all folders within dist directory (mirrors layout of src)
const files = glob.sync('./dist/**/index.js', {
const files = glob.sync(codeFolder + '/**/index.js', {
cwd: __dirname,
ignore: ['./node_modules/**', './dist/utils/**', './dist/constants']
ignore: [
'./node_modules/**',
codeFolder + '/utils/**',
codeFolder + '/constants'
]
})

// Loop over all folders found within dist loading only the relevant function
files.forEach(functionFile => {
// Get folder name from file name (removing any dashes)
const folderName = path
.basename(path.dirname(functionFile))
.replace(/[-]/g, '.')
.replace(/[-]/g, '')

// Load single function from default
!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === folderName // eslint-disable-line no-unused-expressions
? (exports[folderName] = require(functionFile).default) // eslint-disable-line global-require
Expand Down
3 changes: 3 additions & 0 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@
"mocha": "^5.0.4",
"rimraf": "^2.6.2",
"sinon": "^4.1.2"
},
"engines": {
"node": "6.14.0"
}
}
36 changes: 27 additions & 9 deletions functions/src/utils/async.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
/**
* Async await wrapper for easy error handling
* @param {Promise} promise - Promise to wrap responses of
* @return {Promise} Resolves and rejects with an array
* @example
* async function asyncFunctionWithThrow() {
* const [err, snap] = await to(
* admin.database().ref('some').once('value')
* );
* if (err) {
* console.error('Error getting data:', err.message || err)
* throw err
* }
* if (!snap.val()) throw new Error('Data not found');
* console.log('Data found:', snap.val())
* }
*/
export function to(promise) {
return promise
.then(data => {
return [null, data]
})
.catch(err => [err])
return promise.then(data => [null, data]).catch(err => [err])
}

/**
* Run promises in a waterfall instead of all the same time (Promise.all)
* @param {Array} callbacks - List of promises to run in order
* @return {Promise} Resolves when all promises have completed in order
*/
export function promiseWaterfall(callbacks) {
// Don't assume we're running in an environment with promises
return callbacks.reduce(function(accumulator, callback) {
return accumulator.then(callback)
}, Promise.resolve())
return callbacks.reduce(
(accumulator, callback) => accumulator.then(callback),
Promise.resolve()
)
}
25 changes: 25 additions & 0 deletions functions/test/unit/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as admin from 'firebase-admin' // eslint-disable-line no-unused-vars

describe('Cloud Functions', () => {
let myFunctions
let configStub
let adminInitStub
let admin

before(() => {
/* eslint-disable global-require */
adminInitStub = sinon.stub(admin, 'initializeApp')
myFunctions = require(`${__dirname}/../../index`)
/* eslint-enable global-require */
})

after(() => {
// Restoring our stubs to the original methods.
configStub.restore()
adminInitStub.restore()
})

it('exports an object', () => {
expect(myFunctions).to.be.an('object')
})
})
122 changes: 122 additions & 0 deletions functions/test/unit/indexUser.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const firebasemock = require('firebase-mock')
let mockauth = new firebasemock.MockFirebase()
let mockdatabase = new firebasemock.MockFirebase()
let mockfirestore = new firebasemock.MockFirestore()
let mocksdk = firebasemock.MockFirebaseSdk(
function() {
return mockdatabase
},
function() {
return mockauth
},
function() {
return mockfirestore
}
)

let mockapp = mocksdk.initializeApp()

describe('indexUsers Cloud Function', () => {
let myFunctions
let configStub
let adminInitStub
let functions

beforeEach(() => {
// Since index.js makes calls to functions.config and admin.initializeApp at the top of the file,
// we need to stub both of these functions before requiring index.js. This is because the
// functions will be executed as a part of the require process.
// Here we stub admin.initializeApp to be a dummy function that doesn't do anything.
/* eslint-disable global-require */
process.env.GCLOUD_PROJECT = 'FakeProjectId'
mockdatabase = new firebasemock.MockFirebase()
mockauth = new firebasemock.MockFirebase()
mockfirestore = new firebasemock.MockFirestore()
adminInitStub = sinon.stub(mocksdk, 'initializeApp')
// Next we stub functions.config(). Normally config values are loaded from Cloud Runtime Config;
// here we'll just provide some fake values for firebase.databaseURL and firebase.storageBucket
// so that an error is not thrown during admin.initializeApp's parameter check
functions = require('firebase-functions')
configStub = sinon.stub(functions, 'config').returns({
firebase: {
databaseURL: 'https://not-a-project.firebaseio.com',
storageBucket: 'not-a-project.appspot.com',
projectId: 'not-a-project.appspot',
messagingSenderId: '823357791673'
}
// You can stub any other config values needed by your functions here, for example:
// foo: 'bar'
})
// Now we require index.js and save the exports inside a namespace called myFunctions
// if we use ../ without dirname here, it can not be run with --prefix from parent folder
myFunctions = require(`${__dirname}/../../index`)
mockdatabase.autoFlush()
mockauth.autoFlush()
mockfirestore.autoFlush()
/* eslint-enable global-require */
})

afterEach(() => {
// Restoring stubs to the original methods
configStub.restore()
adminInitStub.restore()
})

it('adds display name if it did not exist before', async () => {
const fakeEvent = {
data: new firebasemock.DeltaDocumentSnapshot(
mockapp,
null,
{
displayName: 'bob',
createdTime: new Date()
},
'users/123'
),
params: {
userId: '123ABC'
}
}
// Invoke function with fake event
try {
await myFunctions.indexUser(fakeEvent)
} catch (err) {
expect(err).to.exist
expect(
err.message.indexOf('The project not-a-project.appspot does not exist')
).to.not.equal(-1)
}
})

it('returns null if display name is not changed', async () => {
const fakeEvent = {
// The DeltaSnapshot constructor is used by the Functions SDK to transform a raw event from
// your database into an object with utility functions such as .val().
// Its signature is: DeltaSnapshot(app: firebase.app.App, adminApp: firebase.app.App,
// data: any, delta: any, path?: string);
// We can pass null for the first 2 parameters. The data parameter represents the state of
// the database item before the event, while the delta parameter represents the change that
// occured to cause the event to fire. The last parameter is the database path, which we are
// not making use of in this test. So we will omit it.
data: new firebasemock.DeltaDocumentSnapshot(
mockapp,
{
displayName: 'bob',
createdTime: new Date() + 50
},
{
displayName: 'bob',
createdTime: new Date()
},
'users/123'
),
params: {
userId: '123ABC'
}
}
// Invoke webhook with our fake request and response objects. This will cause the
// assertions in the response object to be evaluated.
const res = await myFunctions.indexUsers(fakeEvent)
expect(res).to.be.null
})
})
Loading

0 comments on commit 7d842df

Please sign in to comment.