s3 image file validation
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
const request = require('supertest');
|
||||
const express = require('express');
|
||||
const usersRouter = require('../../../routes/users');
|
||||
const request = require("supertest");
|
||||
const express = require("express");
|
||||
const usersRouter = require("../../../routes/users");
|
||||
|
||||
// Mock all dependencies
|
||||
jest.mock('../../../models', () => ({
|
||||
jest.mock("../../../models", () => ({
|
||||
User: {
|
||||
findByPk: jest.fn(),
|
||||
update: jest.fn(),
|
||||
@@ -15,41 +15,22 @@ jest.mock('../../../models', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../middleware/auth', () => ({
|
||||
jest.mock("../../../middleware/auth", () => ({
|
||||
authenticateToken: jest.fn((req, res, next) => {
|
||||
req.user = {
|
||||
id: 1,
|
||||
update: jest.fn()
|
||||
update: jest.fn(),
|
||||
};
|
||||
next();
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../../middleware/upload', () => ({
|
||||
uploadProfileImage: jest.fn((req, res, callback) => {
|
||||
// Mock successful upload
|
||||
req.file = {
|
||||
filename: 'test-profile.jpg'
|
||||
};
|
||||
callback(null);
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
promises: {
|
||||
unlink: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('path');
|
||||
const { User, UserAddress } = require('../../../models');
|
||||
const { uploadProfileImage } = require('../../../middleware/upload');
|
||||
const fs = require('fs').promises;
|
||||
const { User, UserAddress } = require("../../../models");
|
||||
|
||||
// Create express app with the router
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/users', usersRouter);
|
||||
app.use("/users", usersRouter);
|
||||
|
||||
// Mock models
|
||||
const mockUserFindByPk = User.findByPk;
|
||||
@@ -58,97 +39,96 @@ const mockUserAddressFindAll = UserAddress.findAll;
|
||||
const mockUserAddressFindByPk = UserAddress.findByPk;
|
||||
const mockUserAddressCreate = UserAddress.create;
|
||||
|
||||
describe('Users Routes', () => {
|
||||
describe("Users Routes", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('GET /profile', () => {
|
||||
it('should get user profile for authenticated user', async () => {
|
||||
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',
|
||||
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');
|
||||
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'] }
|
||||
attributes: { exclude: ["password"] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockUserFindByPk.mockRejectedValue(new Error('Database error'));
|
||||
it("should handle database errors", async () => {
|
||||
mockUserFindByPk.mockRejectedValue(new Error("Database error"));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/profile');
|
||||
const response = await request(app).get("/users/profile");
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /addresses', () => {
|
||||
it('should get user addresses', async () => {
|
||||
describe("GET /addresses", () => {
|
||||
it("should get user addresses", async () => {
|
||||
const mockAddresses = [
|
||||
{
|
||||
id: 1,
|
||||
userId: 1,
|
||||
address1: '123 Main St',
|
||||
city: 'New York',
|
||||
address1: "123 Main St",
|
||||
city: "New York",
|
||||
isPrimary: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userId: 1,
|
||||
address1: '456 Oak Ave',
|
||||
city: 'Boston',
|
||||
address1: "456 Oak Ave",
|
||||
city: "Boston",
|
||||
isPrimary: false,
|
||||
},
|
||||
];
|
||||
|
||||
mockUserAddressFindAll.mockResolvedValue(mockAddresses);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/addresses');
|
||||
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']]
|
||||
order: [
|
||||
["isPrimary", "DESC"],
|
||||
["createdAt", "ASC"],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockUserAddressFindAll.mockRejectedValue(new Error('Database error'));
|
||||
it("should handle database errors", async () => {
|
||||
mockUserAddressFindAll.mockRejectedValue(new Error("Database error"));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/addresses');
|
||||
const response = await request(app).get("/users/addresses");
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /addresses', () => {
|
||||
it('should create a new address', async () => {
|
||||
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',
|
||||
address1: "789 Pine St",
|
||||
address2: "Apt 4B",
|
||||
city: "Chicago",
|
||||
state: "IL",
|
||||
zipCode: "60601",
|
||||
country: "USA",
|
||||
isPrimary: false,
|
||||
};
|
||||
|
||||
@@ -161,38 +141,36 @@ describe('Users Routes', () => {
|
||||
mockUserAddressCreate.mockResolvedValue(mockCreatedAddress);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/addresses')
|
||||
.post("/users/addresses")
|
||||
.send(addressData);
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body).toEqual(mockCreatedAddress);
|
||||
expect(mockUserAddressCreate).toHaveBeenCalledWith({
|
||||
...addressData,
|
||||
userId: 1
|
||||
userId: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle database errors during creation', async () => {
|
||||
mockUserAddressCreate.mockRejectedValue(new Error('Database error'));
|
||||
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',
|
||||
});
|
||||
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' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /addresses/:id', () => {
|
||||
describe("PUT /addresses/:id", () => {
|
||||
const mockAddress = {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
address1: '123 Main St',
|
||||
city: 'New York',
|
||||
address1: "123 Main St",
|
||||
city: "New York",
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -200,68 +178,68 @@ describe('Users Routes', () => {
|
||||
mockUserAddressFindByPk.mockResolvedValue(mockAddress);
|
||||
});
|
||||
|
||||
it('should update user address', async () => {
|
||||
it("should update user address", async () => {
|
||||
const updateData = {
|
||||
address1: '123 Updated St',
|
||||
city: 'Updated City',
|
||||
address1: "123 Updated St",
|
||||
city: "Updated City",
|
||||
};
|
||||
|
||||
mockAddress.update.mockResolvedValue();
|
||||
|
||||
const response = await request(app)
|
||||
.put('/users/addresses/1')
|
||||
.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',
|
||||
address1: "123 Main St",
|
||||
city: "New York",
|
||||
});
|
||||
expect(mockAddress.update).toHaveBeenCalledWith(updateData);
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent address', async () => {
|
||||
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' });
|
||||
.put("/users/addresses/999")
|
||||
.send({ address1: "Updated St" });
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Address not found' });
|
||||
expect(response.body).toEqual({ error: "Address not found" });
|
||||
});
|
||||
|
||||
it('should return 403 for unauthorized user', async () => {
|
||||
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' });
|
||||
.put("/users/addresses/1")
|
||||
.send({ address1: "Updated St" });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ error: 'Unauthorized' });
|
||||
expect(response.body).toEqual({ error: "Unauthorized" });
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockUserAddressFindByPk.mockRejectedValue(new Error('Database error'));
|
||||
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' });
|
||||
.put("/users/addresses/1")
|
||||
.send({ address1: "Updated St" });
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /addresses/:id', () => {
|
||||
describe("DELETE /addresses/:id", () => {
|
||||
const mockAddress = {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
address1: '123 Main St',
|
||||
address1: "123 Main St",
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -269,211 +247,210 @@ describe('Users Routes', () => {
|
||||
mockUserAddressFindByPk.mockResolvedValue(mockAddress);
|
||||
});
|
||||
|
||||
it('should delete user address', async () => {
|
||||
it("should delete user address", async () => {
|
||||
mockAddress.destroy.mockResolvedValue();
|
||||
|
||||
const response = await request(app)
|
||||
.delete('/users/addresses/1');
|
||||
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 () => {
|
||||
it("should return 404 for non-existent address", async () => {
|
||||
mockUserAddressFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.delete('/users/addresses/999');
|
||||
const response = await request(app).delete("/users/addresses/999");
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Address not found' });
|
||||
expect(response.body).toEqual({ error: "Address not found" });
|
||||
});
|
||||
|
||||
it('should return 403 for unauthorized user', async () => {
|
||||
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');
|
||||
const response = await request(app).delete("/users/addresses/1");
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ error: 'Unauthorized' });
|
||||
expect(response.body).toEqual({ error: "Unauthorized" });
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockUserAddressFindByPk.mockRejectedValue(new Error('Database error'));
|
||||
it("should handle database errors", async () => {
|
||||
mockUserAddressFindByPk.mockRejectedValue(new Error("Database error"));
|
||||
|
||||
const response = await request(app)
|
||||
.delete('/users/addresses/1');
|
||||
const response = await request(app).delete("/users/addresses/1");
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /availability', () => {
|
||||
it('should get user availability settings', async () => {
|
||||
describe("GET /availability", () => {
|
||||
it("should get user availability settings", async () => {
|
||||
const mockUser = {
|
||||
defaultAvailableAfter: '09:00',
|
||||
defaultAvailableBefore: '17:00',
|
||||
defaultAvailableAfter: "09:00",
|
||||
defaultAvailableBefore: "17:00",
|
||||
defaultSpecifyTimesPerDay: true,
|
||||
defaultWeeklyTimes: { monday: '09:00-17:00', tuesday: '10:00-16:00' },
|
||||
defaultWeeklyTimes: { monday: "09:00-17:00", tuesday: "10:00-16:00" },
|
||||
};
|
||||
|
||||
mockUserFindByPk.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/availability');
|
||||
const response = await request(app).get("/users/availability");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
generalAvailableAfter: '09:00',
|
||||
generalAvailableBefore: '17:00',
|
||||
generalAvailableAfter: "09:00",
|
||||
generalAvailableBefore: "17:00",
|
||||
specifyTimesPerDay: true,
|
||||
weeklyTimes: { monday: '09:00-17:00', tuesday: '10:00-16:00' },
|
||||
weeklyTimes: { monday: "09:00-17:00", tuesday: "10:00-16:00" },
|
||||
});
|
||||
expect(mockUserFindByPk).toHaveBeenCalledWith(1, {
|
||||
attributes: ['defaultAvailableAfter', 'defaultAvailableBefore', 'defaultSpecifyTimesPerDay', 'defaultWeeklyTimes']
|
||||
attributes: [
|
||||
"defaultAvailableAfter",
|
||||
"defaultAvailableBefore",
|
||||
"defaultSpecifyTimesPerDay",
|
||||
"defaultWeeklyTimes",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockUserFindByPk.mockRejectedValue(new Error('Database error'));
|
||||
it("should handle database errors", async () => {
|
||||
mockUserFindByPk.mockRejectedValue(new Error("Database error"));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/availability');
|
||||
const response = await request(app).get("/users/availability");
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /availability', () => {
|
||||
it('should update user availability settings', async () => {
|
||||
describe("PUT /availability", () => {
|
||||
it("should update user availability settings", async () => {
|
||||
const availabilityData = {
|
||||
generalAvailableAfter: '08:00',
|
||||
generalAvailableBefore: '18:00',
|
||||
generalAvailableAfter: "08:00",
|
||||
generalAvailableBefore: "18:00",
|
||||
specifyTimesPerDay: false,
|
||||
weeklyTimes: { monday: '08:00-18:00' },
|
||||
weeklyTimes: { monday: "08:00-18:00" },
|
||||
};
|
||||
|
||||
mockUserUpdate.mockResolvedValue([1]);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/users/availability')
|
||||
.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 }
|
||||
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'));
|
||||
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',
|
||||
});
|
||||
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' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /:id', () => {
|
||||
it('should get public user profile by ID', async () => {
|
||||
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',
|
||||
firstName: "Jane",
|
||||
lastName: "Smith",
|
||||
username: "janesmith",
|
||||
imageFilename: "jane.jpg",
|
||||
};
|
||||
|
||||
mockUserFindByPk.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/2');
|
||||
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'] }
|
||||
expect(mockUserFindByPk).toHaveBeenCalledWith("2", {
|
||||
attributes: { exclude: ["password", "email", "phone", "address"] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent user', async () => {
|
||||
it("should return 404 for non-existent user", async () => {
|
||||
mockUserFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/999');
|
||||
const response = await request(app).get("/users/999");
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'User not found' });
|
||||
expect(response.body).toEqual({ error: "User not found" });
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockUserFindByPk.mockRejectedValue(new Error('Database error'));
|
||||
it("should handle database errors", async () => {
|
||||
mockUserFindByPk.mockRejectedValue(new Error("Database error"));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/users/2');
|
||||
const response = await request(app).get("/users/2");
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /profile', () => {
|
||||
describe("PUT /profile", () => {
|
||||
const mockUpdatedUser = {
|
||||
id: 1,
|
||||
firstName: 'Updated',
|
||||
lastName: 'User',
|
||||
email: 'updated@example.com',
|
||||
phone: '555-9999',
|
||||
firstName: "Updated",
|
||||
lastName: "User",
|
||||
email: "updated@example.com",
|
||||
phone: "555-9999",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockUserFindByPk.mockResolvedValue(mockUpdatedUser);
|
||||
});
|
||||
|
||||
it('should update user profile', async () => {
|
||||
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',
|
||||
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')
|
||||
.put("/users/profile")
|
||||
.send(profileData);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockUpdatedUser);
|
||||
});
|
||||
|
||||
it('should exclude empty email from update', async () => {
|
||||
it("should exclude empty email from update", async () => {
|
||||
const profileData = {
|
||||
firstName: 'Updated',
|
||||
lastName: 'User',
|
||||
email: '',
|
||||
phone: '555-9999',
|
||||
firstName: "Updated",
|
||||
lastName: "User",
|
||||
email: "",
|
||||
phone: "555-9999",
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.put('/users/profile')
|
||||
.put("/users/profile")
|
||||
.send(profileData);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
@@ -481,178 +458,51 @@ describe('Users Routes', () => {
|
||||
// (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');
|
||||
it("should handle validation errors", async () => {
|
||||
const mockValidationError = new Error("Validation error");
|
||||
mockValidationError.errors = [
|
||||
{ path: 'email', message: 'Invalid email format' }
|
||||
{ path: "email", message: "Invalid email format" },
|
||||
];
|
||||
|
||||
// Mock req.user.update to throw validation error
|
||||
const { authenticateToken } = require('../../../middleware/auth');
|
||||
const { authenticateToken } = require("../../../middleware/auth");
|
||||
authenticateToken.mockImplementation((req, res, next) => {
|
||||
req.user = {
|
||||
id: 1,
|
||||
update: jest.fn().mockRejectedValue(mockValidationError)
|
||||
update: jest.fn().mockRejectedValue(mockValidationError),
|
||||
};
|
||||
next();
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.put('/users/profile')
|
||||
.send({
|
||||
firstName: 'Test',
|
||||
email: 'invalid-email',
|
||||
});
|
||||
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' }]
|
||||
error: "Validation error",
|
||||
details: [{ field: "email", message: "Invalid email format" }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle general database errors', async () => {
|
||||
it("should handle general database errors", async () => {
|
||||
// Reset the authenticateToken mock to use default user
|
||||
const { authenticateToken } = require('../../../middleware/auth');
|
||||
const { authenticateToken } = require("../../../middleware/auth");
|
||||
authenticateToken.mockImplementation((req, res, next) => {
|
||||
req.user = {
|
||||
id: 1,
|
||||
update: jest.fn().mockRejectedValue(new Error('Database error'))
|
||||
update: jest.fn().mockRejectedValue(new Error("Database error")),
|
||||
};
|
||||
next();
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.put('/users/profile')
|
||||
.send({
|
||||
firstName: 'Test',
|
||||
});
|
||||
const response = await request(app).put("/users/profile").send({
|
||||
firstName: "Test",
|
||||
});
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Database error' });
|
||||
expect(response.body).toEqual({ error: "Database error" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /profile/image', () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
imageFilename: 'old-image.jpg',
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockUserFindByPk.mockResolvedValue(mockUser);
|
||||
});
|
||||
|
||||
it('should upload profile image successfully', async () => {
|
||||
mockUser.update.mockResolvedValue();
|
||||
fs.unlink.mockResolvedValue();
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/profile/image');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Profile image uploaded successfully',
|
||||
filename: 'test-profile.jpg',
|
||||
imageUrl: '/uploads/profiles/test-profile.jpg'
|
||||
});
|
||||
expect(fs.unlink).toHaveBeenCalled(); // Old image deleted
|
||||
expect(mockUser.update).toHaveBeenCalledWith({
|
||||
imageFilename: 'test-profile.jpg'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle upload errors', async () => {
|
||||
uploadProfileImage.mockImplementation((req, res, callback) => {
|
||||
callback(new Error('File too large'));
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/profile/image');
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'File too large' });
|
||||
});
|
||||
|
||||
it('should handle missing file', async () => {
|
||||
uploadProfileImage.mockImplementation((req, res, callback) => {
|
||||
req.file = null;
|
||||
callback(null);
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/profile/image');
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'No file uploaded' });
|
||||
});
|
||||
|
||||
it('should handle database update errors', async () => {
|
||||
// Mock upload to succeed but database update to fail
|
||||
uploadProfileImage.mockImplementation((req, res, callback) => {
|
||||
req.file = { filename: 'test-profile.jpg' };
|
||||
callback(null);
|
||||
});
|
||||
|
||||
const userWithError = {
|
||||
...mockUser,
|
||||
update: jest.fn().mockRejectedValue(new Error('Database error'))
|
||||
};
|
||||
mockUserFindByPk.mockResolvedValue(userWithError);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/profile/image');
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Failed to update profile image' });
|
||||
});
|
||||
|
||||
it('should handle case when user has no existing profile image', async () => {
|
||||
// Mock upload to succeed
|
||||
uploadProfileImage.mockImplementation((req, res, callback) => {
|
||||
req.file = { filename: 'test-profile.jpg' };
|
||||
callback(null);
|
||||
});
|
||||
|
||||
const userWithoutImage = {
|
||||
id: 1,
|
||||
imageFilename: null,
|
||||
update: jest.fn().mockResolvedValue()
|
||||
};
|
||||
mockUserFindByPk.mockResolvedValue(userWithoutImage);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/profile/image');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(fs.unlink).not.toHaveBeenCalled(); // No old image to delete
|
||||
});
|
||||
|
||||
it('should continue if old image deletion fails', async () => {
|
||||
// Mock upload to succeed
|
||||
uploadProfileImage.mockImplementation((req, res, callback) => {
|
||||
req.file = { filename: 'test-profile.jpg' };
|
||||
callback(null);
|
||||
});
|
||||
|
||||
const userWithImage = {
|
||||
id: 1,
|
||||
imageFilename: 'old-image.jpg',
|
||||
update: jest.fn().mockResolvedValue()
|
||||
};
|
||||
mockUserFindByPk.mockResolvedValue(userWithImage);
|
||||
fs.unlink.mockRejectedValue(new Error('File not found'));
|
||||
|
||||
const response = await request(app)
|
||||
.post('/users/profile/image');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Profile image uploaded successfully',
|
||||
filename: 'test-profile.jpg',
|
||||
imageUrl: '/uploads/profiles/test-profile.jpg'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
112
backend/tests/unit/utils/s3KeyValidator.test.js
Normal file
112
backend/tests/unit/utils/s3KeyValidator.test.js
Normal file
@@ -0,0 +1,112 @@
|
||||
const { validateS3Keys } = require('../../../utils/s3KeyValidator');
|
||||
|
||||
describe('S3 Key Validator', () => {
|
||||
describe('validateS3Keys', () => {
|
||||
const validUuid1 = '550e8400-e29b-41d4-a716-446655440000';
|
||||
const validUuid2 = '660e8400-e29b-41d4-a716-446655440001';
|
||||
|
||||
test('should accept valid arrays of keys', () => {
|
||||
const keys = [
|
||||
`items/${validUuid1}.jpg`,
|
||||
`items/${validUuid2}.png`,
|
||||
];
|
||||
expect(validateS3Keys(keys, 'items')).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('should accept valid keys for each folder', () => {
|
||||
expect(validateS3Keys([`profiles/${validUuid1}.jpg`], 'profiles')).toEqual({ valid: true });
|
||||
expect(validateS3Keys([`items/${validUuid1}.png`], 'items')).toEqual({ valid: true });
|
||||
expect(validateS3Keys([`messages/${validUuid1}.gif`], 'messages')).toEqual({ valid: true });
|
||||
expect(validateS3Keys([`forum/${validUuid1}.webp`], 'forum')).toEqual({ valid: true });
|
||||
expect(validateS3Keys([`condition-checks/${validUuid1}.jpeg`], 'condition-checks')).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('should accept different valid extensions', () => {
|
||||
const extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
for (const ext of extensions) {
|
||||
expect(validateS3Keys([`items/${validUuid1}.${ext}`], 'items')).toEqual({ valid: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('should be case-insensitive for extensions', () => {
|
||||
expect(validateS3Keys([`items/${validUuid1}.JPG`], 'items')).toEqual({ valid: true });
|
||||
expect(validateS3Keys([`items/${validUuid1}.PNG`], 'items')).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('should accept empty arrays', () => {
|
||||
expect(validateS3Keys([], 'items')).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('should reject non-array input', () => {
|
||||
const result = validateS3Keys('not-an-array', 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toBe('Keys must be an array');
|
||||
});
|
||||
|
||||
test('should reject arrays exceeding maxKeys', () => {
|
||||
const keys = Array(6).fill(`items/${validUuid1}.jpg`);
|
||||
const result = validateS3Keys(keys, 'items', { maxKeys: 5 });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('Maximum 5 keys allowed');
|
||||
});
|
||||
|
||||
test('should reject duplicate keys', () => {
|
||||
const keys = [
|
||||
`items/${validUuid1}.jpg`,
|
||||
`items/${validUuid1}.jpg`,
|
||||
];
|
||||
const result = validateS3Keys(keys, 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toBe('Duplicate keys not allowed');
|
||||
});
|
||||
|
||||
test('should reject keys with wrong folder prefix', () => {
|
||||
const result = validateS3Keys([`profiles/${validUuid1}.jpg`], 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.invalidKeys).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should reject invalid UUID format', () => {
|
||||
const result = validateS3Keys(['items/invalid-uuid.jpg'], 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
});
|
||||
|
||||
test('should reject non-image extensions', () => {
|
||||
const result = validateS3Keys([`items/${validUuid1}.exe`], 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
});
|
||||
|
||||
test('should reject path traversal attempts', () => {
|
||||
const result = validateS3Keys([`../items/${validUuid1}.jpg`], 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
});
|
||||
|
||||
test('should reject empty or null keys in array', () => {
|
||||
expect(validateS3Keys([''], 'items').valid).toBe(false);
|
||||
expect(validateS3Keys([null], 'items').valid).toBe(false);
|
||||
});
|
||||
|
||||
test('should reject invalid folder names', () => {
|
||||
const result = validateS3Keys([`items/${validUuid1}.jpg`], 'invalid-folder');
|
||||
expect(result.valid).toBe(false);
|
||||
});
|
||||
|
||||
test('should return invalid keys in error response', () => {
|
||||
const keys = [
|
||||
`items/${validUuid1}.jpg`,
|
||||
'invalid-key.jpg',
|
||||
`items/${validUuid2}.exe`,
|
||||
];
|
||||
const result = validateS3Keys(keys, 'items');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.invalidKeys).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('should use default maxKeys of 20', () => {
|
||||
const keys = Array(20).fill(0).map((_, i) =>
|
||||
`items/550e8400-e29b-41d4-a716-44665544${String(i).padStart(4, '0')}.jpg`
|
||||
);
|
||||
expect(validateS3Keys(keys, 'items')).toEqual({ valid: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user