Skip to content
Snippets Groups Projects
Commit 490d41b6 authored by tpgus2603's avatar tpgus2603
Browse files

test,refactor 서비스로직 관련 테스트 및 리팩토링

parent fd2daa22
No related branches found
No related tags found
2 merge requests!31Develop,!14[#11] dto설정, 프렌드,서비스로직 테스트 및 로직변경
...@@ -4,7 +4,7 @@ const sequelize = require('../config/sequelize'); ...@@ -4,7 +4,7 @@ const sequelize = require('../config/sequelize');
const User = require('../models/User'); const User = require('../models/User');
const Friend = require('../models/Friend'); const Friend = require('../models/Friend');
const Schedule = require('../models/Schedule'); const Schedule = require('../models/Schedule');
const scheduleService = require('../services/scheduleService'); // scheduleService 임포트 const scheduleService = require('./scheduleService'); // scheduleService 임포트
beforeAll(async () => { beforeAll(async () => {
await sequelize.sync({ force: true }); await sequelize.sync({ force: true });
...@@ -61,6 +61,7 @@ describe('Schedule Service', () => { ...@@ -61,6 +61,7 @@ describe('Schedule Service', () => {
}; };
const schedule = await scheduleService.createSchedule(scheduleData); const schedule = await scheduleService.createSchedule(scheduleData);
expect(schedule).toBeDefined(); expect(schedule).toBeDefined();
expect(schedule.user_id).toBe(2); expect(schedule.user_id).toBe(2);
...@@ -156,19 +157,19 @@ describe('Schedule Service', () => { ...@@ -156,19 +157,19 @@ describe('Schedule Service', () => {
describe('deleteSchedule', () => { describe('deleteSchedule', () => {
test('should delete an existing schedule successfully', async () => { test('should delete an existing schedule successfully', async () => {
const result = await scheduleService.deleteSchedule(2, 1); const result = await scheduleService.deleteSchedule(2, 1);
expect(result).toBe(true); expect(result).toEqual({ message: 'Schedule successfully deleted' });
// 삭제된 스케줄이 실제로 삭제되었는지 확인 // 삭제된 스케줄이 실제로 삭제되었는지 확인
const schedule = await Schedule.findByPk(2); const schedule = await Schedule.findByPk(2);
expect(schedule).toBeNull(); expect(schedule).toBeNull();
}); });
test('should throw error when deleting a non-existing schedule', async () => { test('should throw error when deleting a non-existing schedule', async () => {
await expect(scheduleService.deleteSchedule(999, 1)).rejects.toThrow('Schedule not found'); await expect(scheduleService.deleteSchedule(999, 1)).rejects.toThrow('Schedule not found');
}); });
}); });
describe('getAllSchedules', () => { describe('getAllSchedules', () => {
test('should retrieve all valid schedules for a user', async () => { test('should retrieve all valid schedules for a user', async () => {
...@@ -192,42 +193,52 @@ describe('Schedule Service', () => { ...@@ -192,42 +193,52 @@ describe('Schedule Service', () => {
await expect(scheduleService.getScheduleById(999, 1)).rejects.toThrow('Schedule not found'); await expect(scheduleService.getScheduleById(999, 1)).rejects.toThrow('Schedule not found');
}); });
}); });
// test/schedule.test.js
describe('cleanExpiredSchedules', () => { describe('cleanExpiredSchedules', () => {
test('should delete expired flexible schedules', async () => { test('should delete expired flexible schedules', async () => {
// 만료된 유동 스케줄 생성 // 현재 날짜를 기준으로 만료된 스케줄과 만료되지 않은 스케줄 생성
await Schedule.create({ const now = new Date('2024-05-07T00:00:00Z'); // 테스트를 위한 고정된 현재 날짜
id: 3,
user_id: 1, // Jest의 Fake Timers를 사용하여 Date를 고정
title: 'Expired Flexible Schedule', jest.useFakeTimers('modern');
start_time: new Date('2024-04-25T10:00:00Z'), jest.setSystemTime(now);
end_time: new Date('2024-04-25T11:00:00Z'),
is_fixed: false, // 만료된 유동 스케줄 생성
expiry_date: new Date('2024-04-30T00:00:00Z'), // 이미 만료됨 await Schedule.create({
}); user_id: 1,
title: 'Expired Flexible Schedule',
// 만료되지 않은 유동 스케줄 생성 start_time: new Date('2024-04-25T10:00:00Z'),
await Schedule.create({ end_time: new Date('2024-04-25T11:00:00Z'),
id: 4, is_fixed: false,
user_id: 1, expiry_date: new Date('2024-05-06T00:00:00Z'), // 이미 만료됨
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, await Schedule.create({
expiry_date: new Date('2024-05-14T00:00:00Z'), // 아직 만료되지 않음 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'),
await scheduleService.cleanExpiredSchedules(); is_fixed: false,
expiry_date: new Date('2024-05-14T00:00:00Z'), // 아직 만료되지 않음
// 만료된 스케줄이 삭제되었는지 확인 });
const expiredSchedule = await Schedule.findByPk(3);
expect(expiredSchedule).toBeNull(); // 만료된 스케줄 정리
await scheduleService.cleanExpiredSchedules();
// 만료되지 않은 스케줄은 남아있는지 확인
const validSchedule = await Schedule.findByPk(4); // 만료된 스케줄이 삭제되었는지 확인
expect(validSchedule).toBeDefined(); const expiredSchedule = await Schedule.findOne({ where: { title: 'Expired Flexible Schedule' } });
expect(validSchedule.title).toBe('Valid 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();
}); });
}); });
}); });
...@@ -5,237 +5,257 @@ const Schedule = require('../models/Schedule'); ...@@ -5,237 +5,257 @@ const Schedule = require('../models/Schedule');
const ScheduleResponseDTO = require('../dtos/ScheduleResponseDTO'); const ScheduleResponseDTO = require('../dtos/ScheduleResponseDTO');
class scheduleService { class scheduleService {
/**
/** * 트랜잭션 래퍼 함수
* 트랜잭션 래퍼 함수 */
*/ async withTransaction(callback) {
async withTransaction(callback) { const transaction = await Schedule.sequelize.transaction();
const transaction = await Schedule.sequelize.transaction(); try {
try { const result = await callback(transaction);
const result = await callback(transaction); await transaction.commit();
await transaction.commit(); return result;
return result; } catch (error) {
} catch (error) { await transaction.rollback();
await transaction.rollback(); throw error;
throw error;
}
}
/**
* 공통 where 절 생성
*/
getScheduleWhereClause(userId, id = null) {
const where = {
user_id: userId,
[Op.or]: [
{ is_fixed: true },
{
is_fixed: false,
expiry_date: { [Op.gt]: new Date() }
}
]
};
if (id) {
where.id = id;
}
return where;
} }
}
/**
* 스케줄 유효성 검사 /**
*/ * 공통 where 절 생성
validateScheduleTime(start_time, end_time) { */
if (new Date(start_time) >= new Date(end_time)) { getScheduleWhereClause(userId, id = null) {
throw new Error('Start time must be before end time'); const where = {
} user_id: userId,
[Op.or]: [
{ is_fixed: true },
{
is_fixed: false,
expiry_date: { [Op.gt]: new Date() },
},
],
};
if (id) {
where.id = id;
} }
/** return where;
* 유동 스케줄 만료일 구하기 }
*/
getNextMonday(startTime) {
const date = new Date(startTime);
const day = date.getDay();
const daysUntilNextMonday = (7 - day + 1) % 7;
const nextMonday = new Date(date); /**
nextMonday.setDate(date.getDate() + daysUntilNextMonday); * 스케줄 유효성 검사
nextMonday.setHours(0, 0, 0, 0); // 자정으로 설정 * 이미 컨트롤러에서 검증했으므로, 추가 검증 필요 시 수행
*/
return nextMonday; validateScheduleTime(start_time, end_time) {
if (new Date(start_time) >= new Date(end_time)) {
throw new Error("Start time must be before end time");
} }
}
/**
* 사용자 스케줄 생성 /**
*/ * 유동 스케줄 만료일 구하기
async createSchedule({ userId, title, start_time, end_time, is_fixed }) { */
const schedule = await this.withTransaction(async (transaction) => { getNextMonday(startTime) {
this.validateScheduleTime(start_time, end_time); const date = new Date(startTime);
const overlap = await this.checkScheduleOverlap(userId, start_time, end_time); const day = date.getUTCDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
if (overlap) { const daysUntilNextMonday = (8 - day) % 7 || 7; // Ensure next Monday
throw new Error('Schedule overlaps with existing schedule');
} const nextMonday = new Date(
Date.UTC(
const scheduleData = { date.getUTCFullYear(),
user_id: userId, date.getUTCMonth(),
title, date.getUTCDate() + daysUntilNextMonday,
start_time, 0,
end_time, 0,
is_fixed, 0,
expiry_date: is_fixed ? null : this.getNextMonday(start_time) 0 // Set to midnight UTC
}; )
);
return Schedule.create(scheduleData, { transaction });
}); return nextMonday;
}
return new ScheduleResponseDTO(schedule);
/**
* 사용자 스케줄 생성
*/
async createSchedule({ userId, title, start_time, end_time, is_fixed }) {
const schedule = await this.withTransaction(async (transaction) => {
this.validateScheduleTime(start_time, end_time);
const overlap = await this.checkScheduleOverlap(
userId,
start_time,
end_time
);
if (overlap) {
throw new Error("Schedule overlaps with existing schedule");
}
const scheduleData = {
user_id: userId,
title,
start_time,
end_time,
is_fixed,
expiry_date: is_fixed ? null : this.getNextMonday(start_time),
};
return Schedule.create(scheduleData, { transaction });
});
return new ScheduleResponseDTO(schedule);
}
/**
* 사용자 스케줄 수정
*/
async updateSchedule(id, userId, updateData) {
const updatedSchedule = await this.withTransaction(async (transaction) => {
const schedule = await Schedule.findOne({
where: { id, user_id: userId },
transaction,
});
if (!schedule) {
throw new Error("Schedule not found");
}
// 이미 컨트롤러에서 검증했으므로, 추가 검증이 필요하다면 수행
if (updateData.start_time && updateData.end_time) {
this.validateScheduleTime(updateData.start_time, updateData.end_time);
}
const overlap = await this.checkScheduleOverlap(
userId,
updateData.start_time || schedule.start_time,
updateData.end_time || schedule.end_time,
id
);
if (overlap) {
throw new Error("Schedule overlaps with existing schedule");
}
const is_fixed = schedule.is_fixed;
const updatedDataWithExpiry = {
...updateData,
expiry_date: is_fixed
? null
: this.getNextMonday(updateData.start_time || schedule.start_time),
updatedAt: new Date(),
};
delete updatedDataWithExpiry.is_fixed;
return schedule.update(updatedDataWithExpiry, { transaction });
});
return new ScheduleResponseDTO(updatedSchedule);
}
/**
* 사용자 스케줄 삭제
*/
async deleteSchedule(id, userId) {
return this.withTransaction(async (transaction) => {
const result = await Schedule.destroy({
where: { id, user_id: userId },
transaction,
});
if (!result) {
throw new Error("Schedule not found");
}
// 삭제 성공 메시지 반환
return { message: "Schedule successfully deleted" };
});
}
/**
* 해당 사용자의 스케줄 정보 조회
*/
async getAllSchedules(userId) {
try {
const schedules = await Schedule.findAll({
where: this.getScheduleWhereClause(userId),
order: [["start_time", "ASC"]],
});
const schedulesDTO = schedules.map(
(schedule) => new ScheduleResponseDTO(schedule)
);
return schedulesDTO;
} catch (error) {
throw new Error(`Failed to fetch schedules: ${error.message}`);
} }
}
/**
* 사용자 스케줄 수정 /**
*/ * 해당 사용자의 특정 스케줄 조회
async updateSchedule(id, userId, updateData) { */
const updatedSchedule = await this.withTransaction(async (transaction) => { async getScheduleById(id, userId) {
const schedule = await Schedule.findOne({ try {
where: { id, user_id: userId }, const schedule = await Schedule.findOne({
transaction where: this.getScheduleWhereClause(userId, id),
}); });
if (!schedule) { if (!schedule) {
throw new Error('Schedule not found'); throw new Error("Schedule not found");
} }
this.validateScheduleTime(updateData.start_time, updateData.end_time); return new ScheduleResponseDTO(schedule);
} catch (error) {
const overlap = await this.checkScheduleOverlap( throw new Error(`Failed to fetch schedule: ${error.message}`);
userId,
updateData.start_time,
updateData.end_time,
id
);
if (overlap) {
throw new Error('Schedule overlaps with existing schedule');
}
const is_fixed = schedule.is_fixed;
const updatedDataWithExpiry = {
...updateData,
expiry_date: is_fixed ? null : this.getNextMonday(updateData.start_time),
updatedAt: new Date()
};
delete updatedDataWithExpiry.is_fixed;
return schedule.update(updatedDataWithExpiry, { transaction });
});
return new ScheduleResponseDTO(updatedSchedule);
} }
}
/**
* 사용자 스케줄 삭제 /**
*/ * 만료된 유동 스케줄 정리
async deleteSchedule(id, userId) { */
return this.withTransaction(async (transaction) => { async cleanExpiredSchedules() {
const result = await Schedule.destroy({ try {
where: { id, user_id: userId }, await Schedule.destroy({
transaction where: {
}); is_fixed: false,
expiry_date: { [Op.lte]: new Date() },
if (!result) { },
throw new Error('Schedule not found'); });
} } catch (error) {
throw new Error(`Failed to clean expired schedules: ${error.message}`);
// 삭제 성공 메시지 반환
return { message: 'Schedule successfully deleted' };
});
} }
}
/**
* 해당 사용자의 스케줄 정보 조회 /**
*/ * 스케줄 중복 검사
async getAllSchedules(userId) { */
try { async checkScheduleOverlap(userId, start_time, end_time, excludeId = null) {
const schedules = await Schedule.findAll({ try {
where: this.getScheduleWhereClause(userId), const where = {
order: [['start_time', 'ASC']] user_id: userId,
}); [Op.or]: [
const schedulesDTO = schedules.map(schedule => new ScheduleResponseDTO(schedule)); {
return schedulesDTO; [Op.and]: [
} catch (error) { { start_time: { [Op.lte]: start_time } },
throw new Error(`Failed to fetch schedules: ${error.message}`); { end_time: { [Op.gte]: start_time } },
} ],
} },
{
/** [Op.and]: [
* 해당 사용자의 특정 스케줄 조회 { start_time: { [Op.gte]: start_time } },
*/ { start_time: { [Op.lte]: end_time } },
async getScheduleById(id, userId) { ],
try { },
const schedule = await Schedule.findOne({ ],
where: this.getScheduleWhereClause(userId, id) };
});
if (excludeId) {
if (!schedule) { where.id = { [Op.ne]: excludeId };
throw new Error('Schedule not found'); }
}
const overlappingSchedule = await Schedule.findOne({ where });
return new ScheduleResponseDTO(schedule); return overlappingSchedule;
} catch (error) { } catch (error) {
throw new Error(`Failed to fetch schedule: ${error.message}`); throw new Error(`Failed to check schedule overlap: ${error.message}`);
}
}
/**
* 만료된 유동 스케줄 정리
*/
async cleanExpiredSchedules() {
try {
await Schedule.destroy({
where: {
is_fixed: false,
expiry_date: { [Op.lte]: new Date() }
}
});
} catch (error) {
throw new Error(`Failed to clean expired schedules: ${error.message}`);
}
}
/**
* 스케줄 중복 검사
*/
async checkScheduleOverlap(userId, start_time, end_time, excludeId = null) {
try {
const where = {
user_id: userId,
[Op.or]: [
{
[Op.and]: [
{ start_time: { [Op.lte]: start_time } },
{ end_time: { [Op.gte]: start_time } }
]
},
{
[Op.and]: [
{ start_time: { [Op.gte]: start_time } },
{ start_time: { [Op.lte]: end_time } }
]
}
]
};
if (excludeId) {
where.id = { [Op.ne]: excludeId };
}
const overlappingSchedule = await Schedule.findOne({ where });
return overlappingSchedule;
} catch (error) {
throw new Error(`Failed to check schedule overlap: ${error.message}`);
}
} }
}
} }
module.exports = new scheduleService(); module.exports = new scheduleService();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment