From 80cfcaed1f5cbd08a05782067e6e0a7487d23b69 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 1 Sep 2025 03:18:55 -0500 Subject: [PATCH] feat: enhance order service tests and improve error handling in getOrdersByUser function --- src/services/order.service.test.ts | 435 +++++++++++++++++++++-------- src/services/order.service.ts | 44 +-- 2 files changed, 338 insertions(+), 141 deletions(-) diff --git a/src/services/order.service.test.ts b/src/services/order.service.test.ts index f5ffa51..640ee65 100644 --- a/src/services/order.service.test.ts +++ b/src/services/order.service.test.ts @@ -1,4 +1,5 @@ -import { describe, expect, it, vi } from "vitest"; +import { Decimal } from "@prisma/client/runtime/library"; +import { describe, expect, it, vi, beforeEach } from "vitest"; import { prisma as mockPrisma } from "@/db/prisma"; import { calculateTotal } from "@/lib/cart"; @@ -11,7 +12,7 @@ import { createTestRequest, createTestUser, } from "@/lib/utils.tests"; -import type { CartItemInput } from "@/models/cart.model"; +import type { CartItemWithProduct } from "@/models/cart.model"; import { getSession } from "@/session.server"; import { createOrder, getOrdersByUser } from "./order.service"; @@ -31,20 +32,34 @@ vi.mock("@/lib/cart"); vi.mock("@/session.server"); describe("Order Service", () => { - const mockedItems: CartItemInput[] = [ + beforeEach(() => { + vi.clearAllMocks(); + }); + + const mockedItems: CartItemWithProduct[] = [ { - productId: 1, + product: { + id: 1, + title: "Test Product", + price: 19.99, + imgSrc: "test-product.jpg", + alt: "Test Product Alt", + isOnSale: false, + }, quantity: 2, - title: "Test Product", - price: 19.99, - imgSrc: "test-product.jpg", + attributeValueId: 1, }, { - productId: 2, + product: { + id: 2, + title: "Another Product", + price: 29.99, + imgSrc: "another-product.jpg", + alt: "Another Product Alt", + isOnSale: true, + }, quantity: 1, - title: "Another Product", - price: 29.99, - imgSrc: "another-product.jpg", + attributeValueId: 2, }, ]; @@ -53,61 +68,105 @@ describe("Order Service", () => { const mockedTotalAmount = 200; const mockedRequest = createTestRequest(); - it("should create an order", async () => { - const prismaOrder = { - ...createTestDBOrder(), - items: [createTestDBOrderItem()], - }; - - vi.mocked(getOrCreateUser).mockResolvedValue(mockedUser); - vi.mocked(calculateTotal).mockReturnValue(mockedTotalAmount); - - vi.mocked(mockPrisma.order.create).mockResolvedValue(prismaOrder); - - const order = await createOrder(mockedItems, mockedFormData, "payment-id"); - expect(mockPrisma.order.create).toHaveBeenCalledWith({ - data: { - userId: mockedUser.id, - totalAmount: mockedTotalAmount, - email: mockedFormData.email, - firstName: mockedFormData.firstName, - lastName: mockedFormData.lastName, - company: mockedFormData.company, - address: mockedFormData.address, - city: mockedFormData.city, - country: mockedFormData.country, - region: mockedFormData.region, - zip: mockedFormData.zip, - phone: mockedFormData.phone, - items: { - create: mockedItems.map((item) => ({ - productId: item.productId, - quantity: item.quantity, - title: item.title, - price: item.price, - imgSrc: item.imgSrc, - })), + describe("createOrder", () => { + it("should create an order successfully", async () => { + const prismaOrder = { + ...createTestDBOrder(), + items: [ + { + ...createTestDBOrderItem(), + variantAttributeValue: { + id: 1, + value: "Test Value", + price: 19.99, + product: { + id: 1, + title: "Test Product", + imgSrc: "test-product.jpg", + alt: "Test Product Alt", + isOnSale: false, + }, + }, + }, + ], + }; + + vi.mocked(getOrCreateUser).mockResolvedValue(mockedUser); + vi.mocked(calculateTotal).mockReturnValue(mockedTotalAmount); + vi.mocked(mockPrisma.order.create).mockResolvedValue(prismaOrder); + + const order = await createOrder(mockedItems, mockedFormData, "payment-id"); + + expect(getOrCreateUser).toHaveBeenCalledWith(mockedFormData.email); + expect(calculateTotal).toHaveBeenCalledWith(mockedItems); + expect(mockPrisma.order.create).toHaveBeenCalledWith({ + data: { + userId: mockedUser.id, + totalAmount: mockedTotalAmount, + email: mockedFormData.email, + firstName: mockedFormData.firstName, + lastName: mockedFormData.lastName, + company: mockedFormData.company, + address: mockedFormData.address, + city: mockedFormData.city, + country: mockedFormData.country, + region: mockedFormData.region, + zip: mockedFormData.zip, + phone: mockedFormData.phone, + items: { + create: mockedItems.map((item) => ({ + attributeValueId: item.attributeValueId, + quantity: item.quantity, + title: item.product.title, + price: item.product.price ?? 0, + imgSrc: item.product.imgSrc ?? "", + })), + }, + paymentId: "payment-id", + }, + include: { + items: { + include: { + variantAttributeValue: { + include: { + product: { + select: { + id: true, + title: true, + imgSrc: true, + alt: true, + isOnSale: true, + }, + }, + }, + }, + }, + }, + }, + }); + + expect(order).toEqual({ + ...prismaOrder, + totalAmount: Number(prismaOrder.totalAmount), + items: prismaOrder.items.map((item) => ({ + ...item, + price: Number(item.price), + variantAttributeValue: item.variantAttributeValue, + })), + createdAt: prismaOrder.createdAt, + updatedAt: prismaOrder.updatedAt, + details: { + email: prismaOrder.email, + firstName: prismaOrder.firstName, + lastName: prismaOrder.lastName, + company: prismaOrder.company, + address: prismaOrder.address, + city: prismaOrder.city, + country: prismaOrder.country, + region: prismaOrder.region, + zip: prismaOrder.zip, + phone: prismaOrder.phone, }, - paymentId: "payment-id", - }, - include: { - items: true, - }, - }); - expect(order).toEqual({ - ...prismaOrder, - totalAmount: Number(prismaOrder.totalAmount), - items: prismaOrder.items.map((item) => ({ - ...item, - price: Number(item.price), - imgSrc: item.imgSrc ?? "", - productId: item.productId ?? 0, - createdAt: item.createdAt, - updatedAt: item.updatedAt, - })), - createdAt: prismaOrder.createdAt, - updatedAt: prismaOrder.updatedAt, - details: { email: prismaOrder.email, firstName: prismaOrder.firstName, lastName: prismaOrder.lastName, @@ -118,43 +177,141 @@ describe("Order Service", () => { region: prismaOrder.region, zip: prismaOrder.zip, phone: prismaOrder.phone, - }, - paymentId: prismaOrder.paymentId, + paymentId: prismaOrder.paymentId, + }); }); - }); - it("should get orders by user", async () => { - const prismaOrders = [ - { ...createTestDBOrder(), items: [createTestOrderItem()] }, - { - ...createTestDBOrder({ id: 2 }), - items: [createTestOrderItem({ id: 2 })], - }, - ]; - const mockedSession = createMockSession(mockedUser.id); - vi.mocked(getSession).mockResolvedValue(mockedSession); - vi.mocked(mockPrisma.order.findMany).mockResolvedValue(prismaOrders); - const orders = await getOrdersByUser(mockedRequest); - expect(mockPrisma.order.findMany).toHaveBeenCalledWith({ - where: { userId: mockedUser.id }, - include: { items: true }, - orderBy: { createdAt: "desc" }, + it("should throw error if order creation fails", async () => { + vi.mocked(getOrCreateUser).mockResolvedValue(mockedUser); + vi.mocked(calculateTotal).mockReturnValue(mockedTotalAmount); + vi.mocked(mockPrisma.order.create).mockRejectedValue( + new Error("Database error") + ); + + await expect( + createOrder(mockedItems, mockedFormData, "payment-id") + ).rejects.toThrow("Failed to create order"); + + expect(mockPrisma.order.create).toHaveBeenCalled(); }); - expect(orders).toEqual( - prismaOrders.map((order) => ({ - ...order, - totalAmount: Number(order.totalAmount), - items: order.items.map((item) => ({ - ...item, - price: Number(item.price), - imgSrc: item.imgSrc ?? "", - productId: item.productId ?? 0, - createdAt: item.createdAt, - updatedAt: item.updatedAt, - })), - createdAt: order.createdAt, - updatedAt: order.updatedAt, - details: { + + it("should handle empty items array", async () => { + vi.mocked(getOrCreateUser).mockResolvedValue(mockedUser); + vi.mocked(calculateTotal).mockReturnValue(0); + + const prismaOrder = { + ...createTestDBOrder({ totalAmount: new Decimal(0) }), + items: [], + }; + + vi.mocked(mockPrisma.order.create).mockResolvedValue(prismaOrder); + + const order = await createOrder([], mockedFormData, "payment-id"); + + expect(order.items).toEqual([]); + expect(order.totalAmount).toBe(0); + }); + }); + + describe("getOrdersByUser", () => { + it("should get orders by user successfully", async () => { + const prismaOrders = [ + { + ...createTestDBOrder(), + items: [ + { + ...createTestOrderItem(), + variantAttributeValue: { + id: 1, + value: "Test Value", + price: 19.99, + product: { + id: 1, + title: "Test Product", + imgSrc: "test-product.jpg", + alt: "Test Product Alt", + isOnSale: false, + }, + }, + }, + ], + }, + { + ...createTestDBOrder({ id: 2 }), + items: [ + { + ...createTestOrderItem({ id: 2 }), + variantAttributeValue: { + id: 2, + value: "Test Value 2", + price: 29.99, + product: { + id: 2, + title: "Another Product", + imgSrc: "another-product.jpg", + alt: "Another Product Alt", + isOnSale: true, + }, + }, + }, + ], + }, + ]; + const mockedSession = createMockSession(mockedUser.id); + + vi.mocked(getSession).mockResolvedValue(mockedSession); + vi.mocked(mockPrisma.order.findMany).mockResolvedValue(prismaOrders); + + const orders = await getOrdersByUser(mockedRequest); + + expect(getSession).toHaveBeenCalledWith("session=mock-session-id"); + expect(mockPrisma.order.findMany).toHaveBeenCalledWith({ + where: { userId: mockedUser.id }, + include: { + items: { + include: { + variantAttributeValue: { + include: { + product: { + select: { + id: true, + title: true, + imgSrc: true, + alt: true, + isOnSale: true, + }, + }, + }, + }, + }, + }, + }, + orderBy: { createdAt: "desc" }, + }); + + expect(orders).toEqual( + prismaOrders.map((order) => ({ + ...order, + totalAmount: Number(order.totalAmount), + items: order.items.map((item) => ({ + ...item, + price: Number(item.price), + variantAttributeValue: item.variantAttributeValue, + })), + createdAt: order.createdAt, + updatedAt: order.updatedAt, + details: { + email: order.email, + firstName: order.firstName, + lastName: order.lastName, + company: order.company, + address: order.address, + city: order.city, + country: order.country, + region: order.region, + zip: order.zip, + phone: order.phone, + }, email: order.email, firstName: order.firstName, lastName: order.lastName, @@ -165,34 +322,68 @@ describe("Order Service", () => { region: order.region, zip: order.zip, phone: order.phone, - }, - })) - ); - }); + })) + ); + }); - it("should throw error if user is not authenticated", async () => { - const mockedSession = createMockSession(null); + it("should throw error if user is not authenticated", async () => { + const mockedSession = createMockSession(null); - vi.mocked(getSession).mockResolvedValue(mockedSession); + vi.mocked(getSession).mockResolvedValue(mockedSession); - await expect(getOrdersByUser(mockedRequest)).rejects.toThrow( - "User not authenticated" - ); + await expect(getOrdersByUser(mockedRequest)).rejects.toThrow( + "User not authenticated" + ); - expect(getSession).toHaveBeenCalledWith("session=mock-session-id"); - }); + expect(getSession).toHaveBeenCalledWith("session=mock-session-id"); + expect(mockPrisma.order.findMany).not.toHaveBeenCalled(); + }); + + it("should return empty array if user has no orders", async () => { + const mockedSession = createMockSession(mockedUser.id); - it("should throw error if order creation fails", async () => { - vi.mocked(getOrCreateUser).mockResolvedValue(mockedUser); - vi.mocked(calculateTotal).mockReturnValue(mockedTotalAmount); - vi.mocked(mockPrisma.order.create).mockRejectedValue( - new Error("Database error") - ); + vi.mocked(getSession).mockResolvedValue(mockedSession); + vi.mocked(mockPrisma.order.findMany).mockResolvedValue([]); - await expect( - createOrder(mockedItems, mockedFormData, "payment-id") - ).rejects.toThrow("Failed to create order"); + const orders = await getOrdersByUser(mockedRequest); - expect(mockPrisma.order.create).toHaveBeenCalled(); + expect(orders).toEqual([]); + expect(mockPrisma.order.findMany).toHaveBeenCalledWith({ + where: { userId: mockedUser.id }, + include: { + items: { + include: { + variantAttributeValue: { + include: { + product: { + select: { + id: true, + title: true, + imgSrc: true, + alt: true, + isOnSale: true, + }, + }, + }, + }, + }, + }, + }, + orderBy: { createdAt: "desc" }, + }); + }); + + it("should handle database errors gracefully", async () => { + const mockedSession = createMockSession(mockedUser.id); + + vi.mocked(getSession).mockResolvedValue(mockedSession); + vi.mocked(mockPrisma.order.findMany).mockRejectedValue( + new Error("Database connection error") + ); + + await expect(getOrdersByUser(mockedRequest)).rejects.toThrow( + "Failed to fetch orders" + ); + }); }); -}); +}); \ No newline at end of file diff --git a/src/services/order.service.ts b/src/services/order.service.ts index efdd63a..1592c6e 100644 --- a/src/services/order.service.ts +++ b/src/services/order.service.ts @@ -93,31 +93,37 @@ export async function getOrdersByUser(request: Request): Promise { throw new Error("User not authenticated"); } - const orders = await prisma.order.findMany({ - where: { userId }, - include: { - items: { - include: { - variantAttributeValue: { - include: { - product: { - select: { - id: true, - title: true, - imgSrc: true, - alt: true, - isOnSale: true, + let orders; + + try { + orders = await prisma.order.findMany({ + where: { userId }, + include: { + items: { + include: { + variantAttributeValue: { + include: { + product: { + select: { + id: true, + title: true, + imgSrc: true, + alt: true, + isOnSale: true, + }, }, }, }, }, }, }, - }, - orderBy: { - createdAt: "desc", - }, - }); + orderBy: { + createdAt: "desc", + }, + }); + } catch (error) { + throw new Error("Failed to fetch orders", { cause: error }); + } return orders.map((order) => { const details = {