-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* #238: Created a new mongodb schema for contact * #238: Refactor error formatter to return error instead of throw * #238: Add sinon lib for unit testing * #238: Refactored to return error instead of throwing * #238: Added unit testing for contact and refactored existing test * #238: Added contact query/mutation and schema
- Loading branch information
Showing
14 changed files
with
433 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
const chai = require('chai'); | ||
const sinon = require('sinon'); | ||
const Contact = require('../../../src/models/contact') | ||
const graphQlSchema = require('../../../src/graphql'); | ||
const { Mutation: { submitContact }, Query: { getUserContacts, contacts } } = require('../../../src/graphql/resolver'); | ||
const { mongoConn } = require("../../utils/mongo"); | ||
|
||
const { expect } = chai; | ||
|
||
mongoConn(); | ||
const mockContact = { | ||
fullName: 'test user', | ||
email: 'test@example.com', | ||
purpose: 'QUESTION', | ||
message: 'test message' | ||
} | ||
|
||
describe('Contact Resolver Unit Tests:', function () { | ||
|
||
afterEach(() => sinon.restore()); | ||
|
||
this.timeout(10000) | ||
|
||
const input = { | ||
...mockContact | ||
} | ||
|
||
const req = { logger: { info: (_message) => { }, error: (_message) => { } } } | ||
|
||
context('submitContact', () => { | ||
it('should have createContact(...) as a Mutation resolver', async function () { | ||
const { submitContact } = graphQlSchema.getMutationType().getFields(); | ||
expect(submitContact.name).to.equal('submitContact'); | ||
expect(submitContact.type.toString()).to.equal('Contact!'); | ||
}); | ||
|
||
it('should save new contact information', async () => { | ||
sinon.stub(Contact.prototype, 'save').callsFake(() => ({...input, _id: 'akdn9wqkn'})) | ||
|
||
const contact = await submitContact({}, { input }, { req }); | ||
|
||
expect(contact).to.have.property('email'); | ||
}); | ||
|
||
it('should throw a 500 error', async () => { | ||
sinon.stub(Contact.prototype, 'save').throws(); | ||
|
||
const result = await submitContact({}, { input }, { req }); | ||
|
||
expect(result).to.have.property('extensions'); | ||
expect(result.extensions.code).to.be.equal(500) | ||
}); | ||
}) | ||
|
||
context('getUserContacts', () => { | ||
const mockUserContact = { | ||
_id: 'akdn9wqkn', | ||
fullName: 'test user', | ||
email: 'test@example.com', | ||
purpose: 'QUESTION', | ||
message: 'test message', | ||
lean: () => this | ||
} | ||
|
||
it("should return paginated lists of a user's contacts", async () => { | ||
sinon.stub(Contact, 'countDocuments').returns(1); | ||
sinon.stub(Contact, 'find').returns(mockUserContact); | ||
sinon.stub(mockUserContact, 'lean').returnsThis(); | ||
|
||
const userContacts = await getUserContacts({}, { input }, { req, isAuthenticated: true }); | ||
|
||
expect(userContacts).to.have.property('data'); | ||
expect(userContacts.totalItems).to.equal(1); | ||
}); | ||
|
||
it("should throw a 401, not authenticated error", async () => { | ||
|
||
const error = await getUserContacts({}, { input }, { req, isAuthenticated: false }); | ||
expect(error).to.have.property('extensions'); | ||
expect(error.extensions.code).to.be.equal(401); | ||
}); | ||
|
||
it('should return a 500 error', async () => { | ||
sinon.stub(Contact, 'countDocuments').throws(); | ||
|
||
const result = await getUserContacts({}, { input }, { req, isAuthenticated: true }); | ||
|
||
expect(result).to.have.property('extensions'); | ||
expect(result.extensions.code).to.be.equal(500) | ||
}); | ||
}) | ||
|
||
|
||
context('contacts', () => { | ||
it('should return paginated lists of contacts', async () => { | ||
const mockContact = { | ||
_id: 'akdn9wqkn', | ||
fullName: 'test user', | ||
email: 'test@example.com', | ||
purpose: 'QUESTION', | ||
message: 'test message', | ||
skip: () => ({_id: 'akdn9wqkn', ...input }), | ||
lean: () => ({_id: 'akdn9wqkn', ...input }), | ||
limit: () => ({_id: 'akdn9wqkn', ...input }) | ||
} | ||
sinon.stub(Contact, 'countDocuments').returns(1); | ||
sinon.stub(Contact, 'find').returns(mockContact); | ||
sinon.stub(mockContact, 'skip').returnsThis(); | ||
sinon.stub(mockContact, 'limit').returnsThis(); | ||
sinon.stub(mockContact, 'lean').returnsThis(); | ||
|
||
const allContacts = await contacts({}, { input, pageNumber: 1, pageSize: 1 }, { req, isAuthenticated: true }); | ||
expect(allContacts).to.have.property('data'); | ||
expect(allContacts.totalItems).to.equal(1); | ||
}); | ||
|
||
it("should throw a 401, not authenticated error", async () => { | ||
|
||
const error = await contacts({}, { input }, { req, isAuthenticated: false }); | ||
expect(error).to.have.property('extensions'); | ||
expect(error.extensions.code).to.be.equal(401); | ||
}); | ||
|
||
it('should return a 500 error', async () => { | ||
sinon.stub(Contact, 'countDocuments').returns(1); | ||
sinon.stub(Contact, 'find').throws() | ||
|
||
const result = await contacts({}, { input }, { req, isAuthenticated: true }); | ||
|
||
expect(result).to.have.property('extensions'); | ||
expect(result.extensions.code).to.be.equal(500) | ||
}); | ||
}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
const { expect } = require('chai'); | ||
const graphQlSchema = require('../../../src/graphql'); | ||
|
||
describe('Schema Unit Tests:', function () { | ||
it('should have Query name as schema query type', async function () { | ||
const schemaQuery = graphQlSchema.getQueryType().toString(); | ||
expect(schemaQuery).to.equal('Query'); | ||
}); | ||
|
||
it('should have Mutation name as schema mutation type', async function () { | ||
const schemaMutation = graphQlSchema.getMutationType().toString(); | ||
expect(schemaMutation).to.equal('Mutation'); | ||
}); | ||
|
||
it('should have getSingleContact as a RootQuery field', async function () { | ||
const { getUserContacts } = graphQlSchema.getQueryType().getFields(); | ||
expect(getUserContacts.name).to.equal('getUserContacts'); | ||
expect(getUserContacts.type.toString()).to.equal('Contacts!'); | ||
}); | ||
|
||
it('should have contacts as a RootQuery field', async function () { | ||
const { contacts } = graphQlSchema.getQueryType().getFields(); | ||
expect(contacts.name).to.equal('contacts'); | ||
expect(contacts.type.toString()).to.equal('Contacts!'); | ||
}); | ||
|
||
it('should have _id,fullName,email,purpose,message Contact schema', async function () { | ||
const contact = graphQlSchema.getType('Contact'); | ||
const keys = Object.keys(contact.getFields()); | ||
expect(keys).to.include.members(['_id', 'fullName', 'email', 'purpose', 'message']); | ||
}); | ||
|
||
it('should have correct datatypes for Contact schema', async function () { | ||
const contact = graphQlSchema.getType('Contact'); | ||
const { _id, fullName, email, purpose, message } = contact.getFields(); | ||
|
||
expect(_id.type.toString()).to.equal('ID'); | ||
expect(fullName.type.toString()).to.equal('String!'); | ||
expect(email.type.toString()).to.equal('String!'); | ||
expect(purpose.type.toString()).to.equal('Purpose!'); | ||
expect(message.type.toString()).to.equal('String!'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"Input schema for fetching a single contact" | ||
input userContactQueryInput { | ||
""" | ||
email of user whose contact to fetch. | ||
This field is a required field for fetching all contacts made by a user. | ||
""" | ||
email: String! | ||
} | ||
|
||
input CreatedBetween { | ||
from: String! | ||
to: String! | ||
} | ||
|
||
""" | ||
Input schema for for fetching a list of contacts. | ||
Contains pagination options for how the contacts should be grouped | ||
""" | ||
input contactsQueryInput { | ||
""" | ||
Pagination option for the page of contacts to display to return. | ||
This defaults to 1 if no value is specified. | ||
The contacts are grouped into pages based on the specified pageSize(defaults to 20, if not specified) | ||
""" | ||
pageNumber: Int | ||
|
||
"filter " | ||
datetime: CreatedBetween | ||
|
||
""" | ||
Number of contacts per page. | ||
Pagination option for specifying how the server should group the contacts. | ||
Defaults to 1 if none is specified. | ||
""" | ||
pageSize: Int | ||
} | ||
|
||
"Input Schema type for creating contact" | ||
input createContactInput { | ||
"full name of the user requesting contact" | ||
fullName: String! | ||
|
||
"email address of the user requesting contact" | ||
email: String! | ||
|
||
""" | ||
purpose for requesting contact. | ||
You can specify only one from the list of enumerable values defined | ||
""" | ||
purpose: Purpose! | ||
|
||
"details of the reason or purpose for requesting contact" | ||
message: String! | ||
|
||
} | ||
|
||
"list of enumerable value selectable as purpose for requesting contact" | ||
enum Purpose { | ||
"I want to ask a question" | ||
QUESTION | ||
|
||
"I want to submit a ticket" | ||
TICKET | ||
|
||
"I have to make a suggestion" | ||
SUGGESTION | ||
|
||
"I want to make a comment" | ||
COMMENT | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
const Contact = require('../../../models/contact'); | ||
const errorFormater = require('../../../utils/errorFormater'); | ||
|
||
const contactMutation = { | ||
submitContact: async (_, { input }, { req }) => { | ||
req.logger.info('createContact Function Entry:'); | ||
|
||
const contact = Contact(input); | ||
|
||
try { | ||
await contact.save(); | ||
return contact; | ||
} catch (error) { | ||
return errorFormater(error.message, 500); | ||
} | ||
} | ||
}; | ||
|
||
module.exports = contactMutation; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
const Contact = require('../../../models/contact'); | ||
const errorFormater = require('../../../utils/errorFormater'); | ||
const paginator = require('../../../utils/paginator'); | ||
|
||
const contactQuery = { | ||
getUserContacts: async (_, { input }, { user, req, isAuthenticated }) => { | ||
req.logger?.info('[getUserContacts] Function Entry:'); | ||
if (!isAuthenticated) { | ||
req.logger?.error('[contacts]: User not authenticated to view contact listing'); | ||
return errorFormater('not authenticated', 401); | ||
} | ||
try { | ||
const pagination = paginator(await Contact.countDocuments(input)); | ||
const data = await Contact.find(input).lean(); | ||
return Object.assign(pagination, { data }); | ||
} catch (error) { | ||
return errorFormater(error.message, 500); | ||
} | ||
}, | ||
|
||
contacts: async (_, { input }, { user, req, isAuthenticated }) => { | ||
req.logger?.info('[contacts]: Function Entry:'); | ||
if (!isAuthenticated) { | ||
req.logger?.error('[contacts]: User not authenticated to view contact listing'); | ||
return errorFormater('not authenticated', 401); | ||
} | ||
const filter = input?.datetime?.from && input?.datetime?.to | ||
? { | ||
createdAt: { | ||
$gt: new Date(input.datetime.from), | ||
$lt: new Date(input.datetime.to) | ||
} | ||
} | ||
: {}; | ||
try { | ||
const pagination = input ? paginator(await Contact.countDocuments({}), input.pageNumber, input.pageSize) : paginator(await Contact.countDocuments({})); | ||
const data = await Contact.find(filter).skip(pagination.skip).limit(pagination.limit).lean(); | ||
return Object.assign(pagination, { data }); | ||
} catch (error) { | ||
return errorFormater(error.message, 500); | ||
} | ||
} | ||
}; | ||
|
||
module.exports = contactQuery; |
Oops, something went wrong.