// test/integration.test.js const sequelize = require('../config/sequelize'); const { User, Friend, Meeting, Schedule, MeetingParticipant, ChatRooms } = require('../models'); const FriendService = require('../services/friendService'); const MeetingService = require('../services/meetingService'); const ScheduleService = require('../services/scheduleService'); describe('System Integration Test', () => { beforeAll(async () => { await sequelize.sync({ force: true }); jest.spyOn(ChatRooms.prototype, 'save').mockResolvedValue(undefined); jest.spyOn(ChatRooms, 'findOne').mockResolvedValue({ participants: [], isOnline: new Map(), lastReadAt: new Map(), lastReadLogId: new Map(), save: jest.fn().mockResolvedValue(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' } ]); // 성능 측정을 위한 시작 시간 기록 console.time('Complete User Journey'); }); afterAll(async () => { console.timeEnd('Complete User Journey'); jest.restoreAllMocks(); await sequelize.close(); }); /** * 시나리오 1 * 1. 친구 * 2. 스케줄 관리 * 3. 미팅 * 4. 조회 */ test('Complete User Journey Scenario', async () => { const pagination = { limit: 20, offset: 0 }; /** * 1. 친구 * 친구 요청/수락/거절 * 친구 목록 조회 * 중복 친구 요청 방지 * ++ 친구 스케줄 보기 */ console.time('Friend Operations'); const aliceId = 1, bobId = 2, charlieId = 3; await FriendService.sendFriendRequest(aliceId, bobId); await FriendService.sendFriendRequest(aliceId, charlieId); await FriendService.acceptFriendRequest(bobId, aliceId); await FriendService.rejectFriendRequest(charlieId, aliceId); const aliceFriends = await FriendService.getFriendList(aliceId, pagination); expect(aliceFriends.content.length).toBe(1); expect(aliceFriends.content[0].friendInfo.name).toBe('Bob'); await expect( FriendService.sendFriendRequest(aliceId, bobId) ).rejects.toThrow('Friend request already exists'); /** * 2. 스케줄 관리 * 스케줄 생성/수정/삭제 * 전체 스케줄 조회 * 특정 스케줄 조회 * ++ 페이지네이션 확인 */ console.time('Schedule Operations'); // 2-1. 스케줄 생성 const aliceSchedule = { userId: aliceId, title: '수업', is_fixed: true, events: [ { time_idx: 36 }, { time_idx: 37 }, { time_idx: 38 } ] }; const createdSchedules = await ScheduleService.createSchedules(aliceSchedule); expect(createdSchedules.length).toBe(3); // 2-2. 특정 스케줄 조회 const specificSchedule = await ScheduleService.getScheduleByTimeIdx(aliceId, 36); expect(specificSchedule.title).toBe('수업'); expect(specificSchedule.is_fixed).toBe(true); // 2-3. 전체 스케줄 조회 const allSchedules = await ScheduleService.getAllSchedules(aliceId); expect(allSchedules.length).toBe(3); expect(allSchedules.every(s => s.title === '수업')).toBe(true); // 2-4. 스케줄 수정 const scheduleUpdates = [ { time_idx: 36, title: '중요 수업', is_fixed: true } ]; const updatedSchedules = await ScheduleService.updateSchedules(aliceId, scheduleUpdates); expect(updatedSchedules[0].title).toBe('중요 수업'); // 2-5. 수정된 스케줄 확인 const updatedAllSchedules = await ScheduleService.getAllSchedules(aliceId); const updatedSchedule = updatedAllSchedules.find(s => s.time_idx === 36); expect(updatedSchedule.title).toBe('중요 수업'); // 2-6. 스케줄 삭제 const deleteResult = await ScheduleService.deleteSchedules(aliceId, [37]); expect(deleteResult.deleted_time_idxs).toContain(37); // 2-7. 삭제 확인 const remainingSchedules = await ScheduleService.getAllSchedules(aliceId); expect(remainingSchedules.length).toBe(2); expect(remainingSchedules.every(s => s.time_idx !== 37)).toBe(true); // 2-8. 중복 스케줄 생성 시도 await expect( ScheduleService.createSchedules({ userId: aliceId, title: '중복 스케줄', is_fixed: true, events: [{ time_idx: 36 }] }) ).rejects.toThrow('Schedule overlaps with existing schedule'); console.timeEnd('Schedule Operations'); /** * 3. 미팅 참가 * 미팅 생성/스케줄 자동 등록 ++ create 시 생성자의 스케줄 확인 및 중복 체크 * 중복된 시간 참여 불가 * 미팅 close (생성자) * 미팅 탈퇴 * ++ 친구 초대 */ jest.spyOn(MeetingService, 'getCurrentTimeIdx').mockReturnValue(30); /** * 3. 미팅 참가 시나리오 */ console.time('Meeting Operations'); // 3-1. 미팅 생성 및 스케줄 자동 등록 const aliceConflictMeeting = { title: '스터디 모임', time_idx_start: 36, time_idx_end: 38, created_by: aliceId, type: 'OPEN', time_idx_deadline: 35, location: 'Room A' }; await expect( MeetingService.createMeeting(aliceConflictMeeting) ).rejects.toThrow('해당 시간에 이미 다른 스케줄이 있습니다'); const meetingData = { title: '스터디 모임', time_idx_start: 36, time_idx_end: 38, created_by: bobId, type: 'OPEN', time_idx_deadline: 35, location: 'Room A' }; const meeting = await MeetingService.createMeeting(meetingData); const bobSchedules = await Schedule.findAll({ where: { user_id: bobId, title: `번개 모임: ${meetingData.title}` } }); expect(bobSchedules.length).toBe(3); // 36-38 시간대 // 3-2. 스케줄 충돌로 인한 참가 실패 (Alice) await expect( MeetingService.joinMeeting(meeting.meeting_id, aliceId) ).rejects.toThrow('스케줄이 겹칩니다'); // 3-3. Charlie 참가 성공 await MeetingService.joinMeeting(meeting.meeting_id, charlieId); const charlieSchedules = await Schedule.findAll({ where: { user_id: charlieId, title: `번개 모임: ${meetingData.title}` } }); expect(charlieSchedules.length).toBe(3); // 3-4. Charlie의 미팅 목록 조회 const charlieMyMeetings = await MeetingService.getMyMeetings(charlieId, pagination); expect(charlieMyMeetings.content.length).toBe(1); expect(charlieMyMeetings.content[0].isParticipant).toBe(true); expect(charlieMyMeetings.content[0].title).toBe('스터디 모임'); // 3-5. Charlie 미팅 탈퇴 await MeetingService.leaveMeeting(meeting.meeting_id, charlieId); // 탈퇴 후 스케줄 삭제 확인 const remainingCharlieSchedules = await Schedule.findAll({ where: { user_id: charlieId, title: `번개 모임: ${meetingData.title}` } }); expect(remainingCharlieSchedules.length).toBe(0); // 탈퇴 후 미팅 목록 확인 const charlieMyMeetingsAfterLeave = await MeetingService.getMyMeetings(charlieId, pagination); expect(charlieMyMeetingsAfterLeave.content.length).toBe(0); // 3-6. 생성자 탈퇴 시도 (실패) await expect( MeetingService.leaveMeeting(meeting.meeting_id, bobId) ).rejects.toThrow('모임 생성자는 탈퇴할 수 없습니다'); // 3-7. 미팅 마감 await MeetingService.closeMeeting(meeting.meeting_id); const closedMeeting = await Meeting.findByPk(meeting.meeting_id); expect(closedMeeting.type).toBe('CLOSE'); // 3-8. 마감된 미팅 참가 시도 (실패) await expect( MeetingService.joinMeeting(meeting.meeting_id, charlieId) ).rejects.toThrow('이미 마감된 모임입니다'); /** * 4. 미팅 조회 시나리오 * 전체 미팅 목록 조회 * 참여하고 있는 미팅 목록 조회 * 상세 정보 조회 */ console.time('Meeting Queries'); // 4-1. 전체 미팅 목록 조회 const allMeetings = await MeetingService.getMeetings(aliceId, pagination); expect(allMeetings.content.length).toBe(1); expect(allMeetings.content[0].isScheduleConflict).toBe(true); // 4-2. Bob의 미팅 목록 조회 (생성자) const bobMyMeetings = await MeetingService.getMyMeetings(bobId, pagination); expect(bobMyMeetings.content.length).toBe(1); expect(bobMyMeetings.content[0].isParticipant).toBe(true); expect(bobMyMeetings.content[0].creatorName).toBe('Bob'); // 4-2. Alice의 미팅 목록 조회 (참여 x, 생성 x) const aliceMyMeetings = await MeetingService.getMyMeetings(aliceId, pagination); expect(aliceMyMeetings.content.length).toBe(0); // expect(aliceMyMeetings.content[0].isParticipant).toBe(true); // expect(aliceMyMeetings.content[0].creatorName).toBe('Bob'); // 4-3. 상세 정보 조회 const meetingDetail = await MeetingService.getMeetingDetail(meeting.meeting_id, aliceId); expect(meetingDetail.isScheduleConflict).toBe(true); expect(meetingDetail.creatorName).toBe('Bob'); expect(meetingDetail.participants).toBeDefined(); console.timeEnd('Meeting Queries'); }, 10000); });