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(); }), })); const { User, UserAddress } = require("../../../models"); // Create express app with the router const app = express(); app.use(express.json()); app.use("/users", usersRouter); // 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", isPrimary: false, }; const mockCreatedAddress = { id: 3, ...addressData, userId: 1, }; mockUserAddressCreate.mockResolvedValue(mockCreatedAddress); const response = await request(app) .post("/users/addresses") .send(addressData); expect(response.status).toBe(201); expect(response.body).toEqual(mockCreatedAddress); expect(mockUserAddressCreate).toHaveBeenCalledWith({ ...addressData, userId: 1, }); }); it("should handle database errors during creation", async () => { mockUserAddressCreate.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 Main St", city: "New York", update: jest.fn(), }; beforeEach(() => { mockUserAddressFindByPk.mockResolvedValue(mockAddress); }); it("should update user address", async () => { const updateData = { address1: "123 Updated St", city: "Updated City", }; mockAddress.update.mockResolvedValue(); const response = await request(app) .put("/users/addresses/1") .send(updateData); expect(response.status).toBe(200); expect(response.body).toEqual({ id: 1, userId: 1, address1: "123 Main St", city: "New York", }); expect(mockAddress.update).toHaveBeenCalledWith(updateData); }); it("should return 404 for non-existent address", async () => { mockUserAddressFindByPk.mockResolvedValue(null); 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 return 403 for unauthorized user", async () => { const unauthorizedAddress = { ...mockAddress, userId: 2 }; mockUserAddressFindByPk.mockResolvedValue(unauthorizedAddress); const response = await request(app) .put("/users/addresses/1") .send({ address1: "Updated St" }); expect(response.status).toBe(403); expect(response.body).toEqual({ error: "Unauthorized" }); }); it("should handle database errors", async () => { mockUserAddressFindByPk.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", () => { const mockAddress = { id: 1, userId: 1, address1: "123 Main St", destroy: jest.fn(), }; beforeEach(() => { mockUserAddressFindByPk.mockResolvedValue(mockAddress); }); it("should delete user address", async () => { mockAddress.destroy.mockResolvedValue(); const response = await request(app).delete("/users/addresses/1"); expect(response.status).toBe(204); expect(mockAddress.destroy).toHaveBeenCalled(); }); it("should return 404 for non-existent address", async () => { mockUserAddressFindByPk.mockResolvedValue(null); const response = await request(app).delete("/users/addresses/999"); expect(response.status).toBe(404); expect(response.body).toEqual({ error: "Address not found" }); }); it("should return 403 for unauthorized user", async () => { const unauthorizedAddress = { ...mockAddress, userId: 2 }; mockUserAddressFindByPk.mockResolvedValue(unauthorizedAddress); const response = await request(app).delete("/users/addresses/1"); expect(response.status).toBe(403); expect(response.body).toEqual({ error: "Unauthorized" }); }); it("should handle database errors", async () => { mockUserAddressFindByPk.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"] }, }); }); 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", }; beforeEach(() => { mockUserFindByPk.mockResolvedValue(mockUpdatedUser); }); 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", }; const response = await request(app) .put("/users/profile") .send(profileData); expect(response.status).toBe(200); expect(response.body).toEqual(mockUpdatedUser); }); it("should exclude empty email from update", async () => { const profileData = { firstName: "Updated", lastName: "User", email: "", phone: "555-9999", }; const response = await request(app) .put("/users/profile") .send(profileData); expect(response.status).toBe(200); // Verify email was not included in the update call // (This would need to check the actual update call if we spy on req.user.update) }); it("should handle validation errors", async () => { const mockValidationError = new Error("Validation error"); mockValidationError.errors = [ { path: "email", message: "Invalid email format" }, ]; // Mock req.user.update to throw validation error const { authenticateToken } = require("../../../middleware/auth"); authenticateToken.mockImplementation((req, res, next) => { req.user = { id: 1, update: jest.fn().mockRejectedValue(mockValidationError), }; next(); }); const response = await request(app).put("/users/profile").send({ firstName: "Test", email: "invalid-email", }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Validation error", details: [{ field: "email", message: "Invalid email format" }], }); }); it("should handle general database errors", async () => { // Reset the authenticateToken mock to use default user const { authenticateToken } = require("../../../middleware/auth"); authenticateToken.mockImplementation((req, res, next) => { req.user = { id: 1, update: jest.fn().mockRejectedValue(new Error("Database error")), }; next(); }); const response = await request(app).put("/users/profile").send({ firstName: "Test", }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: "Database error" }); }); }); });