How to stub query helper method Mongoose? - node.js

I'm using Sinon to test my Express/Typescript application. In Mongoose I have models with query helper methods to make query chains.
I have this schema
export const articuloSchema = new Schema({
_id: { type: String, default: v4 },
numeroInventario: { type: String, required: true, unique: true },
descripcion: { type: String, trim: true },
}, {
versionKey: false,
query: {
paginate(page: number, limit: number) {
return this.skip(page - 1).limit(limit);
}
}
});
export type ArticuloType = InferSchemaType<typeof articuloSchema>;
export const Articulo = mongoose.model('Articulo', articuloSchema);
and I want to fake my query helper method with Sinon like this
const getMultipleArticulos (): ArticuloType[] => {
const arr:ArticuloType = []
for (let i = 0; i < 5; i++) {
arr.push({
numeroInventario: i,
descripcion: 'string'
})
}
return arr;
}
it('Should return a list of items that belong to a user', function (done) {
const paginateFake = sinon.fake.resolves(getMultipleArticulos());
sinon.replace(Articulo, 'paginate', paginateFake);
chai.request(app)
.get(`/api/v1/users/${userId}/articulos`)
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(200);
// more expects
done();
});
});
The problem is that I cannot stub those methods, it says that it can't stub non existent property of 'paginate' (which is the query helper method I added to my model).

Related

MongoDB UpdateMany Method

how can i use the update Many Method inside my code?. Right now this code will insert the data over and over inside the table, i need to make it so it will update the old data and add new data if new data is available in the third party api. I'm new to MongoDB any help will be much appreciate it! thanks.
cron.schedule('0 0 * * *', async () => {
const postSchema = new mongoose.Schema({
id: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
status: {
type: String,
required: false
},
});
const Post = mongoose.model('players', postSchema);
async function getPosts() {
const getPlayers = await fetch("http://localhost:3008/api/players");
const response = await getPlayers.json();
for( let i = 0;i < response.players.length; i++){
const post = new Post({
id: response.players[i]['id'],
name: response.players[i]['name'],
status: response.players[i]['status'],
});
post.save();
}
}
console.log("Task submitted successfully")
await getPosts();
});
what i was thinking
const post = await Post.updateMany({
id: response.players[i]['id'],
name: response.players[i]['name'],
status: response.players[i]['status'],
});
I believe you can use bulkWrite and inside bulkWrite you can write updateOne operation with the upsert flag set to true
You can construct an array of operations as follows
let operations = [];
for (let player of players) {
operations.push({
updateOne: {
filter: { id: player.id },
update: { $set: { name: player.name } },
upsert: true,
}
})
}
And finally you can make a call to bulkWrite. Please read the documentation for the operations above

Mongoose post save hook, modifiedPaths() is always empty

Our aim is to have a post hook in place where we can track changed fields.
Model file:
const VariationSchema = new Schema({
_id: {
type: String,
},
title: {
type: String,
},
desc: {
type: String,
},
});
VariationSchema.post('save', async function (doc) {
console.log(doc.modifiedPaths());
});
const VariationModel = mongoose.model('variation', VariationSchema);
module.exports = {
VariationModel,
VariationSchema,
};
Service file:
const variationDocument = await VariationModel.findById(variationId).select({});
variationDocument.desc = (Math.random() + 1).toString(36).substring(7);
await variationDocument.save();
return variationDocument.toJSON();
No matter what we do, doc.modifiedPaths() is always empty. Please help

How can I subclass Model and properly add a static method?

I'm learning how to use Sequelize as the ORM for a small web app. Each record in the (MySQL) database has a uuid field. I want to add a findByUuid static method to all of the models. Here's my attempt:
class Base extends Model {
static findByUuid = async (uuid) => {
return await super.findOne({ where: {uuid: uuid} });
}
}
class List extends Base {
}
List.init({
uuid: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: Sequelize.UUIDV4
},
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
nodelName: 'List',
indexes: [
{ fields: ['name'] },
{ fields: ['uuid'] }
]
});
(async () => {
await sequelize.sync({ force: true });
const list = List.build({name: 'New List'});
await list.save();
console.log('id: ' + list.id);
console.log('uuid: ' + list.uuid);
const direct_retrieved = await List.findOne({ where: {uuid: list.uuid} });
console.log('direct name: ' + direct_retrieved.name);
const retrieved = await List.findByUuid(list.uuid);
console.log('static retrieved: ' + retrieved.name);
})();
And here's the output (excluding the echoed SQL commands):
id: 1
uuid: 371971dd-a4d9-43aa-ac10-afe3c10154b0
direct name: New List
/Users/chuck/Projects/app/node_modules/sequelize/lib/model.js:1692
this.warnOnInvalidOptions(options, Object.keys(this.rawAttributes));
^
TypeError: Cannot convert undefined or null to object
at Function.keys (<anonymous>)
at Function.findAll (/Users/chuck/Projects/app/node_modules/sequelize/lib/model.js:1692:47)
at Function.findOne (/Users/chuck/Projects/app/node_modules/sequelize/lib/model.js:1917:23)
at Function.findByUuid (/Users/chuck/Projects/app/models/models.js:7:36)
at /Users/chuck/Projects/app/models/models.js:181:34
at processTicksAndRejections (node:internal/process/task_queues:93:5)
So directly working with findOne is working, but trying to do so within the static method causes the error, and I have not been able to figure out why.
I think the problem is that the Base class has no knowledge of a uuid field. If I put the static method into the List class, it works properly. So the only solution I have so far is to repeat that code for every actual table since I don't (yet) see a way to define the uuid field in the Base class (which would be preferable anyway.
It turns out that I needed to override init to let Base know about the uuid field. Here's the working solution.
const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = require('./helpers/sequelize');
class Base extends Model {
static findByUuid = async (uuid) => {
return await super.findOne({ where: {uuid: uuid} });
}
static init = (attributes, options = {}) => {
attributes.uuid = {
type: DataTypes.UUID,
allowNull: false,
defaultValue: Sequelize.UUIDV4
};
options.indexes.push({ fields: ['uuid'] });
super.init(attributes, options);
}
}
class List extends Base {
}
List.init({
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'List',
indexes: [
{ fields: ['name'] }
]
});
(async () => {
await sequelize.sync({ force: true });
const list = List.build({name: 'New List'});
await list.save();
console.log('id: ' + list.id);
console.log('uuid: ' + list.uuid);
const direct_retrieved = await List.findOne({ where: {uuid: list.uuid} });
console.log('direct: ' + direct_retrieved.name);
const retrieved = await List.findByUuid(list.uuid);
console.log('static: ' + retrieved.name);
})();

TypeScript, Mongoose, Jasmine - requiring schemas

I've been struggling for days with how to build a proper test workflow. I realise that tests should mock database dependencies but the case is that I need to test the whole process, together with real db queries.
In the following code I need to require my model so that I could perform operations on db such as deleting test data and pushing test data.
The questions are:
What is the proper way of referencing the schema/model?
Or is there a way of writing Jasmine tests using typescript?
The code does not work, as it says that BucketConfigS.remove is not a function:
'use strict';
let BucketConfigS = require('../dist/app/BucketConfig/BucketConfigSchema');
describe('Bucket config collection:', () => {
describe('GetAll service -', () => {
describe('Given that there are no configs', function () {
beforeEach(done => {
done();
});
afterEach(done => {
BucketConfigS.remove({}, done);
done();
});
it('should return an empty array', function () {
// test case
});
});
});
});
I also tried the following require line:
let BucketConfigS = require('../dist/app/BucketConfig/BucketConfigSchema').default;
However it brokes the whole test suite (no test results are written out).
The schema file looks like this:
"use strict";
var DataAccess_1 = require("./../common/DataAccess");
var mongoose = DataAccess_1.DataAccess.mongooseInstance;
var mongooseConnection = DataAccess_1.DataAccess.mongooseConnection;
var BucketConfigSchema = (function () {
function BucketConfigSchema() {
}
Object.defineProperty(BucketConfigSchema, "schema", {
get: function () {
var schema = mongoose.Schema({
AppName: {
type: String,
required: true
},
Platform: {
type: String,
required: true
},
Segment: {
type: String,
required: true
},
UpdateTime: {
type: Date,
default: Date.now
}
});
return schema;
},
enumerable: true,
configurable: true
});
return BucketConfigSchema;
}());
var BucketConfig = mongooseConnection.model("BucketConfig", BucketConfigSchema.schema);
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = BucketConfig;
This is a compilation result of:
import { DataAccess } from "./../common/DataAccess";
import { IBucketConfig } from "./IBucketConfig";
let mongoose = DataAccess.mongooseInstance;
let mongooseConnection = DataAccess.mongooseConnection;
class BucketConfigSchema {
static get schema() {
let schema = mongoose.Schema({
AppName: {
type: String,
required: true
},
Platform: {
type: String,
required: true
},
Segment: {
type: String,
required: true
},
UpdateTime: {
type: Date,
default: Date.now
}
});
return schema;
}
}
let BucketConfig = mongooseConnection.model<IBucketConfig>("BucketConfig", BucketConfigSchema.schema);
export default BucketConfig;
Not sure why, but
let BucketConfigS = require('../dist/app/BucketConfig/BucketConfigSchema').default;
started working...

GraphQL - passing an ObjectType a parameter

I'm using GraphQL and it's working great, however, I can't seem to figure out how to pass a parameter into the fields section of my Event GraphQLObjectType.
I would like to be able to pass in the currentUserId (which is given to me through a token) into the Event GraphQLObjectType so I can add in an isAttending attribute.
I've attached code with comments of what I'm basically trying to do:
const Event = new GraphQLObjectType({
name: 'Event',
description: 'This represents an Event',
fields: (currentUserId) => { // currentUserId is the parameter I would like to pass in
return {
id: {
type: GraphQLInt,
resolve (event) {
return event.id;
}
},
title: {
type: GraphQLString,
resolve (event) {
return event.title;
}
},
attendees: {
type: new GraphQLList(User),
resolve (event) {
return event.getAttendees()
}
},
// this is what I would like to do
isAttending: {
type: GraphQLBool,
resolve (event) {
return event.getAttendees({
where: {
id: currentUserId // that's the parameter I would like pass in
}
}).then(attendee => {
return (attendee.length > 0 ? true : false);
)};
}
}
// end of what I'm trying to do //
};
}
});
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => {
return {
events: {
type: new GraphQLList(Event),
args: {
id: {
type: GraphQLInt
}
},
resolve (root, args) {
// here is the parameter I would like to pass to the event object
let currentUserId = root.userId;
////////
return Db.models.event.findAll({ where: args });
}
},
...
Update
The reason I can't just do data.currentUserId = root.userId, is because it's not visible when I'm returned a collection of event objects, since what is passed into my Event GraphQLOBjectType is only the {event} object.
What it looks like when I do data.currentUserId and there is an array of objects inside data is this:
[{objects}, currentUserId: 1]
As opposed to what we want which is this:
[{object, currentUserId: 1}, {anotherObject, currentUserId: 1}]
If I wanted to have access to the currentUserId in the Event GraphQLObject, the only thing I can think of is to loop through every object and add the currentUserId onto it like this:
return events.map(event => {
event.currentUserId = currentUserId;
return event;
});`
Is this the best solution?
I'm afraid you can't do that. fields doesn't recieve any parameters, so you won't send any either.
Fortunately, you can achieve that in more convenient way.
Everything your parent type (Query) returns in resolve function is visible in child resolve's root parameter.
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => ({
events: {
type: new GraphQLList(Event),
args: {
id: {
type: GraphQLInt
}
},
resolve (root, args) {
return Db.models.event.findAll({ where: args })
.then(data => {
// pass the parameter here
data.currentUserId = root.userId;
return data;
});
}
},
...
Then your Event object would look like this:
const Event = new GraphQLObjectType({
name: 'Event',
description: 'This represents an Event',
fields: () => ({
...
isAttending: {
type: GraphQLBool,
resolve: (event) => {
return event.getAttendees({
where: {
id: event.currentUserId // that's the parameter you've passed through parent resolve
}
}).then(attendee => {
return (attendee.length > 0 ? true : false);
});
}
}
})
});

Resources