What is the perfect replace of toInclude() assertion in expect new version? - node.js

Stuck with this.I am trying to test my login API using expect and new version of expect throwing me some error.
That's my testing code.
it('should login user and return auth token', (done) => {
request(app)
.post('/users/login')
.send({
email : users[1].email,
password : users[1].password
})
.expect((res) => {
expect(res.headers['x-auth']).toBeTruthy();
})
.end((error,res) => {
if(error)
{
return done(error);
}
User.findById(users[1]._id).then((user) => {
expect(user.tokens[0]).toMatchObject({
access : 'auth',
token : res.headers['x-auth']
});
done();
}).catch((error) => done(error));
});
});
And error is
1) POST /users/login
should login user and return auth token:
Error: expect(received).toMatchObject(expected)
Expected value to match object:
{"access": "auth", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1OWYwMzM0ZGExMzRmYjFmNzg4NTkzOTciLCJhY2Nlc3MiOiJhdX
RoIiwiaWF0IjoxNTA4OTE0MDEzfQ.S0KCmLADcCLPWTK1khxNPO03tVMTW0HU117xapm56MM"}
Received:
{"_id": "59f0335da134fb1f788593b3", "access": "auth", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1OWYwMzM0ZGExMzR
mYjFmNzg4NTkzOTciLCJhY2Nlc3MiOiJhdXRoIiwiaWF0IjoxNTA4OTE0MDEzfQ.S0KCmLADcCLPWTK1khxNPO03tVMTW0HU117xapm56MM"}
Difference:
- Expected
+ Received
Object {
+ "_id": "59f0335da134fb1f788593b3",
"access": "auth",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1OWYwMzM0ZGExMzRmYjFmNzg4NTkzOTciLCJhY2Nlc3MiOiJhdXRoIiwiaWF0IjoxNTA
4OTE0MDEzfQ.S0KCmLADcCLPWTK1khxNPO03tVMTW0HU117xapm56MM",
}
I am testing two things but the code itself including _id and showing me that error. In previous version of expect (when Jest was not introduced) it was simple using toInclude() assertion,but now both toContain() and toMatchObject() showing same error.
That's my seed file
const{ObjectID} = require('mongodb');
const jwt = require('jsonwebtoken');
const {Todo} = require('./../../models/todo');
const {User} = require('./../../models/user');
const userOneId = new ObjectID();
const userTwoId = new ObjectID();
const users = [{
_id: userOneId,
email: 'adil.aj95#gmail.com',
password : 'userOnePass',
tokens: [{
access : 'auth',
token : jwt.sign({_id : userOneId,access : 'auth'}, 'abc123').toString()
}]
},
{
_id: userTwoId,
email: 'adil2.aj95#gmail.com',
password : 'userTwoPass',
// tokens: [{
// access : 'auth',
// token : jwt.sign({_id : userTwoId,access : 'auth'}, 'abc123').toString()
// }]
}];

You need just a slight change. Instead of using
expect(user.tokens[0]).toMatchObject({
access : 'auth',
token : res.headers['x-auth']
});
include .toObject() after user like this
expect(user.toObject().tokens[0]).toMatchObject({
access : 'auth',
token : res.headers['x-auth']
});
Why? Your user is a mongoose object that has more info than you would expect. You can see that there is an extra _id property in the token (the error that is thrown shows that). What toObject() does is it returns just the object as you would expect it, without all the mongoose-specific properties (stuff like _id, __v etc).

You can use .toHaveProperty(keyPath, value) for the new expect version by jest.
So the code becomes like this:
expect(user.tokens[0]).toHaveProperty('access', 'auth');
expect(user.tokens[0]).toHaveProperty('token', user.tokens[0].token);

Related

Why it only returns the first id from my mock up database?

In short I am trying to create a simple api that would return the user with the matching id. I use postman to send requests to my localhost created using node.js with express. It works fine when I request the first user but throws in an error when requesting "John". I am coding along a udemy course and can't figure out what the issue is other than the material is outdated. The error is "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client"
users: [
{
id: "123",
name: "Sally",
email: "sally#gmail.com",
password: "bananas",
entries: 0,
joined: new Date(),
},
{
id: "124",
name: "John",
email: "john#gmail.com",
password: "apples",
entries: 0,
joined: new Date(),
},
],
};
app.get("/profile/:id", (req, res) => {
const { id } = req.params;
let found = false;
database.users.forEach((user) => {
if (user.id === id) {
found = true;
return res.json(user);
}
if (!found) {
res.json("User not found");
}
});
});
From the MDN Web Docs:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.
Early termination may be accomplished with:
A simple loop
A for...of
loop
[Array.prototype.every()][every]
[Array.prototype.some()][some]
[Array.prototype.find()][find]
[Array.prototype.findIndex()][findIndex]
This means that your loop will run through all elements and in fact call res.json multiple times resulting in the ERR_HTTP_HEADERS_SENT error . There are many ways to fix this, here's an one example:
app.get("/profile/:id", (req, res) => {
const {id} = req.params;
for (const user of database.users) {
if (user.id === id) {
return res.json(user);
}
}
res.json("User not found");
});

Validation error of type UnknownType: Unknown type CreateUserInput

Using AppSync Query Playground running this query.
mutation UserMutation ($input: CreateUserInput!) {
createUser
(input: $input)
{
uniqueID
recordType
userName
userEmail
userEmailVerified
userCreated
userLastModified
userEnabled
userStatus
userValidFrom
userValidTo
}
}
Query Variables are
{
"input":
{
"uniqueID": "UUID-formatted",
"recordType": "USER",
"userName": "username",
"userEmail": "mailadres",
"userEmailVerified": true,
"userCreated": "2020-12-04T22:32:37.000Z",
"userLastModified": "2020-12-04T22:34:15.000Z",
"userEnabled": true,
"userStatus": "CONFIRMED",
"userValidFrom": "2021-03-12T11:03:37.283Z",
"userValidTo": "9999-12-31T23:59:59.999Z"
}
}
Creates a record in the DynamoDB table as expected.
Which suggest to me that the model and the resolvers are well defined in AppSync.
Running the exact same code in a NodeJS Express environment creates the above error.
API-key and GraphQL endpoint are correct. Exactly the same method for other entity used and works.
This is the NodeJS code
exports.userCreate = (cognitosub, userName, cognitoemail, cognitoemail_verified, cognitoUserCreateDate, cognitoUserLastModifiedDate, cognitoEnabled, cognitoUserStatus, userValidFrom, userValidTo) => {
const mutateQuery = gql(`
mutation UserMutation ($input: CreateUserInput!) {
createUser
(input: $input)
{
uniqueID
recordType
userName
userEmail
userEmailVerified
userCreated
userLastModified
userEnabled
userStatus
userValidFrom
userValidTo
}
}
`);
const mutateVariables = JSON.parse(`{
"uniqueID" : "${cognitosub}",
"recordType" : "USER",
"userName" : "${userName}",
"userEmail" : "${cognitoemail}",
"userEmailVerified" : ${cognitoemail_verified},
"userCreated" : "${cognitoUserCreateDate}",
"userLastModified" : "${cognitoUserLastModifiedDate}",
"userEnabled" : ${cognitoEnabled},
"userStatus" : "${cognitoUserStatus}",
"userValidFrom" : "${utils.dateConvertToISOString(userValidFrom)}",
"userValidTo" : "${utils.dateConvertToISOString(userValidTo)}"
}`)
return new Promise ((resolve, reject) => {
console.debug(`${Date(Date.now())} - utilities.userCreate.mutateVariables`,mutateVariables)
graphqlClient.mutate({
mutation: mutateQuery,
fetchPolicy: 'no-cache', // Mutate suporteert alleen 'no-cache'
variables: {input: mutateVariables}
})
.then((success) => {
// console.debug(`${Date(Date.now())} - utilities.userCreate.then`,success)
if (success === null) {reject('userIsNull')}
else {
resolve(success.data.createUser.uniqueID)}
})
.catch((err) => {
console.debug(`${Date(Date.now())} - utilities.userCreate.catch\n`,err)
reject(err)
})
})
Exact same code is used for a less complicated object with an UUID, Identification, validFrom en ValidTo date. It works like a charm.
I looked at every error and spelling mistake. The code keeps throwing this two errors.
graphQLErrors: [
{
path: null,
locations: [ { line: 1, column: 31, sourceName: null } ],
message: 'Validation error of type UnknownType: Unknown type CreateUserInput'
},
{
path: null,
locations: [ { line: 2, column: 3, sourceName: null } ],
message: "Validation error of type FieldUndefined: Field 'createUser' in type 'Mutation' is undefined # 'createUser'"
}
]
Which are dump of the error-object.
Appolo-client is used top access the DynamoDB. The records created in the AppSync GraphQL playground are perfectly viewable in the DB.
I am out of clues here. Can anyone help?
Today I really looked into this problem and decided not to use the AppSync client from AWS anymore (which created some depency build problems with every npm update by the way...)
I choose to go for the Apollo client latest version which doesn't give any npm update issues and is a up-to-date version of the client that AWS uses in the background if I am well informed (read can read fora in a good way ;-))
I had some issues with the authentication on AppSync but managed to get over this.
This code totally fixes ALL previous error (for me)
const gql = require("graphql-tag");
const ApolloClient = require("#apollo/client").ApolloClient
const ApolloLink = require("#apollo/client").ApolloLink
const InMemoryCache = require("#apollo/client").InMemoryCache;
const createHttpLink = require("#apollo/client").createHttpLink;
const {createAuthLink} = require("aws-appsync-auth-link")
require('cross-fetch/polyfill');
const clientGraphQL = new ApolloClient({
link: ApolloLink.from([
createAuthLink({
url: aws_secrets.AWS_DEV_APPSYNC_ENDPOINT,
region:aws_secrets.REGION,
auth:
{
type: "API_KEY",
apiKey: aws_secrets.AWS_DEV_APPSYNC_API_KEY,
}
}),
createHttpLink({
uri: aws_secrets.AWS_DEV_APPSYNC_ENDPOINT
}),
]),
cache: new InMemoryCache(),
});
Safe code by hiding all the secrets in a special file.
createAuthLink is the only thing I need from AWSAppSync (IMHO).
I didn't manage to get a proper connection with the CreateAuthLink from the Apollo Client.
Hope this helps some of you...

How to get respond from promise in JSON parse?

I'm trying to get response from API call in the JSON parse but do not see a way to get it.
I managed to get respond from promise but it's not usable because i need JSON parse respond (JSON.stringlify worked but also not usable) .
let Admins = scope.api.getChatAdministrators(scope.message._chat._id)
console.log(Admins);
Admins.then(function(result) {
console.log(result);
}) // Why this work?(I get this respond from it)
Respond:
[ ChatMember {
_user:
User {
_id: ID,
_firstName: 'AdminBot',
_lastName: null,
_username: 'Bot' },
_status: 'administrator' },
ChatMember {
_user:
User {
_id: ID,
_firstName: 'Creator',
_lastName: null,
_username: 'Creator' },
_status: 'creator' } ]
let Admins = scope.api.getChatAdministrators(scope.message._chat._id)
console.log(Admins);
Admins.then(function(result) {
console.log(JSON.parse(result));
}).catch((err) => {
console.log('API Call error:', err.message);
});
Tried this to get JSON parse output but i got response "API Call error: Unexpected token o in JSON at position 1"
This will look weird but this will do.
const temp = JSON.stringify(result)
const parsedResult = JSON.parse(temp)
FYI. you can only parse a JSON object in string format.

Mongo - How can I pass collection name to Schema while saving data

I'm at my wits ends with mongoose here and not sure where i'm going wrong. Please help me out.
Say i define my schema like below:
const userDBSchema = new Schema({
userName: {
type:String
},
musicType: {
type: String,
},
}, { collection: 'userData' });
then run a post to the database, using this function:
exports.saveUser = (req, res) => {
const username = req.params.user;
const newUser = new UserInfoDb(req.body, {'collection': username});
newUser.save((err, response) => {
//save operation
});
};
i get this error:
{
"message": "document must have an _id before saving",
"name": "MongooseError"
}
Even when i manually define the id, with the following in my schema:
_id: {
type: mongoose.Schema.Types.ObjectId,
index: true,
required: true,
auto: true
},
I get this error:
{
"errors": {
"_id": {
"message": "Path `_id` is required.",
"name": "ValidatorError",
"properties": {
"message": "Path `_id` is required.",
"type": "required",
"path": "_id"
},
"kind": "required",
"path": "_id"
}
},
"_message": "userDB validation failed",
"message": "userDB validation failed: _id: Path `_id` is required.",
"name": "ValidationError"
}
Ultimately my goal is to That i can pass in the name of a collection I want to access from the POST, then write the body of my request into that collection. I have multiple collections I may need to write to at any given time. I've weighed up the decision to have one collection with everything but decided to go with multiple collections in order to store my data.
Can you confirm that you want to have multiple collections, which each user has his database collection? I think it's not a good decision!
This is my solution. You can create a model for each user with the same schema (userDBSchema you've created).
But you should be sure that you can generate unique collection names for each user. (such as a hash function or anything else)
I want to confirm again this is not really good way when creating for each user a collection to store his info.
Why?
If your system has millions of users, you will have millions of collections, it's very complicated to manage database. It's better with millions of document in a collection. This is my personal thought. Hope to help you
const mongoose = require('mongoose');
const userDBSchema = require('path/to/your/schema/declared');
// Generating unique collection name for each user
const getCollectionNameForUser = (username) => username;
exports.saveUser = (req, res) => {
const username = req.params.user;
const UserInfoDb = mongoose.model(getCollectionNameForUser(username), userDBSchema);
const newUser = new UserInfoDb(req.body);
newUser.save((err, response) => {
//save operation
});
};
Edit - Simple demo
This is my demo simple app, how to create dynamic model with a schema in mongoose, you can view full code and try it on your local database here https://gist.github.com/huynhsamha/4dcf00a1fba96ae7f186b606b33b7e9c
In my demo, I created a schema storing user info
const Schema = mongoose.Schema;
const UserInfoSchema = new Schema({
note: String,
timeline: { type: Date, default: Date.now }
});
And add log (with note) to each user via simple API GET
app.get('/add/:id/:note', async (req, res) => {
const { id, note } = req.params;
// Retrieve model which contain info documents of user with id
const UserInfo = mongoose.model('User_' + id, UserInfoSchema);
const log = new UserInfo({ note });
await log.save();
res.send(log);
})
And this is API to retrieve logs of user
app.get('/:id', async (req, res) => {
const { id } = req.params;
// Retrieve model which contain info documents of user with id
const UserInfo = mongoose.model('User_' + id, UserInfoSchema);
const logs = await UserInfo.find();
res.send(logs);
})
I've tested this code and it's working. Each user has a collection to store his data info.
You can download and check it on your local with code at gist https://gist.github.com/huynhsamha/4dcf00a1fba96ae7f186b606b33b7e9c.

Deleting a single item from a database with mongodb

{
"_id": {
"$oid": "5a2de0a00d6baa43e8b925d0"
},
"name": "test",
"playList": [
{
"url": "https://p.scdn.co/mp3-preview/8aa799e60164f8a1fb311188d9d85ef65d7782c6?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Kenny G",
"songName": "My Heart Will Go On (Love Theme from \"Titanic\")",
"_id": {
"$oid": "5a2de0ad0d6baa43e8b925d1"
}
},
{
"url": "https://p.scdn.co/mp3-preview/7c49854f18e6dfda6cd97ab5e8bc139d7ca82b7c?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "PRODUCE 101",
"songName": "PICK ME",
"_id": {
"$oid": "5a2de13b0d6baa43e8b925d2"
}
}
],
"__v": 0
}
I have a database called channels where each channels contain a playList as shown below. I want to delete a single item when a button is clicked. I can handle the onClick event part, but I am not sure how to implement the routes part.
I know that I start by doing something like
router.delete(''/channels/:id', function(req, res){
something here...
})
but how can I access a particular item (probably with a unique id?) and delete it from the DB?
EDIT
By using the GET below
router.get('/channels/:id',
isLoggedIn,
function(req, res) {
channel.findOne({'name':req.params.id},function(err,channeldata){
if(err || channeldata === null){
res.status(404).send({
message: 'Channel Not Found',
data: []
})
}
else {
res.status(200).json({
message: "channel to "+req.params.id+"success",
data:channeldata
})
}
})
});
I get the data for a single channel in my DB.
But since I am new to this stuff, I am not sure how to access each item of the playList and delete a single data.
EDIT2
var mongoose = require('mongoose');
var ChannelSchema = new mongoose.Schema({
name: {type:String,required:true},
playList: [{
songName: { type : String },
artist: { type : String },
url: { type : String }
}]
})
module.exports = mongoose.model('Channel',ChannelSchema);
You can try the following snippet that contains the DELETE (part of CRUD) endpoint for your resource collection (i.e. the channels):
router.delete('/channels/playlist/song', isLoggedIn, (req, res) => {
const channel_id = req.query.channelId;
const song_id = req.query.songId;
// the following query deletes a song form a playlist of a certain channel
channel.update({_id: ObjectId(channel_id)},{$pull:{playList:{_id:ObjectId(song_id)}}})
.exec()
.then(result => {
// for checking if document was found and deleted
// mongodb actually returns special object `result`
// which has its own certain fields
res.status(200).send({
status: "success",
message: result
});
})
.catch(error => {
// here we see if we had any problem with server or db itself
console.log(error)
res.status(500).send({
success: false,
message: "Something went wrong with DELETE /channels/:id"
})
})
});
I assume that you know what ObjectId() function does
if you do not have it declared, declare the following comment
in the beginning of the file (where you require everything)
const mongoose = require('mongoose'); // you must have this
const ObjectId = mongoose.Types.ObjectId; // gets the function
Let me know if this helps, or if you do not understand something - I will make an edit so that you get it.

Resources