Skip to content
Snippets Groups Projects
Commit f8d1b246 authored by 세현 임's avatar 세현 임
Browse files

[#] Feature/#11

parents 5f0d6b6d 490d41b6
No related branches found
No related tags found
2 merge requests!31Develop,!14[#11] dto설정, 프렌드,서비스로직 테스트 및 로직변경
...@@ -3,7 +3,11 @@ ...@@ -3,7 +3,11 @@
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const Friend = require('../models/Friend'); const Friend = require('../models/Friend');
const User = require('../models/User'); const User = require('../models/User');
const sequelize = require('../config/sequelize'); // 트랜잭션을 위해 추가 const sequelize = require('../config/sequelize');
// DTO 임포트
const FriendRequestDTO = require('../dtos/FriendRequestDTO');
const FriendListDTO = require('../dtos/FriendListDTO');
class FriendService { class FriendService {
/** /**
...@@ -24,7 +28,7 @@ class FriendService { ...@@ -24,7 +28,7 @@ class FriendService {
* 친구 요청 보내기 * 친구 요청 보내기
* @param {number} userId - 친구 요청을 보내는 사용자 ID * @param {number} userId - 친구 요청을 보내는 사용자 ID
* @param {number} friendId - 친구 요청을 받는 사용자 ID * @param {number} friendId - 친구 요청을 받는 사용자 ID
* @returns {Promise<Friend>} - 생성된 친구 요청 객체 * @returns {Promise<FriendRequestDTO>} - 생성된 친구 요청 DTO
* @throws {Error} - 유효하지 않은 요청일 경우 * @throws {Error} - 유효하지 않은 요청일 경우
*/ */
async sendFriendRequest(userId, friendId) { async sendFriendRequest(userId, friendId) {
...@@ -35,12 +39,40 @@ class FriendService { ...@@ -35,12 +39,40 @@ class FriendService {
throw new Error('Cannot send friend request to yourself'); throw new Error('Cannot send friend request to yourself');
} }
// 기존 친구 관계 확인 (이미 친구인 경우)
const existingFriend = await Friend.findOne({
where: {
[Op.or]: [
{ requester_id: userId, receiver_id: friendId },
{ requester_id: friendId, receiver_id: userId },
],
status: 'ACCEPTED',
},
});
if (existingFriend) {
throw new Error('Friend request already exists');
}
try { try {
return await Friend.create({ const friendRequest = await Friend.create({
requester_id: userId, requester_id: userId,
receiver_id: friendId, receiver_id: friendId,
status: 'PENDING' status: 'PENDING'
}); });
// DTO로 변환하여 반환
const friendRequestWithDetails = await Friend.findByPk(friendRequest.id, {
include: [
{ model: User, as: 'requester', attributes: ['id', 'name', 'email'] },
{ model: User, as: 'receiver', attributes: ['id', 'name', 'email'] }
]
});
// 디버깅을 위해 로그 추가
//console.log('FriendRequestWithDetails:', friendRequestWithDetails.toJSON());
return new FriendRequestDTO(friendRequestWithDetails.toJSON());
} catch (error) { } catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') { if (error.name === 'SequelizeUniqueConstraintError') {
throw new Error('Friend request already exists'); throw new Error('Friend request already exists');
...@@ -52,46 +84,48 @@ class FriendService { ...@@ -52,46 +84,48 @@ class FriendService {
/** /**
* 받은 친구 요청 목록 조회 * 받은 친구 요청 목록 조회
* @param {number} userId - 요청을 받은 사용자 ID * @param {number} userId - 요청을 받은 사용자 ID
* @returns {Promise<Array>} - 받은 친구 요청 목록 * @returns {Promise<Array<FriendRequestDTO>>} - 받은 친구 요청 목록 DTO 배열
*/ */
async getReceivedRequests(userId) { async getReceivedRequests(userId) {
return Friend.findAll({ const receivedRequests = await Friend.findAll({
where: { where: {
receiver_id: userId, receiver_id: userId,
status: 'PENDING' status: 'PENDING'
}, },
include: [{ include: [
model: User, { model: User, as: 'requester', attributes: ['id', 'name', 'email'] },
as: 'requester', { model: User, as: 'receiver', attributes: ['id', 'name', 'email'] } // 추가
attributes: ['id', 'name', 'email'] ]
}]
}); });
return receivedRequests.map(req => new FriendRequestDTO(req));
} }
/** /**
* 보낸 친구 요청 목록 조회 * 보낸 친구 요청 목록 조회
* @param {number} userId - 요청을 보낸 사용자 ID * @param {number} userId - 요청을 보낸 사용자 ID
* @returns {Promise<Array>} - 보낸 친구 요청 목록 * @returns {Promise<Array<FriendRequestDTO>>} - 보낸 친구 요청 목록 DTO 배열
*/ */
async getSentRequests(userId) { async getSentRequests(userId) {
return Friend.findAll({ const sentRequests = await Friend.findAll({
where: { where: {
requester_id: userId, requester_id: userId,
status: 'PENDING' status: 'PENDING'
}, },
include: [{ include: [
model: User, { model: User, as: 'receiver', attributes: ['id', 'name', 'email'] },
as: 'receiver', { model: User, as: 'requester', attributes: ['id', 'name', 'email'] } // 추가
attributes: ['id', 'name', 'email'] ]
}]
}); });
return sentRequests.map(req => new FriendRequestDTO(req));
} }
/** /**
* 친구 요청 수락 * 친구 요청 수락
* @param {number} userId - 요청을 수락하는 사용자 ID * @param {number} userId - 요청을 수락하는 사용자 ID
* @param {number} friendId - 친구 요청을 보낸 사용자 ID * @param {number} friendId - 친구 요청을 보낸 사용자 ID
* @returns {Promise<Friend>} - 업데이트된 친구 요청 객체 * @returns {Promise<FriendRequestDTO>} - 업데이트된 친구 요청 DTO
* @throws {Error} - 친구 요청이 존재하지 않을 경우 * @throws {Error} - 친구 요청이 존재하지 않을 경우
*/ */
async acceptFriendRequest(userId, friendId) { async acceptFriendRequest(userId, friendId) {
...@@ -113,7 +147,16 @@ class FriendService { ...@@ -113,7 +147,16 @@ class FriendService {
await request.update({ status: 'ACCEPTED' }, { transaction }); await request.update({ status: 'ACCEPTED' }, { transaction });
await transaction.commit(); await transaction.commit();
return request;
// DTO로 변환하여 반환
const updatedRequest = await Friend.findByPk(request.id, {
include: [
{ model: User, as: 'requester', attributes: ['id', 'name', 'email'] },
{ model: User, as: 'receiver', attributes: ['id', 'name', 'email'] }
]
});
return new FriendRequestDTO(updatedRequest);
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
...@@ -146,9 +189,11 @@ class FriendService { ...@@ -146,9 +189,11 @@ class FriendService {
/** /**
* 친구 목록 조회 * 친구 목록 조회
* @param {number} userId - 친구 목록을 조회할 사용자 ID * @param {number} userId - 친구 목록을 조회할 사용자 ID
* @returns {Promise<Array>} - 친구 목록 * @param {number} limit - 한 페이지에 표시할 친구 수
* @param {number} offset - 페이징 오프셋
* @returns {Promise<Array<FriendListDTO>>} - 친구 목록 DTO 배열
*/ */
async getFriendList(userId) { async getFriendList(userId, limit = 20, offset = 0) {
const friends = await Friend.findAll({ const friends = await Friend.findAll({
where: { where: {
[Op.or]: [ [Op.or]: [
...@@ -168,22 +213,16 @@ class FriendService { ...@@ -168,22 +213,16 @@ class FriendService {
as: 'receiver', as: 'receiver',
attributes: ['id', 'name', 'email'] attributes: ['id', 'name', 'email']
} }
] ],
order: [['id', 'ASC']], // 일관된 정렬 순서 추가
limit,
offset
}); });
return friends.map(friend => { // 디버깅을 위해 로그 추가
const isRequester = friend.requester_id === userId; //console.log(`getFriendList: Retrieved ${friends.length} friends with limit=${limit} and offset=${offset}`);
const friendInfo = isRequester ? friend.receiver : friend.requester;
return friends.map(friend => new FriendListDTO(friend, userId));
return {
id: friend.id,
status: friend.status,
createdAt: friend.createdAt,
updatedAt: friend.updatedAt,
friendInfo: friendInfo,
relationshipType: isRequester ? 'sent' : 'received'
};
});
} }
/** /**
......
// test/friendService.test.js
const sequelize = require('../config/sequelize'); // Sequelize 인스턴스 임포트
const User = require('../models/User');
const Friend = require('../models/Friend');
const friendService = require('../services/friendService'); // FriendService 임포트
// Sequelize의 Op를 가져오기 위해 추가
const { Op } = require('sequelize');
beforeAll(async () => {
await sequelize.sync({ force: true });
});
beforeEach(async () => {
await sequelize.sync({ force: true });
// 더미 사용자 생성
await User.bulkCreate([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' },
]);
});
afterAll(async () => {
// 모든 테스트가 끝난 후 데이터베이스 연결을 종료합니다.
await sequelize.close();
});
describe('Friend Service', () => {
describe('validUser', () => {
test('should return user when user exists', async () => {
const user = await friendService.validUser(1);
expect(user).toBeDefined();
expect(user.name).toBe('Alice');
});
test('should throw error when user does not exist', async () => {
await expect(friendService.validUser(999)).rejects.toThrow('User not found');
});
});
describe('sendFriendRequest', () => {
test('should send a friend request successfully', async () => {
const friendRequestDTO = await friendService.sendFriendRequest(1, 3); // Alice sends request to Charlie
console.log('sendFriendRequest DTO:', friendRequestDTO); // 디버깅을 위한 로그 추가
expect(friendRequestDTO).toBeDefined();
expect(friendRequestDTO.requester.id).toBe(1);
expect(friendRequestDTO.receiver.id).toBe(3);
expect(friendRequestDTO.status).toBe('PENDING');
});
test('should throw error when sending friend request to self', async () => {
await expect(friendService.sendFriendRequest(1, 1)).rejects.toThrow('Cannot send friend request to yourself');
});
test('should throw error when sending duplicate friend request', async () => {
// Alice sends a friend request to Bob
await friendService.sendFriendRequest(1, 2);
// Bob accepts Alice's request
await friendService.acceptFriendRequest(2, 1);
// Alice tries to send another friend request to Bob
await expect(friendService.sendFriendRequest(1, 2)).rejects.toThrow('Friend request already exists');
});
test('should throw error when user does not exist', async () => {
await expect(friendService.sendFriendRequest(1, 999)).rejects.toThrow('User not found');
await expect(friendService.sendFriendRequest(999, 1)).rejects.toThrow('User not found');
});
});
describe('getReceivedRequests', () => {
test('friend requests', async () => {
await friendService.sendFriendRequest(3, 1);
const receivedRequests = await friendService.getReceivedRequests(1);
expect(receivedRequests.length).toBe(1);
expect(receivedRequests[0].requester.name).toBe('Charlie');
});
test('not send request', async () => {
const receivedRequests = await friendService.getReceivedRequests(2); // Bob has no pending requests
expect(receivedRequests.length).toBe(0);
});
});
describe('getSentRequests', () => {
test('should retrieve sent friend requests', async () => {
await friendService.sendFriendRequest(1, 3);
const sentRequests = await friendService.getSentRequests(1);
expect(sentRequests.length).toBe(1);
expect(sentRequests[0].receiver.name).toBe('Charlie');
});
test('should return empty array when no sent requests', async () => {
const sentRequests = await friendService.getSentRequests(3); // Charlie has not sent any PENDING requests
expect(sentRequests.length).toBe(0);
});
});
describe('acceptFriendRequest', () => {
test('should accept a pending friend request successfully', async () => {
await friendService.sendFriendRequest(3, 1);
const updatedRequestDTO = await friendService.acceptFriendRequest(1, 3);
expect(updatedRequestDTO).toBeDefined();
expect(updatedRequestDTO.status).toBe('ACCEPTED');
// Db상태 확인
const request = await Friend.findOne({
where: {
requester_id: 3,
receiver_id: 1,
},
});
expect(request.status).toBe('ACCEPTED');
});
test('should throw error when accepting non-existing friend request', async () => {
await expect(friendService.acceptFriendRequest(1, 999)).rejects.toThrow('Friend request not found');
});
});
describe('rejectFriendRequest', () => {
test('should reject a pending friend request successfully', async () => {
await friendService.sendFriendRequest(2, 3);
const result = await friendService.rejectFriendRequest(3, 2);
expect(result).toBe(1);
const request = await Friend.findOne({
where: {
requester_id: 2,
receiver_id: 3,
},
});
expect(request).toBeNull();
});
test('should throw error when rejecting non-existing friend request', async () => {
await expect(friendService.rejectFriendRequest(1, 999)).rejects.toThrow('Friend request not found');
});
});
describe('getFriendList', () => {
test('should retrieve friend list with correct pagination', async () => {
await friendService.sendFriendRequest(1, 2);
await friendService.acceptFriendRequest(2, 1);
await friendService.sendFriendRequest(1, 3);
await friendService.acceptFriendRequest(3, 1);
// 추가 더미데이터 생성
for (let i = 4; i <= 23; i++) {
// Create dummy users
await User.create({
id: i,
name: `User${i}`,
email: `user${i}@example.com`,
});
// Alice랑 친구맺기
await friendService.sendFriendRequest(1, i);
await friendService.acceptFriendRequest(i, 1);
}
// Alice 친구: Bob (2), Charlie (3), User4부터 User23까지 (총 22명)
const limit = 5;
const offset = 0;
const friendsPage1 = await friendService.getFriendList(1, limit, offset);
//console.log('getFriendList Page 1:', friendsPage1); // 디버깅을 위한 로그 추가
expect(friendsPage1.length).toBe(limit);
const expectedNamesPage1 = ['Bob', 'Charlie', 'User4', 'User5', 'User6'];
const receivedNamesPage1 = friendsPage1.map(friend => friend.friendInfo.name);
expectedNamesPage1.forEach(name => {
expect(receivedNamesPage1).toContain(name);
});
const friendsPage2 = await friendService.getFriendList(1, limit, limit);
//console.log('getFriendList Page 2:', friendsPage2); // 디버깅을 위한 로그 추가
expect(friendsPage2.length).toBe(limit);
const expectedNamesPage2 = ['User7', 'User8', 'User9', 'User10', 'User11'];
const receivedNamesPage2 = friendsPage2.map(friend => friend.friendInfo.name);
expectedNamesPage2.forEach(name => {
expect(receivedNamesPage2).toContain(name);
});
});
test('should return empty array when user has no friends', async () => {
const friends = await friendService.getFriendList(999); // Non-existing user
expect(friends.length).toBe(0);
});
});
describe('deleteFriend', () => {
test('should delete an existing friend relationship successfully', async () => {
await friendService.sendFriendRequest(1, 2);
await friendService.acceptFriendRequest(2, 1);
const result = await friendService.deleteFriend(1, 2);
expect(result).toBe(1);
const relationship = await Friend.findOne({
where: {
[Op.or]: [
{ requester_id: 1, receiver_id: 2 },
{ requester_id: 2, receiver_id: 1 },
],
status: 'ACCEPTED',
},
});
expect(relationship).toBeNull();
});
test('should throw error when deleting a non-existing friend relationship', async () => {
await expect(friendService.deleteFriend(1, 999)).rejects.toThrow('Friend relationship not found');
});
});
});
// test/schedule.test.js
const sequelize = require('../config/sequelize');
const User = require('../models/User');
const Friend = require('../models/Friend');
const Schedule = require('../models/Schedule');
const scheduleService = require('./scheduleService'); // scheduleService 임포트
beforeAll(async () => {
await sequelize.sync({ force: true });
// 더미 사용자 생성
await User.bulkCreate([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
]);
// 더미 친구 관계 생성
await Friend.create({
id: 1,
requester_id: 1,
receiver_id: 2,
status: 'ACCEPTED',
});
// 더미 스케줄 생성
await Schedule.create({
id: 1,
user_id: 1,
title: 'Alice\'s Fixed Schedule',
start_time: new Date('2024-05-01T09:00:00Z'),
end_time: new Date('2024-05-01T10:00:00Z'),
is_fixed: true,
expiry_date: null,
});
await Schedule.create({
id: 2,
user_id: 1,
title: 'Alice\'s Flexible Schedule',
start_time: new Date('2024-05-02T11:00:00Z'),
end_time: new Date('2024-05-02T12:00:00Z'),
is_fixed: false,
expiry_date: new Date('2024-05-08T00:00:00Z'), // 다음 월요일
});
});
afterAll(async () => {
// 데이터베이스 연결 종료
await sequelize.close();
});
describe('Schedule Service', () => {
describe('createSchedule', () => {
test('should create a new fixed schedule successfully', async () => {
const scheduleData = {
userId: 2,
title: 'Bob\'s Fixed Schedule',
start_time: new Date('2024-05-03T14:00:00Z'),
end_time: new Date('2024-05-03T15:00:00Z'),
is_fixed: true,
};
const schedule = await scheduleService.createSchedule(scheduleData);
expect(schedule).toBeDefined();
expect(schedule.user_id).toBe(2);
expect(schedule.title).toBe('Bob\'s Fixed Schedule');
expect(schedule.is_fixed).toBe(true);
expect(schedule.expiry_date).toBeNull();
});
test('should create a new flexible schedule with expiry date', async () => {
const scheduleData = {
userId: 2,
title: 'Bob\'s Flexible Schedule',
start_time: new Date('2024-05-04T16:00:00Z'),
end_time: new Date('2024-05-04T17:00:00Z'),
is_fixed: false,
};
const schedule = await scheduleService.createSchedule(scheduleData);
expect(schedule).toBeDefined();
expect(schedule.user_id).toBe(2);
expect(schedule.title).toBe('Bob\'s Flexible Schedule');
expect(schedule.is_fixed).toBe(false);
expect(schedule.expiry_date).toBeInstanceOf(Date);
// expiry_date가 다음 월요일로 설정되었는지 확인
const expectedExpiryDate = new Date('2024-05-06T00:00:00Z'); // 2024-05-06은 다음 월요일
expect(schedule.expiry_date.toISOString()).toBe(expectedExpiryDate.toISOString());
});
test('should throw error when schedule times overlap with existing schedule', async () => {
const scheduleData = {
userId: 1,
title: 'Alice\'s Overlapping Schedule',
start_time: new Date('2024-05-01T09:30:00Z'), // 기존 스케줄과 겹침
end_time: new Date('2024-05-01T10:30:00Z'),
is_fixed: false,
};
await expect(scheduleService.createSchedule(scheduleData)).rejects.toThrow('Schedule overlaps with existing schedule');
});
test('should throw error when start_time is after end_time', async () => {
const scheduleData = {
userId: 1,
title: 'Invalid Schedule',
start_time: new Date('2024-05-05T18:00:00Z'),
end_time: new Date('2024-05-05T17:00:00Z'), // start_time이 더 나중
is_fixed: false,
};
await expect(scheduleService.createSchedule(scheduleData)).rejects.toThrow('Start time must be before end time');
});
});
describe('updateSchedule', () => {
test('should update an existing schedule successfully', async () => {
const updateData = {
title: 'Alice\'s Updated Flexible Schedule',
start_time: new Date('2024-05-02T11:30:00Z'),
end_time: new Date('2024-05-02T12:30:00Z'),
};
const updatedSchedule = await scheduleService.updateSchedule(2, 1, updateData);
expect(updatedSchedule).toBeDefined();
expect(updatedSchedule.title).toBe('Alice\'s Updated Flexible Schedule');
expect(updatedSchedule.start_time.toISOString()).toBe(new Date('2024-05-02T11:30:00Z').toISOString());
expect(updatedSchedule.end_time.toISOString()).toBe(new Date('2024-05-02T12:30:00Z').toISOString());
expect(updatedSchedule.expiry_date).toBeInstanceOf(Date);
});
test('should throw error when updating a non-existing schedule', async () => {
const updateData = {
title: 'Non-existing Schedule',
start_time: new Date('2024-05-06T10:00:00Z'),
end_time: new Date('2024-05-06T11:00:00Z'),
};
await expect(scheduleService.updateSchedule(999, 1, updateData)).rejects.toThrow('Schedule not found');
});
test('should throw error when updated schedule overlaps with existing schedule', async () => {
const updateData = {
title: 'Alice\'s Overlapping Update',
start_time: new Date('2024-05-01T09:30:00Z'), // 기존 스케줄과 겹침
end_time: new Date('2024-05-01T10:30:00Z'),
};
await expect(scheduleService.updateSchedule(2, 1, updateData)).rejects.toThrow('Schedule overlaps with existing schedule');
});
});
describe('deleteSchedule', () => {
test('should delete an existing schedule successfully', async () => {
const result = await scheduleService.deleteSchedule(2, 1);
expect(result).toEqual({ message: 'Schedule successfully deleted' });
// 삭제된 스케줄이 실제로 삭제되었는지 확인
const schedule = await Schedule.findByPk(2);
expect(schedule).toBeNull();
});
test('should throw error when deleting a non-existing schedule', async () => {
await expect(scheduleService.deleteSchedule(999, 1)).rejects.toThrow('Schedule not found');
});
});
describe('getAllSchedules', () => {
test('should retrieve all valid schedules for a user', async () => {
// 사용자 Alice의 모든 스케줄 조회
const schedules = await scheduleService.getAllSchedules(1);
expect(schedules.length).toBe(1); // id=1 스케줄은 is_fixed=true
expect(schedules[0].title).toBe('Alice\'s Fixed Schedule');
});
});
describe('getScheduleById', () => {
test('should retrieve a specific schedule by ID', async () => {
const schedule = await scheduleService.getScheduleById(1, 1);
expect(schedule).toBeDefined();
expect(schedule.title).toBe('Alice\'s Fixed Schedule');
});
test('should throw error when retrieving a non-existing schedule', async () => {
await expect(scheduleService.getScheduleById(999, 1)).rejects.toThrow('Schedule not found');
});
});
// test/schedule.test.js
describe('cleanExpiredSchedules', () => {
test('should delete expired flexible schedules', async () => {
// 현재 날짜를 기준으로 만료된 스케줄과 만료되지 않은 스케줄 생성
const now = new Date('2024-05-07T00:00:00Z'); // 테스트를 위한 고정된 현재 날짜
// Jest의 Fake Timers를 사용하여 Date를 고정
jest.useFakeTimers('modern');
jest.setSystemTime(now);
// 만료된 유동 스케줄 생성
await Schedule.create({
user_id: 1,
title: 'Expired Flexible Schedule',
start_time: new Date('2024-04-25T10:00:00Z'),
end_time: new Date('2024-04-25T11:00:00Z'),
is_fixed: false,
expiry_date: new Date('2024-05-06T00:00:00Z'), // 이미 만료됨
});
// 만료되지 않은 유동 스케줄 생성
await Schedule.create({
user_id: 1,
title: 'Valid Flexible Schedule',
start_time: new Date('2024-05-07T10:00:00Z'),
end_time: new Date('2024-05-07T11:00:00Z'),
is_fixed: false,
expiry_date: new Date('2024-05-14T00:00:00Z'), // 아직 만료되지 않음
});
// 만료된 스케줄 정리
await scheduleService.cleanExpiredSchedules();
// 만료된 스케줄이 삭제되었는지 확인
const expiredSchedule = await Schedule.findOne({ where: { title: 'Expired Flexible Schedule' } });
expect(expiredSchedule).toBeNull();
// 만료되지 않은 스케줄은 남아있는지 확인
const validSchedule = await Schedule.findOne({ where: { title: 'Valid Flexible Schedule' } });
expect(validSchedule).toBeDefined();
expect(validSchedule.title).toBe('Valid Flexible Schedule');
// Jest의 Fake Timers를 복구
jest.useRealTimers();
});
});
});
// services/scheduleService.js
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const Schedule = require('../models/Schedule'); const Schedule = require('../models/Schedule');
const ScheduleResponseDTO = require('../dtos/ScheduleResponseDTO');
class scheduleService { class scheduleService {
/** /**
* transactin wrapper 함수 * 트랜잭션 래퍼 함수
*/ */
async withTransaction(callback) { async withTransaction(callback) {
const transaction = await Schedule.sequelize.transaction(); const transaction = await Schedule.sequelize.transaction();
...@@ -28,11 +30,9 @@ class scheduleService { ...@@ -28,11 +30,9 @@ class scheduleService {
{ is_fixed: true }, { is_fixed: true },
{ {
is_fixed: false, is_fixed: false,
expiry_date: { expiry_date: { [Op.gt]: new Date() },
[Op.gt]: new Date() },
} ],
}
]
}; };
if (id) { if (id) {
...@@ -44,10 +44,11 @@ class scheduleService { ...@@ -44,10 +44,11 @@ class scheduleService {
/** /**
* 스케줄 유효성 검사 * 스케줄 유효성 검사
* 이미 컨트롤러에서 검증했으므로, 추가 검증 필요 시 수행
*/ */
validateScheduleTime(start_time, end_time) { validateScheduleTime(start_time, end_time) {
if (new Date(start_time) >= new Date(end_time)) { if (new Date(start_time) >= new Date(end_time)) {
throw new Error('Start time must be before end time'); throw new Error("Start time must be before end time");
} }
} }
...@@ -56,12 +57,20 @@ class scheduleService { ...@@ -56,12 +57,20 @@ class scheduleService {
*/ */
getNextMonday(startTime) { getNextMonday(startTime) {
const date = new Date(startTime); const date = new Date(startTime);
const day = date.getDay(); const day = date.getUTCDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
const daysUntilNextMonday = (7 - day + 1) % 7; const daysUntilNextMonday = (8 - day) % 7 || 7; // Ensure next Monday
const nextMonday = new Date(date); const nextMonday = new Date(
nextMonday.setDate(date.getDate() + daysUntilNextMonday); Date.UTC(
nextMonday.setHours(0, 0, 0, 0); // 자정으로 설정 date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() + daysUntilNextMonday,
0,
0,
0,
0 // Set to midnight UTC
)
);
return nextMonday; return nextMonday;
} }
...@@ -70,12 +79,16 @@ class scheduleService { ...@@ -70,12 +79,16 @@ class scheduleService {
* 사용자 스케줄 생성 * 사용자 스케줄 생성
*/ */
async createSchedule({ userId, title, start_time, end_time, is_fixed }) { async createSchedule({ userId, title, start_time, end_time, is_fixed }) {
return this.withTransaction(async (transaction) => { const schedule = await this.withTransaction(async (transaction) => {
this.validateScheduleTime(start_time, end_time); this.validateScheduleTime(start_time, end_time);
const overlap = await this.checkScheduleOverlap(userId, start_time, end_time); const overlap = await this.checkScheduleOverlap(
userId,
start_time,
end_time
);
if (overlap) { if (overlap) {
throw new Error('Schedule overlaps with existing schedule'); throw new Error("Schedule overlaps with existing schedule");
} }
const scheduleData = { const scheduleData = {
...@@ -84,49 +97,58 @@ class scheduleService { ...@@ -84,49 +97,58 @@ class scheduleService {
start_time, start_time,
end_time, end_time,
is_fixed, is_fixed,
expiry_date: is_fixed ? null : this.getNextMonday(start_time) expiry_date: is_fixed ? null : this.getNextMonday(start_time),
}; };
return Schedule.create(scheduleData, { transaction }); return Schedule.create(scheduleData, { transaction });
}); });
return new ScheduleResponseDTO(schedule);
} }
/** /**
* 사용자 스케줄 수정 * 사용자 스케줄 수정
*/ */
async updateSchedule(id, userId, updateData) { async updateSchedule(id, userId, updateData) {
return this.withTransaction(async (transaction) => { const updatedSchedule = await this.withTransaction(async (transaction) => {
const schedule = await Schedule.findOne({ const schedule = await Schedule.findOne({
where: { id, user_id: userId }, where: { id, user_id: userId },
transaction transaction,
}); });
if (!schedule) { if (!schedule) {
throw new Error('Schedule not found'); throw new Error("Schedule not found");
} }
// 이미 컨트롤러에서 검증했으므로, 추가 검증이 필요하다면 수행
if (updateData.start_time && updateData.end_time) {
this.validateScheduleTime(updateData.start_time, updateData.end_time); this.validateScheduleTime(updateData.start_time, updateData.end_time);
}
const overlap = await this.checkScheduleOverlap( const overlap = await this.checkScheduleOverlap(
userId, userId,
updateData.start_time, updateData.start_time || schedule.start_time,
updateData.end_time, updateData.end_time || schedule.end_time,
id id
); );
if (overlap) { if (overlap) {
throw new Error('Schedule overlaps with existing schedule'); throw new Error("Schedule overlaps with existing schedule");
} }
const is_fixed = schedule.is_fixed; const is_fixed = schedule.is_fixed;
const updatedData = { const updatedDataWithExpiry = {
...updateData, ...updateData,
expiry_date: is_fixed ? null : this.getNextMonday(updateData.start_time), expiry_date: is_fixed
updatedAt: new Date() ? null
: this.getNextMonday(updateData.start_time || schedule.start_time),
updatedAt: new Date(),
}; };
delete updatedData.is_fixed; delete updatedDataWithExpiry.is_fixed;
return schedule.update(updatedData, { transaction }); return schedule.update(updatedDataWithExpiry, { transaction });
}); });
return new ScheduleResponseDTO(updatedSchedule);
} }
/** /**
...@@ -136,14 +158,15 @@ class scheduleService { ...@@ -136,14 +158,15 @@ class scheduleService {
return this.withTransaction(async (transaction) => { return this.withTransaction(async (transaction) => {
const result = await Schedule.destroy({ const result = await Schedule.destroy({
where: { id, user_id: userId }, where: { id, user_id: userId },
transaction transaction,
}); });
if (!result) { if (!result) {
throw new Error('Schedule not found'); throw new Error("Schedule not found");
} }
return true; // 삭제 성공 메시지 반환
return { message: "Schedule successfully deleted" };
}); });
} }
...@@ -152,10 +175,14 @@ class scheduleService { ...@@ -152,10 +175,14 @@ class scheduleService {
*/ */
async getAllSchedules(userId) { async getAllSchedules(userId) {
try { try {
return Schedule.findAll({ const schedules = await Schedule.findAll({
where: this.getScheduleWhereClause(userId), where: this.getScheduleWhereClause(userId),
order: [['start_time', 'ASC']] order: [["start_time", "ASC"]],
}); });
const schedulesDTO = schedules.map(
(schedule) => new ScheduleResponseDTO(schedule)
);
return schedulesDTO;
} catch (error) { } catch (error) {
throw new Error(`Failed to fetch schedules: ${error.message}`); throw new Error(`Failed to fetch schedules: ${error.message}`);
} }
...@@ -167,41 +194,37 @@ class scheduleService { ...@@ -167,41 +194,37 @@ class scheduleService {
async getScheduleById(id, userId) { async getScheduleById(id, userId) {
try { try {
const schedule = await Schedule.findOne({ const schedule = await Schedule.findOne({
where: this.getScheduleWhereClause(userId, id) where: this.getScheduleWhereClause(userId, id),
}); });
if (!schedule) { if (!schedule) {
throw new Error('Schedule not found'); throw new Error("Schedule not found");
} }
return schedule; return new ScheduleResponseDTO(schedule);
} catch (error) { } catch (error) {
throw new Error(`Failed to fetch schedule: ${error.message}`); throw new Error(`Failed to fetch schedule: ${error.message}`);
} }
} }
/** /**
* 만료된 유동 스케줄 정리 -> utils에 cron job 추가해서 실행하도록 설정 * 만료된 유동 스케줄 정리
*/ */
async cleanExpiredSchedules() { async cleanExpiredSchedules() {
try { try {
await Schedule.destroy({ await Schedule.destroy({
where: { where: {
is_fixed: false, is_fixed: false,
expiry_date: { expiry_date: { [Op.lte]: new Date() },
[Op.lte]: new Date() },
}
}
}); });
} catch (error) { } catch (error) {
throw new Error(`Failed to clean expired schedules: ${error.message}`); throw new Error(`Failed to clean expired schedules: ${error.message}`);
} }
} }
/** /**
* 스케줄 중복 검사 -> 기존 스케줄 시간대에 추가 못하도록 * 스케줄 중복 검사
*/ */
async checkScheduleOverlap(userId, start_time, end_time, excludeId = null) { async checkScheduleOverlap(userId, start_time, end_time, excludeId = null) {
try { try {
...@@ -209,20 +232,18 @@ class scheduleService { ...@@ -209,20 +232,18 @@ class scheduleService {
user_id: userId, user_id: userId,
[Op.or]: [ [Op.or]: [
{ {
// 새로운 스케줄이 기존 스케줄 내 존재
[Op.and]: [ [Op.and]: [
{ start_time: { [Op.lte]: start_time } }, { start_time: { [Op.lte]: start_time } },
{ end_time: { [Op.gte]: start_time } } { end_time: { [Op.gte]: start_time } },
] ],
}, },
{ {
// 새로운 스케줄이 기존 스케줄을 포함
[Op.and]: [ [Op.and]: [
{ start_time: { [Op.gte]: start_time } }, { start_time: { [Op.gte]: start_time } },
{ start_time: { [Op.lte]: end_time } } { start_time: { [Op.lte]: end_time } },
] ],
} },
] ],
}; };
if (excludeId) { if (excludeId) {
......
...@@ -9,12 +9,10 @@ const friendService = require('../services/friendService'); // FriendService 임 ...@@ -9,12 +9,10 @@ const friendService = require('../services/friendService'); // FriendService 임
const { Op } = require('sequelize'); const { Op } = require('sequelize');
beforeAll(async () => { beforeAll(async () => {
// 테스트 전에 데이터베이스를 동기화하고 테이블을 생성합니다.
await sequelize.sync({ force: true }); await sequelize.sync({ force: true });
}); });
beforeEach(async () => { beforeEach(async () => {
// 각 테스트 전에 데이터베이스를 초기화하여 독립성을 보장합니다.
await sequelize.sync({ force: true }); await sequelize.sync({ force: true });
// 더미 사용자 생성 // 더미 사용자 생성
...@@ -45,11 +43,12 @@ describe('Friend Service', () => { ...@@ -45,11 +43,12 @@ describe('Friend Service', () => {
describe('sendFriendRequest', () => { describe('sendFriendRequest', () => {
test('should send a friend request successfully', async () => { test('should send a friend request successfully', async () => {
const friendRequest = await friendService.sendFriendRequest(1, 3); // Alice sends request to Charlie const friendRequestDTO = await friendService.sendFriendRequest(1, 3); // Alice sends request to Charlie
expect(friendRequest).toBeDefined(); console.log('sendFriendRequest DTO:', friendRequestDTO); // 디버깅을 위한 로그 추가
expect(friendRequest.requester_id).toBe(1); expect(friendRequestDTO).toBeDefined();
expect(friendRequest.receiver_id).toBe(3); expect(friendRequestDTO.requester.id).toBe(1);
expect(friendRequest.status).toBe('PENDING'); expect(friendRequestDTO.receiver.id).toBe(3);
expect(friendRequestDTO.status).toBe('PENDING');
}); });
test('should throw error when sending friend request to self', async () => { test('should throw error when sending friend request to self', async () => {
...@@ -57,12 +56,12 @@ describe('Friend Service', () => { ...@@ -57,12 +56,12 @@ describe('Friend Service', () => {
}); });
test('should throw error when sending duplicate friend request', async () => { test('should throw error when sending duplicate friend request', async () => {
// Alice sends a friend request to Bob
await friendService.sendFriendRequest(1, 2); await friendService.sendFriendRequest(1, 2);
// Bob accepts Alice's request
await friendService.acceptFriendRequest(2, 1); await friendService.acceptFriendRequest(2, 1);
// Alice tries to send another friend request to Bob
await expect(friendService.sendFriendRequest(1, 2)).rejects.toThrow('Friend request already exists'); await expect(friendService.sendFriendRequest(1, 2)).rejects.toThrow('Friend request already exists');
}); });
...@@ -82,14 +81,13 @@ describe('Friend Service', () => { ...@@ -82,14 +81,13 @@ describe('Friend Service', () => {
}); });
test('not send request', async () => { test('not send request', async () => {
const receivedRequests = await friendService.getReceivedRequests(2); const receivedRequests = await friendService.getReceivedRequests(2); // Bob has no pending requests
expect(receivedRequests.length).toBe(0); expect(receivedRequests.length).toBe(0);
}); });
}); });
describe('getSentRequests', () => { describe('getSentRequests', () => {
test('should retrieve sent friend requests', async () => { test('should retrieve sent friend requests', async () => {
await friendService.sendFriendRequest(1, 3); await friendService.sendFriendRequest(1, 3);
const sentRequests = await friendService.getSentRequests(1); const sentRequests = await friendService.getSentRequests(1);
...@@ -105,12 +103,11 @@ describe('Friend Service', () => { ...@@ -105,12 +103,11 @@ describe('Friend Service', () => {
describe('acceptFriendRequest', () => { describe('acceptFriendRequest', () => {
test('should accept a pending friend request successfully', async () => { test('should accept a pending friend request successfully', async () => {
await friendService.sendFriendRequest(3, 1); await friendService.sendFriendRequest(3, 1);
const updatedRequest = await friendService.acceptFriendRequest(1, 3); const updatedRequestDTO = await friendService.acceptFriendRequest(1, 3);
expect(updatedRequest).toBeDefined(); expect(updatedRequestDTO).toBeDefined();
expect(updatedRequest.status).toBe('ACCEPTED'); expect(updatedRequestDTO.status).toBe('ACCEPTED');
// Db상태 확인 // Db상태 확인
const request = await Friend.findOne({ const request = await Friend.findOne({
...@@ -122,17 +119,15 @@ describe('Friend Service', () => { ...@@ -122,17 +119,15 @@ describe('Friend Service', () => {
expect(request.status).toBe('ACCEPTED'); expect(request.status).toBe('ACCEPTED');
}); });
test('없는 요청수락', async () => { test('should throw error when accepting non-existing friend request', async () => {
await expect(friendService.acceptFriendRequest(1, 999)).rejects.toThrow('Friend request not found'); await expect(friendService.acceptFriendRequest(1, 999)).rejects.toThrow('Friend request not found');
}); });
}); });
describe('rejectFriendRequest', () => { describe('rejectFriendRequest', () => {
test('should reject a pending friend request successfully', async () => { test('should reject a pending friend request successfully', async () => {
await friendService.sendFriendRequest(2, 3); await friendService.sendFriendRequest(2, 3);
const result = await friendService.rejectFriendRequest(3, 2); const result = await friendService.rejectFriendRequest(3, 2);
expect(result).toBe(1); expect(result).toBe(1);
...@@ -172,16 +167,26 @@ describe('Friend Service', () => { ...@@ -172,16 +167,26 @@ describe('Friend Service', () => {
await friendService.acceptFriendRequest(i, 1); await friendService.acceptFriendRequest(i, 1);
} }
// Alice 친구: Bob (2), Charlie (3),User4 to User23 (20 friends) // Alice 친구: Bob (2), Charlie (3), User4부터 User23까지 (총 22명)
const limit = 5; const limit = 5;
const offset = 0; const offset = 0;
const friendsPage1 = await friendService.getFriendList(1, limit, offset); const friendsPage1 = await friendService.getFriendList(1, limit, offset);
//console.log('getFriendList Page 1:', friendsPage1); // 디버깅을 위한 로그 추가
expect(friendsPage1.length).toBe(limit); expect(friendsPage1.length).toBe(limit);
expect(['Bob', 'Charlie', 'User4', 'User5', 'User6']).toContain(friendsPage1[0].friendInfo.name); const expectedNamesPage1 = ['Bob', 'Charlie', 'User4', 'User5', 'User6'];
const receivedNamesPage1 = friendsPage1.map(friend => friend.friendInfo.name);
expectedNamesPage1.forEach(name => {
expect(receivedNamesPage1).toContain(name);
});
const friendsPage2 = await friendService.getFriendList(1, limit, limit); const friendsPage2 = await friendService.getFriendList(1, limit, limit);
//console.log('getFriendList Page 2:', friendsPage2); // 디버깅을 위한 로그 추가
expect(friendsPage2.length).toBe(limit); expect(friendsPage2.length).toBe(limit);
expect(['User7', 'User8', 'User9', 'User10', 'User11']).toContain(friendsPage2[0].friendInfo.name); const expectedNamesPage2 = ['User7', 'User8', 'User9', 'User10', 'User11'];
const receivedNamesPage2 = friendsPage2.map(friend => friend.friendInfo.name);
expectedNamesPage2.forEach(name => {
expect(receivedNamesPage2).toContain(name);
});
}); });
test('should return empty array when user has no friends', async () => { test('should return empty array when user has no friends', async () => {
...@@ -192,15 +197,12 @@ describe('Friend Service', () => { ...@@ -192,15 +197,12 @@ describe('Friend Service', () => {
describe('deleteFriend', () => { describe('deleteFriend', () => {
test('should delete an existing friend relationship successfully', async () => { test('should delete an existing friend relationship successfully', async () => {
await friendService.sendFriendRequest(1, 2); await friendService.sendFriendRequest(1, 2);
await friendService.acceptFriendRequest(2, 1); await friendService.acceptFriendRequest(2, 1);
const result = await friendService.deleteFriend(1, 2); const result = await friendService.deleteFriend(1, 2);
expect(result).toBe(1); expect(result).toBe(1);
const relationship = await Friend.findOne({ const relationship = await Friend.findOne({
where: { where: {
[Op.or]: [ [Op.or]: [
......
...@@ -94,17 +94,6 @@ describe('User and Friend Relationships', () => { ...@@ -94,17 +94,6 @@ describe('User and Friend Relationships', () => {
expect(bob.sentRequests[0].receiver.name).toBe('Alice'); expect(bob.sentRequests[0].receiver.name).toBe('Alice');
}); });
test('self friend reqeust', async () => {
await expect(
Friend.create({
id: 3,
requester_id: 1,
receiver_id: 1, // 자신에게 요청
status: 'PENDING',
})
).rejects.toThrow();
});
test('already request test', async () => { test('already request test', async () => {
// Alice가 Bob에게 이미 친구 요청을 보냈으므로, 다시 보내면 에러 발생 // Alice가 Bob에게 이미 친구 요청을 보냈으므로, 다시 보내면 에러 발생
await expect( await expect(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment