Nestjs Testing in signup BadRequestException: email in use error - javascript

user.service.ts
async findWithMail(email:string):Promise<any> {
return this.userRepository.findOne({email});
}
auth.service.ts
async signup(email:string,password:string,name?:string,surname?:string,phone:string){
if(email) {
const users = await this.userService.findWithMail(email);
if(users) {
throw new BadRequestException('email in use');
}
}
if(!password) return {error:"password must be!"};
const salt = randomBytes(8).toString('hex');
const hash = (await scrypt(password,salt,32)) as Buffer;
const result = salt + '.' +hash.toString('hex');
password = result;
const user = await
this.userService.create(email,password,name,surname,phone);
return user;
}
auth.service.spec.ts
let service:AuthService;
let fakeUsersService: Partial<UserService>;
describe('Auth Service',()=>{
beforeEach(async() => {
fakeUsersService = {
findWithMail:() => Promise.resolve([]),
create:(email:string,password:string) => Promise.resolve({email,password} as User),
}
const module = await Test.createTestingModule({
providers:[AuthService,{
provide:UserService,
useValue:fakeUsersService
}],
}).compile();
service = module.get(AuthService);
});
it('can create an instance of auth service',async()=> {
expect(service).toBeDefined();
})
it('throws an error if user signs up with email that is in use', async () => {
await service.signup('asdf#asdf.com', 'asdf')
});
})
When ı try to run my test its give me error even this email is not in database its give error: BadRequestException: email in use. I couldnt figure out how to solve problem

You can use isExists method instead of findOne.
Also you can add extra check for your findWithMail method. Check the length of db request result. Like if (dbReqResult.length === 0) return false; else true

please put your attention on your mocked user service, especially on findWithEmail function, this part
beforeEach(async() => {
fakeUsersService = {
findWithMail:() => Promise.resolve([]),
create:(email:string,password:string) =>
Promise.resolve({email,password} as User),
}
...
try to resolve the promise to be null not [] (empty array) or change your if(users) on your auth.service to be if(users.length > 0), why? it because empty array means to be thruthy value so when run through this process on your auth.service
if(email) {
const users = await this.userService.findWithMail(email);
// on this part below
if(users) {
throw new BadRequestException('email in use');
}
}
the 'users' executed to be truthy value so it will invoke the error. I hope my explanation will help you, thank you

Related

I get All NFTs ID when I only should get the one that belong to the current account

I get All NFTs token ID when I only should get the one that belongs to the current account.
I'm Also getting the token ID for the current account but also all the other Tokens ID.
How do i resolve this ?
This is the code
const { enableWeb3, account, isWeb3Enabled, Moralis, deactivateWeb3 } = useMoralis();
useEffect(() => {
if(isWeb3Enabled) return
if(typeof window !== 'undefined'){
if(window.localStorage.getItem('connected')){
enableWeb3();
}
}
}, [isWeb3Enabled])
useEffect(() => {
Moralis.onAccountChanged((account) => {
if(account == null){
window.localStorage.removeItem('connected')
deactivateWeb3();
}
})
}, [])
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://dxt.dexit.network"));
const contractAddress = "0x075D8f52dC5B1F35005dBa8Db40734CBcaBEbd8F"; // replace with the actual contract address
const abi = require('../abi.json'); // replace with the actual ABI of the contract
const contract = new web3.eth.Contract(abi.abi, contractAddress);
async function getNFTs(address) {
const events = await contract.getPastEvents("Transfer", {
filter: { to: address },
fromBlock: 0,
toBlock: "latest"
});
return events.map(event => event.returnValues.tokenId);
}
const test2 = async () => {
console.log(await getNFTs(account));
}
test2();
This is the result As you can see i get all NFTs but I only want token ID 45
return events.map(event => event.returnValues.tokenId);
you are mapping all the events that have returnValues.tokenId truthy. That means if returnValues.tokenIdhas any value you will map them into the new array. This is the logic of .map in javascript, you are mapping all the elements that satisfy the given condition.
You are most likely emitting an event when you create an NFT.
event NftItemCreated(
// you have returnValues
// I dont know what ReturnValuesStruct is, maybe you have address recorded here
ReturnValuesStruct returnValues,
// you should also add this field
address creator,
);
Now you will be logging the creator address of the NFT. Then you should write this logic
async function getNFTs(address) {
const events = await contract.getPastEvents("Transfer", {
filter: { to: address },
fromBlock: 0,
toBlock: "latest"
});
const addressNfts=events.map((event)=>{
if (event.creator===address){
return event.returnValues.tokenId
}
})
return addressNfts
}

Code runs after catch statement catches and error and returns in react native firebase

I am having issue whenever I catch an error and return from a function, by code after the catch block still runs. Here is my two functions that I use:
usernameTaken: async (username) => {
const user = await firebase.firestore().collection("uniqueUsers").doc(username).get();
if (user.exists) {
alert("Username is taken. Try again with another username.");
throw new Error('Username is taken. Try again with another username.');
}
},
changeUsername: async (currentUsername, newUsername) => {
try {
var user = Firebase.getCurrentUser();
Firebase.usernameTaken(newUsername);
} catch (err) {
alert(err.message);
return;
}
await db.collection('uniqueUsers').doc(currentUsername).delete();
await db.collection("users").doc(user.uid).update({username: newUsername});
await db.collection("uniqueUsers").doc(newUsername).set({username: newUsername});
alert("Congratulations! You have successfully updated your username.");
}
I would greatly appreciate any help for this problem, as I have been struggling with it for over 2 days now and can't seem to find a solution.
In your original code, the usernameTaken() promise is floating, because you didn't use await. Because it was floating, your catch() handler will never catch it's error.
changeUsername: async (currentUsername, newUsername) => {
try {
const user = Firebase.getCurrentUser();
/* here -> */ await Firebase.usernameTaken(newUsername);
} catch (err) {
alert(err.message);
return;
}
/* ... other stuff ... */
}
Additional Points
usernameTaken should return a boolean
You should change usernameTaken to return a boolean. This is arguably better rather than using alert() (which blocks execution of your code) or throwing an error.
usernameTaken: async (username) => {
const usernameDoc = await firebase.firestore().collection("uniqueUsers").doc(username).get();
return usernameDoc.exists; // return a boolean whether the doc exists
}
Securely claim and release usernames
Based on your current code, you have no protections for someone coming along and just deleting any usernames in your database or claiming a username that was taken between the time you last checked it's availability and when you call set() for the new username. You should secure your database so that a user can only write to a username they own.
Add the owner's ID to the document:
"/uniqueUsers/{username}": {
username: "username",
uid: "someUserId"
}
This then allows you to lock edits/deletions to the user who owns that username.
service cloud.firestore {
match /databases/{database}/documents {
match /uniqueUsers/{username} {
// new docs must have { username: username, uid: currentUser.uid }
allow create: if request.auth != null
&& request.resource.data.username == username
&& request.resource.data.uid == request.auth.uid
&& request.resource.data.keys().hasOnly(["uid", "username"]);
// any logged in user can get this doc
allow read: if request.auth != null;
// only the linked user can delete this doc
allow delete: if request.auth != null
&& request.auth.uid == resource.data.uid;
// only the linked user can edit this doc, as long as username and uid are the same
allow update: if request.auth != null
&& request.auth.uid == resource.data.uid
&& request.resource.data.diff(resource.data).unchangedKeys().hasAll(["uid", "username"]) // make sure username and uid are unchanged
&& request.resource.data.diff(resource.data).changedKeys().size() == 0; // make sure no other data is added
}
}
}
Atomically update your database
You are modifying your database in a way that could corrupt it. You could delete the old username, then fail to update your current username which would mean that you never link your new username. To fix this, you should use a batched write to apply all these changes together. If any one were to fail, nothing is changed.
await db.collection("uniqueUsers").doc(currentUsername).delete();
await db.collection("users").doc(user.uid).update({username: newUsername});
await db.collection("uniqueUsers").doc(newUsername).set({username: newUsername});
becomes
const db = firebase.firestore();
const batch = db.batch();
batch.delete(db.collection("uniqueUsers").doc(currentUsername));
batch.update(db.collection("users").doc(user.uid), { username: newUsername });
batch.set(db.collection("uniqueUsers").doc(newUsername), { username: newUsername });
await batch.commit();
Usernames should be case-insensitive
Your current usernames are case-sensitive which is not recommended if you expect your users to type/write out their profile's URL. Consider how "example.com/MYUSERNAME", "example.com/myUsername" and "example.com/myusername" would all be different users. If someone scribbled out their username on a piece of paper, you'd want all of those to go to the same user's profile.
usernameTaken: async (username) => {
const usernameDoc = await firebase.firestore().collection("uniqueUsers").doc(username.toLowerCase()).get();
return usernameDoc.exists; // return a boolean whether the doc exists
},
changeUsername: async (currentUsername, newUsername) => {
const lowerCurrentUsername = currentUsername.toLowerCase();
const lowerNewUsername = newUsername.toLowerCase();
/* ... */
return lowerNewUsername; // return the new username to show success
}
The result
Combining this all together, gives:
usernameTaken: async (username) => {
const usernameDoc = await firebase.firestore().collection("uniqueUsers").doc(username).get();
return usernameDoc.exists; // return a boolean
},
changeUsername: async (currentUsername, newUsername) => {
const user = Firebase.getCurrentUser();
if (user === null) {
throw new Error("You must be signed in first!");
}
const taken = await Firebase.usernameTaken(newUsername);
if (taken) {
throw new Error("Sorry, that username is taken.");
}
const lowerCurrentUsername = currentUsername.toLowerCase();
const lowerNewUsername = newUsername.toLowerCase();
const db = firebase.firestore();
const batch = db.batch();
batch.delete(db.collection("uniqueUsers").doc(lowerCurrentUsername));
batch.update(db.collection("users").doc(user.uid), {
username: lowerNewUsername
});
batch.set(db.collection("uniqueUsers").doc(lowerNewUsername), {
username: lowerNewUsername,
uid: user.uid
});
await batch.commit();
return lowerNewUsername;
}
// elsewhere in your code
changeUsername("olduser", "newuser")
.then(
(username) => {
alert("Your username was successfully changed to #" + username + "!");
},
(error) => {
console.error(error);
alert("We couldn't update your username!");
}
);
Note: If you are using all of the above recommendations (like the security rules), one of the expected ways batch.commit() will fail is if someone takes the username before the current user. If you get a permissions error, assume that someone took the username before you.
Try this check if your values are empty or not defined throw some error in try block e.g.
cosnt user = Firebase.getCurrentUser();
const name = Firebase.usernameTaken(newUsername);
// throwing error
if(name == "") throw "is empty";
await db.collection('uniqueUsers').doc(currentUsername).delete();

Unsure of how to unit test password validation function that uses bCrypt.compare(), getting undefined

I am trying to write a jest unit test for my validateUser function, but when I call the function in my test I am always getting the value undefined returned.
additional context
I am using nestJS as my api framework, I have mocked my user findOne query function to return expectedUserObjHashed.
I am using bcrypt to handle hashing and comparing passwords, I am using hashSync and compare from the library in this test
(not sure if this was the proper way to validate) but I added log statements to verify that my test is making it into the if(result) block and not throwing an exception.
I am guessing this is some async problem but I've been trying to a few hours a day for a couple days and im not sure where its occuring.
// This is the set up for my test
const saltRounds = 10;
let hashedPassword: string;
let expectedUserObjHashed: any;
beforeAll(() => {
hashedPassword = hashSync('test123!', saltRounds);
expectedUserObjHashed = {
id: 1,
email: 'test#test.com',
first_name: 'foo',
last_name: 'barr',
password: hashedPassword,
};
});
it('should validate password', async () => {
expect(
await service.validateUser(
// expectedUserObjUnhashed is a duplicate of expectedUserObjHashed minus having the password property hashed
expectedUserObjUnhashed.email,
expectedUserObjUnhashed.password,
),
// validatedUserObj is the same as the other UserObj objects but the password property is removed
).toStrictEqual(validatedUserObj);
});
async validateUser(email: string, password: string): Promise<any> {
// findUserByEmail() is mocked in the test to return expectedUserObjHashed (noted above before code blocks)
const user = await this.userService.findUserByEmail(email);
if (user !== undefined) {
compare(password, user.password, function (err, result) {
if (result) {
const { password, ...userInfo } = user;
console.log(userInfo);
return userInfo;
} else {
throw new UnauthorizedException();
}
});
} else {
throw new BadRequestException();
}
}
Update: I wrote a controller to test the validateUser() function in postman and it seems that it is not getting any return value (which is probably why the test got undefined) but right in the bcrypt.compare() callback (right before the return userInfo line) I logged userInfo and it is defined, so now i'm unsure why the callback is not returning userInfo as instructed.
Update 2:
I played around with my implementation of bcrypt.compare() and I got it to work like this:
...
const isMatch = await compare(password, user.password);
if (isMatch) {
const { password, ...userInfo } = user;
return userInfo;
} else {
throw new UnauthorizedException();
}
...
I would still like to know why my original implementation did not work for my knowledge.
compare(password, user.password, function (err, result) {
if (result) {
const { password, ...userInfo } = user;
console.log(userInfo);
return userInfo;
} else {
throw new UnauthorizedException();
}
Function inside compare is a callback that works after compare is done. So compare is firing and it returns nothing and if it's done that function is running so console.log it's working but return don't have any effect. When you added await you actually waited for compare to be complete.

Adonis JS Primary key value is missing for user

I'm trying to make a migratiion of users model from adonis to postgres but I keep getting this error Primary key value is missing for user
My model look like this:
class User extends Model {
static boot () {
super.boot()
this.addHook('beforeSave', async (userInstance) => {
if (userInstance.dirty.password) {
userInstance.password = await Hash.make(userInstance.password)
}
})
}
tokens () {
return this.hasMany('App/Models/Token')
}
}
module.exports = User
And the migration I'm trying to run is:
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class UserSchema extends Schema {
up () {
this.create('users', (table) => {
table.increments()
table.string('username', 80).notNullable().unique()
table.string('email', 254).notNullable().unique()
table.string('password', 60).notNullable()
table.timestamps()
})
}
down () {
this.drop('users')
}
}
module.exports = UserSchema
I tried adding primary() to table.increments() asuming it is generating the auto increments id in postgre database. When I check the database the user is sometimes added, but with id's that goes from 2 to 4 and so on
The controller looks like this
const User = use('App/Models/User')
class UserController {
async register({request, auth, response}) {
const username = request.input("username")
const email = request.input("email")
const password = request.input("password")
let user = new User()
user.username = username
user.email = email
user.password = password
user = await user.save()
let accessToken = await auth.generate(user)
return response.json({"user": user, "access_token": accessToken})
}
async login({request, auth, response}) {
const email = request.input("email")
const password = request.input("password");
try {
if (await auth.attempt(email, password)) {
let user = await User.findBy('email', email)
let accessToken = await auth.generate(user)
return response.json({"user":user, "access_token": accessToken})
}
}
catch (e) {
return response.json({message: 'You first need to register!'})
}
}
show ({ auth, params }) {
if (auth.user.id !== Number(params.id)) {
return "You cannot see someone else's profile"
}
return auth.user
}
}
module.exports = UserController
The problem is that you overwrite user model instance in user = await user.save().
as #Costin say's simply storing result of await user.save() in new variable will solve the issue.
for example:
...
let saved = await user.save()
let accessToken = await auth.generate(user)
...
table.increments() need column name as parameter -> table.increments(name)
Please read official knex documentation : https://knexjs.org/#Schema-increments
Example :
table.increments('id').primary()
The issue is with
user = await user.save()
that will return true when user is saved to db, passing that to auth.generate is what cause that error.
Just use something like
let success = await user.save()
and check success value after. Might want to return the token and 200 status only if user is saved (success is true).

How to get data passed to mongoose schema constructor

I am testing my application and need to verify that mongoose schema constructor is called with correct data.
let's say I do this:
const UserData = new User(user)
console.log(UserData.contructor.args)
I would expect log of the user object.
Probably the data is passed to constructor of mongoose schema?
Can some one please advise me how to access it?
Here is specific case I am trying to solve.
export const signup = async (req, res, next) => {
try {
//if user object is missing return error
if (!req.body.user)
return next(boom.unauthorized('No user data received.'))
//get user data
const user = req.body.user,
{ auth: { local: { password, password_2 } } } = user
//check if both passwords match
if (password !== password_2)
return next(boom.unauthorized('Passwords do not match.'))
//check if password is valid
if (!Password.validate(password)) {
const errorData = Password.validate(password, { list: true })
return next(boom.notAcceptable('Invalid password.', errorData))
}
//creates new mongo user
const UserData = new User(user)
//sets user password hash
UserData.setPassword(password)
//saves user to database
await UserData.save()
//returns new users authorization data
return res.json({ user: UserData.toAuthJSON() })
} catch(err) {
//if mongo validation error return callback with error
if(err.name === 'ValidationError') {
return next(boom.unauthorized(err.message))
}
// all other server errors
return next(boom.badImplementation('Something went wrong', err))
}
}
And part of test:
describe('Success', () => {
it('Should create new instance of User with request data', async () => {
const req = { body },
res = {},
local = { password: '1aaaBB', password_2: '1aaaBB'},
constructorStub = sandbox.stub(User.prototype, 'constructor')
req.body.user.auth.local = {...local}
await signup(req, res, next)
expect(constructorStub.calledOnceWith({...req.body.user})).to.be.true
})
})
EDIT: I can verify that is is called with expect(constructorStub.calledOnce).to.be.true
Just can't get to verify data passed.
Edit: After talking for some time sounds like what you need is to validate that you are creating a new user correctly.
My suggestion here is to create a new function createUserFromRequest that would take in request and return a new User.
You can then test this function easily as it's pure (no side effects, just input and output).
At this point, most of the logic in your handler is in this function so it would be probably not worth testing the handler itself, but you could still do it, for example by mocking the function above.
Example:
function createUserFromRequest(request) {
//get user data
const user = req.body.user,
{ auth: { local: { password, password_2 } } } = user
//check if both passwords match
if (password !== password_2)
return next(boom.unauthorized('Passwords do not match.'))
//check if password is valid
if (!Password.validate(password)) {
const errorData = Password.validate(password, { list: true })
return next(boom.notAcceptable('Invalid password.', errorData))
}
//creates new mongo user
const UserData = new User(user)
//sets user password hash
UserData.setPassword(password)
return UserData;
}
Please note: stubs and mocking are usually a code smell: there could either be a better way of testing, or it could be a sign of a need to refactor the code into something more easily testable. They usually point to tightly coupled or cluttered code.
Check out this great article on that topic: https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a

Categories

Resources