diff --git a/requirements/inventoryController/getAllInvInProject.md b/requirements/inventoryController/getAllInvInProject.md new file mode 100644 index 000000000..9a331a9ab --- /dev/null +++ b/requirements/inventoryController/getAllInvInProject.md @@ -0,0 +1,17 @@ +Check mark: ✅ +Cross Mark: ❌ + +# Post Badge + +> ## Positive case + +1. ❌ Receives a POST request in the **/api/userProfile** route +2. ❌ Returns 200 if successfully fetch inventory data + + > ## Negative case + +3. ❌ Returns error 404 if the API does not exist +4. ❌ Returns error code 403 if the user is not authorized to view the inventory data +5. ❌ Returns error code 404 if an error occurs when populating or saving. + +> ## Edge case \ No newline at end of file diff --git a/requirements/inventoryController/getAllInvInProjectWBS.md b/requirements/inventoryController/getAllInvInProjectWBS.md new file mode 100644 index 000000000..f2d7a004d --- /dev/null +++ b/requirements/inventoryController/getAllInvInProjectWBS.md @@ -0,0 +1,17 @@ +Check mark: ✅ +Cross Mark: ❌ + +# Post Badge + +> ## Positive case + +1. ❌ Receives a POST request in the **/api/userProfile** route +2. ✅ Returns 200 if successfully found data + +> ## Negative case + +1. ❌ Returns error 404 if the API does not exist +2. ✅ Returns 403 if user is not authorized to view inventory data +3. ✅ Returns 404 if an error occurs while fetching data + +> ## Edge case \ No newline at end of file diff --git a/requirements/inventoryController/postInvInProjectWBS.md b/requirements/inventoryController/postInvInProjectWBS.md new file mode 100644 index 000000000..3ac80540b --- /dev/null +++ b/requirements/inventoryController/postInvInProjectWBS.md @@ -0,0 +1,19 @@ +Check mark: ✅ +Cross Mark: ❌ + +# Post Badge + +> ## Positive case + +1. ❌ Receives a POST request in the **/api/userProfile** route +2. ✅ Returns status code 201, if the inventory was successfully created and saved +3. ✅ Returns status code 201, if the inventory item was succesfully updated and saved. + +> ## Negative case + +1. ❌ Returns error 404 if the API does not exist +2. ✅ Returns error 403 if the user is not authorized to view data +3. ✅ Returns error 500 if an error occurs when saving +4. ✅ Returns error 400 if a valid project was found but quantity and type id were missing + +> ## Edge case \ No newline at end of file diff --git a/src/controllers/inventoryController.js b/src/controllers/inventoryController.js index d126cc7e4..76b4c899f 100644 --- a/src/controllers/inventoryController.js +++ b/src/controllers/inventoryController.js @@ -13,7 +13,7 @@ const inventoryController = function (Item, ItemType) { // use req.params.projectId and wbsId // Run a mongo query on the Item model to find all items with both the project and wbs // sort the mongo query so that the Wasted false items are listed first - return Item.find({ + await Item.find({ project: mongoose.Types.ObjectId(req.params.projectId), wbs: req.params.wbsId && req.params.wbsId !== 'Unassigned' @@ -283,9 +283,9 @@ const inventoryController = function (Item, ItemType) { } // update the original item by decreasing by the quantity and adding a note - if (req.body.quantity && req.params.invId && projectExists && wbsExists) { + if (req.body.quantity && req.param.invId && projectExists && wbsExists) { return Item.findByIdAndUpdate( - req.params.invId, + req.param.invId, { $decr: { quantity: req.body.quantity }, $push: { @@ -797,4 +797,4 @@ const inventoryController = function (Item, ItemType) { }; }; -module.exports = inventoryController; +module.exports = inventoryController; \ No newline at end of file diff --git a/src/controllers/inventoryController.spec.js b/src/controllers/inventoryController.spec.js new file mode 100644 index 000000000..bc4c4277a --- /dev/null +++ b/src/controllers/inventoryController.spec.js @@ -0,0 +1,334 @@ +/* eslint-disable new-cap */ + +jest.mock('../utilities/permissions', () => ({ + hasPermission: jest.fn(), // Mocking the hasPermission function + })); + const { mockReq, mockRes, assertResMock } = require('../test'); + + const inventoryItem = require('../models/inventoryItem'); + const inventoryItemType = require('../models/inventoryItemType'); + const inventoryController = require('./inventoryController'); + const projects = require('../models/project'); + const wbs = require('../models/wbs'); + + const { hasPermission } = require('../utilities/permissions'); + + const makeSut = () => { + const { getAllInvInProjectWBS, postInvInProjectWBS, getAllInvInProject } = inventoryController( + inventoryItem, + inventoryItemType, + ); + return { getAllInvInProjectWBS, postInvInProjectWBS, getAllInvInProject }; + }; + + const flushPromises = () => new Promise(setImmediate); + + describe('Unit test for inventoryController', () => { + beforeAll(() => { + jest.clearAllMocks(); + }); + beforeEach(() => { + mockReq.params.userid = '5a7e21f00317bc1538def4b7'; + mockReq.params.userId = '5a7e21f00317bc1538def4b7'; + mockReq.params.wbsId = '5a7e21f00317bc1538def4b7'; + mockReq.params.projectId = '5a7e21f00317bc1538def4b7'; + mockReq.body = { + project: '5a7e21f00317bc1538def4b7', + wbs: '5a7e21f00317bc1538def4b7', + itemType: '5a7e21f00317bc1538def4b7', + item: '5a7e21f00317bc1538def4b7', + quantity: 1, + typeId: '5a7e21f00317bc1538def4b7', + cost: 20, + poNum: '123', + }; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + describe('getAllInvInProjectWBS', () => { + test('Returns 403 if user is not authorized to view inventory data', async () => { + const { getAllInvInProjectWBS } = makeSut(); + hasPermission.mockResolvedValue(false); + const response = await getAllInvInProjectWBS(mockReq, mockRes); + assertResMock(403, 'You are not authorized to view inventory data.', response, mockRes); + expect(hasPermission).toHaveBeenCalledTimes(1); + }); + + test('Returns 404 if an error occurs while fetching inventory data', async () => { + const { getAllInvInProjectWBS } = makeSut(); + // Mocking hasPermission function + hasPermission.mockResolvedValue(true); + + // Mock error + const error = new Error('Error fetching inventory data'); + + // Mock chainable methods: populate, sort, then, catch + const mockInventoryItem = { + populate: jest.fn().mockReturnThis(), + sort: jest.fn().mockReturnThis(), + then: jest.fn().mockImplementationOnce(() => Promise.reject(error)), + catch: jest.fn().mockReturnThis(), + }; + + // Mock the inventoryItem.find method + jest.spyOn(inventoryItem, 'find').mockImplementationOnce(() => mockInventoryItem); + + // Call the function + const response = await getAllInvInProjectWBS(mockReq, mockRes); + await flushPromises(); + + // Assertions + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(404, error, response, mockRes); + }); + + test('Returns 200 if successfully found data', async () => { + const { getAllInvInProjectWBS } = makeSut(); + hasPermission.mockResolvedValue(true); + + const mockData = [ + { + _id: '123', + project: '123', + wbs: '123', + itemType: '123', + item: '123', + quantity: 1, + date: new Date().toISOString(), + }, + ]; + + const mockInventoryItem = { + populate: jest.fn().mockReturnThis(), + sort: jest.fn().mockResolvedValue(mockData), + then: jest.fn().mockResolvedValue(() => {}), + catch: jest.fn().mockReturnThis(), + }; + + // Mock the inventoryItem.find method + jest.spyOn(inventoryItem, 'find').mockImplementation(() => mockInventoryItem); + + // Call the function + const response = await getAllInvInProjectWBS(mockReq, mockRes); + await flushPromises(); + + // Assertions + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(200, mockData, response, mockRes); + }); + }); + describe('postInvInProjectWBS', () => { + test('Returns error 403 if the user is not authorized to view data', async () => { + const { getAllInvInProjectWBS } = makeSut(); + hasPermission.mockReturnValue(false); + const response = await getAllInvInProjectWBS(mockReq, mockRes); + assertResMock(403, 'You are not authorized to view inventory data.', response, mockRes); + expect(hasPermission).toHaveBeenCalledTimes(1); + }); + + test('Returns error 400 if an error occurs while fetching an item', async () => { + mockReq.params.wbsId = 'Unassigned'; + const { postInvInProjectWBS } = makeSut(); + hasPermission.mockReturnValue(true); + // look up difference betewewen mockimplmenonce and mockimplementation + // how to incorpoate into the test + // and how to setup mocking variables as well + const mockProjectExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnValue(null), + }; + + jest.spyOn(projects, 'findOne').mockImplementationOnce(() => mockProjectExists); + + const response = await postInvInProjectWBS(mockReq, mockRes); + await flushPromises(); + + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock( + 400, + 'Valid Project, Quantity and Type Id are necessary as well as valid wbs if sent in and not Unassigned', + response, + mockRes, + ); + }); + test('Returns error 500 if an error occurs when saving', async () => { + const mockProjectExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + const mockWbsExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + const mockInventoryItem = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnValue(null), + }; + const { postInvInProjectWBS } = makeSut(); + // const hasPermissionSpy = mockHasPermission(true); + hasPermission.mockReturnValue(true); + + jest.spyOn(projects, 'findOne').mockImplementationOnce(() => mockProjectExists); + jest.spyOn(wbs, 'findOne').mockImplementationOnce(() => mockWbsExists); + jest.spyOn(inventoryItem, 'findOne').mockImplementationOnce(() => mockInventoryItem); + + jest.spyOn(inventoryItem.prototype, 'save').mockRejectedValueOnce(new Error('Error saving')); + const response = await postInvInProjectWBS(mockReq, mockRes); + await flushPromises(); + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(500, new Error('Error saving'), response, mockRes); + }); + + test('Receives a 201 success if the inventory was successfully created and saved', async () => { + const resolvedInventoryItem = new inventoryItem({ + project: mockReq.body.projectId, + wbs: mockReq.body.wbsId, + type: mockReq.body.typeId, + quantity: mockReq.body.quantity, + cost: mockReq.body.cost, + poNum: mockReq.body.poNum, + }); + const mockProjectExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + const mockWbsExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + const mockInventoryItem = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnValue(null), + }; + const { postInvInProjectWBS } = makeSut(); + + hasPermission.mockReturnValue(true); + jest.spyOn(projects, 'findOne').mockImplementationOnce(() => mockProjectExists); + jest.spyOn(wbs, 'findOne').mockImplementationOnce(() => mockWbsExists); + jest.spyOn(inventoryItem, 'findOne').mockImplementationOnce(() => mockInventoryItem); + jest + .spyOn(inventoryItem.prototype, 'save') + .mockImplementationOnce(() => Promise.resolve(resolvedInventoryItem)); + + const response = await postInvInProjectWBS(mockReq, mockRes); + await flushPromises(); + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(201, resolvedInventoryItem, response, mockRes); + }); + + test('Returns a 201, if the inventory item was succesfully updated and saved.', async () => { + const resolvedInventoryItem = { + project: mockReq.body.projectId, + wbs: mockReq.body.wbsId, + type: mockReq.body.typeId, + quantity: mockReq.body.quantity, + cost: mockReq.body.cost, + poNum: mockReq.body.poNum, + }; + + const updatedResolvedInventoryItem = { + project: mockReq.body.projectId, + wbs: mockReq.body.wbsId, + type: mockReq.body.typeId, + quantity: mockReq.body.quantity + 1, + costPer: 200, + }; + + const mockProjectExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + const mockWbsExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + const mockInventoryExists = { + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + }; + + const { postInvInProjectWBS } = makeSut(); + hasPermission.mockReturnValue(true); + + jest.spyOn(projects, 'findOne').mockImplementationOnce(() => mockProjectExists); + jest.spyOn(wbs, 'findOne').mockImplementationOnce(() => mockWbsExists); + jest.spyOn(inventoryItem, 'findOne').mockImplementationOnce(() => mockInventoryExists); + jest + .spyOn(inventoryItem, 'findOneAndUpdate') + .mockImplementationOnce(() => Promise.resolve(resolvedInventoryItem)); + + jest + .spyOn(inventoryItem, 'findByIdAndUpdate') + .mockImplementationOnce(() => Promise.resolve(updatedResolvedInventoryItem)); + + const response = await postInvInProjectWBS(mockReq, mockRes); + await flushPromises(); + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(201, updatedResolvedInventoryItem, response, mockRes); + }); + }); + + describe('getAllInvInProject', () => { + test('Returns 403 if user is not authorized to view inventory data', async () => { + const { getAllInvInProject } = makeSut(); + hasPermission.mockResolvedValue(false); + const response = await getAllInvInProject(mockReq, mockRes); + assertResMock(403, 'You are not authorized to view inventory data.', response, mockRes); + expect(hasPermission).toHaveBeenCalledTimes(1); + }); + + test('Returns 404 if an error occurs while fetching inventory data', async () => { + const { getAllInvInProject } = makeSut(); + hasPermission.mockResolvedValue(true); + + const error = new Error('Error fetching inventory data'); + + const mockInventoryItem = { + populate: jest.fn().mockReturnThis(), + sort: jest.fn().mockReturnThis(), + then: jest.fn().mockImplementationOnce(() => Promise.reject(error)), + catch: jest.fn().mockReturnThis(), + }; + + jest.spyOn(inventoryItem, 'find').mockImplementationOnce(() => mockInventoryItem); + + const response = await getAllInvInProject(mockReq, mockRes); + await flushPromises(); + + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(404, error, response, mockRes); + }); + + test('Returns 200 if successfully found data', async () => { + const { getAllInvInProject } = makeSut(); + hasPermission.mockResolvedValue(true); + + const mockData = [ + { + _id: '123', + project: '123', + wbs: '123', + itemType: '123', + item: '123', + quantity: 1, + date: new Date().toISOString(), + }, + ]; + + const mockInventoryItem = { + populate: jest.fn().mockReturnThis(), + sort: jest.fn().mockResolvedValue(mockData), + catch: jest.fn().mockReturnThis(), + }; + + jest.spyOn(inventoryItem, 'find').mockImplementation(() => mockInventoryItem); + + const response = await getAllInvInProject(mockReq, mockRes); + await flushPromises(); + + expect(hasPermission).toHaveBeenCalledTimes(1); + assertResMock(200, mockData, response, mockRes); + }); + }); + }); \ No newline at end of file