Mocha/Chai expect an error when calling an async function - node.js

Im using mocha / chai to to do some testing.
The function is supposed to throw new Error if called with the same arguments.
The function:
Create a new user and save it do my database (mongoDB), if a user with the same discord id doesn't exist already. fetchUser(disc_id)searches for an existing user, by using findOne() from mongoose.
async function createUser(disc_id, moodleToken) {
const profileData = await fetchUser(disc_id);
if (profileData) {
throw new Error("The account already exist in the database!");
}
const newUser = profileModel.create({
discord_id: disc_id,
moodle_token: moodleToken,
}, (err) => {
if (err) throw err;
console.log("Document created!")
})
return newUser;
};
fetchUser
function fetchUser (disc_id) {
return profileModel.findOne({ discord_id: disc_id });
}
Testing
Im using sinon to create the "test-database". The following test passes just fine
describe("Create user", function () {
it("Should create a new user in database if the discord id is not in the database", async function () {
const discord_id = "2341243451";
const moodle_token = "fwefw89fwefHFEF0F90ls";
sinon.stub(profileSchema, "findOne").returns(null);
sinon.stub(profileSchema, "create").returns({
discord_id, moodle_token
});
const returnedUser = await createUser(discord_id, moodle_token);
expect(returnedUser.discord_id).to.equal(discord_id);
})
This tests that it is possible to create and save a new user to my database, if no existing user has the same discord id.
If I want to test the opposite, to create a new user, but a current user already exists with the same id, it should throw the error: "The account already exist in the database!"
it("Should throw an error if there exists a user with that discord id", async () => {
const discord_id = "2341243451";
const moodle_token = "fwefw89fwefHFEF0F90ls";
const fakeObject = {
discord_id: discord_id,
moodle_token: moodle_token
};
sinon.stub(profileSchema, "findOne").returns(fakeObject);
sinon.stub(profileSchema, "create").returns({ discord_id, moodle_token })
I have tried to following without success:
try {
await createUser(discord_id, moodle_token);
} catch (err) {
console.log(err);
expect(err).to.equal("The account already exist in the database!");
}
expect(async function () { await createUser(discord_id, moodle_token); }).to.throw("The account already exist in the database!");
expect(() => { createUser(discord_id, moodle_token)}).to.throw("The account already exist in the database!");
rejects(async () => { await createUser(discord_id, moodle_token); })
How should i test such a function?

I found a way to solve the problem, i restructured my createUser() to this:
async function createUser(disc_id, moodleToken) {
const profileData = await fetchUser(disc_id);
if (profileData) {
throw TypeError("The account already exist in the database!");
}
const newUser = new profileModel({
discord_id: disc_id,
moodle_token: moodleToken,
})
await newUser.save();
return newUser;
}
The test passes if they are like this:
it("Should throw an error if there exists a user with that discord id", async () => {
const discord_id = "2341243451";
const moodle_token = "fwefw89fwefHFEF0F90ls";
const fakeObject = {
discord_id: "2341243451",
moodle_token: "fwefw89fwefHFEF0F90ls"
};
sinon.stub(profileSchema, "findOne").returns(fakeObject);
await createUser(discord_id, moodle_token).catch((error) => {
expect(error.message).to.equal("The account already exist in the database!")
})
Don't know if it is the right way to do it, but if i change sinon.stub to return null the test wont pass.
So i guess this is a way to handle it.

Related

Push Data to MongoDB on Google SignIn Firebase

I wanted to write a method where onClick the google sign in starts and after successful sign in it makes a post request to my API.But the weird problem is 30% of the times the sign in data doesnt come to mongo db.I even called signout function in the catch block.Please help if someone notice any error!!
const Hero = () => {
const [user, setUser] = useState(null);
const [fetchUser, setFetchUser] = useState(null);
const handleGoogleSignIn = () => {
const googleProvider = new GoogleAuthProvider();
signInWithPopup(auth, googleProvider)
.then(async (result) => {
console.log(result);
try {
const { data } = await axios.post(
"https://myAPIherokuapp.com/api/v1/9c142e80023e07c3/registerUser",
{ name: result.user.displayName, email: result.user.email }
);
console.log(data);
} catch (err) {
console.log(err);
signOut(auth)
}
})
.catch((error) => {
console.log(error);
});
};
Maybe try async/await at the handleGoogleSignIn level? e.g.
const handleGoogleSignIn = async () => {
const googleProvider = await new GoogleAuthProvider();
const userResult = await signInWithPopup(auth, googleProvier);
await axios.post('url', userResult);
...
}
I think that should help?

Callable cloud functions - handle error in android

im trying to delete an user from firestore and from auth.
I have this callable cloud function:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
await db.collection(collection)
.doc(uid)
.delete();
await auth.deleteUser(uid);
return uid;
}
in android i have this:
fun deleteFromFirebase(){
val data = hashMapOf(
"userEmail" to user.email,
"collection" to "User"
)
functions // Optional region: .getInstance("europe-west1")
.getHttpsCallable("deleteUser")
.call(data)
.addOnCompleteListener() { task ->
if(!task.isSuccessful)
{
Log.d("User", "ERROR")
val e = task.exception
if (e != null) {
Log.d("Admin", e.message.toString())
}
}else{
Log.d("User", "Deleted")
//make something
}
}
}
If the user in auth and the document nin firestore exist, works great.
But i tryed to generate some error.
So I deleted the user from auth and ran the function. The Android log says D/User: User deleted
but in the console from google cloud:
Function execution took 1878 ms, finished with status code: 200
Exception from a finished function: Error: There is no user record corresponding to the provided identifier.
How can I handle the error and get correctly in android? Thanks!
The deleteUserByEmail function is async and returns a Promise. Your return statement runs before the promises is resolved. Try refactoring the code as shown below:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
// add await, continues after Promise is resolved
await deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
console.log(error) // <-- check for any errors
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
return await Promise.all([
db.collection(collection).doc(uid).delete(),
auth.deleteUser(uid)
])
}

Which mongoose query fails using async await

I have a small requirement.
I am trying to add documents into 2 different collections as shown below. In the below code, **Test1_Model** and **Test2_Model** are Mongoose Models in Node.js.
try {
const test1 = new Test1_Model({ name: "Dummy" });
const saveTest1 = await test1.save();
const test2 = new Test2_Model({ field: "Something" })
const saveTest2 = await test2.save();
} catch(err) {
console.log(err);
}
Now the requirement is to know that which of the above mongoose query returned an error and which one completed successfully. Yes, if test1.save() fails then test2.save() will not execute only but there can be a situation that test1.save() completes but test2.save() fails. So the aim is to know that exactly which of the query failed.
The above problem can be solved by replacing async/await into Promise handling using .then() and .catch(). You can find that solution below.
try {
const test1 = new Test1_Model({ name: "Dummy" });
const saveTest1 = test1.save().then().catch(err => {
throw new Error('Test1 Failed');
});
const test2 = new Test2_Model({ field: "Something" })
const saveTest2 = test2.save().then().catch(err => {
throw new Error('Test2 Failed');
});
} catch(err) {
console.log(err);
}
This solves the problem but the aim is to know that by using async/await, can we do something like this.
Thanks.
You can create a wrapper for every promise and throw an error or pass data from it.
const promiseHandler = (promise) => {
return promise
.then(data => ([data, undefined]))
.catch(error => Promise.resolve([undefined, error]));
}
try {
const test1 = new Test1_Model({ name: "Dummy" });
const [saveTest1, error] = await handle(test1.save());
if (error) throw new Error(`Error is on saveTest ${error}`)
const test2 = new Test2_Model({ field: "Something" })
const [saveTest2, error] = await handle(test2.save());
if (error) throw new Error(`Error is on saveTest2 ${error}`)
} catch (error) {
console.log(error)
}

I get undefined value when I make a request to the MongoDB database using the mongodb engine in Nodejs

I am using ExpressJS and MongoDB to create a blog for myself. I have created a mini library with the mongodb module to request the MongoDB database.
Here is the library:
'use strict'
const { MongoClient, ObjectId } = require('mongodb')
const { config } = require('../config')
const USER = encodeURIComponent(config.mongodb.user)
const PASS = encodeURIComponent(config.mongodb.pass)
const NAME = config.mongodb.name
const HOST = config.mongodb.host
const URL = `mongodb+srv://${USER}:${PASS}#${HOST}/${NAME}?retryWrites=true&w=majority`
const OPTIONS = {
useNewUrlParser: true,
useUnifiedTopology: true
}
class MongoLib {
constructor () {
this.client = new MongoClient(URL, OPTIONS)
this.name = NAME
}
connect () {
if (!MongoLib.connection) {
MongoLib.connection = new Promise((resolve, reject) => {
this.client.connect(err => {
if (err) reject(err)
console.log('Connected successfully to MongoDB.')
resolve(this.client.db(this.name))
})
})
}
return MongoLib.connection
}
getAll (collection, query) {
return this.connect().then(db => {
return db.collection(collection).find({ query }).toArray()
})
}
get (collection, id) {
return this.connect().then(db => {
return db.collection(collection).findOne({ _id: ObjectId(id) })
})
}
create (collection, data) {
return this.connect().then(db => {
return db.collection(collection).insertOne(data)
}).then(result => result.insertedId)
}
update (collection, id, data) {
return this.connect().then(db => {
return db.collection(collection).updateOne({ _id: ObjectId(id) }, { $set: data }, { upsert: true })
}).then(result => result.upsertedId || id)
}
delete (collection, id) {
return this.connect().then(db => {
return db.collection(collection).deleteOne({ _id: ObjectId(id) })
}).then(() => id)
}
}
module.exports = MongoLib
The database is connecting correctly because I have a seed that injects data into the database using the create method of the library that you just saw.
In the service layer, I create a class with a method called getUser, which will call the getAll method of the MongoDB library, to which we pass a query so that it looks for the user.
'use strict'
const MongoLib = require('../lib/mongo')
const bcrypt = require('bcrypt')
class UsersService {
constructor () {
this.collection = 'users'
this.mongoDB = new MongoLib()
}
async getUser ({ email }) {
// { email } is getted by basic authentication as a "username" to login
// I am receiving this data perfectly
const [user] = await this.mongoDB.getAll(this.collection, { email })
// But the problem start here, the value of user is undefined
return user
}
async createUser ({ user }) {
const { name, email, password } = user
const hashedPassword = await bcrypt.hash(password, 10)
const createUserId = await this.mongoDB.create(this.collection, {
name,
email,
password: hashedPassword
})
return createUserId
}
}
module.exports = UsersService
The problem here is that the user value is undefined. I don't understand why it causes conflict. I'm using async-await to wait for the database request to finish, and the data is in the database correctly.
Does anyone have an idea about this error? If more information needs it, please let me know.
Suspect your query is wrong, you are sending { { email: email } } to mongodb
getAll (collection, query) {
return this.connect().then(db => {
return db.collection(collection).find(query).toArray()
})
}

how to handle catch in sinon, unit testing nodejs

in my UserService:
createUser: async (data) => {
const user = new UserDBEntryMapper(data);
const createdUser = await UserModel.create(user).catch(error => {
this.handleError(error);
});
return createdUser ? new UserBOMapper(createdUser) : null;
}
in my user.test.js
it.only('Create New User', async () => {
const stub = sinon.stub(UserModel, "create").returns(user);
const created = await UserService.createUser(user);
expect(stub.calledOnce).to.be.true;
});
throwing the error as:
Create New User:
TypeError: UserModel.create(...).catch is not a function
If I remove catch block in UserService the test passes, but we need a catch block there. How do I handle this?
Note: UserModel.create() is sequelize default function
Promise needs to be returned in a mock. Something like this
const stub = sinon.stub(UserModel, "create").returns(Promise.resolve(user));

Resources