// onUserEmailWrite.spec.ts
// ...
functions = require('firebase-functions');
spyOn(functions, 'config').and.returnValue({
firebase: {
databaseURL: 'https://not-a-project.firebaseio.com',
storageBucket: 'not-a-project.appspot.com',
apiKey: '...',
authDomain: 'not-a-project.firebaseapp.com',
}
});
admin = require('firebase-admin');
spyOn(admin, 'initializeApp');
myFunctions = require('./onUserEmailWrite');
// ...
event = {
params: {
userId: 'AbmlJhRrdwg2kf6OKwKYlu0lLv52'
},
data: new functions.database.DeltaSnapshot(null, admin, null, '[email protected]')
};
// ...
spyOn(admin, 'auth').and.returnValue({
updateUser: (uid, properties) => {
expect(uid).toBe(event.params.userId);
expect(properties).toEqual({
email: '[email protected]',
});
return Promise.resolve();
}
});
await myFunctions.onUserEmailWrite(event);
// ...
// onUserEmailWrite.ts
// ...
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
admin.initializeApp(functions.config().firebase);
// ...
functions.database.ref('/users/{userId}/email').onWrite(async event => {
// ...
const userId = event.data.adminRef.parent.key;
const email = event.data.val();
admin.auth().updateUser(userId, {email: email}); // causes issue
// ...
});
// ...
jasmineChanging back firebase-admin to 5.3.0 made my unit tests run fine again. Any 5.4.* version does not resolve the issue.
Started
(node:2441) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.
F
Failures:
1) onUserEmailWrite can skip if user is being deleted.
Message:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Stack:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
at ontimeout (timers.js:386:14)
at tryOnTimeout (timers.js:250:5)
at Timer.listOnTimeout (timers.js:214:5)
1 spec, 1 failure
Hey there! I couldn't figure out what this issue is about, so I've labeled it for a human to triage. Hang tight.
Hmmm this issue does not seem to follow the issue template. Make sure you provide all the required information.
How did you test on 5.4.0, 5.4.1 and 5.4.2 if you were affected by #109?
@hiranya911, I just stated it as a fact, that I had to go back to 5.3.0 for the issue to disappear.
As far as I can see the only admin SDK related code here is the require() statement, and the admin namespace passed to DeltaSnapshot. Is that correct? Do you call admin.initializeApp() anywhere? If not can you add that and see if it resolves the issue?
@hiranya911, Yes I do. I updated the snippets above to reflect this.
Is this guaranteed to run before DeltaSnapshot() or onUserEmailWrite() is called?
@hiranya911, Yes. admin.initializeApp() will always be called before those two functions. I updated the snippet above for more clarity.
I'm not familiar with the libraries you're using. What does spyOn(admin, 'initializeApp') do? Also is there a way to get a stacktrace of the original error (the error that caused the promise rejection)? This error occurs when something tries to access the default Firebase app before it's initialized (see https://github.com/firebase/firebase-admin-node/blob/master/src/firebase-namespace.ts#L116). So something is trying to access a service before admin.initializeApp() is called.
@laurenzlong any thoughts on what might be going here?
@hiranya911, it mocks/stubs to avoid the error. Please see Mocking functions.config() and admin.initializeApp() for reference, albeit it's using Mocha instead of Jasmine.
Thank you for the insight, I misdiagnosed the location of the issue. I've updated the snippets above to show the exact location. The relevant code above is the output from the command line. No other helpful stack trace is available.
I tried the official samples (using Mocha and Sinon) with the latest Admin SDK, and it works fine.
Cloud Functions
makeUpperCase
Uppercasing undefined input
✓ should upper case input and write it to /uppercase
addMessage
✓ should return a 303 redirect
@hiranya911, please try invoking admin.auth().updateUser(userId, {email: email}); this is where the issue was after going through the code again.
Ok, it makes more sense now. You shouldn't be able to call admin.auth() without initializing the default app first (which is mocked out in the test cases). I slightly modified the official sample from Cloud Functions by adding a call to admin.auth().updateUser(), and now I get the following error:
1) Cloud Functions makeUpperCase should upper case input and write it to /uppercase:
Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.
at FirebaseAppError.Error (native)
at FirebaseAppError.FirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:39:28)
at FirebaseAppError.PrefixedFirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:85:28)
at new FirebaseAppError (node_modules/firebase-admin/lib/utils/error.js:119:28)
at FirebaseNamespaceInternals.app (node_modules/firebase-admin/lib/firebase-namespace.js:90:19)
at FirebaseNamespace.serviceNamespace [as auth] (node_modules/firebase-admin/lib/firebase-namespace.js:154:32)
at exports.makeUppercase.functions.database.ref.onWrite.event (src/index.js:61:13)
at Object.<anonymous> (node_modules/firebase-functions/lib/cloud-functions.js:59:27)
at next (native)
at node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at Object.cloudFunction (node_modules/firebase-functions/lib/cloud-functions.js:53:36)
at Context.it (src/example.test.ts:112:50)
But this occurs in both 5.3.0 and 5.4.3 for me. I can't see how this could have ever worked, since admin.auth() internally looks up the default app.
@hiranya911, in my updated snippet above, I have a spy/stub for admin.auth().updateUser(). I presume that get's called as expected on 5.3.0.
Ok. In the latest admin SDK admin.auth is not a function, but a getter that returns a function. See https://github.com/firebase/firebase-admin-node/blob/master/src/firebase-namespace.ts#L284
You need to update your tests/stubs accordingly.
Following worked for me in mocha/sinon world:
const updateUserStub = sinon.stub().returns('done');
// admin.auth must return a callable object, so that admin.auth() would work.
sinon.stub(admin, 'auth').get(function getterFn(){
return () => {
return {updateUser: updateUserStub};
};
})
@zgosalvez Please update your stubs accordingly. All namespaces except admin.database are now exposed as getters that return functions. Soon, we will also expose admin.database in the same fashion (see #112).
Man, that took a while. I'm so glad it got resolved today. Thank you again for the guidance @hiranya911! For anyone who wants a reference in Jasmine:
spyOnProperty(admin, 'auth', 'get').and.returnValue(() => {
return {
updateUser: (uid, properties) => {
expect(uid).toBe(event.params.userId);
expect(properties).toEqual({
email: '[email protected]',
});
return Promise.resolve();
}
};
});
Most helpful comment
Following worked for me in mocha/sinon world: