Files
rentall-app/backend/tests/unit/routes/users.test.js
2026-01-18 19:18:35 -05:00

544 lines
16 KiB
JavaScript

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