From 7e0fe4e060a8b396acc268ab1ac87fcc8489ec4e Mon Sep 17 00:00:00 2001 From: tpgus2603 <kakaneymar2424@gmail.com> Date: Thu, 21 Nov 2024 02:57:26 +0900 Subject: [PATCH] =?UTF-8?q?refactor=20=EB=AA=A8=EB=8D=B8=EC=97=B0=EA=B4=80?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=EC=A4=91=EC=95=99=ED=99=94(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dtos/CreateMeetingRequestDTO.js | 2 +- models/Friend.js | 10 +- models/index.js | 38 ++- models/meeting.js | 6 +- models/meetingParticipant.js | 8 +- models/schedule.js | 4 +- models/user.js | 4 +- services/chatService.js | 2 +- services/friendService.js | 3 +- services/friendService.test.js | 6 +- {test => services}/friendrelation.test.js | 3 +- services/meetingService.js | 42 +-- services/meetingService.test.js | 313 ++++++++++++++++++++++ services/scheduleService.js | 20 +- 14 files changed, 401 insertions(+), 60 deletions(-) rename {test => services}/friendrelation.test.js (97%) create mode 100644 services/meetingService.test.js diff --git a/dtos/CreateMeetingRequestDTO.js b/dtos/CreateMeetingRequestDTO.js index fa3801d..f5d87a1 100644 --- a/dtos/CreateMeetingRequestDTO.js +++ b/dtos/CreateMeetingRequestDTO.js @@ -20,7 +20,7 @@ class CreateMeetingRequestDTO { start_time: Joi.date().iso().required(), end_time: Joi.date().iso().greater(Joi.ref('start_time')).required(), location: Joi.string().allow('', null).optional(), - deadline: Joi.date().iso().greater(Joi.ref('start_time')).optional(), + deadline: Joi.date().iso().less(Joi.ref('start_time')).optional(), type: Joi.string().valid('OPEN', 'CLOSE').required(), created_by: Joi.number().integer().positive().required() }); diff --git a/models/Friend.js b/models/Friend.js index 6354113..add987c 100644 --- a/models/Friend.js +++ b/models/Friend.js @@ -25,11 +25,11 @@ const Friend = sequelize.define('Friend', { ] }); -// 관계 설정 -Friend.belongsTo(User, { foreignKey: 'requester_id', as: 'requester' }); // 친구 요청을 보낸 사용자 -Friend.belongsTo(User, { foreignKey: 'receiver_id', as: 'receiver' }); // 친구 요청을 받은 사용자 +// // 관계 설정 +// Friend.belongsTo(User, { foreignKey: 'requester_id', as: 'requester' }); // 친구 요청을 보낸 사용자 +// Friend.belongsTo(User, { foreignKey: 'receiver_id', as: 'receiver' }); // 친구 요청을 받은 사용자 -User.hasMany(Friend, { foreignKey: 'requester_id', as: 'sentRequests' }); // 친구 요청을 보낸 목록 -User.hasMany(Friend, { foreignKey: 'receiver_id', as: 'receivedRequests' }); // 친구 요청을 받은 목록 +// User.hasMany(Friend, { foreignKey: 'requester_id', as: 'sentRequests' }); // 친구 요청을 보낸 목록 +// User.hasMany(Friend, { foreignKey: 'receiver_id', as: 'receivedRequests' }); // 친구 요청을 받은 목록 module.exports = Friend; diff --git a/models/index.js b/models/index.js index ec18287..adb731e 100644 --- a/models/index.js +++ b/models/index.js @@ -1,18 +1,38 @@ // models/index.js const sequelize = require('../config/sequelize'); - const User = require('./User'); +const Friend = require('./Friend'); const Schedule = require('./Schedule'); const Meeting = require('./Meeting'); -const MeetingParticipant = require('./MeetingParticipant'); //폴더명수정 -const Friend = require('./Friend'); +const MeetingParticipant = require('./MeetingParticipant'); +const ChatRoom = require('./ChatRooms'); + +// 관계 설정 +Friend.belongsTo(User, { foreignKey: 'requester_id', as: 'requester' }); // 친구 요청을 보낸 사용자 +Friend.belongsTo(User, { foreignKey: 'receiver_id', as: 'receiver' }); // 친구 요청을 받은 사용자 + +User.hasMany(Friend, { foreignKey: 'requester_id', as: 'sentRequests' }); // 친구 요청을 보낸 목록 +User.hasMany(Friend, { foreignKey: 'receiver_id', as: 'receivedRequests' }); // 친구 요청을 받은 목록 +// 연관 관계 설정 +Meeting.belongsTo(User, { foreignKey: 'created_by', as: 'creator' }); +User.hasMany(Meeting, { foreignKey: 'created_by', as: 'meetings' }); + +MeetingParticipant.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' }); +Meeting.hasMany(MeetingParticipant, { foreignKey: 'meeting_id', as: 'participants' }); + +MeetingParticipant.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); +User.hasMany(MeetingParticipant, { foreignKey: 'user_id', as: 'meetingParticipations' }); + +Schedule.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); +User.hasMany(Schedule, { foreignKey: 'user_id', as: 'schedules' }); module.exports = { - sequelize, - User, - Schedule, - Meeting, - MeetingParticipant, - Friend, + sequelize, + User, + Friend, + Schedule, + Meeting, + MeetingParticipant, + ChatRoom, }; diff --git a/models/meeting.js b/models/meeting.js index 68c0813..45b940b 100644 --- a/models/meeting.js +++ b/models/meeting.js @@ -44,9 +44,9 @@ const Meeting = sequelize.define('Meeting', { }); -// 연관 관계 설정 -Meeting.belongsTo(User, { foreignKey: 'created_by', as: 'creator' }); -User.hasMany(Meeting, { foreignKey: 'created_by', as: 'meetings' }); +// // 연관 관계 설정 +// Meeting.belongsTo(User, { foreignKey: 'created_by', as: 'creator' }); +// User.hasMany(Meeting, { foreignKey: 'created_by', as: 'meetings' }); module.exports = Meeting; diff --git a/models/meetingParticipant.js b/models/meetingParticipant.js index c069ec6..288d3b9 100644 --- a/models/meetingParticipant.js +++ b/models/meetingParticipant.js @@ -20,11 +20,11 @@ const MeetingParticipant = sequelize.define('MeetingParticipant', { timestamps: false, }); -MeetingParticipant.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' }); -Meeting.hasMany(MeetingParticipant, { foreignKey: 'meeting_id', as: 'participants' }); +// MeetingParticipant.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' }); +// Meeting.hasMany(MeetingParticipant, { foreignKey: 'meeting_id', as: 'participants' }); -MeetingParticipant.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); -User.hasMany(MeetingParticipant, { foreignKey: 'user_id', as: 'meetingParticipations' }); +// MeetingParticipant.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); +// User.hasMany(MeetingParticipant, { foreignKey: 'user_id', as: 'meetingParticipations' }); module.exports = MeetingParticipant; diff --git a/models/schedule.js b/models/schedule.js index 19a78b2..92aca80 100644 --- a/models/schedule.js +++ b/models/schedule.js @@ -30,7 +30,7 @@ const Schedule = sequelize.define('Schedule', { timestamps: true, // created_at과 updated_at 자동 관리 }); -Schedule.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); -User.hasMany(Schedule, { foreignKey: 'user_id', as: 'schedules' }); +// Schedule.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); +// User.hasMany(Schedule, { foreignKey: 'user_id', as: 'schedules' }); module.exports = Schedule; \ No newline at end of file diff --git a/models/user.js b/models/user.js index bd35bfe..7ed6043 100644 --- a/models/user.js +++ b/models/user.js @@ -5,11 +5,11 @@ const sequelize = require('../config/sequelize'); const User = sequelize.define('User', { name: { - type: DataTypes.STRING, // VARCHAR + type: DataTypes.STRING, allowNull: false, }, email: { - type: DataTypes.STRING, // VARCHAR + type: DataTypes.STRING, allowNull: false, unique: true, validate: { diff --git a/services/chatService.js b/services/chatService.js index 4135d15..079a400 100644 --- a/services/chatService.js +++ b/services/chatService.js @@ -1,4 +1,4 @@ -const ChatRoom = require('../models/chatRooms'); +const ChatRoom = require('../models/ChatRooms'); const { v4: uuidv4 } = require('uuid'); class ChatService { diff --git a/services/friendService.js b/services/friendService.js index 3f18b76..bb995bb 100644 --- a/services/friendService.js +++ b/services/friendService.js @@ -1,8 +1,7 @@ // services/friendService.js const { Op } = require('sequelize'); -const Friend = require('../models/Friend'); -const User = require('../models/User'); +const { Friend,User} = require('../models'); const sequelize = require('../config/sequelize'); // DTO 임포트 diff --git a/services/friendService.test.js b/services/friendService.test.js index fe70afc..db3e03e 100644 --- a/services/friendService.test.js +++ b/services/friendService.test.js @@ -1,8 +1,10 @@ // test/friendService.test.js const sequelize = require('../config/sequelize'); // Sequelize 인스턴스 임포트 -const User = require('../models/User'); -const Friend = require('../models/Friend'); +// const User = require('../models/User'); +// const Friend = require('../models/Friend'); +const { Friend,User} = require('../models'); + const friendService = require('./friendService'); // FriendService 임포트 // Sequelize의 Op를 가져오기 위해 추가 diff --git a/test/friendrelation.test.js b/services/friendrelation.test.js similarity index 97% rename from test/friendrelation.test.js rename to services/friendrelation.test.js index 4c5c1ed..d94267c 100644 --- a/test/friendrelation.test.js +++ b/services/friendrelation.test.js @@ -1,8 +1,7 @@ // test/friend.test.js const sequelize = require('../config/sequelize'); // 환경 변수에 따라 다른 인스턴스 사용 -const User = require('../models/User'); -const Friend = require('../models/Friend'); +const { Friend,User} = require('../models'); beforeAll(async () => { // 데이터베이스 동기화 diff --git a/services/meetingService.js b/services/meetingService.js index c0b3f43..6eb1e4f 100644 --- a/services/meetingService.js +++ b/services/meetingService.js @@ -2,8 +2,8 @@ const { v4: uuidv4 } = require('uuid'); const { Op } = require('sequelize'); -const { Meeting, MeetingParticipant, User, Schedule } = require('../models'); -const ChatRoom = require('../models/chatRooms'); +const { Meeting, MeetingParticipant, User, Schedule } = require('../models'); // models/index.js를 통해 임포트 +const ChatRoom = require('../models/ChatRooms'); const chatController = require('../controllers/chatController'); const MeetingResponseDTO = require('../dtos/MeetingResponseDTO'); const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO'); @@ -13,7 +13,8 @@ const ScheduleService = require('./scheduleService'); // ScheduleService 임포 class MeetingService { /** * 번개 모임 생성 - * @returns 생성된 모임 ID와 채팅방 ID + * @param {object} meetingData - 모임 생성 데이터 + * @returns {Promise<object>} - 생성된 모임 ID와 채팅방 ID */ async createMeeting(meetingData) { // DTO를 사용하여 요청 데이터 검증 @@ -39,7 +40,7 @@ class MeetingService { } // 트랜잭션을 사용하여 모임 생성과 스케줄 추가를 원자적으로 처리 - const result = await Meeting.sequelize.transaction(async (transaction) => { + const result = await ScheduleService.withTransaction(async (transaction) => { // 채팅방 생성 const chatRoomData = { participants: [user.name], @@ -71,14 +72,14 @@ class MeetingService { user_id: created_by, }, { transaction }); - // 스케줄 추가 + // 스케줄 추가 (트랜잭션 전달) await ScheduleService.createSchedule({ userId: created_by, title: `번개 모임: ${title}`, start_time: new Date(start_time), end_time: new Date(end_time), is_fixed: true, - }); + }, transaction); return { meeting_id: newMeeting.id, chatRoomId }; }); @@ -88,7 +89,8 @@ class MeetingService { /** * 번개 모임 목록 조회 - * @return:모임 목록 DTO 배열 + * @param {number} userId - 사용자 ID + * @returns {Promise<Array<MeetingResponseDTO>>} - 모임 목록 DTO 배열 */ async getMeetings(userId) { const meetings = await Meeting.findAll({ @@ -122,7 +124,8 @@ class MeetingService { /** * 번개 모임 마감 - * @returns 마감된 모임 객체 + * @param {number} meetingId - 모임 ID + * @returns {Promise<Meeting>} - 마감된 모임 객체 */ async closeMeeting(meetingId) { const meeting = await Meeting.findByPk(meetingId); @@ -139,8 +142,12 @@ class MeetingService { return meeting; } - - //번개모임 참가 + /** + * 번개 모임 참가 + * @param {number} meetingId - 모임 ID + * @param {number} userId - 사용자 ID + * @returns {Promise<void>} + */ async joinMeeting(meetingId, userId) { const meeting = await Meeting.findByPk(meetingId); if (!meeting) { @@ -164,7 +171,7 @@ class MeetingService { } // 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리 - await Meeting.sequelize.transaction(async (transaction) => { + await ScheduleService.withTransaction(async (transaction) => { // 참가자 추가 await MeetingParticipant.create({ meeting_id: meetingId, user_id: userId }, { transaction }); @@ -178,32 +185,33 @@ class MeetingService { throw new Error('스케줄이 겹칩니다. 다른 모임에 참가하세요.'); } - // 스케줄 추가 + // 스케줄 추가 (트랜잭션 전달) await ScheduleService.createSchedule({ userId: userId, title: `번개 모임: ${meeting.title}`, start_time: new Date(meeting.start_time), end_time: new Date(meeting.end_time), is_fixed: true, - }); + }, transaction); // 채팅방 참가 - const user = await User.findOne({ where: { id: userId } }); - const chatRoom = await ChatRoom.findOne({ where: { meeting_id: meetingId } }); + const user = await User.findOne({ where: { id: userId }, transaction }); + const chatRoom = await ChatRoom.findOne({ where: { meeting_id: meetingId }, transaction }); if (chatRoom && !chatRoom.participants.includes(user.name)) { chatRoom.participants.push(user.name); chatRoom.isOnline.set(user.name, true); chatRoom.lastReadAt.set(user.name, new Date()); chatRoom.lastReadLogId.set(user.name, null); - await chatRoom.save(); + await chatRoom.save({ transaction }); } }); } /** * 번개 모임 상세 조회 - * @return 모임 상세 DTO + * @param {number} meetingId - 모임 ID + * @returns {Promise<MeetingDetailResponseDTO>} - 모임 상세 DTO */ async getMeetingDetail(meetingId) { const meeting = await Meeting.findByPk(meetingId, { diff --git a/services/meetingService.test.js b/services/meetingService.test.js new file mode 100644 index 0000000..2195d65 --- /dev/null +++ b/services/meetingService.test.js @@ -0,0 +1,313 @@ +// test/meetingService.test.js + +const { sequelize, User, Friend, Schedule, Meeting, MeetingParticipant, ChatRoom } = require('../models'); // models/index.js를 통해 임포트 +const MeetingService = require('../services/meetingService'); +const ScheduleService = require('../services/scheduleService'); // ScheduleService 임포트 +const chatController = require('../controllers/chatController'); + +// Jest를 사용하여 chatController 모킹 +jest.mock('../controllers/chatController', () => ({ + createChatRoomInternal: jest.fn() +})); + +describe('MeetingService', () => { + 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.bulkCreate([ + { + 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, + }, + { + 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'), // 다음 월요일 + }, + ]); + + // 기본적인 채팅방 생성 모킹 설정 (성공) + chatController.createChatRoomInternal.mockResolvedValue({ + success: true, + chatRoomId: 'chatroom-1234' + }); + }); + + afterAll(async () => { + // 데이터베이스 연결 종료 + await sequelize.close(); + }); + + beforeEach(() => { + // 각 테스트 전에 mock 호출 이력 초기화 + jest.clearAllMocks(); + }); + + describe('createMeeting', () => { + test('번개 모임을 성공적으로 생성해야 한다', async () => { + const meetingData = { + title: 'Tech Talk', + description: 'A discussion on the latest tech trends.', + start_time: '2024-05-10T10:00:00Z', + end_time: '2024-05-10T12:00:00Z', + location: 'Online', + deadline: '2024-05-09T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + const result = await MeetingService.createMeeting(meetingData); + expect(result).toHaveProperty('meeting_id'); + expect(result).toHaveProperty('chatRoomId'); + expect(result.chatRoomId).toBe('chatroom-1234'); + + // 모임이 DB에 생성되었는지 확인 + const meeting = await Meeting.findByPk(result.meeting_id); + expect(meeting).toBeDefined(); + expect(meeting.title).toBe('Tech Talk'); + + // 참가자가 추가되었는지 확인 + const participant = await MeetingParticipant.findOne({ + where: { meeting_id: result.meeting_id, user_id: 1 } + }); + expect(participant).toBeDefined(); + + // 스케줄이 추가되었는지 확인 + const schedule = await Schedule.findOne({ + where: { user_id: 1, title: '번개 모임: Tech Talk' } + }); + expect(schedule).toBeDefined(); + expect(schedule.start_time.toISOString()).toBe('2024-05-10T10:00:00.000Z'); + expect(schedule.end_time.toISOString()).toBe('2024-05-10T12:00:00.000Z'); + expect(schedule.is_fixed).toBe(true); + expect(schedule.expiry_date).toBeNull(); + + // chatController.createChatRoomInternal이 호출되었는지 확인 + expect(chatController.createChatRoomInternal).toHaveBeenCalledTimes(1); + expect(chatController.createChatRoomInternal).toHaveBeenCalledWith({ + participants: ['Alice'] + }); + }); + + test('사용자의 스케줄이 겹치는 경우 모임 생성을 실패해야 한다', async () => { + // Alice의 기존 스케줄 생성 (이미 beforeAll에서 생성됨) + const overlappingMeetingData = { + title: 'Overlap Meeting', + description: 'This meeting overlaps with Alice\'s fixed schedule.', + start_time: '2024-05-01T09:30:00Z', // Alice's Fixed Schedule과 겹침 + end_time: '2024-05-01T11:00:00Z', + location: 'Office', + deadline: '2024-04-30T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + await expect(MeetingService.createMeeting(overlappingMeetingData)) + .rejects + .toThrow('스케줄이 겹칩니다. 다른 시간을 선택해주세요.'); + }); + + test('모임 생성 시 스케줄의 유동 스케줄이 만료되면 스케줄 충돌이 발생하지 않아야 한다', async () => { + const meetingData = { + title: 'Morning Meeting', + description: 'Meeting after flexible schedule expiry.', + start_time: '2024-05-09T09:00:00Z', // Flexible Schedule의 expiry_date가 지난 시점 + end_time: '2024-05-09T10:00:00Z', + location: 'Conference Room', + deadline: '2024-05-08T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + const result = await MeetingService.createMeeting(meetingData); + expect(result).toHaveProperty('meeting_id'); + expect(result).toHaveProperty('chatRoomId'); + expect(result.chatRoomId).toBe('chatroom-1234'); + + // 스케줄이 추가되었는지 확인 + const schedule = await Schedule.findOne({ + where: { user_id: 1, title: '번개 모임: Morning Meeting' } + }); + expect(schedule).toBeDefined(); + expect(schedule.start_time.toISOString()).toBe('2024-05-09T09:00:00.000Z'); + expect(schedule.end_time.toISOString()).toBe('2024-05-09T10:00:00.000Z'); + expect(schedule.is_fixed).toBe(true); + expect(schedule.expiry_date).toBeNull(); + }); + + test('모임 생성 시 채팅방 생성 실패하면 에러를 던져야 한다', async () => { + // chatController.createChatRoomInternal을 실패하도록 모킹 + chatController.createChatRoomInternal.mockResolvedValueOnce({ + success: false + }); + + const meetingData = { + title: 'Failed ChatRoom Meeting', + description: 'This meeting will fail to create chat room.', + start_time: '2024-05-11T10:00:00Z', + end_time: '2024-05-11T12:00:00Z', + location: 'Online', + deadline: '2024-05-10T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + await expect(MeetingService.createMeeting(meetingData)) + .rejects + .toThrow('채팅방 생성 실패'); + }); + + test('모임 생성 시 유효하지 않은 데이터는 검증 에러를 던져야 한다', async () => { + const invalidMeetingData = { + title: '', // 빈 제목 + start_time: 'invalid-date', + end_time: '2024-05-10T09:00:00Z', // start_time보다 이전 + type: 'INVALID_TYPE', + created_by: -1, // 음수 ID + }; + + await expect(MeetingService.createMeeting(invalidMeetingData)) + .rejects + .toThrow('Validation error'); + }); + }); + + describe('joinMeeting', () => { + test('번개 모임에 성공적으로 참가해야 한다', async () => { + // Alice가 모임 생성 + const meetingData = { + title: 'Networking Event', + description: 'An event to network with professionals.', + start_time: '2024-06-15T10:00:00Z', + end_time: '2024-06-15T12:00:00Z', + location: 'Conference Hall', + deadline: '2024-06-14T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + const { meeting_id } = await MeetingService.createMeeting(meetingData); + + // Bob가 모임에 참가 + await MeetingService.joinMeeting(meeting_id, 2); + + // 참가자가 추가되었는지 확인 + const participant = await MeetingParticipant.findOne({ + where: { meeting_id: meeting_id, user_id: 2 } + }); + expect(participant).toBeDefined(); + + // 스케줄이 추가되었는지 확인 + const schedule = await Schedule.findOne({ + where: { user_id: 2, title: '번개 모임: Networking Event' } + }); + expect(schedule).toBeDefined(); + expect(schedule.start_time.toISOString()).toBe('2024-06-15T10:00:00.000Z'); + expect(schedule.end_time.toISOString()).toBe('2024-06-15T12:00:00.000Z'); + expect(schedule.is_fixed).toBe(true); + expect(schedule.expiry_date).toBeNull(); + + // chatController.createChatRoomInternal이 호출되지 않았는지 확인 (이미 모임 생성 시 호출됨) + expect(chatController.createChatRoomInternal).toHaveBeenCalledTimes(1); + }); + + test('모임 참가 시 스케줄이 겹치는 경우 참가를 실패해야 한다', async () => { + // Alice가 모임 생성 + const meetingData1 = { + title: 'Morning Yoga', + description: 'Start your day with yoga.', + start_time: '2024-07-01T06:00:00Z', + end_time: '2024-07-01T07:00:00Z', + location: 'Gym', + deadline: '2024-06-30T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + const { meeting_id: meetingId1 } = await MeetingService.createMeeting(meetingData1); + + // Bob의 기존 스케줄 생성 + await ScheduleService.createSchedule({ + userId: 2, + title: 'Work', + start_time: new Date('2024-07-01T06:30:00Z'), + end_time: new Date('2024-07-01T08:30:00Z'), + is_fixed: true, + }, null); // 트랜잭션 없이 생성 + + // Bob가 모임에 참가 시도 + await expect(MeetingService.joinMeeting(meetingId1, 2)) + .rejects + .toThrow('스케줄이 겹칩니다. 다른 모임에 참가하세요.'); + }); + + test('모임 참가 시 이미 참가한 사용자는 다시 참가할 수 없어야 한다', async () => { + // Alice가 모임 생성 + const meetingData = { + title: 'Evening Run', + description: 'Join us for an evening run.', + start_time: '2024-08-20T18:00:00Z', + end_time: '2024-08-20T19:00:00Z', + location: 'Park', + deadline: '2024-08-19T23:59:59Z', + type: 'OPEN', + created_by: 1, + }; + + const { meeting_id } = await MeetingService.createMeeting(meetingData); + + // Bob가 모임에 참가 + await MeetingService.joinMeeting(meeting_id, 2); + + // Bob가 다시 모임에 참가하려 시도 + await expect(MeetingService.joinMeeting(meeting_id, 2)) + .rejects + .toThrow('이미 참가한 사용자입니다.'); + }); + + test('모임 마감된 경우 참가를 실패해야 한다', async () => { + // Alice가 모임 생성 (이미 마감됨) + const meetingData = { + title: 'Afternoon Workshop', + description: 'A workshop on web development.', + start_time: '2024-09-10T14:00:00Z', + end_time: '2024-09-10T16:00:00Z', + location: 'Office', + deadline: '2024-09-09T23:59:59Z', + type: 'CLOSE', + created_by: 1, + }; + + const { meeting_id } = await MeetingService.createMeeting(meetingData); + + // Bob가 모임에 참가 시도 + await expect(MeetingService.joinMeeting(meeting_id, 2)) + .rejects + .toThrow('이미 마감된 모임입니다.'); + }); + }); +}); diff --git a/services/scheduleService.js b/services/scheduleService.js index 66d0bbc..2226a98 100644 --- a/services/scheduleService.js +++ b/services/scheduleService.js @@ -1,5 +1,5 @@ // services/scheduleService.js - +const sequelize = require('../config/sequelize'); const { Op } = require('sequelize'); const Schedule = require('../models/Schedule'); const ScheduleResponseDTO = require('../dtos/ScheduleResponseDTO'); @@ -9,16 +9,16 @@ 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; + const transaction = await sequelize.transaction(); // 직접 sequelize 사용 + try { + const result = await callback(transaction); + await transaction.commit(); + return result; + } catch (error) { + await transaction.rollback(); + throw error; + } } - } /** * 공통 where 절 생성 -- GitLab