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');
const User = require('../models/User');
const Friend = require('../models/Friend');
const Schedule = require('../models/Schedule');
const scheduleService = require('../services/scheduleService'); // scheduleService 임포트
const scheduleService = require('./scheduleService'); // scheduleService 임포트
beforeAll(async () => {
await sequelize.sync({ force: true });
......@@ -61,6 +61,7 @@ describe('Schedule Service', () => {
};
const schedule = await scheduleService.createSchedule(scheduleData);
expect(schedule).toBeDefined();
expect(schedule.user_id).toBe(2);
......@@ -156,19 +157,19 @@ describe('Schedule Service', () => {
describe('deleteSchedule', () => {
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);
expect(schedule).toBeNull();
// 삭제된 스케줄이 실제로 삭제되었는지 확인
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');
await expect(scheduleService.deleteSchedule(999, 1)).rejects.toThrow('Schedule not found');
});
});
});
describe('getAllSchedules', () => {
test('should retrieve all valid schedules for a user', async () => {
......@@ -192,42 +193,52 @@ describe('Schedule Service', () => {
await expect(scheduleService.getScheduleById(999, 1)).rejects.toThrow('Schedule not found');
});
});
// test/schedule.test.js
describe('cleanExpiredSchedules', () => {
test('should delete expired flexible schedules', async () => {
// 만료된 유동 스케줄 생성
await Schedule.create({
id: 3,
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-04-30T00:00:00Z'), // 이미 만료됨
});
// 만료되지 않은 유동 스케줄 생성
await Schedule.create({
id: 4,
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.findByPk(3);
expect(expiredSchedule).toBeNull();
// 만료되지 않은 스케줄은 남아있는지 확인
const validSchedule = await Schedule.findByPk(4);
expect(validSchedule).toBeDefined();
expect(validSchedule.title).toBe('Valid Flexible Schedule');
// 현재 날짜를 기준으로 만료된 스케줄과 만료되지 않은 스케줄 생성
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();
});
});
});
});
......@@ -5,237 +5,257 @@ const Schedule = require('../models/Schedule');
const ScheduleResponseDTO = require('../dtos/ScheduleResponseDTO');
class scheduleService {
/**
* 트랜잭션 래퍼 함수
*/
async withTransaction(callback) {
const transaction = await Schedule.sequelize.transaction();
try {
const result = await callback(transaction);
await transaction.commit();
return result;
} catch (error) {
await transaction.rollback();
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;
/**
* 트랜잭션 래퍼 함수
*/
async withTransaction(callback) {
const transaction = await Schedule.sequelize.transaction();
try {
const result = await callback(transaction);
await transaction.commit();
return result;
} catch (error) {
await transaction.rollback();
throw error;
}
/**
* 스케줄 유효성 검사
*/
validateScheduleTime(start_time, end_time) {
if (new Date(start_time) >= new Date(end_time)) {
throw new Error('Start time must be before end time');
}
}
/**
* 공통 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;
}
/**
* 유동 스케줄 만료일 구하기
*/
getNextMonday(startTime) {
const date = new Date(startTime);
const day = date.getDay();
const daysUntilNextMonday = (7 - day + 1) % 7;
return where;
}
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) => {
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);
}
/**
* 유동 스케줄 만료일 구하기
*/
getNextMonday(startTime) {
const date = new Date(startTime);
const day = date.getUTCDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
const daysUntilNextMonday = (8 - day) % 7 || 7; // Ensure next Monday
const nextMonday = new Date(
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() + daysUntilNextMonday,
0,
0,
0,
0 // Set to midnight UTC
)
);
return nextMonday;
}
/**
* 사용자 스케줄 생성
*/
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) => {
const schedule = await Schedule.findOne({
where: { id, user_id: userId },
transaction
});
if (!schedule) {
throw new Error('Schedule not found');
}
this.validateScheduleTime(updateData.start_time, updateData.end_time);
const overlap = await this.checkScheduleOverlap(
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 getScheduleById(id, userId) {
try {
const schedule = await Schedule.findOne({
where: this.getScheduleWhereClause(userId, id),
});
if (!schedule) {
throw new Error("Schedule not found");
}
return new ScheduleResponseDTO(schedule);
} catch (error) {
throw new Error(`Failed to fetch schedule: ${error.message}`);
}
/**
* 사용자 스케줄 삭제
*/
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 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 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 getScheduleById(id, userId) {
try {
const schedule = await Schedule.findOne({
where: this.getScheduleWhereClause(userId, id)
});
if (!schedule) {
throw new Error('Schedule not found');
}
return new ScheduleResponseDTO(schedule);
} catch (error) {
throw new Error(`Failed to fetch schedule: ${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}`);
}
}
/**
* 스케줄 중복 검사
*/
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();
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