const request = require("supertest"); const express = require("express"); const usersRouter = require("../../../routes/users"); // Mock all dependencies jest.mock("../../../models", () => ({ User: { findByPk: jest.fn(), update: jest.fn(), }, UserAddress: { findAll: jest.fn(), findByPk: jest.fn(), create: jest.fn(), }, })); jest.mock("../../../middleware/auth", () => ({ authenticateToken: jest.fn((req, res, next) => { req.user = { id: 1, update: jest.fn(), }; next(); }), optionalAuth: jest.fn((req, res, next) => next()), requireAdmin: jest.fn((req, res, next) => next()), })); jest.mock("../../../services/UserService", () => ({ createUserAddress: jest.fn(), updateUserAddress: jest.fn(), deleteUserAddress: jest.fn(), updateProfile: jest.fn(), })); jest.mock("../../../utils/logger", () => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), withRequestId: jest.fn(() => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), })), sanitize: jest.fn((data) => data), })); const { User, UserAddress } = require("../../../models"); const userService = require("../../../services/UserService"); // Create express app with the router const app = express(); app.use(express.json()); app.use("/users", usersRouter); // Add error handler middleware app.use((err, req, res, next) => { res.status(500).json({ error: err.message }); }); // Mock models const mockUserFindByPk = User.findByPk; const mockUserUpdate = User.update; const mockUserAddressFindAll = UserAddress.findAll; const mockUserAddressFindByPk = UserAddress.findByPk; const mockUserAddressCreate = UserAddress.create; describe("Users Routes", () => { beforeEach(() => { jest.clearAllMocks(); }); describe("GET /profile", () => { it("should get user profile for authenticated user", async () => { const mockUser = { id: 1, firstName: "John", lastName: "Doe", email: "john@example.com", phone: "555-1234", imageFilename: "profile.jpg", }; mockUserFindByPk.mockResolvedValue(mockUser); const response = await request(app).get("/users/profile"); expect(response.status).toBe(200); expect(response.body).toEqual(mockUser); expect(mockUserFindByPk).toHaveBeenCalledWith(1, { attributes: { exclude: ["password"] }, }); }); it("should handle database errors", async () => { mockUserFindByPk.mockRejectedValue(new Error("Database error")); const response = await request(app).get("/users/profile"); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("GET /addresses", () => { it("should get user addresses", async () => { const mockAddresses = [ { id: 1, userId: 1, address1: "123 Main St", city: "New York", isPrimary: true, }, { id: 2, userId: 1, address1: "456 Oak Ave", city: "Boston", isPrimary: false, }, ]; mockUserAddressFindAll.mockResolvedValue(mockAddresses); const response = await request(app).get("/users/addresses"); expect(response.status).toBe(200); expect(response.body).toEqual(mockAddresses); expect(mockUserAddressFindAll).toHaveBeenCalledWith({ where: { userId: 1 }, order: [ ["isPrimary", "DESC"], ["createdAt", "ASC"], ], }); }); it("should handle database errors", async () => { mockUserAddressFindAll.mockRejectedValue(new Error("Database error")); const response = await request(app).get("/users/addresses"); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("POST /addresses", () => { it("should create a new address", async () => { const addressData = { address1: "789 Pine St", address2: "Apt 4B", city: "Chicago", state: "IL", zipCode: "60601", country: "USA", }; const mockCreatedAddress = { id: 3, ...addressData, userId: 1, }; userService.createUserAddress.mockResolvedValue(mockCreatedAddress); const response = await request(app) .post("/users/addresses") .send(addressData); expect(response.status).toBe(201); expect(response.body).toEqual(mockCreatedAddress); expect(userService.createUserAddress).toHaveBeenCalledWith(1, addressData); }); it("should handle database errors during creation", async () => { userService.createUserAddress.mockRejectedValue(new Error("Database error")); const response = await request(app).post("/users/addresses").send({ address1: "789 Pine St", city: "Chicago", }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("PUT /addresses/:id", () => { const mockAddress = { id: 1, userId: 1, address1: "123 Updated St", city: "Updated City", }; it("should update user address", async () => { const updateData = { address1: "123 Updated St", city: "Updated City", }; userService.updateUserAddress.mockResolvedValue(mockAddress); const response = await request(app) .put("/users/addresses/1") .send(updateData); expect(response.status).toBe(200); expect(response.body).toEqual(mockAddress); expect(userService.updateUserAddress).toHaveBeenCalledWith(1, "1", updateData); }); it("should return 404 for non-existent address", async () => { userService.updateUserAddress.mockRejectedValue(new Error("Address not found")); const response = await request(app) .put("/users/addresses/999") .send({ address1: "Updated St" }); expect(response.status).toBe(404); expect(response.body).toEqual({ error: "Address not found" }); }); it("should handle database errors", async () => { userService.updateUserAddress.mockRejectedValue(new Error("Database error")); const response = await request(app) .put("/users/addresses/1") .send({ address1: "Updated St" }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("DELETE /addresses/:id", () => { it("should delete user address", async () => { userService.deleteUserAddress.mockResolvedValue(); const response = await request(app).delete("/users/addresses/1"); expect(response.status).toBe(204); expect(userService.deleteUserAddress).toHaveBeenCalledWith(1, "1"); }); it("should return 404 for non-existent address", async () => { userService.deleteUserAddress.mockRejectedValue(new Error("Address not found")); const response = await request(app).delete("/users/addresses/999"); expect(response.status).toBe(404); expect(response.body).toEqual({ error: "Address not found" }); }); it("should handle database errors", async () => { userService.deleteUserAddress.mockRejectedValue(new Error("Database error")); const response = await request(app).delete("/users/addresses/1"); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("GET /availability", () => { it("should get user availability settings", async () => { const mockUser = { defaultAvailableAfter: "09:00", defaultAvailableBefore: "17:00", defaultSpecifyTimesPerDay: true, defaultWeeklyTimes: { monday: "09:00-17:00", tuesday: "10:00-16:00" }, }; mockUserFindByPk.mockResolvedValue(mockUser); const response = await request(app).get("/users/availability"); expect(response.status).toBe(200); expect(response.body).toEqual({ generalAvailableAfter: "09:00", generalAvailableBefore: "17:00", specifyTimesPerDay: true, weeklyTimes: { monday: "09:00-17:00", tuesday: "10:00-16:00" }, }); expect(mockUserFindByPk).toHaveBeenCalledWith(1, { attributes: [ "defaultAvailableAfter", "defaultAvailableBefore", "defaultSpecifyTimesPerDay", "defaultWeeklyTimes", ], }); }); it("should handle database errors", async () => { mockUserFindByPk.mockRejectedValue(new Error("Database error")); const response = await request(app).get("/users/availability"); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("PUT /availability", () => { it("should update user availability settings", async () => { const availabilityData = { generalAvailableAfter: "08:00", generalAvailableBefore: "18:00", specifyTimesPerDay: false, weeklyTimes: { monday: "08:00-18:00" }, }; mockUserUpdate.mockResolvedValue([1]); const response = await request(app) .put("/users/availability") .send(availabilityData); expect(response.status).toBe(200); expect(response.body).toEqual({ message: "Availability updated successfully", }); expect(mockUserUpdate).toHaveBeenCalledWith( { defaultAvailableAfter: "08:00", defaultAvailableBefore: "18:00", defaultSpecifyTimesPerDay: false, defaultWeeklyTimes: { monday: "08:00-18:00" }, }, { where: { id: 1 }, } ); }); it("should handle database errors", async () => { mockUserUpdate.mockRejectedValue(new Error("Database error")); const response = await request(app).put("/users/availability").send({ generalAvailableAfter: "08:00", generalAvailableBefore: "18:00", }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("GET /:id", () => { it("should get public user profile by ID", async () => { const mockUser = { id: 2, firstName: "Jane", lastName: "Smith", username: "janesmith", imageFilename: "jane.jpg", }; mockUserFindByPk.mockResolvedValue(mockUser); const response = await request(app).get("/users/2"); expect(response.status).toBe(200); expect(response.body).toEqual(mockUser); expect(mockUserFindByPk).toHaveBeenCalledWith("2", { attributes: { exclude: ["password", "email", "phone", "address", "verificationToken", "passwordResetToken", "isBanned", "bannedAt", "bannedBy", "banReason"] }, }); }); it("should return 404 for non-existent user", async () => { mockUserFindByPk.mockResolvedValue(null); const response = await request(app).get("/users/999"); expect(response.status).toBe(404); expect(response.body).toEqual({ error: "User not found" }); }); it("should handle database errors", async () => { mockUserFindByPk.mockRejectedValue(new Error("Database error")); const response = await request(app).get("/users/2"); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("PUT /profile", () => { const mockUpdatedUser = { id: 1, firstName: "Updated", lastName: "User", email: "updated@example.com", phone: "555-9999", }; it("should update user profile", async () => { const profileData = { firstName: "Updated", lastName: "User", email: "updated@example.com", phone: "555-9999", address1: "123 New St", city: "New City", }; userService.updateProfile.mockResolvedValue(mockUpdatedUser); const response = await request(app) .put("/users/profile") .send(profileData); expect(response.status).toBe(200); expect(response.body).toEqual(mockUpdatedUser); expect(userService.updateProfile).toHaveBeenCalledWith(1, profileData); }); it("should handle database errors", async () => { userService.updateProfile.mockRejectedValue(new Error("Database error")); const response = await request(app).put("/users/profile").send({ firstName: "Test", }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); describe("POST /admin/:id/ban", () => { const mockTargetUser = { id: 2, role: "user", isBanned: false, banUser: jest.fn().mockResolvedValue(), }; beforeEach(() => { mockUserFindByPk.mockResolvedValue(mockTargetUser); }); it("should ban a user with reason", async () => { const response = await request(app) .post("/users/admin/2/ban") .send({ reason: "Violation of terms" }); expect(response.status).toBe(200); expect(response.body.message).toContain("banned successfully"); expect(mockTargetUser.banUser).toHaveBeenCalledWith(1, "Violation of terms"); }); it("should return 400 when reason is not provided", async () => { const response = await request(app) .post("/users/admin/2/ban") .send({}); expect(response.status).toBe(400); expect(response.body).toEqual({ error: "Ban reason is required" }); }); it("should return 404 for non-existent user", async () => { mockUserFindByPk.mockResolvedValue(null); const response = await request(app) .post("/users/admin/999/ban") .send({ reason: "Test" }); expect(response.status).toBe(404); expect(response.body).toEqual({ error: "User not found" }); }); it("should return 403 when trying to ban admin", async () => { const adminUser = { ...mockTargetUser, role: "admin" }; mockUserFindByPk.mockResolvedValue(adminUser); const response = await request(app) .post("/users/admin/2/ban") .send({ reason: "Test" }); expect(response.status).toBe(403); expect(response.body).toEqual({ error: "Cannot ban admin users" }); }); it("should return 400 when user is already banned", async () => { const bannedUser = { ...mockTargetUser, isBanned: true }; mockUserFindByPk.mockResolvedValue(bannedUser); const response = await request(app) .post("/users/admin/2/ban") .send({ reason: "Test" }); expect(response.status).toBe(400); expect(response.body).toEqual({ error: "User is already banned" }); }); }); describe("POST /admin/:id/unban", () => { const mockBannedUser = { id: 2, isBanned: true, unbanUser: jest.fn().mockResolvedValue(), }; beforeEach(() => { mockUserFindByPk.mockResolvedValue(mockBannedUser); }); it("should unban a banned user", async () => { const response = await request(app) .post("/users/admin/2/unban"); expect(response.status).toBe(200); expect(response.body.message).toContain("unbanned successfully"); expect(mockBannedUser.unbanUser).toHaveBeenCalled(); }); it("should return 404 for non-existent user", async () => { mockUserFindByPk.mockResolvedValue(null); const response = await request(app) .post("/users/admin/999/unban"); expect(response.status).toBe(404); expect(response.body).toEqual({ error: "User not found" }); }); it("should return 400 when user is not banned", async () => { const notBannedUser = { ...mockBannedUser, isBanned: false }; mockUserFindByPk.mockResolvedValue(notBannedUser); const response = await request(app) .post("/users/admin/2/unban"); expect(response.status).toBe(400); expect(response.body).toEqual({ error: "User is not banned" }); }); }); });