From 356ff4eafc509f0ac800e7b85520b84b44d8829a Mon Sep 17 00:00:00 2001 From: ismaelmurekezi Date: Sun, 16 Jun 2024 16:26:11 +0200 Subject: [PATCH] adding features to update order --- src/__test__/order.test.ts | 112 ++++++++++-------- src/controllers/checkout.controller.ts | 3 +- src/controllers/orderController.ts | 51 ++++++-- src/database/config/config.js | 2 +- src/database/config/db.config.ts | 2 + .../seeders/20240522075149-seed-orders.js | 4 +- src/routes/order.route.ts | 2 +- src/services/orderStatus.ts | 8 ++ 8 files changed, 121 insertions(+), 63 deletions(-) create mode 100644 src/services/orderStatus.ts diff --git a/src/__test__/order.test.ts b/src/__test__/order.test.ts index db4011c..4019651 100644 --- a/src/__test__/order.test.ts +++ b/src/__test__/order.test.ts @@ -1,96 +1,114 @@ - - import { Request, Response } from "express"; import Order from "../database/models/order"; +import { findVendorByUserId } from "../services/orderStatus"; +import Product from "../database/models/product"; import { modifyOrderStatus } from "../controllers/orderController"; -import sinon from "sinon"; -interface CustomRequest extends Request { - token: { - userId: string; - }; -} +jest.mock("../services/orderStatus"); +jest.mock("../database/models/order"); +jest.mock("../database/models/product"); describe("modifyOrderStatus", () => { - let req: Partial; + let req: Partial & { token: { id: string } }; let res: Partial; - let json: sinon.SinonSpy; - let status: sinon.SinonStub; - let findByPkStub: sinon.SinonStub; - let consoleErrorMock: sinon.SinonStub; + let jsonSpy: jest.SpyInstance; + let statusSpy: jest.SpyInstance; beforeEach(() => { req = { params: { orderId: "1" }, body: { status: "Shipped" }, - token: { userId: "1" }, + token: { id: "user1" }, }; - json = sinon.spy(); - status = sinon.stub().returns({ json }); - res = { status } as unknown as Response; - findByPkStub = sinon.stub(Order, "findByPk"); + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; - consoleErrorMock = sinon.stub(console, "error").callsFake(() => {}); + jsonSpy = jest.spyOn(res, "json"); + statusSpy = jest.spyOn(res, "status"); }); afterEach(() => { - sinon.restore(); + jest.clearAllMocks(); }); - it("should return status 400 for invalid order status", async () => { - req.body.status = "InvalidStatus"; + it("should return 404 if no vendor is found", async () => { + (findVendorByUserId as jest.Mock).mockResolvedValue(null); await modifyOrderStatus(req as Request, res as Response); - sinon.assert.calledWith(status, 400); - sinon.assert.calledWith(json, { error: "Invalid order status" }); + expect(statusSpy).toHaveBeenCalledWith(404); + expect(jsonSpy).toHaveBeenCalledWith({ message: "No vendor found" }); }); - it("should return status 404 if order is not found", async () => { - findByPkStub.resolves(null); + it("should return 400 if invalid status is provided", async () => { + req.body.status = "InvalidStatus"; + (findVendorByUserId as jest.Mock).mockResolvedValue({ + vendorId: "vendor1", + }); await modifyOrderStatus(req as Request, res as Response); - sinon.assert.calledWith(status, 404); - sinon.assert.calledWith(json, { error: "Order not found" }); + expect(statusSpy).toHaveBeenCalledWith(400); + expect(jsonSpy).toHaveBeenCalledWith({ error: "Invalid order status" }); }); - it("should return status 403 if user is not the owner of the order", async () => { - findByPkStub.resolves({ userId: "2", save: sinon.stub() } as any); + it("should return 404 if order is not found", async () => { + (findVendorByUserId as jest.Mock).mockResolvedValue({ + vendorId: "vendor1", + }); + (Order.findByPk as jest.Mock).mockResolvedValue(null); await modifyOrderStatus(req as Request, res as Response); - sinon.assert.calledWith(status, 403); - sinon.assert.calledWith(json, { - error: "Only the vendor can update the order status", - }); + expect(statusSpy).toHaveBeenCalledWith(404); + expect(jsonSpy).toHaveBeenCalledWith({ error: "Order not found" }); }); it("should return status 200 and update the order status", async () => { - const save = sinon.stub(); - findByPkStub.resolves({ - userId: "1", + const mockOrder = { + orderId: "1", status: "Pending", - save, - } as any); + products: [{ productId: "product1", status: "Pending" }], + }; + + const mockVendor = { vendorId: "vendor1" }; + + (findVendorByUserId as jest.Mock).mockResolvedValue(mockVendor); + (Order.findByPk as jest.Mock).mockResolvedValue(mockOrder); + (Product.findOne as jest.Mock).mockResolvedValue(true); + (Order.update as jest.Mock).mockResolvedValue([1]); await modifyOrderStatus(req as Request, res as Response); - sinon.assert.calledWith(status, 200); - sinon.assert.calledWith(json, { - message: "Order has been shipped", - order: { userId: "1", status: "Shipped", save }, + expect(Order.update).toHaveBeenCalledWith( + { products: mockOrder.products }, + { where: { orderId: mockOrder.orderId } } + ); + + expect(Order.update).toHaveBeenCalledWith( + { status: "Pending" }, + { where: { orderId: mockOrder.orderId } } + ); + + expect(statusSpy).toHaveBeenCalledWith(200); + expect(jsonSpy).toHaveBeenCalledWith({ + message: `Order has been shipped`, + order: mockOrder, }); - sinon.assert.calledOnce(save); }); it("should return status 500 if an internal server error occurs", async () => { - findByPkStub.throws(new Error("Internal server error")); + const errorMessage = "Database error"; + (findVendorByUserId as jest.Mock).mockRejectedValue( + new Error(errorMessage) + ); await modifyOrderStatus(req as Request, res as Response); - sinon.assert.calledWith(status, 500); - sinon.assert.calledWith(json, { error: "Internal server error" }); + expect(statusSpy).toHaveBeenCalledWith(500); + expect(jsonSpy).toHaveBeenCalledWith({ error: errorMessage }); }); }); diff --git a/src/controllers/checkout.controller.ts b/src/controllers/checkout.controller.ts index 1df74ec..5ed68b5 100644 --- a/src/controllers/checkout.controller.ts +++ b/src/controllers/checkout.controller.ts @@ -24,7 +24,8 @@ export const createOrder = async (req: Request, res: Response) => { const orderItems = cartItems.map(item => ({ productId: item.productId, quantity: item.quantity, - price: item.price + price: item.price, + status:"pending" })); const totalAmount = orderItems.reduce((total, item) => total + (item.price * item.quantity), 0); diff --git a/src/controllers/orderController.ts b/src/controllers/orderController.ts index 5b0baa9..1959bac 100644 --- a/src/controllers/orderController.ts +++ b/src/controllers/orderController.ts @@ -1,6 +1,8 @@ import { Request, Response } from "express"; import Order from "../database/models/order"; +import { findVendorByUserId } from "../services/orderStatus"; +import Product from "../database/models/product"; const allowedStatuses = ["Pending", "Shipped", "Delivered", "Cancelled"]; @@ -8,7 +10,12 @@ export const modifyOrderStatus = async (req: Request, res: Response) => { try { const { orderId } = req.params; const { status } = req.body; - const userId = (req as any).token.userId; + const userId = (req as any).token.id; + + const vendor = await findVendorByUserId(userId); + if (!vendor) { + return res.status(404).json({ message: "No vendor found" }); + } if (!allowedStatuses.includes(status)) { return res.status(400).json({ error: "Invalid order status" }); @@ -20,21 +27,43 @@ export const modifyOrderStatus = async (req: Request, res: Response) => { return res.status(404).json({ error: "Order not found" }); } - if (order.userId !== userId) { - return res - .status(403) - .json({ error: "Only the vendor can update the order status" }); + let isOrderDelivered = true; + const updateProductStatusPromises = order.products.map( + async (item: any) => { + const productId = item.productId; + const isVendorProduct = await Product.findOne({ + where: { productId, vendorId: vendor.vendorId }, + }); + if (isVendorProduct) { + item.status = status; + } + if (item.status !== "Delivered") { + isOrderDelivered = false; + } + } + ); + + await Promise.all(updateProductStatusPromises); + await Order.update({ products: order.products }, { where: { orderId } }); + + let newOrderStatus = order.status; + if (isOrderDelivered) { + newOrderStatus = "Delivered"; + } else { + newOrderStatus = "Pending"; } - order.status = status; - await order.save(); + await Order.update({ status: newOrderStatus }, { where: { orderId } }); - res - .status(200) - .json({ message: `Order has been ${status.toLowerCase()}`, order }); - } catch (error:any) { + return res.status(200).json({ + message: `Order has been ${status.toLowerCase()}`, + order, + }); + } catch (error: any) { console.error(`Failed to update order status: ${error}`); res.status(500).json({ error: error.message }); } }; + + diff --git a/src/database/config/config.js b/src/database/config/config.js index f67499c..ea7f392 100644 --- a/src/database/config/config.js +++ b/src/database/config/config.js @@ -15,4 +15,4 @@ const config = { }, }; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/src/database/config/db.config.ts b/src/database/config/db.config.ts index 3428a5a..6962d82 100644 --- a/src/database/config/db.config.ts +++ b/src/database/config/db.config.ts @@ -34,3 +34,5 @@ const connectSequelize: Sequelize = new Sequelize(getURL(), { }); export default connectSequelize; + + diff --git a/src/database/seeders/20240522075149-seed-orders.js b/src/database/seeders/20240522075149-seed-orders.js index e8acd39..b45355a 100644 --- a/src/database/seeders/20240522075149-seed-orders.js +++ b/src/database/seeders/20240522075149-seed-orders.js @@ -28,12 +28,12 @@ module.exports = { userId: user.userId, paymentMethod: 'Bank Transfer', status: 'pending', - products: JSON.stringify({ + products: JSON.stringify([{ // @ts-ignore productId: product.productId, // @ts-ignore productName: product.name - }), + }]), createdAt: new Date(), updatedAt: new Date() }))) diff --git a/src/routes/order.route.ts b/src/routes/order.route.ts index c1f47d6..04def2c 100644 --- a/src/routes/order.route.ts +++ b/src/routes/order.route.ts @@ -8,6 +8,6 @@ const router = express.Router(); router.put("/order/:orderId/order-status", VerifyAccessToken, updateOrderStatus); router.get('/order/:orderId/status',VerifyAccessToken, getOrderStatus); -router.put('/order/:orderId/status',VerifyAccessToken, verifyAdmin, updateOrderStatus); +router.put('/order/:orderId/status',VerifyAccessToken, modifyOrderStatus); export default router; diff --git a/src/services/orderStatus.ts b/src/services/orderStatus.ts new file mode 100644 index 0000000..e43a6fd --- /dev/null +++ b/src/services/orderStatus.ts @@ -0,0 +1,8 @@ +import Vendor from "../database/models/vendor" +export const findVendorByUserId = async(userId: any)=>{ + const vendor = await Vendor.findOne({where: {userId: userId}}) + if(!vendor){ + return false + } + return vendor +} \ No newline at end of file