Skip to content

Commit

Permalink
Merge pull request #87 from atlp-rwanda/ft-update-order
Browse files Browse the repository at this point in the history
Ft update order
  • Loading branch information
iamfrerot authored Jun 16, 2024
2 parents c3efa1e + 8cc4532 commit 6b2206d
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 81 deletions.
112 changes: 65 additions & 47 deletions src/__test__/order.test.ts
Original file line number Diff line number Diff line change
@@ -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<CustomRequest>;
let req: Partial<Request> & { token: { id: string } };
let res: Partial<Response>;
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 });
});
});
3 changes: 2 additions & 1 deletion src/controllers/checkout.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
51 changes: 40 additions & 11 deletions src/controllers/orderController.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@

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"];

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" });
Expand All @@ -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 });
}
};



2 changes: 1 addition & 1 deletion src/database/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ const config = {
},
};

module.exports = config;
module.exports = config;
2 changes: 2 additions & 0 deletions src/database/config/db.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ const connectSequelize: Sequelize = new Sequelize(getURL(), {
});

export default connectSequelize;


45 changes: 24 additions & 21 deletions src/database/seeders/20240522075149-seed-orders.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
"use strict";

const { v4: uuidv4 } = require('uuid');
const { v4: uuidv4 } = require("uuid");

/** @type {import('sequelize-cli').Migration} */
module.exports = {
Expand All @@ -17,34 +17,37 @@ module.exports = {
LIMIT 3;
`);

const orders = users.flatMap(user =>
products.map(product => ({
const orders = users.flatMap((user) =>
products.map((product) => ({
orderId: uuidv4(),
deliveryAddress: JSON.stringify({
street: 'KG 111 ST',
city: 'Kigali'
street: "KG 111 ST",
city: "Kigali",
}),
// @ts-ignore
userId: user.userId,
paymentMethod: 'Bank Transfer',
status: 'pending',
products: JSON.stringify([{
// @ts-ignore
productId: product.productId,
// @ts-ignore
productName: product.name,
quantity: 3
}]),
paymentMethod: "Bank Transfer",
status: "pending",
products: JSON.stringify([
{
// @ts-ignore
productId: product.productId,
// @ts-ignore
productName: product.name,
status: "pending",
quantity: 3,
},
]),
createdAt: new Date(),
updatedAt: new Date()
})))

await queryInterface.bulkInsert('Orders', orders, {});
updatedAt: new Date(),
}))
);

await queryInterface.bulkInsert("Orders", orders, {});
},

async down(queryInterface, Sequelize) {
// @ts-ignore
await queryInterface.bulkDelete('Orders', null, {})
}
await queryInterface.bulkDelete("Orders", null, {});
},
};
8 changes: 8 additions & 0 deletions src/services/orderStatus.ts
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 6b2206d

Please sign in to comment.