Rspec is not working properly with mongodb - mongodb

I have the following user class
class User
include MongoMapper::Document
key :email, String, :unique => true
end
and my rspec code
before do
#user = FactoryGirl.create(:user)
end
describe "when email address is already taken" do
before do
user_with_same_email = #user.dup
user_with_same_email.email = #user.email
user_with_same_email.save
end
it { should_not be_valid }
end
raises the following error
1) User when email address is already taken
Failure/Error: it { should_not be_valid }
expected #<User _id: BSON::ObjectId('5236bec5ebe8660fcb00001a'), accepted_tender_ids: [], avatar: #<Avatar _id: BSON::ObjectId('5236bec3ebe8660fcb000001'), digest: "c7358a60a79905a7d4e3383ba905f1baaab278b06a6f751971344cb91763068f", image: "c7358a60a79905a7d4e3383ba905f1baaab278b06a6f751971344cb91763068f">, busy: false, completed_count: 0, completed_tender_ids: [], confirmed: true, dialogs: [], dislikes: 0, email: "maurice_langworth#treutel.com", role: "customer", subspecializations: [], type: "personal"> not to be valid
Expected user is not valid but it is valid

user_with_same_email will fail to save, so there will not be a second user in the database with your email; assuming that subject is #user, there will be no conflict and the user will be valid.
Did you forget to define subject as user_with_same_email?

Related

Mongoose 6 - How to get only the custom validation error message without the error?

const userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: [true, 'Email address is required'],
minlength: [4, 'Must be at least 4 characters long']
}
});
When I log the error I get:
User validation failed: email: Must be at least 4 characters long
How can I get only my custom message:
Must be at least 4 characters long
The only thing I do as of now is
const messageString = message.split(':');
messageString[3]
But is there a better way? I saw some solutions for this but its all for mongoose version 5 or less and it doesnt apply to version 6 as the error object has different properties.
On a side note (maybe this should be a seperate question) but as the error object is different now, how can I check if its a mongoose validation error? The err object doesnt come with it anymore.
Error: User validation failed: email: Must be at least 4 characters long
at ValidationError.inspect (/Users/... {
errors: {
email: ValidatorError: Must be at least 4 characters long
at validate (/Users/user/....
at processTicksAndRejections (node:internal/process/task_queues:78:11) {
properties: [Object],
kind: 'minlength',
path: 'email',
value: 'hgg',
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'User validation failed'
}
Also, I dont want to have to access the modal property 'email' when accessing any other property as in my errorhandler, I want to keep it reusable for other errors too

How can I retrieve an id from MongoDB create operation during a transaction?

I am trying to create an audit trail using Apollo Server and Mongoose. When a user initially registers, I create a document in the users collection and a document in the history collection for each piece of data they provided (username, password, email, etc) . For each history collection document, I include the id for the user document to create a relationship. Works perfectly.
However, when I add a transaction in (see below), the userId for the user document comes back as undefined, so I cannot add it to the history entry documents. I am assuming that the id for a document does not get created until the entire transaction has been completed?
Any ideas?
Mutation: {
register: async (_, { data }) => {
// Start a mongo session & transaction
const session = await mongoose.startSession()
session.startTransaction()
try {
// Hash password and create user
const hashedPassword = await bcrypt.hash(data.password, 12)
const user = await User.create(
[{ ...data, password: hashedPassword }],
{ session }
)
// Add history entries
HistoryEntry.create([
{
user: user.id,
action: 'registered'
},
{
user: user.id,
action: 'set',
object: 'profile',
instance: user.id,
property: 'firstName',
value: firstName
},
{
user: user.id,
action: 'set',
object: 'profile',
instance: user.id,
property: 'lastName',
value: lastName
},
{
user: user.id,
action: 'set',
object: 'profile',
instance: user.id,
property: 'password'
}
])
if (loginType === 'email') {
HistoryEntry.create({
user: user.id,
action: 'set',
object: 'profile',
instance: user.id,
property: 'email',
value: login
})
}
if (loginType === 'mobile') {
HistoryEntry.create({
user: user.id,
action: 'set',
object: 'profile',
instance: user.id,
property: 'mobile',
value: login
})
}
// commit the changes if everything was successful
await session.commitTransaction()
return {
ok: true,
user
}
} catch (err) {
// if anything fails above, rollback the changes in the transaction
await session.abortTransaction()
return formatErrors(err)
} finally {
// end the session
session.endSession()
}
}
}
If you think about it, how can you add a HistoryEntry if you haven't added User yet? It's not a 'history' as you are currently doing it. I believe you got two options here - set _id on User manually new Schema({ _id: { type: Schema.ObjectId, auto: true }}) and then generate it within the transaction: var userId = ObjectId(); and use for both User and History Entries.
And the second option, more semantically correct in this context, I believe - you should attach to post-save hook:
schema.post('save', function(doc) {
console.log('%s has been saved', doc._id);
});
So, whenever an User is created, a post-save hook is fired to update History.
Came across the same issue recently, hope you have figured it out already. I may add this for future seekers.
Following create function returns an array of created documents.
const user = await User.create(
[{ ...data, password: hashedPassword }],
{ session }
);
Therefore access the user id as user[0]._id
Pass the session also to HistoryEntry.create()
HistoryEntry.create([{...},{...}], {session})
Note: In this use case, I personally prefer #marek second option to use a post-save hook.

Create user with avatar

I want to add an avatar in the user registration, but I don't know how, Please can someone share with me a full example (form, JS front, and JS backend). I'm using SailsJS 1.0 (the stable version) with VueJs.
Thanks in advance .
I figured it out. Watch these platzi tutorials:
https://courses.platzi.com/classes/1273-sails-js/10757-uploading-backend-file/
https://courses.platzi.com/classes/1273-sails-js/10758-uploading-frontend-files/
https://courses.platzi.com/classes/1273-sails-js/10759-downloading-files/
Here is what the videos tell you to do:
npm i sails-hook-uploads.
In api/controllers/entrance/signup.js
Above inputs key add a new key/value of files: ['avatar'],
In the inputs add:
avatar: {
type: 'ref',
required: true
}
In the body of the fn find var newUserRecord and above this add (even if avatar is not required, make sure to do this line, otherwise you will have a "timeout of unconsuemd file stream":
const avatarInfo = await sails.uploadOne(inputs.avatar);
Then in the first argument object of var newUserRecord = await User.create(_.extend({ add:
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
In api/models/User.js, add these attributes to your User model:
avatarFd: {
type: 'string',
required: false,
description: 'will either have "text" or "avatarFd"'
},
avatarMime: {
type: 'string',
required: false,
description: 'required if "avatarFd" provided'
},
Then create a download endpoint, here is how the action would look for it:
const user = await User.findOne(id);
this.res.type(paste.photoMime);
const avatarStream = await sails.startDownload(paste.photoFd);
return exits.success(avatarStream);
Add to the routes a route for this download avatar endpoint.
Then you can display this avatar by pointing the <img src=""> the source in here to this download endpoint.
------APPENDIX-----
----signup.js-----
module.exports = {
friendlyName: 'Signup',
description: 'Sign up for a new user account.',
extendedDescription:
`This creates a new user record in the database, signs in the requesting user agent
by modifying its [session](https://sailsjs.com/documentation/concepts/sessions), and
(if emailing with Mailgun is enabled) sends an account verification email.
If a verification email is sent, the new user's account is put in an "unconfirmed" state
until they confirm they are using a legitimate email address (by clicking the link in
the account verification message.)`,
files: ['avatar'],
inputs: {
emailAddress: {
required: true,
type: 'string',
isEmail: true,
description: 'The email address for the new account, e.g. m#example.com.',
extendedDescription: 'Must be a valid email address.',
},
password: {
required: true,
type: 'string',
maxLength: 200,
example: 'passwordlol',
description: 'The unencrypted password to use for the new account.'
},
fullName: {
required: true,
type: 'string',
example: 'Frida Kahlo de Rivera',
description: 'The user\'s full name.',
},
avatar: {
}
},
exits: {
success: {
description: 'New user account was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided fullName, password and/or email address are invalid.',
extendedDescription: 'If this request was sent from a graphical user interface, the request '+
'parameters should have been validated/coerced _before_ they were sent.'
},
emailAlreadyInUse: {
statusCode: 409,
description: 'The provided email address is already in use.',
},
},
fn: async function (inputs) {
var newEmailAddress = inputs.emailAddress.toLowerCase();
// must do this even if inputs.avatar is not required
const avatarInfo = await sails.uploadOne(inputs.avatar);
// Build up data for the new user record and save it to the database.
// (Also use `fetch` to retrieve the new ID so that we can use it below.)
var newUserRecord = await User.create(_.extend({
emailAddress: newEmailAddress,
password: await sails.helpers.passwords.hashPassword(inputs.password),
fullName: inputs.fullName,
tosAcceptedByIp: this.req.ip,
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
}, sails.config.custom.verifyEmailAddresses? {
emailProofToken: await sails.helpers.strings.random('url-friendly'),
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
emailStatus: 'unconfirmed'
}:{}))
.intercept('E_UNIQUE', 'emailAlreadyInUse')
.intercept({name: 'UsageError'}, 'invalid')
.fetch();
// If billing feaures are enabled, save a new customer entry in the Stripe API.
// Then persist the Stripe customer id in the database.
if (sails.config.custom.enableBillingFeatures) {
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
emailAddress: newEmailAddress
}).timeout(5000).retry();
await User.updateOne(newUserRecord.id)
.set({
stripeCustomerId
});
}
// Store the user's new id in their session.
this.req.session.userId = newUserRecord.id;
if (sails.config.custom.verifyEmailAddresses) {
// Send "confirm account" email
await sails.helpers.sendTemplateEmail.with({
to: newEmailAddress,
subject: 'Please confirm your account',
template: 'email-verify-account',
templateData: {
fullName: inputs.fullName,
token: newUserRecord.emailProofToken
}
});
} else {
sails.log.info('Skipping new account email verification... (since `verifyEmailAddresses` is disabled)');
}
// add to pubilc group
const publicGroup = await Group.fetchPublicGroup();
await Group.addMember(publicGroup.id, newUserRecord.id);
}
};

"email" validation rule crash sails server - Mongo with Sails.js

while the email validation rule fails on module of the sails.js, the server is crashing.
Here the snippet of my module:
// The user's email address
email: {
type: 'string',
email: true,
required: true,
unique: true
},
And the error as below :
err: Error (E_VALIDATION) :: 1 attribute is invalid
at WLValidationError.WLError (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\error\WLError.js:26:15)
at new WLValidationError (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\error\WLValidationError.js:20:28)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\query\validate.js:45:43
at allValidationsChecked (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:203:5)
at done (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:135:19)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:32:16
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:184:23
at done (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:135:19)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:32:16
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:157:64
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:125:13
at Array.forEach (native)
at _each (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:46:24)
at Object.async.each (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:124:9)
at validate (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:156:11)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:125:13
Invalid attributes sent to User:
• email
• undefined should be a email (instead of "admin#gmailasd", which is a string)
The correct way to declare an email field is like this :
email: {
type: 'email',
required: true,//Email field will be required for insert or update
unique: true //Insert or update will crash if you try to insert duplicate email
},
You can see all different attribut types here http://sailsjs.org/documentation/concepts/models-and-orm/attributes
If you want to catch insert/update errors you can do this on your controller :
MyModel.create({email:email}).exec(function(err, model)
{
if(err)
{
//Check if it's a validation error or a crash
if(err.code == "E_VALIDATION")
sails.log.debug("valid fail, check form");
else
sails.log.debug("crash");
}
else
{
//do what you want to do with the data
}
});
Her the answer.
Thanks to jaumard, i found the problem.
I used undefined field in error, without checking if exists before
err.originalError.code but it was undefined.
So the correct way is :
err.originalError && err.originalError.code && err.originalError.code === 11000
and not
err.originalError.code === 11000.
Previous versions of Sails recommended that email validation was achieved like this
email: {
type: 'string',
email: true,
required: true
},
The current version should be like this
email: {
type: 'email',
required: true
},

Cannot save a document in Mongoose - Validator required failed for path error

Following is my schema:
var userSchema = new Schema({
username: {
type: String,
required: true
},
password: {
type: String,
required: false
}
});
Now, when I attempt to save a document of the above schema, I get the following error:
{ message: 'Validation failed',
name: 'ValidationError',
errors:
{ username:
{ message: 'Validator "required" failed for path username',
name: 'ValidatorError',
path: 'username',
type: 'required' } } }
The above is the error object returned by mongoose upon save. I searched for this error but could not understand what is wrong. The document that I am trying to save is as follows:
{
username: "foo"
password: "bar"
}
Any idea what this means? I searched the mongoose docs too but could not find anything under the validation section.
First, you are missing a comma (,) after foo.
Now, is { username: "foo", password: "bar" } JSON sent via http, our an actual object in your server-side code ?
If it is, try to console.log(youVariable.username) and see if it shows undefined or the value foo. If you see undefined, then your object is not parsed properly.
You can make sure that whom ever is sending the POST request is sending a "application/json" in the header, you could be receiving something else, thus your JSON isn't parsed to a valid javascript object.