I was writing some integration tests for /POST endpoint for creating users. After my test block was done I wanted to remove the entry. Here is my code below.
it('Add member to DB', async () => {
var user_id;
chai.request(server)
.post('/post/createmember')
.set('Authorization', token)
.send({email: 'newmember#to.add', password: '12345678', projectsId: [], firstname: 'NewTest', lastname: 'member', phone: '0912345678'})
.end(async (err, res) => {
res.should.have.status(200)
res.body.should.have.lengthOf(1)
res.body[0].should.have.property('email', 'newmember#to.add')
res.body[0].should.have.property('password', '*')
res.body[0].should.have.property('firstName', 'NewTest')
res.body[0].should.have.property('lastName', 'member')
res.body[0].should.have.property('phone', '0912345678')
})
// Can't be deleted
await User.findOneAndDelete({'email':'newmember#to.add'})
done()
})
After my the tests are done, I wanted to delete the test entry. But the User.findOneAndDelete doesn't seem to work. What am I doing wrong?
The assertion above run fine.
Related
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.
I have created one React app. For data fetching, I used axios. My app works fine as expected. But in my terminal, I am getting warning like this Line 34:6: React Hook useEffect has a missing dependency: 'props.match.params.id'. Either include it or remove the dependency array react-hooks/exhaustive-deps. I don't want to use // eslint-disable-next-line react-hooks/exhaustive-deps. Is there any alternative solution?
useEffect(() => {
axios
.get("http://localhost:5000/students/" + props.match.params.id)
.then(response => {
setState({
name: response.data.name,
birthday: response.data.birthday,
address: response.data.address,
zipcode: response.data.zipcode,
city: response.data.city,
phone: response.data.phone,
email: response.data.email
});
})
.catch(function(error) {
console.log(error);
});
}, []);
If you don't want to disable the eslint rule, you need to follow it, simply add the prop to your dependency array:
useEffect(() => {
axios
.get("http://localhost:5000/students/" + props.match.params.id)
.then(response => {
setState({
name: response.data.name,
birthday: response.data.birthday,
address: response.data.address,
zipcode: response.data.zipcode,
city: response.data.city,
phone: response.data.phone,
email: response.data.email
});
})
.catch(function(error) {
console.log(error);
});
}, [props.match.params.id]);
This means that if the id changes, your effect will be unmounted and called again, which seems to make sense, considering the match.params.id is used inside your effect.
I am getting inconsistent results with JEST when I try to remove items from a MongoDB Collection using the beforeEach() Hook.
My Mongoose schema and model defined as:
// Define Mongoose wafer sort schema
const waferSchema = new mongoose.Schema({
productType: {
type: String,
required: true,
enum: ['A', 'B'],
},
updated: {
type: Date,
default: Date.now,
index: true,
},
waferId: {
type: String,
required: true,
trim: true,
minlength: 7,
},
sublotId: {
type: String,
required: true,
trim: true,
minlength: 7,
},
}
// Define unique key for the schema
const Wafer = mongoose.model('Wafer', waferSchema);
module.exports.Wafer = Wafer;
My JEST tests:
describe('API: /WT', () => {
// Happy Path for Posting Object
let wtEntry = {};
beforeEach(async () => {
wtEntry = {
productType: 'A',
waferId: 'A01A001.3',
sublotId: 'A01A001.1',
};
await Wafer.deleteMany({});
// I also tried to pass in done and then call done() after the delete
});
describe('GET /:id', () => {
it('Return Wafer Sort Entry with specified ID', async () => {
// Create a new wafer Entry and Save it to the DB
const wafer = new Wafer(wtEntry);
await wafer.save();
const res = await request(apiServer).get(`/WT/${wafer.id}`);
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('productType', 'A');
expect(res.body).toHaveProperty('waferId', 'A01A001.3');
expect(res.body).toHaveProperty('sublotId', 'A01A001.1');
});
}
So the error I always get is related to duplicate keys when I run my tests more than once:
MongoError: E11000 duplicate key error collection: promis_tests.promiswts index: waferId_1_sublotId_1 dup key: { : "A01A001.3", : "A01A001.1" }
But I do not understand how I can get this duplicate key error if the beforeEach() were firing properly. Am I trying to clear the collection improperly? I've tried passing in a done element to the before each callback and invoking it after delete command. I've also tried implementing the delete in beforeAll(), afterEach(), and afterAll() but still get inconsistent results. I'm pretty stumped on this one. I might just removed the schema key all together but I would like to understand what is going on here with the beforeEach(). Thanks in advance for any advice.
It might be because you are not actually using the promise API that mongoose has to offer. By default, mongooses functions like deleteMany() do not return a promise. You will have to call .exec() at the end of the function chain to return a promise e.g. await collection.deleteMany({}).exec(). So you are running into a race condition. deleteMany() also accepts a callback, so you could always wrap it in a promise. I would do something like this:
describe('API: /WT', () => {
// Happy Path for Posting Object
const wtEntry = {
productType: 'A',
waferId: 'A01A001.3',
sublotId: 'A01A001.1',
};
beforeEach(async () => {
await Wafer.deleteMany({}).exec();
});
describe('GET /:id', () => {
it('Return Wafer Sort Entry with specified ID', async () => {
expect.assertions(4);
// Create a new wafer Entry and Save it to the DB
const wafer = await Wafer.create(wtEntry);
const res = await request(apiServer).get(`/WT/${wafer.id}`);
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('productType', 'A');
expect(res.body).toHaveProperty('waferId', 'A01A001.3');
expect(res.body).toHaveProperty('sublotId', 'A01A001.1');
});
}
Also, always expect the assertions with asynchronous code
https://jestjs.io/docs/en/asynchronous.html
You can read more about mongoose promises and query objects here
https://mongoosejs.com/docs/promises.html
Without deleting the schema index this seems to be the most reliable solution. Not 100% sure why it works over async await Wafer.deleteMany({});
beforeEach((done) => {
wtEntry = {
productType: 'A',
waferId: 'A01A001.3',
sublotId: 'A01A001.1',
};
mongoose.connection.collections.promiswts.drop(() => {
// Run the next test!
done();
});
});
I have a mongoose schema with a unique field and I am trying to write a backend (express) integration test which checks that POSTing the same entity twice results in HTTP 400. When testing manually behaviour is as excpected. Automatic testing however requires a wait:
it('should not accept two projects with the same name', function(done) {
var project = // ...
postProjectExpect201(project,
() => {
setTimeout( () => {
postProjectExpect400(project, done);
},100);
}
);
});
The two post... methods do as named and the code above works fine, but if the timeout is removed, BOTH requests receive HTTP 200 (though only one entity created in the database).
I'm new to those technologies and I'm not sure what's going on. Could this be a mongodb related concurrency issue and if so how should I deal with it?
The database call looks like this:
Project.create(req.body)
.then(respondWithResult(res, 201))
.catch(next);
I already tried connecting to mongodb with ?w=1 option btw.
Update:
To be more verbose: Project is a mongoose model and next is my express error handler which catches the duplicate error.
The test functions:
var postProjectExpect201=function(project, done, validateProject) {
request(app)
.post('/api/projects')
.send(project)
.expect(201)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
validateProject && validateProject(res.body);
done();
});
};
var postProjectExpect400=function(project, done) {
request(app)
.post('/api/projects')
.send(project)
.expect(400)
.end((err, res) => {
if (err) {
return done(err);
}
done();
});
};
I've been all over SO and Sailsjs.org trying to figure out what's going wrong, and to no avail. Just trying to learn the basics of SailsJS. I have a UserController, whose create() method gets called when a POST request is sent to /user.
create: function (req, res) {
var params = req.params.all();
User.create({
name: params.FirstName + ' ' + params.LastName,
email: params.Email,
password: params.Password,
jobTitle: params.JobTitle
}).exec(function createCB(err,created)
{
created.save(function(err)
{
// No error . . . still nothing in db
});
return res.json({name: created.name, jobTitle: created.jobTitle, email: created.email, password: created.password});
});
}
No errors here. All the request params are coming in fine and going back to the client without trouble. But nothing is actually being written to the database.
In development.js:
connections: {
mongo: {
adapter: 'sails-mongo',
host: 'localhost',
port: 27017,
// user: 'username',
// password: 'password',
database: 'sails_test'
}
},
models: {
connection: 'mongo'
}
I've tried this with the above both there in development.js, as well as separately in connections.js and models.js, respectively. No difference.
In User.js:
attributes: {
FirstName : { type: 'string' },
LastName : { type: 'string' },
Email : { type: 'string' },
Password : { type: 'string' },
JobTitle : { type: 'string' }
}
My front end request:
$.ajax({
method: 'post',
url: '/user',
data: {
FirstName: 'Yo',
LastName: 'Momma',
Email: 'yourmom#yourdadshouse.com',
Password: 'YouWish123',
JobTitle: 'Home Maker Extraordinaire'
},
success: function (sailsResponse)
{
$('#result').html(sailsResponse).fadeIn();
},
error: function()
{
console.log('error');
}
});
Again, none of this is producing an explicit error. There is just nothing being inserted into the database. Or if there is, I don't know how to find it. I've confirmed the existence of this db in the mongo shell, thusly:
show dbs
My db, sails_test shows up in the list. And I've confirmed that there isn't anything in it like so:
db.sails_test.find()
I would very much appreciate some guidance here :)
Update:
Turns out the data is being written just fine. I'm just unable to query the database from the command line. I confirmed this by first creating a sample user, and then using Waterline's findOne() method:
User.findOne({FirstName: params.FirstName}).exec(function (err, user) {
if (err) {
res.send(400);
} else if (user) {
return res.json({firstName: user.FirstName, lastName: user.LastName, jobTitle: user.JobTitle, email: user.Email, password: user.Password});
} else {
return res.send('no users match those criteria');
}
});
The above works as expected. So my problem now is simply that I cannot interact with the database from the command line. db.<collectionName>.find({}) produces nothing.
This was simply a failure to understand the MongoDb docs. I read db.collection.find({}) as DatabaseName.CollectionName.find({}), when you literally need to use db. So if my database is Test, and my collection is Users, the query is use Test, and then db.Users.find({}).
Also of note, 3T Mongo Chef is a pretty rockin' GUI (graphical user interface) for nosql databases, and it's free for non-commercial use.