Unit testing firebase admin SDK authentication on node.JS - node.js

Is there any way I can write unit tests for firebase authentication to store locally? I want to do something like mongodb-memory-server, so all the data saved doesn't persists when the tests are over. I want to create some tests to those two functions specifically:
async function createResearcherOnFirebase(email, password) {
return admin.auth().createUser({
email,
password,
}).then((researcherRecord) => researcherRecord.uid).catch(() => false);
}
async function grantResearcherRole(uid) {
return admin.auth().setCustomUserClaims(uid, { researcher: true })
.then(() => true).catch(() => false);
}

While for some Firebase products there is now an emulator suite that you can run locally for precisely such use-cases, Firebase Authentication is currently not in there.
If you don't want to hit the real project, you will have to mock the service and inject it.

Related

Can we access request header on Firebase Auth triggers i.e onCreate?

How can one access the IP and country in firebase auth onCreate trigger? Is there any other way to get this info?
For the front end, I'm using firebase-ui and for user register using the following method.
app.auth().createUserWithEmailAndPassword(email,password)
Cloud Functions code:
exports.processSignUp = functions.auth.user().onCreate(async user => {
return admin.auth().setCustomUserClaims(user.uid, {
clientIp: 'xxx.xxx.xx.xx', // required here headers['x-forwarded-for']
country: 'countryName' // required here headers['x-appengine-country']
})
.then(() => {
})
.catch(error => {
console.log(error);
});
});
The Cloud Function is triggered by the Google/Firebase infrastructure, and thus the headers are the values from that infrastructure. No information about the user/device that called createUserWithEmailAndPassword is available beyond what is passed in the user object.
If you need more information, consider implementing a callable function that you call directly from your code passing both the credentials (that you now pass to createUserWithEmailAndPassword) and the additional information you need, and then creating the user in the Cloud Functions code itself.

How to pass arguments from Dart to Cloud functions written in Typescript and it also gives me error code UNAUTHENTICATED

Dart function (passing token to sendToDevice):
Future<void> _sendNotification() async {
CloudFunctions functions = CloudFunctions.instance;
HttpsCallable callable = functions.getHttpsCallable(functionName: "sendToDevice");
callable.call({
'token': await FirebaseMessaging().getToken(),
});
}
index.ts file where I have defined sendToDevice method.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
const fcm = admin.messaging();
export const sendToDevice = functions.firestore
.document('users/uid')
.onCreate(async snapshot => {
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'Dummy title',
body: `Dummy body`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return fcm.sendToDevice(tokens, payload); // how to get tokens here passed from above function?
}
);
Questions:
How can I receive tokens passed from my Dart function _sendNotification to Typescript's sendToDevice function.
When I was directly passing tokens inside index.ts file, I was getting this exception:
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(functionsError, Cloud function failed with exception., {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED})
Can anyone please explain if I am supposed to authenticate something here? The command firebase login shows I am already signed in. I am very new to Typescript so please bear with these stupid questions.
Your Flutter side of code seems right, what's wrong is on the Cloud Function.
The sendToDevice function is not a callable function. It is a Cloud Firestore Triggers, it is only meant to be automatically called whenever a document matches users/{uid} is created.
Instead, you'll want to create a Callable Function, see below
export const sendToDevice = functions.https
.onCall(async (data) => {
const { token } = data; // Data is what you'd send from callable.call
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'Dummy title',
body: `Dummy body`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return fcm.sendToDevice(token, payload);
}
);
You have created a database trigger, what you should do is create a callable function as shown below
exports.sendToDevice = functions.https.onCall(async (data, context) => {
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'Dummy title',
body: `Dummy body`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return await fcm.sendToDevice(data.token, payload);
});
There are few things to mention here:
1st The function used in 'getHttpsCallable' must be triggered by https trigger (reference here). Here we have a function triggered by firestore document create, so it won't work.
2nd You do not have parameter of your function, but you call it with parameters. If you need example of calling cloud function with parameter you can find it on pud.dev
3rd I do not have at the moment possibility to play with it, but I think that if you implement https triggered function with token parameter you should be able to pass this parameter.
I hope it will help!
UPDATE:
According to doc https triggered function has to be created with functions.https. There is a nice example in the doc. To function triggered this way you can add request body when you can pass needed data.
This answer might not solve your problem but will give you a few things to try, and you'll learn along the way. Unfortunately I wasn't able to get the callable https working with the emulator. I'll probably submit a github issue about it soon. The flutter app keeps just getting different types of undecipherable errors depending on the local URL I try.
It's good that you've fixed one of the problems: you were using document trigger (onCreate) instead of a https callable. But now, you're running a https callable and the Flutter apps needs to communicate with your functions directly. In the future, you could run the functions emulator locally, and do a lot of console.log'ing to understand if it actually gets triggered.
I have a few questions/ things you can try:
Is your user logged in the flutter app? FirebaseAuth.instance.currentUser() will tell you.
Does this problem happen on both iOS and android?
Add some logs to your typescript function, and redeploy. Read the latest logs through StackDriver or in terminal, firebase functions:log --only sendToDevice. (sendToDevice is your callable function name)
Are you deploying to the cloud and testing with the latest deployment of your functions? You can actually test with a local emulator. On Android, the url is 10.0.2.2:5001 as shown above. You also need to run adb reverse tcp:5001 tcp:5001 in the terminal. If you're on the cloud, then firebase login doesn't matter, I think your functions should already have the credentials.
To call the emulator https callable:
HttpsCallable callable = CloudFunctions.instance
.useFunctionsEmulator(origin: "http://10.0.2.2:5001")
.getHttpsCallable(functionName: "sendToDevice");
And iOS you need to follow the solution here.
One mistake I spotted. You should at least do return await fcm.sendToDevice() where you wait for the promise to resolve, because otherwise the cloud function runtime will terminate your function before it resolves. Alternatively, for debugging, instead of returning sendToDevice in your cloud function, you could have saved it into a variable, and console.log'd it. You would see its actually a promise (or a Future in dart's terminology) that hadn't actually resolved.
const messagingDevicesResponse: admin.messaging.MessagingDevicesResponse = await fcm.sendToDevice(
token,
payload
);
console.log({ messagingDevicesResponse });
return;
Make the function public
The problem is asociated with credentials. You can change the security policy of the CF and sheck if the problem is fixed. Se how to manage permisions on CF here

Jest to mock Stripe

I want to test my nodejs code using Jest.
In my code I use stripe.
When requiring stripe you have to use this line
const stripe=require('stripe')("apikey");
in order to be able to access the stripe methods.
Obviously that's the library I want to mock, but if I do
jest.mock('stripe');
I cannot mock the stripe methods I need as it's like doing a require without passing the key in the higher order function.
I could not find any correlation around.
Is there a way to achieve that?
I figure you'll need to build your entire Stripe SDK mock. This is what's working for me atm:
// Stripe SDK mock
jest.mock('stripe', () => {
return jest.fn().mockImplementation(() => {
return {
skus: {
retrieve: (sku, callback) => {
callback({}, {});
}
}
};
});
});
The above mock will return empty sku object when calling stripe.skus.retrieve(req.query.sku, function(err, sku) { ... })
Same should go for the rest. If you want this mock to return different kinds of data depending on input, you'll need to implement that logic in the mock.
Hope this helps,

How should I do my automated tests of nodeJs APIs exploiting Neo4j database

Im currently working on several NodeJS APIs that do exploit a Neo4J Database. I want to do automated tests on each endpoints and make sure everything works (eg: POST a user should create a node user in my graph with the expected labels and properties)
As a junior developer Im not really sure where to start.
My initial idea is to set up a fixtures Neo4j database (by executing a long cypher query on a void graph that will generate fixtures data)
The issue is that this fixtures database will be affected by the different tests I do.
For Instance if i want to test a DELETE endpoint I will assert that a data will be deleted of my database.
I see two solutions for that :
Before testing an endpoint I generate some needed data. But then I
will have to make sure to delete them after the test to not impair
the fixtures and affect other endpoint tests.
Before testing an endpoint I clean the database, execute the fixtures query, and execute a second query to add some extra data to test my endpoint. Then for each endpoint im sure to have a clean database with eventually some extra data.
The first solution seems effort/time-consuming while the second seems a bit rough. Maybe my conception of automated tests is really wrong.
Here is an example of a test I have done (using mochajs and chai) Where I change the admin of an organisation :
describe('UPDATE organisation', () => {
before((doneBefore) => {
const admin1 = updateOrganisationUtilities.createAdmin('admin#test.com');
const admin2 = updateOrganisationUtilities.createAdmin('admin2#test.com');
Promise.all([admin1, admin2]).then(
(admins) => {
idAdmin = admins[0].id;
idAdmin2 = admins[1].id;
updateOrganisationUtilities.bindAdminToOrganisation(idAdmin, idOrganisation)
.then(() => {
doneBefore();
});
});
});
after((doneAfter) => {
Promise.all([
updateOrganisationUtilities.deleteExtraData(),
]).then(() => {
doneAfter();
});
});
it('Should update an organisation', (doneIt) => {
const req = {
method: 'PUT',
url: `/organisations/${idOrganisation}`,
payload: JSON.stringify({
name: 'organisation-test',
address: 'here',
trainingNumber: '15',
type: 'organisation.types.university',
expirationDate: new Date(),
idAdmin: idAdmin2,
}),
headers: {
authorization: token,
},
};
server.inject(req, (res) => {
assert.equal(res.statusCode, 200);
assert.exists(res.result.id);
assert.equal(res.result.address, 'here');
assert.equal(res.result.trainingNumber, '15');
assert.equal(res.result.type, 'organisation.types.university');
updateOrganisationUtilities.getLinkBetweenAdminAndOrga(res.result.id, idAdmin2)
.then((link) => {
assert.include(link, 'IS_ADMIN');
assert.include(link, 'ROLE');
doneIt();
});
});
});
});
As you can see :
I create some usefull extra data in before()
I delete the extra data in after()
The method updateOrganisationUtilities.getLinkBetweenAdminAndOrga()
check if the changes in Neo4j has been correctly applied.
It seems working but Im afraid that it's going to be a PITA to maintain this kind of structure when I will have hundreds of tests that might alter the database. Moreover I fear consuming too much time with this way of testing my endpoints since I have to write specific functions/queries before and after each endpoint test.
I'm sorry if my question seems wide open but im very curious about how experienced developers deal with endpoints automated tests.
To ensure that unexpected changes from one test do not affect another test, you should completely empty out the DB after each test, and create the appropriate fixture before each test. Not only would this ensure that you are testing with the data you expect, but it will make implementing the tests easier.

How to mock external service when testing a NodeJS API

I have JSON API built with koa which I am trying to cover with integration tests.
A simple test would look like this:
describe("GET: /users", function() {
it ("should respond", function (done) {
request(server)
.get('/api/users')
.expect(200, done);
});
});
Now the issue comes when the actions behind a controller - lets say saveUser at POST /users - use external resources. For instance I need to validate the users phone number.
My controller looks like this:
save: async function(ctx, next) {
const userFromRequest = await parse(ctx);
try {
// validate data
await ctx.repo.validate(userFromRequest);
// validate mobile code
await ctx.repo.validateSMSCode(
userFromRequest.mobile_number_verification_token,
userFromRequest.mobile_number.prefix + userFromRequest.mobile_number.number
);
const user = await ctx.repo.create(userFromRequest);
return ctx.data(201, { user });
} catch (e) {
return ctx.error(422, e.message, e.meta);
}
}
I was hoping to be able to mock the ctx.repo on the request object but I can't seem to able to get a hold on it from test, which means that my tests are actually hitting the phone number verification service.
Are there any ways I could go around hitting that verification service ?
Have you considered using a mockup library like https://github.com/mfncooper/mockery?
Typically, when writing tests requiring external services, I mock the service client library module. For example, using mocha:
mockery = require('mockery');
repo = require('your-repo-module');
before(function() {
mockery.enable();
repo.validateSMSCode = function() {...};
mockery.registerMock('your-repo-module', repo);
}
This way, every time you require your-repo-module, the mocked module will be loaded rather than the original one. Until you disable the mock, obviously...
app.context is the prototype from which ctx is created from. You may
add additional properties to ctx by editing app.context. This is
useful for adding properties or methods to ctx to be used across your
entire app, which may be more performant (no middleware) and/or easier
(fewer require()s) at the expense of relying more on ctx, which could
be considered an anti-pattern.
app.context.someProp = "Some Value";
app.use(async (ctx) => {
console.log(ctx.someProp);
});
For your sample your re-define app.context.repo.validateSMSCode like this, assuming that you have following setup lines in your test:
import app from '../app'
import supertest from 'supertest'
app.context.repo.validateSMSCode = async function(ctx, next) {
// Your logic here.
};
const request = supertest.agent(app.listen())
After re-defining app.context.repo.validateSMSCode method that your will define in your test, will work, instead of original method.
https://github.com/koajs/koa/blob/v2.x/docs/api/index.md#appcontext
https://github.com/koajs/koa/issues/652

Resources