diff --git a/src/main/java/com/m9d/sroom/common/repository/course/CourseJdbcRepositoryImpl.java b/src/main/java/com/m9d/sroom/common/repository/course/CourseJdbcRepositoryImpl.java index 6bfd4417..e32c94ae 100644 --- a/src/main/java/com/m9d/sroom/common/repository/course/CourseJdbcRepositoryImpl.java +++ b/src/main/java/com/m9d/sroom/common/repository/course/CourseJdbcRepositoryImpl.java @@ -25,7 +25,6 @@ public CourseEntity save(CourseEntity course) { course.getMemberId(), course.getCourseTitle(), course.getDuration(), - course.getLastViewTime(), course.getThumbnail(), course.isScheduled(), course.getWeeks(), diff --git a/src/main/java/com/m9d/sroom/common/repository/course/CourseRepositorySql.groovy b/src/main/java/com/m9d/sroom/common/repository/course/CourseRepositorySql.groovy index 71ada7b6..07c124d7 100644 --- a/src/main/java/com/m9d/sroom/common/repository/course/CourseRepositorySql.groovy +++ b/src/main/java/com/m9d/sroom/common/repository/course/CourseRepositorySql.groovy @@ -8,9 +8,9 @@ class CourseRepositorySql { public static final String SAVE = """ INSERT - INTO COURSE (member_id, course_title, course_duration, last_view_time, thumbnail, is_scheduled, weeks, - expected_end_date, daily_target_time) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + INTO COURSE (member_id, course_title, course_duration, thumbnail, is_scheduled, weeks, expected_end_date, + daily_target_time) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) """ public static final String GET_BY_ID = """ diff --git a/src/main/java/com/m9d/sroom/common/repository/review/ReviewJdbcRepositoryImpl.java b/src/main/java/com/m9d/sroom/common/repository/review/ReviewJdbcRepositoryImpl.java index dd9e559c..d43c5b56 100644 --- a/src/main/java/com/m9d/sroom/common/repository/review/ReviewJdbcRepositoryImpl.java +++ b/src/main/java/com/m9d/sroom/common/repository/review/ReviewJdbcRepositoryImpl.java @@ -24,8 +24,7 @@ public ReviewEntity save(ReviewEntity review) { review.getMemberId(), review.getLectureId(), review.getSubmittedRating(), - review.getContent(), - review.getSubmittedDate()); + review.getContent()); return getById(jdbcTemplate.queryForObject(LectureRepositorySql.GET_LAST_ID, Long.class)); } diff --git a/src/main/java/com/m9d/sroom/common/repository/review/ReviewRepositorySql.groovy b/src/main/java/com/m9d/sroom/common/repository/review/ReviewRepositorySql.groovy index 54698b01..a7bfdcf6 100644 --- a/src/main/java/com/m9d/sroom/common/repository/review/ReviewRepositorySql.groovy +++ b/src/main/java/com/m9d/sroom/common/repository/review/ReviewRepositorySql.groovy @@ -7,8 +7,8 @@ class ReviewRepositorySql { public static final String SAVE = """ INSERT - INTO REVIEW (source_code, member_id, lecture_id, submitted_rating, content, submitted_date) - VALUES (?, ?, ?, ?, ?, ?) + INTO REVIEW (source_code, member_id, lecture_id, submitted_rating, content) + VALUES (?, ?, ?, ?, ?) """ public static final String GET_BY_ID = """ diff --git a/src/main/java/com/m9d/sroom/course/dto/request/NewLecture.java b/src/main/java/com/m9d/sroom/course/dto/request/NewLecture.java index 847dc081..739e9d14 100644 --- a/src/main/java/com/m9d/sroom/course/dto/request/NewLecture.java +++ b/src/main/java/com/m9d/sroom/course/dto/request/NewLecture.java @@ -29,4 +29,9 @@ public class NewLecture { @Schema(description = "예상 종료 시간", example = "2023-12-31") private String expectedEndDate; + public static NewLecture createWithoutSchedule(String lectureCode) { + return NewLecture.builder() + .lectureCode(lectureCode) + .build(); + } } diff --git a/src/main/java/com/m9d/sroom/material/MaterialService.java b/src/main/java/com/m9d/sroom/material/MaterialService.java index 7cf4c51f..757a9f84 100644 --- a/src/main/java/com/m9d/sroom/material/MaterialService.java +++ b/src/main/java/com/m9d/sroom/material/MaterialService.java @@ -95,14 +95,14 @@ public List submitQuizResults(Long memberId, Long cou List quizInfoResponseList = new ArrayList<>(); for (SubmittedQuizRequest submittedQuizRequest : submittedQuizList) { - if (quizService.isSubmittedAlready(courseVideoId, submittedQuizRequest.getQuizId())) { + if (quizService.isSubmittedAlready(courseVideoId, submittedQuizRequest.getId())) { throw new CourseQuizDuplicationException(); } CourseQuizEntity courseQuizEntity = quizService.createCourseQuizEntity(courseVideoEntity.getCourseId(), - courseVideoEntity.getVideoId(), courseVideoId, submittedQuizRequest.getQuizId(), + courseVideoEntity.getVideoId(), courseVideoId, submittedQuizRequest.getId(), submittedQuizRequest.toVo(), memberId); - quizInfoResponseList.add(new SubmittedQuizInfoResponse(submittedQuizRequest.getQuizId(), + quizInfoResponseList.add(new SubmittedQuizInfoResponse(submittedQuizRequest.getId(), courseQuizEntity.getId())); } log.info("subject = quizSubmitted, correctAnswerRate = {}", diff --git a/src/main/java/com/m9d/sroom/material/dto/request/FeedbackRequest.java b/src/main/java/com/m9d/sroom/material/dto/request/FeedbackRequest.java index 464e43d4..641784b0 100644 --- a/src/main/java/com/m9d/sroom/material/dto/request/FeedbackRequest.java +++ b/src/main/java/com/m9d/sroom/material/dto/request/FeedbackRequest.java @@ -1,10 +1,17 @@ package com.m9d.sroom.material.dto.request; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; import lombok.Setter; @Setter +@AllArgsConstructor +@NoArgsConstructor public class FeedbackRequest { + @JsonProperty("is_satisfactory") private Boolean is_satisfactory; public boolean isSatisfactory() { diff --git a/src/main/java/com/m9d/sroom/material/dto/request/SubmittedQuizRequest.java b/src/main/java/com/m9d/sroom/material/dto/request/SubmittedQuizRequest.java index 6cc22c3b..7f1ce810 100644 --- a/src/main/java/com/m9d/sroom/material/dto/request/SubmittedQuizRequest.java +++ b/src/main/java/com/m9d/sroom/material/dto/request/SubmittedQuizRequest.java @@ -1,23 +1,25 @@ package com.m9d.sroom.material.dto.request; import com.m9d.sroom.quiz.vo.QuizSubmittedInfo; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.sql.Timestamp; @Setter +@AllArgsConstructor +@NoArgsConstructor public class SubmittedQuizRequest { + @Getter private Long id; private String submitted_answer; private Boolean is_correct; - public Long getQuizId() { - return id; - } - public String getSubmittedAnswer() { return submitted_answer; } diff --git a/src/main/java/com/m9d/sroom/material/dto/request/SummaryEditRequest.java b/src/main/java/com/m9d/sroom/material/dto/request/SummaryEditRequest.java index d08ded45..0e36b5fb 100644 --- a/src/main/java/com/m9d/sroom/material/dto/request/SummaryEditRequest.java +++ b/src/main/java/com/m9d/sroom/material/dto/request/SummaryEditRequest.java @@ -1,11 +1,15 @@ package com.m9d.sroom.material.dto.request; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor public class SummaryEditRequest { private String content; diff --git a/src/main/java/com/m9d/sroom/member/MemberService.java b/src/main/java/com/m9d/sroom/member/MemberService.java index 71970fe4..592ccdde 100644 --- a/src/main/java/com/m9d/sroom/member/MemberService.java +++ b/src/main/java/com/m9d/sroom/member/MemberService.java @@ -42,9 +42,9 @@ public MemberService(MemberRepository memberRepository, JwtUtil jwtUtil) { public Login authenticateMember(String credential) throws Exception { GoogleIdToken idToken = verifyCredential(credential); - MemberEntity member = findOrCreateMemberByMemberCode(idToken.getPayload().getSubject()); - log.info("member login. memberId = {}", member.getMemberId()); - return generateLogin(member, (String) idToken.getPayload().get("picture")); + MemberEntity memberEntity = findOrCreateMember(idToken.getPayload().getSubject()); + log.info("member login. memberId = {}", memberEntity.getMemberId()); + return generateLogin(memberEntity, (String) idToken.getPayload().get("picture")); } public GoogleIdToken verifyCredential(String credential) throws Exception { @@ -62,7 +62,7 @@ public GoogleIdToken verifyCredential(String credential) throws Exception { return idToken; } - public MemberEntity findOrCreateMemberByMemberCode(String memberCode) { + public MemberEntity findOrCreateMember(String memberCode) { return memberRepository.findByCode(memberCode) .orElseGet(() -> createNewMember(memberCode)); } @@ -107,19 +107,19 @@ public Login renewTokens(Long memberId, String pictureUrl) { return generateLogin(member, pictureUrl); } - public Login generateLogin(MemberEntity member, String picture) { - String accessToken = jwtUtil.generateAccessToken(member.getMemberId(), picture); + public Login generateLogin(MemberEntity memberEntity, String picture) { + String accessToken = jwtUtil.generateAccessToken(memberEntity.getMemberId(), picture); - member.setRefreshToken(jwtUtil.generateRefreshToken(member.getMemberId(), picture)); - memberRepository.updateById(member.getMemberId(), member); + memberEntity.setRefreshToken(jwtUtil.generateRefreshToken(memberEntity.getMemberId(), picture)); + memberRepository.updateById(memberEntity.getMemberId(), memberEntity); return Login.builder() .accessToken(accessToken) - .refreshToken(member.getRefreshToken()) + .refreshToken(memberEntity.getRefreshToken()) .accessExpiresAt((Long) jwtUtil.getDetailFromToken(accessToken).get(EXPIRATION_TIME)) - .name(member.getMemberName()) + .name(memberEntity.getMemberName()) .profile(picture) - .bio(member.getBio()) + .bio(memberEntity.getBio()) .build(); } diff --git a/src/main/java/com/m9d/sroom/playlist/PlaylistService.java b/src/main/java/com/m9d/sroom/playlist/PlaylistService.java index 4e5e4bdc..d5405378 100644 --- a/src/main/java/com/m9d/sroom/playlist/PlaylistService.java +++ b/src/main/java/com/m9d/sroom/playlist/PlaylistService.java @@ -1,8 +1,11 @@ package com.m9d.sroom.playlist; import com.m9d.sroom.common.entity.PlaylistEntity; +import com.m9d.sroom.common.entity.PlaylistVideoEntity; +import com.m9d.sroom.common.entity.VideoEntity; import com.m9d.sroom.common.repository.playlist.PlaylistRepository; import com.m9d.sroom.common.repository.playlistvideo.PlaylistVideoRepository; +import com.m9d.sroom.common.repository.video.VideoRepository; import com.m9d.sroom.playlist.vo.Playlist; import com.m9d.sroom.video.vo.PlaylistItem; import com.m9d.sroom.playlist.vo.PlaylistWithItemList; @@ -14,6 +17,7 @@ import com.m9d.sroom.youtube.YoutubeMapper; import com.m9d.sroom.youtube.vo.PlaylistItemInfo; import com.m9d.sroom.youtube.vo.PlaylistVideoInfo; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.*; @@ -21,18 +25,20 @@ import java.util.stream.Collectors; @Service +@Slf4j public class PlaylistService { private final PlaylistRepository playlistRepository; - private final YoutubeMapper youtubeService; private final VideoService videoService; + private final VideoRepository videoRepository; private final PlaylistVideoRepository playlistVideoRepository; - public PlaylistService(PlaylistRepository playlistRepository, YoutubeMapper youtubeService, VideoService videoService, PlaylistVideoRepository playlistVideoRepository) { + public PlaylistService(PlaylistRepository playlistRepository, YoutubeMapper youtubeService, VideoService videoService, VideoRepository videoRepository, PlaylistVideoRepository playlistVideoRepository) { this.playlistRepository = playlistRepository; this.youtubeService = youtubeService; this.videoService = videoService; + this.videoRepository = videoRepository; this.playlistVideoRepository = playlistVideoRepository; } @@ -44,11 +50,11 @@ public Playlist getRecentPlaylist(String playlistCode) { if (playlistEntityOptional.isPresent()) { reviewCount = playlistEntityOptional.get().getReviewCount(); accumulatedRating = playlistEntityOptional.get().getAccumulatedRating(); - if(DateUtil.hasRecentUpdate(playlistEntityOptional.get().getUpdatedAt(), PlaylistConstant.PLAYLIST_UPDATE_THRESHOLD_HOURS)) { + if (DateUtil.hasRecentUpdate(playlistEntityOptional.get().getUpdatedAt(), PlaylistConstant.PLAYLIST_UPDATE_THRESHOLD_HOURS)) { return playlistEntityOptional.get().toPlaylist(); } } - + return youtubeService.getPlaylist(playlistCode, reviewCount, accumulatedRating); } @@ -57,6 +63,12 @@ public PlaylistWithItemList getRecentPlaylistWithItemList(String playlistCode) { } private List getRecentPlaylistItemList(String playlistCode) { + Optional playlistEntityOptional = playlistRepository.findByCode(playlistCode); + if (playlistEntityOptional.isPresent() && DateUtil.hasRecentUpdate(playlistEntityOptional.get().getUpdatedAt(), + PlaylistConstant.PLAYLIST_UPDATE_THRESHOLD_HOURS)) { + return getPlaylistItemFromDB(playlistEntityOptional.get().getPlaylistId()); + } + String nextPageToken = null; int pageCount = PlaylistConstant.MAX_PLAYLIST_ITEM / PlaylistConstant.DEFAULT_INDEX_COUNT; @@ -123,6 +135,16 @@ public void putPlaylistWithItemList(PlaylistWithItemList playlistWithItemList) { } } + private List getPlaylistItemFromDB(Long playlistId) { + List videoEntityList = videoRepository.getListByPlaylistId(playlistId); + + List playlistItemList = new ArrayList<>(); + for (int i = 0; i < videoEntityList.size(); i++) { + playlistItemList.add(new PlaylistItem(videoEntityList.get(i).toVideo(), i)); + } + return playlistItemList; + } + public EnrollContentInfo getEnrollContentInfo(String playlistCode) { PlaylistEntity playlistEntity = playlistRepository.getByCode(playlistCode); diff --git a/src/main/java/com/m9d/sroom/quiz/QuizService.java b/src/main/java/com/m9d/sroom/quiz/QuizService.java index b8ca13d5..e01a9b3f 100644 --- a/src/main/java/com/m9d/sroom/quiz/QuizService.java +++ b/src/main/java/com/m9d/sroom/quiz/QuizService.java @@ -88,7 +88,7 @@ private QuizSubmittedInfo getSubmittedInfo(Long quizId, Long courseVideoId) { public void validateSubmittedQuizzes(Long videoId, Long courseVideoId, List submittedQuizRequestList) { - QuizEntity quiz = quizRepository.findById(submittedQuizRequestList.get(0).getQuizId()) + QuizEntity quiz = quizRepository.findById(submittedQuizRequestList.get(0).getId()) .orElseThrow(QuizNotFoundException::new); if (!quiz.getVideoId().equals(videoId)) { @@ -96,7 +96,7 @@ public void validateSubmittedQuizzes(Long videoId, Long courseVideoId, } for (SubmittedQuizRequest submittedQuiz : submittedQuizRequestList) { - if (courseQuizRepository.findByQuizIdAndCourseVideoId(submittedQuiz.getQuizId(), + if (courseQuizRepository.findByQuizIdAndCourseVideoId(submittedQuiz.getId(), courseVideoId).isPresent()) { throw new CourseQuizDuplicationException(); } diff --git a/src/main/java/com/m9d/sroom/review/dto/ReviewSubmitRequest.java b/src/main/java/com/m9d/sroom/review/dto/ReviewSubmitRequest.java index 77d0acd0..a5b6f5d3 100644 --- a/src/main/java/com/m9d/sroom/review/dto/ReviewSubmitRequest.java +++ b/src/main/java/com/m9d/sroom/review/dto/ReviewSubmitRequest.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.springframework.data.relational.core.sql.In; @Data @Builder @@ -15,4 +16,11 @@ public class ReviewSubmitRequest { private Integer submittedRating; private String reviewContent; + + public static ReviewSubmitRequest createReview(Integer submittedRating, String reviewContent) { + return ReviewSubmitRequest.builder() + .submittedRating(submittedRating) + .reviewContent(reviewContent) + .build(); + } } diff --git a/src/main/java/com/m9d/sroom/search/dto/request/LectureTimeRecord.java b/src/main/java/com/m9d/sroom/search/dto/request/LectureTimeRecord.java index bc2b2645..45a410e9 100644 --- a/src/main/java/com/m9d/sroom/search/dto/request/LectureTimeRecord.java +++ b/src/main/java/com/m9d/sroom/search/dto/request/LectureTimeRecord.java @@ -1,11 +1,15 @@ package com.m9d.sroom.search.dto.request; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import javax.validation.constraints.NotNull; @Setter +@AllArgsConstructor +@NoArgsConstructor public class LectureTimeRecord { @NotNull diff --git a/src/main/java/com/m9d/sroom/search/exception/LectureNotFoundException.java b/src/main/java/com/m9d/sroom/search/exception/LectureNotFoundException.java index d6c75528..c8d36531 100644 --- a/src/main/java/com/m9d/sroom/search/exception/LectureNotFoundException.java +++ b/src/main/java/com/m9d/sroom/search/exception/LectureNotFoundException.java @@ -4,7 +4,7 @@ public class LectureNotFoundException extends NotFoundException { - private static final String MESSAGE = "잘못된 입력입니다. 강의를 찾을 수 없습니다."; + public static final String MESSAGE = "잘못된 입력입니다. 강의를 찾을 수 없습니다."; public LectureNotFoundException() { super(MESSAGE); diff --git a/src/main/java/com/m9d/sroom/util/DateUtil.java b/src/main/java/com/m9d/sroom/util/DateUtil.java index 711a072a..40af8748 100644 --- a/src/main/java/com/m9d/sroom/util/DateUtil.java +++ b/src/main/java/com/m9d/sroom/util/DateUtil.java @@ -22,7 +22,7 @@ public class DateUtil { public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("Asia/Seoul")); - public Long convertTimeToSeconds(String time) { + public static Long convertTimeToSeconds(String time) { String[] parts = time.split(":"); int length = parts.length; diff --git a/src/test/java/com/m9d/sroom/course/CourseControllerTest.java b/src/test/java/com/m9d/sroom/course/CourseControllerTest.java new file mode 100644 index 00000000..29fb3050 --- /dev/null +++ b/src/test/java/com/m9d/sroom/course/CourseControllerTest.java @@ -0,0 +1,348 @@ +package com.m9d.sroom.course; + +import com.m9d.sroom.course.dto.request.NewLecture; +import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.search.constant.SearchConstant; +import com.m9d.sroom.search.dto.request.LectureTimeRecord; +import com.m9d.sroom.util.ControllerTest; +import com.m9d.sroom.util.constant.ContentConstant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +public class CourseControllerTest extends ControllerTest { + + @Test + @DisplayName("내 강의실에서 강의코스 리스트와 정보를 불러옵니다.") + void getCourses() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); // 영상과 강의자료를 미리 저장하여 ai 서버를 통한 강의자료 생성을 막는 과정입니다. + + //when + enrollNewCourseWithVideo(login); + + //then + mockMvc.perform(get("/courses") + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.unfinished_course").isNotEmpty()) + .andExpect(jsonPath("$.completion_rate").isNotEmpty()) + .andExpect(jsonPath("$.courses[0].course_title").value(ContentConstant.VIDEO_TITLE)) + .andDo(print()); + } + + @Test + @DisplayName("단일영상 신규 코스등록에 성공합니다 - 일정관리 안함") + void saveCourseWithVideo() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + + //when + MvcResult mvcResult = mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "false") + .content(objectMapper.writeValueAsString( + NewLecture.createWithoutSchedule(ContentConstant.VIDEO_CODE_LIST[0])))) + .andReturn(); + String courseId = objectMapper.readTree(mvcResult.getResponse().getContentAsString()).path("course_id").asText(); + + // then + mockMvc.perform(get("/courses/{courseId}", courseId) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(false))) + .andExpect(jsonPath("$.course_title", is(ContentConstant.VIDEO_TITLE))) + .andExpect(jsonPath("$.total_video_count", is(1))) + .andExpect(jsonPath("$.sections[0].videos[0].video_title", is(ContentConstant.VIDEO_TITLE))) + .andDo(print()); + } + + @Test + @DisplayName("재생목록 신규 코스등록에 성공합니다 - 일정관리 안함") + void saveCourseWithPlaylist() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + + //when + MvcResult postResult = mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "false") + .content(objectMapper.writeValueAsString( + NewLecture.createWithoutSchedule(ContentConstant.PLAYLIST_CODE)))) + .andReturn(); + String courseId = objectMapper.readTree(postResult.getResponse().getContentAsString()).path("course_id").asText(); + + //then + mockMvc.perform(get("/courses/{courseId}", courseId) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(false))) + .andExpect(jsonPath("$.course_title", is(ContentConstant.PLAYLIST_TITLE))) + .andExpect(jsonPath("$.total_video_count", is(ContentConstant.VIDEO_CODE_LIST.length))) + .andExpect(jsonPath("$.sections[0].videos", hasSize(ContentConstant.VIDEO_CODE_LIST.length))) + .andDo(print()); + } + + @Test + @DisplayName("재생목록 신규 코스등록에 성공합니다 - 일정관리 함") + void saveCourseWithPlaylistSchedule() throws Exception { + //given + Login login = getNewLogin(); + int videoCountPerSection = 2; + int sectionCount = 2; + NewLecture newLecture = NewLecture.builder() + .lectureCode(ContentConstant.PLAYLIST_CODE) + .dailyTargetTime(60) + .scheduling(List.of(videoCountPerSection, videoCountPerSection)) + .expectedEndDate("2024-07-29") + .build(); + saveContents(); + + //when + MvcResult postResult = mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "true") + .content(objectMapper.writeValueAsString(newLecture))) + .andReturn(); + String courseId = objectMapper.readTree(postResult.getResponse().getContentAsString()).path("course_id").asText(); + + //then + mockMvc.perform(get("/courses/{courseId}", courseId) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(true))) + .andExpect(jsonPath("$.course_title", is(ContentConstant.PLAYLIST_TITLE))) + .andExpect(jsonPath("$.total_video_count", is(ContentConstant.VIDEO_CODE_LIST.length))) + .andExpect(jsonPath("$.sections", hasSize(sectionCount))) + .andExpect(jsonPath("$.sections[0].videos", hasSize(videoCountPerSection))) + .andDo(print()); + } + + @Test + @DisplayName("단일영상 기존 코스등록에 성공합니다") + void saveVideoInCourse() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithPlaylist(login); + + //when + mockMvc.perform(post("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "true") + .content(objectMapper.writeValueAsString( + NewLecture.createWithoutSchedule(ContentConstant.VIDEO_CODE_LIST[0])))); + + //expected + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(false))) + .andExpect(jsonPath("$.total_video_count", + is(ContentConstant.VIDEO_CODE_LIST.length + 1))) + .andExpect(jsonPath("$.sections[0].videos", + hasSize(ContentConstant.VIDEO_CODE_LIST.length + 1))) + .andDo(print()); + } + + @Test + @DisplayName("재생목록 기존 코스등록에 성공합니다") + void savePlaylistInCourse() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithPlaylist(login); + + //when + mockMvc.perform(post("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString( + NewLecture.createWithoutSchedule(ContentConstant.PLAYLIST_CODE)))); + + //expected + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(false))) + .andExpect(jsonPath("$.total_video_count", + is(ContentConstant.VIDEO_CODE_LIST.length * 2))) + .andExpect(jsonPath("$.sections[0].videos", + hasSize(ContentConstant.VIDEO_CODE_LIST.length * 2))) + .andDo(print()); + } + + @Test + @DisplayName("수강 페이지 정보를 적절히 받아옵니다 - 스케줄링 함") + void getCourseDetailSchedule200() throws Exception { + //given + Login login = getNewLogin(); + int videoCountPerSection = 2; + int sectionCount = 2; + NewLecture newLecture = NewLecture.builder() + .lectureCode(ContentConstant.PLAYLIST_CODE) + .dailyTargetTime(60) + .scheduling(List.of(videoCountPerSection, videoCountPerSection)) + .expectedEndDate("2024-07-29") + .build(); + saveContents(); + + //when + enrollNewCourseWithPlaylistSchedule(login, newLecture); + + //then + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(true))) + .andExpect(jsonPath("$.total_video_count", + is(ContentConstant.VIDEO_CODE_LIST.length))) + .andExpect(jsonPath("$.sections[0].videos", + hasSize(videoCountPerSection))) + .andExpect(jsonPath("$.sections", hasSize(sectionCount))) + .andDo(print()); + } + + @Test + @DisplayName("수강 페이지 정보를 적절히 받아옵니다 - 스케줄링 안함") + void getCourseDetail200() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + + //when + enrollNewCourseWithPlaylist(login); + + //then + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.use_schedule", is(false))) + .andExpect(jsonPath("$.total_video_count", + is(ContentConstant.VIDEO_CODE_LIST.length))) + .andExpect(jsonPath("sections[0].videos", hasSize(ContentConstant.VIDEO_CODE_LIST.length))) + .andDo(print()); + } + + @Test + @DisplayName("강의 코스 삭제를 완료하고, 업데이트 된 강의 코스 리스트를 불러옵니다") + void deleteCourse200() throws Exception { + //given + Login login = getNewLogin(); + enrollNewCourseWithPlaylist(login); + + //when + mockMvc.perform(delete("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())); + + //expected + mockMvc.perform(get("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.courses", hasSize(0))) + .andExpect(jsonPath("$.courses[0]").doesNotExist()) + .andDo(print()); + } + + @Test + @DisplayName("영상 재생시간 저장을 성공합니다.") + void saveVideoTime() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + int viewDuration = 10; + + //when + mockMvc.perform(put("/lectures/{courseVideoId}/time", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString(new LectureTimeRecord(viewDuration)))); + + //then + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("sections[0].videos[0].last_view_duration", is(viewDuration))) + .andExpect(jsonPath("sections[0].videos[0].max_duration", is(viewDuration))) + .andDo(print()); + } + + @Test + @DisplayName("영상의 수강기준 이상을 시청하면 수강이 완료됩니다.") + void updateVideoCompletion() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + int viewDuration = (int) ((SearchConstant.MINIMUM_VIEW_PERCENT_FOR_COMPLETION + 0.1) + * ContentConstant.VIDEO_DURATION_LIST[0]); + + //when + mockMvc.perform(put("/lectures/{courseVideoId}/time", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString(new LectureTimeRecord(viewDuration)))); + + //then + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("sections[0].videos[0].is_completed", is(true))) + .andExpect(jsonPath("completed_video_count", is(1))) + .andExpect(jsonPath("$.progress", greaterThan(0))) + .andDo(print()); + } + + @Test + @DisplayName("'완료하기' 버튼을 누르면 영상 수강이 완료됩니다.") + void updateCompleteManually() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + + //when + mockMvc.perform(put("/lectures/{courseVideoId}/time", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .param("isCompletedManually", "true") + .content(objectMapper.writeValueAsString(new LectureTimeRecord(0)))); + + //then + mockMvc.perform(get("/courses/{courseId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("sections[0].videos[0].is_completed", is(true))) + .andExpect(jsonPath("completed_video_count", is(1))) + .andExpect(jsonPath("$.progress", is(100))) + .andDo(print()); + } +} diff --git a/src/test/java/com/m9d/sroom/course/controller/CourseControllerTest.java b/src/test/java/com/m9d/sroom/course/controller/CourseControllerTest.java deleted file mode 100644 index 6d51771e..00000000 --- a/src/test/java/com/m9d/sroom/course/controller/CourseControllerTest.java +++ /dev/null @@ -1,236 +0,0 @@ -package com.m9d.sroom.course.controller; - -import com.m9d.sroom.course.dto.request.NewLecture; -import com.m9d.sroom.member.dto.response.Login; -import com.m9d.sroom.util.ControllerTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.http.MediaType; - -import java.util.List; - -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -public class CourseControllerTest extends ControllerTest { - - @Test - @DisplayName("내 강의실에서 강의코스 리스트와 정보를 불러옵니다.") - void getCourses() throws Exception { - //given - Login login = getNewLogin(); - - //expected - mockMvc.perform(get("/courses") - .header("Authorization", login.getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.unfinished_course").isNotEmpty()) - .andExpect(jsonPath("$.completion_rate").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("단일영상 신규 코스등록에 성공합니다 - 일정관리 안함") - void saveCourseWithVideo() throws Exception { - //given - Login login = getNewLogin(); - NewLecture newLecture = NewLecture.builder() - .lectureCode(VIDEO_CODE) - .build(); - - String content = objectMapper.writeValueAsString(newLecture); - String useSchedule = "false"; - - //expected - mockMvc.perform(post("/courses") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("use_schedule", useSchedule) - .content(content)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.course_id").isNotEmpty()) - .andExpect(jsonPath("$.lecture_id").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("단일영상 신규 코스등록에 성공합니다 - 일정관리 함") - void saveCourseWithVideoSchedule() throws Exception { - //given - Login login = getNewLogin(); - String expectedEndTime = "2023-07-29"; - NewLecture newLecture = NewLecture.builder() - .lectureCode(VIDEO_CODE) - .dailyTargetTime(30) - .scheduling(List.of(1)) - .expectedEndDate(expectedEndTime) - .build(); - - String content = objectMapper.writeValueAsString(newLecture); - String useSchedule = "true"; - - //expected - mockMvc.perform(post("/courses") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("use_schedule", useSchedule) - .content(content)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.course_id").isNotEmpty()) - .andExpect(jsonPath("$.lecture_id").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("재생목록 신규 코스등록에 성공합니다 - 일정관리 안함") - void saveCourseWithPlaylist() throws Exception { - //given - Login login = getNewLogin(); - NewLecture newLecture = NewLecture.builder() - .lectureCode(PLAYLIST_CODE) - .build(); - - String content = objectMapper.writeValueAsString(newLecture); - String useSchedule = "false"; - - //expected - mockMvc.perform(post("/courses") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("use_schedule", useSchedule) - .content(content)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.course_id").isNotEmpty()) - .andExpect(jsonPath("$.lecture_id").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("재생목록 신규 코스등록에 성공합니다 - 일정관리 함") - void saveCourseWithPlaylistSchedule() throws Exception { - //given - Login login = getNewLogin(); - String expectedEndTime = "2023-07-29"; - NewLecture newLecture = NewLecture.builder() - .lectureCode(VIDEO_CODE) - .dailyTargetTime(60) - .scheduling(List.of(1, 2, 3, 4)) - .expectedEndDate(expectedEndTime) - .build(); - - String content = objectMapper.writeValueAsString(newLecture); - String useSchedule = "false"; - - //expected - mockMvc.perform(post("/courses") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("use_schedule", useSchedule) - .content(content)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.course_id").isNotEmpty()) - .andExpect(jsonPath("$.lecture_id").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("단일영상 기존 코스등록에 성공합니다") - void saveVideoInCourse() throws Exception { - //given - Login login = getNewLogin(); - Long courseId = enrollNewCourseWithVideo(login); - NewLecture newLecture = new NewLecture(); - newLecture.setLectureCode(VIDEO_CODE); - - String content = objectMapper.writeValueAsString(newLecture); - - //expected - mockMvc.perform(post("/courses/{courseId}", courseId) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .content(content)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.course_id").isNotEmpty()) - .andExpect(jsonPath("$.lecture_id").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("재생목록 기존 코스등록에 성공합니다") - void savePlaylistInCourse() throws Exception { - //given - Login login = getNewLogin(); - Long courseId = enrollNewCourseWithVideo(login); - NewLecture newLecture = new NewLecture(); - newLecture.setLectureCode(PLAYLIST_CODE); - - String content = objectMapper.writeValueAsString(newLecture); - - //expected - mockMvc.perform(post("/courses/{courseId}", courseId) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .content(content)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.course_id").isNotEmpty()) - .andExpect(jsonPath("$.lecture_id").isNotEmpty()) - .andDo(print()); - } - - @Test - @DisplayName("수강 페이지 정보를 적절히 받아옵니다 - 스케줄링 함") - void getCourseDetailSchedule200() throws Exception { - //given - Login login = getNewLogin(); - Long courseId = enrollNewCourseWithPlaylistSchedule(login); - - //expected - mockMvc.perform(get("/courses/{courseId}", courseId) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections", hasSize(3))) - .andDo(print()); - } - - @Test - @DisplayName("수강 페이지 정보를 적절히 받아옵니다 - 스케줄링 안함") - void getCourseDetail200() throws Exception { - //given - Login login = getNewLogin(); - Long courseId = enrollNewCourseWithPlaylist(login); - - //expected - mockMvc.perform(get("/courses/{courseId}", courseId) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections", hasSize(1))) - .andExpect(jsonPath("$.sections[0].videos", hasSize(PLAYLIST_VIDEO_COUNT))) - .andDo(print()); - } - - @Test - @DisplayName("강의 코스 삭제를 완료하고, 업데이트 된 강의 코스 리스트를 불러옵니다") - void deleteCourse200() throws Exception { - //given - Login login = getNewLogin(); - - Long courseId1 = enrollNewCourseWithPlaylist(login); - Long courseId2 = enrollNewCourseWithPlaylist(login); - - System.out.println(courseId1); - System.out.println(courseId2); - - //expected - mockMvc.perform(delete("/courses/{courseId}", courseId1) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.courses", hasSize(1))) - .andExpect(jsonPath("$.courses[0]").isNotEmpty()) - .andDo(print()); - } -} diff --git a/src/test/java/com/m9d/sroom/course/service/CourseServiceTest.java b/src/test/java/com/m9d/sroom/course/service/CourseServiceTest.java deleted file mode 100644 index b5e8acfd..00000000 --- a/src/test/java/com/m9d/sroom/course/service/CourseServiceTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.m9d.sroom.course.service; - -import com.m9d.sroom.course.dto.request.NewLecture; -import com.m9d.sroom.course.dto.response.EnrolledCourseInfo; -import com.m9d.sroom.course.dto.response.MyCourses; -import com.m9d.sroom.common.entity.MemberEntity; -import com.m9d.sroom.util.ServiceTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.springframework.transaction.annotation.Transactional; - -@Transactional -public class CourseServiceTest extends ServiceTest { - - @Test - @DisplayName("유저가 등록한 강의 코스들의 정보를 불러옵니다.") - void getCourseListTest() { - //given - MemberEntity member = getNewMember(); - Long memberId = member.getMemberId(); - - String courseInsertSql = "INSERT INTO COURSE(member_id, course_title, thumbnail, last_view_time, progress) values(?, ?, ?, ?, ?)"; - jdbcTemplate.update(courseInsertSql, memberId, "course1", " ", "2023-06-13", 100); - jdbcTemplate.update(courseInsertSql, memberId, "course2", " ", "2023-04-14", 93); - jdbcTemplate.update(courseInsertSql, memberId, "course3", " ", "2023-11-21", 40); - - String lectureInsertSql = "INSERT INTO LECTURE(course_id, channel, is_playlist) values(?, ?, ?)"; - String getCourseIdSql = "SELECT course_id FROM COURSE WHERE course_title = ?"; - String courseVideoInsertSql = "INSERT INTO COURSEVIDEO(course_id, is_complete, video_id, summary_id, video_index) values(?, ?, 0, 0, 0)"; - for (int i = 1; i <= 3; i++) { - String courseId = jdbcTemplate.queryForObject(getCourseIdSql, String.class, "course".concat(String.valueOf(i))); - jdbcTemplate.update(lectureInsertSql, Long.valueOf(courseId), "channel1", "false"); - jdbcTemplate.update(lectureInsertSql, Long.valueOf(courseId), "channel2", "false"); - jdbcTemplate.update(courseVideoInsertSql, Long.valueOf(courseId), true); - jdbcTemplate.update(courseVideoInsertSql, Long.valueOf(courseId), false); - } - - - - - //when - MyCourses myCourses = courseService.getMyCourses(memberId); - - //then - System.out.println(myCourses); - } - - - @Test - @DisplayName("신규 코스 등록에 성공합니다.") - void createNewCourse() { - //given - MemberEntity member = getNewMember(); - - //when - NewLecture newLecture = NewLecture.builder() - .lectureCode(VIDEO_CODE) - .build(); - EnrolledCourseInfo enrolledCourseInfo = courseService.enrollCourse(member.getMemberId(), newLecture, false); - - //then - Long courseIdInLectureTable = courseRepository.getCourseIdByLectureId(enrolledCourseInfo.getLectureId()); - Assertions.assertEquals(enrolledCourseInfo.getCourseId(), courseIdInLectureTable, "lecture table의 courseId와 등록된 courseId가 다릅니다."); - } -} diff --git a/src/test/java/com/m9d/sroom/dashboard/controller/DashboardControllerTest.java b/src/test/java/com/m9d/sroom/dashboard/DashboardControllerTest.java similarity index 77% rename from src/test/java/com/m9d/sroom/dashboard/controller/DashboardControllerTest.java rename to src/test/java/com/m9d/sroom/dashboard/DashboardControllerTest.java index eea327b2..88379f43 100644 --- a/src/test/java/com/m9d/sroom/dashboard/controller/DashboardControllerTest.java +++ b/src/test/java/com/m9d/sroom/dashboard/DashboardControllerTest.java @@ -1,15 +1,18 @@ -package com.m9d.sroom.dashboard.controller; +package com.m9d.sroom.dashboard; import com.m9d.sroom.member.dto.response.Login; import com.m9d.sroom.util.ControllerTest; +import com.m9d.sroom.util.constant.ContentConstant; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Transactional; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@Transactional public class DashboardControllerTest extends ControllerTest { @Test @@ -17,6 +20,9 @@ public class DashboardControllerTest extends ControllerTest { void Dashboards200() throws Exception { //given Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + //expected mockMvc.perform(get("/dashboards") @@ -25,6 +31,7 @@ void Dashboards200() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.correctness_rate").isNotEmpty()) .andExpect(jsonPath("$.completion_rate").isNotEmpty()) - .andExpect(jsonPath("$.total_learning_time").isNotEmpty()); + .andExpect(jsonPath("$.total_learning_time").isNotEmpty()) + .andExpect(jsonPath("$.latest_lectures[0].course_title").value(ContentConstant.VIDEO_TITLE)); } } diff --git a/src/test/java/com/m9d/sroom/dashboard/service/DashboardServiceTest.java b/src/test/java/com/m9d/sroom/dashboard/service/DashboardServiceTest.java deleted file mode 100644 index f233ebb5..00000000 --- a/src/test/java/com/m9d/sroom/dashboard/service/DashboardServiceTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.m9d.sroom.dashboard.service; - - -import com.m9d.sroom.dashboard.dto.response.Dashboard; -import com.m9d.sroom.common.entity.MemberEntity; -import com.m9d.sroom.util.ServiceTest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.transaction.annotation.Transactional; - -@Transactional -public class DashboardServiceTest extends ServiceTest { - - @Test - @DisplayName("유저의 대시보드 정보를 불러옵니다") - void getDashboardTest() { - //given - MemberEntity member = getNewMember(); - Long memberId = member.getMemberId(); - - int total_correct_count = 1; - int total_solved_count = 2; - int completion_rate = 23; - int total_learning_time = 100; - - int expect_correctness_rate = 50; - - String memberInsertSql = "UPDATE MEMBER SET (total_solved_count, total_correct_count, completion_rate, total_learning_time) = values(?, ?, ?, ?)"; - String courseInsertSql = "INSERT INTO COURSE(member_id, course_title, thumbnail, last_view_time, course_duration) values(?, ?, ?, ?, ?)"; - String dailyLogInsertSql = "INSERT INTO COURSE_DAILY_LOG(member_id, course_id, daily_log_date, quiz_count) values(?, ?, ?, ?)"; - String lectureInsertSql = "INSERT INTO LECTURE(course_id, channel, is_playlist)values(?, ?, false)"; - String courseVideoInsertSql = "INSERT INTO COURSEVIDEO(course_id, is_complete, video_id, summary_id, video_index) values(?, ?, 0, 0, 0)"; - - jdbcTemplate.update(memberInsertSql, total_solved_count, total_correct_count, completion_rate, total_learning_time); - - jdbcTemplate.update(courseInsertSql, memberId, "course1", " ", "2023-05-01 10:00:00", 600); - jdbcTemplate.update(courseInsertSql, memberId, "course2", " ", "2023-05-10 10:00:00", 3600); - jdbcTemplate.update(courseInsertSql, memberId, "course3", " ", "2023-04-20 10:00:00", 10); - - jdbcTemplate.update(lectureInsertSql, 1, "채널 1"); - jdbcTemplate.update(lectureInsertSql, 1, "채널 1"); - jdbcTemplate.update(lectureInsertSql, 1, "채널 2"); - - jdbcTemplate.update(courseVideoInsertSql, 1, true); - jdbcTemplate.update(courseVideoInsertSql, 1, false); - - jdbcTemplate.update(dailyLogInsertSql, memberId, 1, "2023-07-28", 2); - jdbcTemplate.update(dailyLogInsertSql, memberId, 1, "2023-07-27", 1); - jdbcTemplate.update(dailyLogInsertSql, memberId, 2, "2023-07-26", 3); - - //when - Dashboard dashboardInfo = dashboardService.getDashboard(memberId); - - //then - Assertions.assertEquals(expect_correctness_rate, dashboardInfo.getCorrectnessRate()); - Assertions.assertEquals(completion_rate, dashboardInfo.getCompletionRate()); - Assertions.assertEquals(total_learning_time, dashboardInfo.getTotalLearningTime()); - - Assertions.assertEquals("2023-05-10 10:00:00", dashboardInfo.getLatestLectures().get(0).getLastViewTime()); - Assertions.assertEquals("2023-05-01 10:00:00", dashboardInfo.getLatestLectures().get(1).getLastViewTime()); - - Assertions.assertEquals(3, dashboardInfo.getLearningHistories().size()); - - Assertions.assertNotNull(dashboardInfo.getMotivation()); - } -} diff --git a/src/test/java/com/m9d/sroom/global/UtilTest.java b/src/test/java/com/m9d/sroom/global/UtilTest.java index 1028956c..fd56c0dc 100644 --- a/src/test/java/com/m9d/sroom/global/UtilTest.java +++ b/src/test/java/com/m9d/sroom/global/UtilTest.java @@ -1,13 +1,12 @@ package com.m9d.sroom.global; import com.google.gson.Gson; +import com.m9d.sroom.util.DateUtil; import com.m9d.sroom.util.SroomTest; -import com.m9d.sroom.youtube.resource.SearchReq; import com.m9d.sroom.youtube.dto.search.SearchDto; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; public class UtilTest extends SroomTest { @@ -22,11 +21,11 @@ void convertTimeFormat() throws Exception { String hourDouble = "11:03:52"; //when - Long onlySecondToSecond = dateUtil.convertTimeToSeconds(onlySecond); - Long minuteSingleToSecond = dateUtil.convertTimeToSeconds(minuteSingle); - Long minuteDoubleToSecond = dateUtil.convertTimeToSeconds(minuteDouble); - Long hourSingleToSecond = dateUtil.convertTimeToSeconds(hourSingle); - Long hourDoubleToSecond = dateUtil.convertTimeToSeconds(hourDouble); + Long onlySecondToSecond = DateUtil.convertTimeToSeconds(onlySecond); + Long minuteSingleToSecond = DateUtil.convertTimeToSeconds(minuteSingle); + Long minuteDoubleToSecond = DateUtil.convertTimeToSeconds(minuteDouble); + Long hourSingleToSecond = DateUtil.convertTimeToSeconds(hourSingle); + Long hourDoubleToSecond = DateUtil.convertTimeToSeconds(hourDouble); //then Assertions.assertEquals(onlySecondToSecond, 11L); @@ -348,21 +347,21 @@ void mappingObject() throws Exception { } - @Test - @DisplayName("web client non-block을 테스트합니다.") - void testNonBlocking() throws Exception { - SearchReq lectureListReq = SearchReq.builder() - .keyword("네트워크") - .filter("all") - .limit(10) - .pageToken(null) - .build(); - System.out.println(System.currentTimeMillis()); - Mono test = youtubeApi.getSearchVo(lectureListReq); - System.out.println(System.currentTimeMillis()); - SearchDto searchVo = youtubeService.safeGetVo(test); - System.out.println(System.currentTimeMillis()); - System.out.println(searchVo.toString()); - System.out.println(System.currentTimeMillis()); - } +// @Test +// @DisplayName("web client non-block을 테스트합니다.") +// void testNonBlocking() throws Exception { +// SearchReq lectureListReq = SearchReq.builder() +// .keyword("네트워크") +// .filter("all") +// .limit(10) +// .pageToken(null) +// .build(); +// System.out.println(System.currentTimeMillis()); +// Mono test = youtubeApi.getSearchVo(lectureListReq); +// System.out.println(System.currentTimeMillis()); +// SearchDto searchVo = youtubeService.safeGetVo(test); +// System.out.println(System.currentTimeMillis()); +// System.out.println(searchVo.toString()); +// System.out.println(System.currentTimeMillis()); +// } } diff --git a/src/test/java/com/m9d/sroom/lecture/controller/LectureResponseControllerTest.java b/src/test/java/com/m9d/sroom/lecture/controller/LectureResponseControllerTest.java deleted file mode 100644 index 1ffa6703..00000000 --- a/src/test/java/com/m9d/sroom/lecture/controller/LectureResponseControllerTest.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.m9d.sroom.lecture.controller; - -import com.m9d.sroom.search.dto.response.KeywordSearchResponse; -import com.m9d.sroom.member.dto.response.Login; -import com.m9d.sroom.util.ControllerTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.http.MediaType; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.hamcrest.Matchers.*; - -public class LectureResponseControllerTest extends ControllerTest { - - - @Test - @DisplayName("입력된 limit 개수만큼 검색 결과가 반환됩니다.") - void keywordSearch200() throws Exception { - //given - Login login = getNewLogin(); - String keyword = "네트워크"; - String limit = String.valueOf(7); - - //expected - mockMvc.perform(get("/lectures") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("keyword", keyword) - .queryParam("limit", limit)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.result_per_page").value(Integer.parseInt(limit))); - } - - @Test - @DisplayName("keyword를 입력하지 않은 경우 400에러가 발생합니다.") - void keywordSearch400() throws Exception { - //given - Login login = getNewLogin(); - - //expected - mockMvc.perform(get("/lectures") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken())) - .andExpect(status().isBadRequest()); - } - - @Test - @DisplayName("pageToken을 입력하면 다음 페이지가 반환됩니다.") - void searchWithPageToken200() throws Exception { - //given - Login login = getNewLogin(); - String keyword = "네트워크"; - KeywordSearchResponse keywordSearch = getKeywordSearch(login, keyword); - - //expected - mockMvc.perform(get("/lectures") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("keyword", keyword) - .queryParam("next_page_token", keywordSearch.getNextPageToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.lectures[0].lecture_code", not(keywordSearch.getLectures().get(0).getLectureCode()))); // 첫번째 반환된 페이지의 값과 다르다는 뜻입니다. - } - - @Test - @DisplayName("부적절한 pageToken을 입력하면 400에러가 발생합니다.") - void searchWithPageToken400() throws Exception { - //given - Login login = getNewLogin(); - String notValidPageToken = "thisisnotpagetoken"; - - //expected - mockMvc.perform(get("/lectures") - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("keyword", "keyword") - .queryParam("next_page_token", notValidPageToken)) - .andExpect(status().isNotFound()); - } - - @Test - @DisplayName("동영상 상세검색에 성공합니다.") - void getVideoDetail200() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = VIDEO_CODE; - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", "false")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.lecture_title").isNotEmpty()) - .andExpect(jsonPath("$.lecture_code", is(lectureCode))) - .andExpect(jsonPath("$.thumbnail").isNotEmpty()); - } - - @Test - @DisplayName("재생목록 상세검색에 성공합니다.") - void getPlaylistDetail200() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = PLAYLIST_CODE; - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", "true")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.lecture_title").isNotEmpty()) - .andExpect(jsonPath("$.lecture_code", is(lectureCode))) - .andExpect(jsonPath("$.thumbnail").isNotEmpty()); - } - - @Test - @DisplayName("부적절한 영상 코드를 입력하면 not found error가 발생합니다.") - void shouldReturnNotFoundErrorInvalidVideoCode() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = "부적절한영상코드"; - String isPlaylist = "false"; - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", isPlaylist)) - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.message", is("입력한 lectureId에 해당하는 영상이 없습니다."))); - } - - @Test - @DisplayName("부적절한 재생목록 코드를 입력하면 not found error가 발생합니다.") - void shouldReturnNotFoundErrorInvalidPlaylistCode() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = "부적절한재생목록코드"; - String isPlaylist = "true"; - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", isPlaylist)) - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.message", is("입력한 lectureId에 해당하는 영상이 없습니다."))); - } - - @Test - @DisplayName("이미 등록한 강의는 registered가 true입니다.") - void RegisteredTrueLecture() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = "PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC"; - Boolean isPlaylist = true; - //registerLecture(login, lectureCode); // 강의를 등록합니다. - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", String.valueOf(isPlaylist))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.lectureTitle").isNotEmpty()) - .andExpect(jsonPath("$.lectureCode", is(lectureCode))) - .andExpect(jsonPath("$.isRegistered", is("true"))); - } - - @Test - @DisplayName("index_only = true일 경우 목차정보만 반환합니다.") - void indexOnlyResponse200() throws Exception { - //given - Login login = getNewLogin(); - String isPlaylist = "true"; - String indexOnly = "true"; - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", PLAYLIST_CODE) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", isPlaylist) - .queryParam("index_only", indexOnly)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.index_list.length()", is(PLAYLIST_VIDEO_COUNT))); - } - - @Test - @DisplayName("review_only = true일 경우 후기정보만 반환합니다.") - void reviewOnlyResponse200() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = "PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC"; - String isPlaylist = "true"; - String reviewOnly = "true"; - String reviewOffset = "3"; - String reviewLimit = "5"; - int reviewCount = 8; - //registerReview(lectureCode, reviewCount); - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", isPlaylist) - .queryParam("review_only", reviewOnly) - .queryParam("review_offset", reviewOffset) - .queryParam("review_limit", reviewLimit)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.reviews[0].index", is(reviewOffset + 1))) - .andExpect(jsonPath("$.reviews.length()", is(Integer.parseInt(reviewLimit)))); - - } - - @Test - @DisplayName("review_only와 index_only가 모두 true일 경우 400에러가 발생합니다.") - void twoOnlyParamTrue400() throws Exception { - //given - Login login = getNewLogin(); - String lectureCode = "PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC"; - String indexOnly = "true"; - String reviewOnly = "true"; - - //expected - mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", "true") - .queryParam("review_only", reviewOnly) - .queryParam("index_only", indexOnly)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.message", is("reviewOnly와 indexOnly를 동시에 true로 설정할 수 없습니다."))); - } - - @Test - @DisplayName("유저 ID를 통해 추천 강의 5개를 불러옵니다.") - void getRecommendations() throws Exception { - //given - Login login = getNewLogin(); - - //expected - mockMvc.perform(get("/lectures/recommendations") - .header("Authorization", login.getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.recommendations").isArray()) - .andDo(print()); - } -} diff --git a/src/test/java/com/m9d/sroom/lecture/service/LectureResponseServiceTest.java b/src/test/java/com/m9d/sroom/lecture/service/LectureResponseServiceTest.java deleted file mode 100644 index ad596b0f..00000000 --- a/src/test/java/com/m9d/sroom/lecture/service/LectureResponseServiceTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.m9d.sroom.lecture.service; - -import com.m9d.sroom.common.entity.PlaylistEntity; -import com.m9d.sroom.common.entity.VideoEntity; -import com.m9d.sroom.recommendation.dto.RecommendLecture; -import com.m9d.sroom.common.entity.MemberEntity; -import com.m9d.sroom.util.ServiceTest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class LectureResponseServiceTest extends ServiceTest { - - @Test - @DisplayName("평점이 높은 순으로 비디오를 불러옵니다.") - void getTopRatedVideoTest() { - //given - saveVideo("code1", 100, "channel1", "thumbnail url", 0, 0, "title1", " ", 1500); - saveVideo("code2", 321, "channel1", "thumbnail url", 1470, 300, "title2", " ", 300); - saveVideo("code3", 12, "channel2", "thumbnail url", 900, 200, "title3", " ", 300000); - - //when - List topRatedVideos = videoService.getTopRatedVideos(3); - - //then - Assertions.assertEquals("title2", topRatedVideos.get(0).getTitle()); - Assertions.assertEquals("title3", topRatedVideos.get(1).getTitle()); - Assertions.assertEquals("title1", topRatedVideos.get(2).getTitle()); - } - - @Test - @DisplayName("평점이 높은 순으로 플레이리스트를 불러옵니다.") - void getTopRatedPlaylistTest() { - //given - savePlaylist("code1", 100, "channel1", "thumbnail url", 100, 150, "title1"); - savePlaylist("code2", 100, "channel1", "thumbnail url", 700, 150, "title2"); - savePlaylist("code3", 100, "channel1", "thumbnail url", 600, 150, "title3"); - - //when - List topRatedPlaylists = playlistService.getTopRatedPlaylists(3); - - //then - Assertions.assertEquals("title2", topRatedPlaylists.get(0).getTitle()); - Assertions.assertEquals("title3", topRatedPlaylists.get(1).getTitle()); - Assertions.assertEquals("title1", topRatedPlaylists.get(2).getTitle()); - } - - @Test - @DisplayName("유저가 가장 많이 등록한 강의 채널리스트를 불러옵니다.") - void getMostEnrolledChannelTest() { - //given - MemberEntity member = getNewMember(); - Long memberId = member.getMemberId(); - - courseRepository.saveLecture(memberId, 1L, 1L, "channel1", false, 1); - courseRepository.saveLecture(memberId, 1L, 2L, "channel1", false, 2); - courseRepository.saveLecture(memberId, 1L, 3L, "channel1", false, 3); - - courseRepository.saveLecture(memberId, 2L, 4L, "channel2", false, 1); - courseRepository.saveLecture(memberId, 2L, 5L, "channel2", false, 2); - - courseRepository.saveLecture(memberId, 3L, 6L, "channel3", true, 1); - - //when - List mostEnrolledChannels = lectureService.getMostEnrolledChannels(memberId); - - //then - Assertions.assertEquals("channel1", mostEnrolledChannels.get(0)); - Assertions.assertEquals("channel2", mostEnrolledChannels.get(1)); - Assertions.assertEquals("channel3", mostEnrolledChannels.get(2)); - } -} diff --git a/src/test/java/com/m9d/sroom/material/MaterialControllerTest.java b/src/test/java/com/m9d/sroom/material/MaterialControllerTest.java new file mode 100644 index 00000000..bfae1788 --- /dev/null +++ b/src/test/java/com/m9d/sroom/material/MaterialControllerTest.java @@ -0,0 +1,209 @@ +package com.m9d.sroom.material; + +import com.m9d.sroom.course.dto.request.NewLecture; +import com.m9d.sroom.course.dto.response.CourseDetail; +import com.m9d.sroom.common.entity.MemberEntity; +import com.m9d.sroom.material.dto.request.FeedbackRequest; +import com.m9d.sroom.material.dto.request.SubmittedQuizRequest; +import com.m9d.sroom.material.dto.request.SummaryEditRequest; +import com.m9d.sroom.material.model.MaterialStatus; +import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.util.ControllerTest; +import com.m9d.sroom.util.TestConstant; +import com.m9d.sroom.util.constant.ContentConstant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.*; + +@Transactional +public class MaterialControllerTest extends ControllerTest { + + @Test + @DisplayName("생성이 완료된 강의자료를 받아오는데 성공합니다.") + void getMaterials200() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + + //when, then + mockMvc.perform(get("/materials/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status", is(1))) + .andExpect(jsonPath("$.total_quiz_count", is(ContentConstant.QUIZ_COUNT_LIST[0]))) + .andExpect(jsonPath("$.quizzes[0].id").isNotEmpty()) + .andExpect(jsonPath("$.quizzes[0].options", + hasSize(ContentConstant.QUIZ_OPTION_COUNT_LIST[0]))) + .andExpect(jsonPath("$.summary_brief.content").isNotEmpty()); + } + + @Test + @DisplayName("강의노트 수정을 성공합니다.") + void updateSummary() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + String summaryUpdated = "수정된 요약본!"; + + //when + mockMvc.perform(put("/materials/summaries/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString(new SummaryEditRequest(summaryUpdated)))); + + + //then + mockMvc.perform(get("/materials/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.summary_brief.is_modified", is(true))) + .andExpect(jsonPath("$.summary_brief.content", is(summaryUpdated))); + } + + @Test + @DisplayName("퀴즈 채점 결과를 저장합니다.") + void saveGradingResults() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + + List quizRequests = new ArrayList<>(); + for (int i = 0; i < ContentConstant.QUIZ_COUNT_LIST[0]; i++) { + quizRequests.add(new SubmittedQuizRequest((long) i + 1, "1", true)); + } + + //when + mockMvc.perform(post("/materials/quizzes/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString(quizRequests))); + + //then + mockMvc.perform(get("/materials/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.quizzes[0].is_submitted", is(true))) + .andExpect(jsonPath("$.quizzes[0].submitted_answer").isNotEmpty()) + .andDo(print()); + } + + @Test + @DisplayName("오답노트 등록에 성공합니다.") + void scrapQuiz() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + + List quizRequests = new ArrayList<>(); + for (int i = 0; i < ContentConstant.QUIZ_COUNT_LIST[0]; i++) { + quizRequests.add(new SubmittedQuizRequest((long) i + 1, "1", true)); + } + + //when + mockMvc.perform(post("/materials/quizzes/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString(quizRequests))); + mockMvc.perform(put("/materials/quizzes/{courseQuizId}/scrap", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())); + + //then + mockMvc.perform(get("/materials/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.quizzes[0].is_scrapped", is(true))) + .andDo(print()); + } + + @Test + @DisplayName("pdf 변환을 위한 강의자료 불러오기 성공") + void getMaterials4Pdf() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + + //when, then + mockMvc.perform(get("/courses/materials/{courseId}", 1) + .header("Authorization", login.getAccessToken())) + .andExpect(jsonPath("$.status", is(MaterialStatus.CREATED.getValue()))) + .andExpect(jsonPath("$.materials[0].video_title", is(ContentConstant.VIDEO_TITLE))) + .andExpect(jsonPath("$.materials[0].summary_brief.content").isNotEmpty()) + .andExpect(jsonPath("$.materials[0].quizzes[0].question").isNotEmpty()) + .andExpect(jsonPath("$.materials[0].quizzes[0].options[0]").isNotEmpty()) + .andDo(print()); + } + + @Test + @DisplayName("강의노트 사용자 피드백이 성공합니다.") + void feedbackSummary() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + String feedback = "{\"is_satisfactory\": true}"; + + //when + mockMvc.perform(post("/materials/{materialId}/feedback", 1) + .header("Authorization", login.getAccessToken()) + .contentType(MediaType.APPLICATION_JSON) + .param("type", "summary") + .content(feedback)); + + //then + mockMvc.perform(get("/materials/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.summary_brief.feedback_info.has_feedback", is(true))) + .andExpect(jsonPath("$.summary_brief.feedback_info.satisfactory", + is(true))) + .andDo(print()); + } + + @Test + @DisplayName("퀴즈 사용자 피드백이 성공합니다.") + void feedbackQuiz() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + String feedback = "{\"is_satisfactory\": false}"; + + //when + mockMvc.perform(post("/materials/{materialId}/feedback", 1) + .header("Authorization", login.getAccessToken()) + .contentType(MediaType.APPLICATION_JSON) + .param("type", "quiz") + .content(feedback)); + + //then + mockMvc.perform(get("/materials/{courseVideoId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.quizzes[0].feedback_info.has_feedback", is(true))) + .andExpect(jsonPath("$.quizzes[0].feedback_info.satisfactory", + is(false))) + .andDo(print()); + } +} diff --git a/src/test/java/com/m9d/sroom/material/controller/MaterialControllerTest.java b/src/test/java/com/m9d/sroom/material/controller/MaterialControllerTest.java deleted file mode 100644 index b3caba81..00000000 --- a/src/test/java/com/m9d/sroom/material/controller/MaterialControllerTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.m9d.sroom.material.controller; - -import com.m9d.sroom.course.dto.response.CourseDetail; -import com.m9d.sroom.common.entity.MemberEntity; -import com.m9d.sroom.util.ControllerTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.http.MediaType; -import org.springframework.transaction.annotation.Transactional; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.hamcrest.Matchers.*; - -@Transactional -public class MaterialControllerTest extends ControllerTest { - - @Test - @DisplayName("생성이 완료된 강의자료를 받아오는데 성공합니다.") - void getMaterials200() throws Exception { - //given - MemberEntity member = getNewMember(); - CourseDetail courseDetail = registerNewVideo(member.getMemberId(), VIDEO_CODE); - Long videoId = courseDetail.getLastViewVideo().getVideoId(); - insertSummaryAndQuizzes(courseDetail.getCourseId(), videoId); - - //expected - mockMvc.perform(get("/materials/lectures/{videoId}", videoId) - .contentType(MediaType.APPLICATION_JSON) - .header("Authrization", getNewLogin(member).getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.status", is(1))) - .andExpect(jsonPath("$.total_quiz_count", is(3))) - .andExpect(jsonPath("$.quizzes[0].type", is(1))) - .andExpect(jsonPath("$.quizzes[1].type", is(2))) - .andExpect(jsonPath("$.quizzes[2].type", is(3))) - .andExpect(jsonPath("$.quizzes[0].select_option_5").isNotEmpty()) - .andExpect(jsonPath("$.summary.is_modified", is(false))) - .andExpect(jsonPath("$.summary.content").isNotEmpty()); - } - - @Test - @DisplayName("생성이 미완료된 강의자료의 경우 status = 0 입니다.") - void getMaterials202() throws Exception { - //given - MemberEntity member = getNewMember(); - CourseDetail courseDetail = registerNewVideo(member.getMemberId(), VIDEO_CODE); - Long videoId = courseDetail.getLastViewVideo().getVideoId(); - - //expected - mockMvc.perform(get("/materials/lectures/{videoId}", videoId) - .contentType(MediaType.APPLICATION_JSON) - .header("Authrization", getNewLogin(member).getAccessToken())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.status", is(0))); - } -} diff --git a/src/test/java/com/m9d/sroom/member/controller/MemberControllerTest.java b/src/test/java/com/m9d/sroom/member/MemberControllerTest.java similarity index 73% rename from src/test/java/com/m9d/sroom/member/controller/MemberControllerTest.java rename to src/test/java/com/m9d/sroom/member/MemberControllerTest.java index 798a98bc..f67b2204 100644 --- a/src/test/java/com/m9d/sroom/member/controller/MemberControllerTest.java +++ b/src/test/java/com/m9d/sroom/member/MemberControllerTest.java @@ -1,19 +1,24 @@ -package com.m9d.sroom.member.controller; +package com.m9d.sroom.member; +import com.m9d.sroom.common.entity.MemberEntity; import com.m9d.sroom.member.dto.request.GoogleIdKey; +import com.m9d.sroom.member.dto.request.NameUpdateRequest; import com.m9d.sroom.member.dto.request.RefreshToken; +import com.m9d.sroom.member.dto.response.Login; import com.m9d.sroom.util.ControllerTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.Map; +import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @Transactional public class MemberControllerTest extends ControllerTest { @@ -49,4 +54,23 @@ void refresh401() throws Exception { .content(refreshTokenJson)) .andExpect(status().isUnauthorized()); } + + @Test + @DisplayName("프로필 이름이 수정됩니다.") + void updateMemberName() throws Exception { + //given + Login login = getNewLogin(); + + //when + String newProfile = "수정된 멤버 닉네임"; + NameUpdateRequest nameUpdateRequest = new NameUpdateRequest(newProfile); + + //then + mockMvc.perform(put("/members/profile") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString(nameUpdateRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name", is(newProfile))); + } } diff --git a/src/test/java/com/m9d/sroom/member/MemberServiceTest.java b/src/test/java/com/m9d/sroom/member/MemberServiceTest.java new file mode 100644 index 00000000..a19838aa --- /dev/null +++ b/src/test/java/com/m9d/sroom/member/MemberServiceTest.java @@ -0,0 +1,146 @@ +package com.m9d.sroom.member; + +import com.m9d.sroom.common.entity.MemberEntity; +import com.m9d.sroom.member.dto.request.RefreshToken; +import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.member.exception.MemberNotMatchException; +import com.m9d.sroom.member.exception.RefreshRenewedException; +import com.m9d.sroom.util.ServiceTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Transactional +public class MemberServiceTest extends ServiceTest { + + @Autowired + private MemberService memberService; + + + @Test + @DisplayName("신규 회원을 생성합니다.") + void createNewMember200() { + //given + UUID uuid = UUID.randomUUID(); + String memberCode = uuid.toString(); + + + //when + memberService.findOrCreateMember(memberCode); + String actualResult = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM MEMBER WHERE MEMBER_CODE = ?", String.class, memberCode); + + + //then + assertThat(actualResult).isEqualTo("1"); + } + +// @Test +// @DisplayName("로그인 성공 시 회원정보에 맞는 Login 객체를 반환합니다.") +// void returnLoginResponse200() { +// //given +// MemberEntity member = getNewMember(); +// +// //when +// Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); +// Long expectedExpirationTime = System.currentTimeMillis() / 1000 + jwtUtil.ACCESS_TOKEN_EXPIRATION_PERIOD / 1000; +// Long delta = Math.abs(expectedExpirationTime - login.getAccessExpiresAt()); +// +// //then +// assertTrue(delta < 5); +// assertThat(login.getName()).isEqualTo(member.getMemberName()); +// } +// +// @Test +// @DisplayName("올바른 refresh token을 입력하면 갱신에 성공합니다.") +// void renewRefreshToken200() { +// //given +// MemberEntity member = getNewMember(); +// Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); +// +// //when +// Login reLogin = memberService.verifyRefreshTokenAndReturnLogin( +// member.getMemberId(), +// RefreshToken.builder().refreshToken(login.getRefreshToken()).build()); +// +// //then +// assertNotEquals(member.getRefreshToken(), reLogin.getRefreshToken()); +// } +// +// @Test +// @DisplayName("재로그인되면 access token이 갱신됩니다.") +// void verifyRefreshToken200() throws InterruptedException { +// //given +// MemberEntity member = getNewMember(); +// Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); +// RefreshToken refreshToken = RefreshToken.builder() +// .refreshToken(login.getRefreshToken()) +// .build(); +// +// //when +// Thread.sleep(2000); +// Login reLogin = memberService.verifyRefreshTokenAndReturnLogin(member.getMemberId(), refreshToken); +// +// //then +// Assertions.assertNotEquals(login.getAccessToken(), reLogin.getAccessToken()); +// } +// +// @Test +// @DisplayName("refreshToken과 accessToken의 member정보가 다르면 400에러가 발생합니다.") +// void memberNotMatchAccessAndRefresh400() { +// //given +// MemberEntity member1 = getNewMember(); +// +// MemberEntity member2 = getNewMember(); +// Login login2 = memberService.generateLogin(member2, (String) idToken.getPayload().get("picture")); +// +// //when +// Throwable exception = null; +// try { +// memberService.verifyRefreshTokenAndReturnLogin( +// member1.getMemberId(), +// RefreshToken.builder() +// .refreshToken(login2.getRefreshToken()) +// .build()); +// } catch (Throwable e) { +// exception = e; +// } +// +// //then +// Assertions.assertNotNull(exception); +// Assertions.assertTrue(exception instanceof MemberNotMatchException); +// } +// +// @Test +// @DisplayName("저장된 refresh token이 아니라면 401에러가 발생합니다.") +// void refreshRenew401() throws InterruptedException { +// //given +// MemberEntity member = getNewMember(); +// Login loginFirst = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); +// +// //when +// Throwable exception = null; +// Thread.sleep(2000); +// Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); //2초 뒤 재로그인,refresh token이 갱신되어 새로 저장됩니다. +// try { +// memberService.verifyRefreshTokenAndReturnLogin( +// member.getMemberId(), +// RefreshToken.builder() +// .refreshToken(loginFirst.getRefreshToken()) +// .build()); +// } catch (Throwable e) { +// exception = e; +// } +// +// //then +// Assertions.assertNotNull(exception); +// Assertions.assertTrue(exception instanceof RefreshRenewedException); +// } +} diff --git a/src/test/java/com/m9d/sroom/member/service/MemberServiceTest.java b/src/test/java/com/m9d/sroom/member/service/MemberServiceTest.java deleted file mode 100644 index 29b986d2..00000000 --- a/src/test/java/com/m9d/sroom/member/service/MemberServiceTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.m9d.sroom.member.service; - -import com.m9d.sroom.common.entity.MemberEntity; -import com.m9d.sroom.member.MemberService; -import com.m9d.sroom.member.dto.request.RefreshToken; -import com.m9d.sroom.member.dto.response.Login; -import com.m9d.sroom.member.exception.MemberNotMatchException; -import com.m9d.sroom.member.exception.RefreshRenewedException; -import com.m9d.sroom.util.ServiceTest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -import java.util.UUID; - -import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Transactional -public class MemberServiceTest extends ServiceTest { - - @Autowired - private MemberService memberService; - - - @Test - @DisplayName("신규 회원을 생성합니다.") - void createNewMember200() { - //given - UUID uuid = UUID.randomUUID(); - String memberCode = uuid.toString(); - - - //when - memberService.findOrCreateMemberByMemberCode(memberCode); - String actualResult = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM MEMBER WHERE MEMBER_CODE = ?", String.class, memberCode); - - - //then - assertThat(actualResult).isEqualTo("1"); - } - - @Test - @DisplayName("로그인 성공 시 회원정보에 맞는 Login 객체를 반환합니다.") - void returnLoginResponse200() { - //given - MemberEntity member = getNewMember(); - - //when - Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); - Long expectedExpirationTime = System.currentTimeMillis() / 1000 + jwtUtil.ACCESS_TOKEN_EXPIRATION_PERIOD / 1000; - Long delta = Math.abs(expectedExpirationTime - login.getAccessExpiresAt()); - - //then - assertTrue(delta < 5); - assertThat(login.getName()).isEqualTo(member.getMemberName()); - } - - @Test - @DisplayName("올바른 refresh token을 입력하면 갱신에 성공합니다.") - void renewRefreshToken200() { - //given - MemberEntity member = getNewMember(); - Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); - - //when - Login reLogin = memberService.verifyRefreshTokenAndReturnLogin( - member.getMemberId(), - RefreshToken.builder().refreshToken(login.getRefreshToken()).build()); - - //then - assertNotEquals(member.getRefreshToken(), reLogin.getRefreshToken()); - } - - @Test - @DisplayName("재로그인되면 access token이 갱신됩니다.") - void verifyRefreshToken200() throws InterruptedException { - //given - MemberEntity member = getNewMember(); - Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); - RefreshToken refreshToken = RefreshToken.builder() - .refreshToken(login.getRefreshToken()) - .build(); - - //when - Thread.sleep(2000); - Login reLogin = memberService.verifyRefreshTokenAndReturnLogin(member.getMemberId(), refreshToken); - - //then - Assertions.assertNotEquals(login.getAccessToken(), reLogin.getAccessToken()); - } - - @Test - @DisplayName("refreshToken과 accessToken의 member정보가 다르면 400에러가 발생합니다.") - void memberNotMatchAccessAndRefresh400() { - //given - MemberEntity member1 = getNewMember(); - - MemberEntity member2 = getNewMember(); - Login login2 = memberService.generateLogin(member2, (String) idToken.getPayload().get("picture")); - - //when - Throwable exception = null; - try { - memberService.verifyRefreshTokenAndReturnLogin( - member1.getMemberId(), - RefreshToken.builder() - .refreshToken(login2.getRefreshToken()) - .build()); - } catch (Throwable e) { - exception = e; - } - - //then - Assertions.assertNotNull(exception); - Assertions.assertTrue(exception instanceof MemberNotMatchException); - } - - @Test - @DisplayName("저장된 refresh token이 아니라면 401에러가 발생합니다.") - void refreshRenew401() throws InterruptedException { - //given - MemberEntity member = getNewMember(); - Login loginFirst = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); - - //when - Throwable exception = null; - Thread.sleep(2000); - Login login = memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); //2초 뒤 재로그인,refresh token이 갱신되어 새로 저장됩니다. - try { - memberService.verifyRefreshTokenAndReturnLogin( - member.getMemberId(), - RefreshToken.builder() - .refreshToken(loginFirst.getRefreshToken()) - .build()); - } catch (Throwable e) { - exception = e; - } - - //then - Assertions.assertNotNull(exception); - Assertions.assertTrue(exception instanceof RefreshRenewedException); - } - - -} diff --git a/src/test/java/com/m9d/sroom/recommendation/RecommendationControllerTest.java b/src/test/java/com/m9d/sroom/recommendation/RecommendationControllerTest.java new file mode 100644 index 00000000..19913db6 --- /dev/null +++ b/src/test/java/com/m9d/sroom/recommendation/RecommendationControllerTest.java @@ -0,0 +1,43 @@ +package com.m9d.sroom.recommendation; + +import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.util.ControllerTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Transactional; + +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@Transactional +public class RecommendationControllerTest extends ControllerTest { + + @Autowired + private RecommendationScheduler recommendationScheduler; + + @Test + @DisplayName("추천 리스트를 불러옵니다.") + void Recommendation200() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + recommendationScheduler.temporalUpdateDomainRecommendations(); + + //expected + mockMvc.perform(get("/lectures/recommendations") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.general_recommendations", hasSize(4))) + .andExpect(jsonPath("$.channel_recommendations", hasSize(4))) + .andExpect(jsonPath("$.society_recommendations", hasSize(1))) + .andExpect(jsonPath("$.science_recommendations", hasSize(1))) + .andExpect(jsonPath("$.economic_recommendations", hasSize(1))) + .andExpect(jsonPath("$.tech_recommendations", hasSize(1))); + } +} diff --git a/src/test/java/com/m9d/sroom/review/ReviewControllerTest.java b/src/test/java/com/m9d/sroom/review/ReviewControllerTest.java new file mode 100644 index 00000000..3bd3fcfd --- /dev/null +++ b/src/test/java/com/m9d/sroom/review/ReviewControllerTest.java @@ -0,0 +1,75 @@ +package com.m9d.sroom.review; + +import com.m9d.sroom.common.entity.ReviewEntity; +import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.review.dto.ReviewSubmitRequest; +import com.m9d.sroom.util.ControllerTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; + +import static com.m9d.sroom.util.constant.ContentConstant.VIDEO_TITLE; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +public class ReviewControllerTest extends ControllerTest { + + @Test + @DisplayName("리뷰 작성에 성공합니다.") + public void postReview200() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); + + int submittedRating = 4; + String reviewContent = "재미있어요"; + + //when + MvcResult mvcResult = mockMvc.perform(post("/reviews/lectures/{lecture_id}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .content(objectMapper.writeValueAsString( + ReviewSubmitRequest.createReview(submittedRating,reviewContent)))) + .andReturn(); + + //then + String getReviewQuery = "SELECT * FROM REVIEW"; + ReviewEntity review = jdbcTemplate.queryForObject(getReviewQuery, ReviewEntity.getRowmapper()); + Assertions.assertEquals(submittedRating, review.getSubmittedRating()); + Assertions.assertEquals(reviewContent, review.getContent()); + } + + @Test + @DisplayName("코스 리뷰 작성 가능 강의 리스트를 조회합니다.") + public void lectureList200() throws Exception { + //given + Login login = getNewLogin(); + saveContents(); + enrollNewCourseWithVideo(login); //course 1 + enrollNewCourseWithVideo(login); //course 2 + + + //when + String updateMaxDurationQuery = "UPDATE COURSEVIDEO SET max_duration = ? WHERE course_video_id = ?"; + jdbcTemplate.update(updateMaxDurationQuery, 3000, 1); //course1 만 50% 이상 수강 + + //expected + mockMvc.perform(get("/reviews/courses/{course_id}", 1) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(jsonPath("$.lectures[0].title").value(VIDEO_TITLE)) + .andExpect(jsonPath("$.lectures[0].is_review_allowed").value(true)); + + mockMvc.perform(get("/reviews/courses/{course_id}", 2) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(jsonPath("$.lectures[0].title").value(VIDEO_TITLE)) + .andExpect(jsonPath("$.lectures[0].is_review_allowed").value(false)); + } +} diff --git a/src/test/java/com/m9d/sroom/search/SearchControllerTest.java b/src/test/java/com/m9d/sroom/search/SearchControllerTest.java new file mode 100644 index 00000000..aec9df8a --- /dev/null +++ b/src/test/java/com/m9d/sroom/search/SearchControllerTest.java @@ -0,0 +1,254 @@ +package com.m9d.sroom.search; + +import com.m9d.sroom.search.dto.response.KeywordSearchResponse; +import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.search.exception.LectureNotFoundException; +import com.m9d.sroom.util.ControllerTest; +import com.m9d.sroom.util.constant.ContentConstant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.*; + +public class SearchControllerTest extends ControllerTest { + + +// @Test +// @DisplayName("입력된 limit 개수만큼 검색 결과가 반환됩니다.") +// void keywordSearch200() throws Exception { +// //given +// Login login = getNewLogin(); +// String keyword = "네트워크"; +// String limit = String.valueOf(7); +// +// //expected +// mockMvc.perform(get("/lectures") +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("keyword", keyword) +// .queryParam("limit", limit)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.result_per_page").value(Integer.parseInt(limit))); +// } + + @Test + @DisplayName("keyword를 입력하지 않은 경우 400에러가 발생합니다.") + void keywordSearch400() throws Exception { + //given + Login login = getNewLogin(); + + //expected + mockMvc.perform(get("/lectures") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken())) + .andExpect(status().isBadRequest()); + } + +// @Test +// @DisplayName("pageToken을 입력하면 다음 페이지가 반환됩니다.") +// void searchWithPageToken200() throws Exception { +// //given +// Login login = getNewLogin(); +// String keyword = "네트워크"; +// KeywordSearchResponse keywordSearch = getKeywordSearch(login, keyword); +// +// //expected +// mockMvc.perform(get("/lectures") +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("keyword", keyword) +// .queryParam("next_page_token", keywordSearch.getNextPageToken())) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.lectures[0].lecture_code", not(keywordSearch.getLectures().get(0).getLectureCode()))); // 첫번째 반환된 페이지의 값과 다르다는 뜻입니다. +// } + + @Test + @DisplayName("부적절한 pageToken을 입력하면 400에러가 발생합니다.") + void searchWithPageToken400() throws Exception { + //given + Login login = getNewLogin(); + String notValidPageToken = "thisisnotpagetoken"; + + //expected + mockMvc.perform(get("/lectures") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("keyword", "keyword") + .queryParam("next_page_token", notValidPageToken)) + .andExpect(status().isNotFound()); + } + +// @Test +// @DisplayName("동영상 상세검색에 성공합니다.") +// void getVideoDetail200() throws Exception { +// //given +// Login login = getNewLogin(); +// String lectureCode = ContentConstant.VIDEO_CODE_LIST[0]; +// +// //expected +// mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("is_playlist", "false")) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.lecture_title").isNotEmpty()) +// .andExpect(jsonPath("$.lecture_code", is(lectureCode))) +// .andExpect(jsonPath("$.thumbnail").isNotEmpty()); +// } + +// @Test +// @DisplayName("재생목록 상세검색에 성공합니다.") +// void getPlaylistDetail200() throws Exception { +// //given +// Login login = getNewLogin(); +// String lectureCode = ContentConstant.PLAYLIST_CODE; +// +// //expected +// mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("is_playlist", "true")) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.lecture_title").isNotEmpty()) +// .andExpect(jsonPath("$.lecture_code", is(lectureCode))) +// .andExpect(jsonPath("$.thumbnail").isNotEmpty()); +// } + + @Test + @DisplayName("부적절한 영상 코드를 입력하면 not found error가 발생합니다.") + void shouldReturnNotFoundErrorInvalidVideoCode() throws Exception { + //given + Login login = getNewLogin(); + String lectureCode = "부적절한영상코드"; + String isPlaylist = "false"; + + //expected + mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("is_playlist", isPlaylist)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.message", is(LectureNotFoundException.MESSAGE))); + } + + @Test + @DisplayName("부적절한 재생목록 코드를 입력하면 not found error가 발생합니다.") + void shouldReturnNotFoundErrorInvalidPlaylistCode() throws Exception { + //given + Login login = getNewLogin(); + String lectureCode = "부적절한재생목록코드"; + String isPlaylist = "true"; + + //expected + mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("is_playlist", isPlaylist)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.message", is(LectureNotFoundException.MESSAGE))); + } + +// @Test +// @DisplayName("이미 등록한 강의는 registered가 true입니다.") +// void RegisteredTrueLecture() throws Exception { +// //given +// Login login = getNewLogin(); +// String lectureCode = "PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC"; +// Boolean isPlaylist = true; +// //registerLecture(login, lectureCode); // 강의를 등록합니다. +// +// //expected +// mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("is_playlist", String.valueOf(isPlaylist))) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.lectureTitle").isNotEmpty()) +// .andExpect(jsonPath("$.lectureCode", is(lectureCode))) +// .andExpect(jsonPath("$.isRegistered", is("true"))); +// } + +// @Test +// @DisplayName("index_only = true일 경우 목차정보만 반환합니다.") +// void indexOnlyResponse200() throws Exception { +// //given +// Login login = getNewLogin(); +// String isPlaylist = "true"; +// String indexOnly = "true"; +// +// //expected +// mockMvc.perform(get("/lectures/{lectureCode}", ContentConstant.PLAYLIST_CODE) +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("is_playlist", isPlaylist) +// .queryParam("index_only", indexOnly)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.index_list.length()", is(ContentConstant.VIDEO_CODE_LIST.length))); +// } + +// @Test +// @DisplayName("review_only = true일 경우 후기정보만 반환합니다.") +// void reviewOnlyResponse200() throws Exception { +// //given +// Login login = getNewLogin(); +// String lectureCode = "PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC"; +// String isPlaylist = "true"; +// String reviewOnly = "true"; +// String reviewOffset = "3"; +// String reviewLimit = "5"; +// int reviewCount = 8; +// //registerReview(lectureCode, reviewCount); +// +// //expected +// mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) +// .contentType(MediaType.APPLICATION_JSON) +// .header("Authorization", login.getAccessToken()) +// .queryParam("is_playlist", isPlaylist) +// .queryParam("review_only", reviewOnly) +// .queryParam("review_offset", reviewOffset) +// .queryParam("review_limit", reviewLimit)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.reviews[0].index", is(reviewOffset + 1))) +// .andExpect(jsonPath("$.reviews.length()", is(Integer.parseInt(reviewLimit)))); +// +// } + + @Test + @DisplayName("review_only와 index_only가 모두 true일 경우 400에러가 발생합니다.") + void twoOnlyParamTrue400() throws Exception { + //given + Login login = getNewLogin(); + String lectureCode = "PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC"; + String indexOnly = "true"; + String reviewOnly = "true"; + + //expected + mockMvc.perform(get("/lectures/{lectureCode}", lectureCode) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("is_playlist", "true") + .queryParam("review_only", reviewOnly) + .queryParam("index_only", indexOnly)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.message", is(LectureNotFoundException.MESSAGE))); + } + +// @Test +// @DisplayName("유저 ID를 통해 추천 강의 5개를 불러옵니다.") +// void getRecommendations() throws Exception { +// //given +// Login login = getNewLogin(); +// +// //expected +// mockMvc.perform(get("/lectures/recommendations") +// .header("Authorization", login.getAccessToken())) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.recommendations").isArray()) +// .andDo(print()); +// } +} diff --git a/src/test/java/com/m9d/sroom/util/ControllerTest.java b/src/test/java/com/m9d/sroom/util/ControllerTest.java index 1ad43861..9cdb64b9 100644 --- a/src/test/java/com/m9d/sroom/util/ControllerTest.java +++ b/src/test/java/com/m9d/sroom/util/ControllerTest.java @@ -1,39 +1,70 @@ package com.m9d.sroom.util; +import com.m9d.sroom.course.CourseService; import com.m9d.sroom.course.dto.request.NewLecture; import com.m9d.sroom.course.dto.response.CourseDetail; import com.m9d.sroom.course.dto.response.EnrolledCourseInfo; +import com.m9d.sroom.member.MemberService; import com.m9d.sroom.search.dto.response.KeywordSearchResponse; -import com.m9d.sroom.search.dto.response.PlaylistDetail; import com.m9d.sroom.common.entity.MemberEntity; import com.m9d.sroom.member.dto.response.Login; +import com.m9d.sroom.util.constant.ContentConstant; +import com.m9d.sroom.video.VideoService; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MvcResult; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.UUID; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - +@Slf4j public class ControllerTest extends SroomTest { - protected Login getNewLogin() { - MemberEntity member = getNewMember(); - return memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); + @Autowired + protected MemberService memberService; + + @Autowired + protected JwtUtil jwtUtil; + + @Autowired + protected CourseService courseService; + + @Autowired + protected VideoService videoService; + + @Test + @DisplayName("AWS health test를 통과합니다.") + void healthTest() throws Exception { + + //expected + mockMvc.perform(get("/") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); } - protected Login getNewLogin(MemberEntity member) { - return memberService.generateLogin(member, (String) idToken.getPayload().get("picture")); + protected MemberEntity getNewMemberEntity() { + String memberCode = UUID.randomUUID().toString(); + return memberService.findOrCreateMember(memberCode); } - protected MemberEntity getNewMember() { - UUID uuid = UUID.randomUUID(); + protected Login getNewLogin() { + MemberEntity memberEntity = getNewMemberEntity(); + return memberService.generateLogin(memberEntity, TestConstant.MEMBER_PROFILE); + } - String memberCode = uuid.toString(); - return memberService.findOrCreateMemberByMemberCode(memberCode); + protected Login getNewLogin(MemberEntity member) { + return memberService.generateLogin(member, TestConstant.MEMBER_PROFILE); } protected KeywordSearchResponse getKeywordSearch(Login login, String keyword) throws Exception { @@ -48,57 +79,37 @@ protected KeywordSearchResponse getKeywordSearch(Login login, String keyword) th return objectMapper.readValue(jsonContent, KeywordSearchResponse.class); } - protected PlaylistDetail getPlaylistDetail(Login login, String playlistCode) throws Exception { - MockHttpServletResponse response = mockMvc.perform(get("/lectures/{lectureCode}", playlistCode) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", login.getAccessToken()) - .queryParam("is_playlist", "true")) - .andExpect(status().isOk()) - .andReturn().getResponse(); - - String jsonContent = response.getContentAsString(); - System.out.println(jsonContent); - return objectMapper.readValue(jsonContent, PlaylistDetail.class); + protected void enrollNewCourseWithVideo(Login login) throws Exception { + mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "false") + .content(objectMapper.writeValueAsString( + NewLecture.createWithoutSchedule(ContentConstant.VIDEO_CODE_LIST[0])))); } - protected Long enrollNewCourseWithVideo(Login login) { - Object obj = jwtUtil.getDetailFromToken(login.getAccessToken()).get("memberId"); - Long memberId = Long.valueOf((String) obj); - - NewLecture newLecture = NewLecture.builder() - .lectureCode(VIDEO_CODE) - .build(); - EnrolledCourseInfo courseId = courseService.enrollCourse(memberId, newLecture, false); - return courseId.getCourseId(); + protected void enrollNewCourseWithPlaylistSchedule(Login login, NewLecture newLecture) throws Exception { + mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "true") + .content(objectMapper.writeValueAsString(newLecture))); } - protected Long enrollNewCourseWithPlaylistSchedule(Login login) { - Long memberId = Long.valueOf((String) jwtUtil.getDetailFromToken(login.getAccessToken()).get("memberId")); - - NewLecture newLecture = NewLecture.builder() - .lectureCode(PLAYLIST_CODE) - .scheduling(new ArrayList<>(Arrays.asList(2, 1, 2))) - .dailyTargetTime(20) - .expectedEndDate("2023-09-22") - .build(); - EnrolledCourseInfo courseInfo = courseService.enrollCourse(memberId, newLecture, true); - return courseInfo.getCourseId(); - } - - protected Long enrollNewCourseWithPlaylist(Login login) { - Long memberId = Long.valueOf((String) jwtUtil.getDetailFromToken(login.getAccessToken()).get("memberId")); - - NewLecture newLecture = NewLecture.builder() - .lectureCode(PLAYLIST_CODE) - .build(); - EnrolledCourseInfo courseInfo = courseService.enrollCourse(memberId, newLecture, false); - return courseInfo.getCourseId(); + protected void enrollNewCourseWithPlaylist(Login login) throws Exception { + mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", login.getAccessToken()) + .queryParam("use_schedule", "false") + .content(objectMapper.writeValueAsString( + NewLecture.createWithoutSchedule(ContentConstant.PLAYLIST_CODE)))); } protected CourseDetail registerNewVideo(Long memberId, String videoCode) { - EnrolledCourseInfo courseInfo = courseService.enrollCourse(memberId, getNewLectureWithoutSchedule(videoCode), false); + EnrolledCourseInfo courseInfo = courseService.enroll(memberId, getNewLectureWithoutSchedule(videoCode), + false, null); - CourseDetail courseDetail = courseService.getCourseDetail(memberId, courseInfo.getCourseId()); + CourseDetail courseDetail = courseService.getCourseDetail(courseInfo.getCourseId()); return courseDetail; } @@ -109,6 +120,55 @@ protected NewLecture getNewLectureWithoutSchedule(String lectureCode) { .build(); } - protected void insertSummaryAndQuizzes(Long courseId, Long videoId) { + protected void saveContents() { + savePlaylist(); + saveVideoList(); + savePlaylistVideo(); + saveSummaryList(); + saveQuizList(); + saveQuizOptionList(); + saveRecommend(); + } + + private void savePlaylist() { + jdbcTemplate.update(ContentConstant.PLAYLIST_INSERT_SQL, ContentConstant.PLAYLIST_CODE, + new Timestamp(System.currentTimeMillis() - 60 * 1000)); + } + + private void saveVideoList() { + for (int i = 0; i < ContentConstant.VIDEO_CODE_LIST.length; i++) { + jdbcTemplate.update(ContentConstant.VIDEO_INSERT_SQL[i], ContentConstant.VIDEO_CODE_LIST[i], + new Timestamp(System.currentTimeMillis())); + } + } + + private void savePlaylistVideo() { + for (int i = 0; i < ContentConstant.VIDEO_CODE_LIST.length; i++) { + jdbcTemplate.update(ContentConstant.PLAYLIST_VIDEO_INSERT_SQL, 1, i + 1, i); + } + } + + private void saveSummaryList() { + for (String summaryInsertSql : ContentConstant.SUMMARY_INSERT_SQL) { + jdbcTemplate.update(summaryInsertSql); + } + } + + private void saveQuizList() { + for (String quizInsertSql : ContentConstant.QUIZ_INSERT_SQL) { + jdbcTemplate.update(quizInsertSql); + } + } + + private void saveQuizOptionList() { + for (String quizOptionInsertSql : ContentConstant.QUIZ_OPTION_INSERT_SQL) { + jdbcTemplate.update(quizOptionInsertSql); + } + } + + private void saveRecommend() { + for (int i = 1; i <= 4; i++) { + jdbcTemplate.update(ContentConstant.RECOMMEND_INSERT_SQL, i); + } } } diff --git a/src/test/java/com/m9d/sroom/util/ServiceTest.java b/src/test/java/com/m9d/sroom/util/ServiceTest.java index 52af7a61..f5a9371f 100644 --- a/src/test/java/com/m9d/sroom/util/ServiceTest.java +++ b/src/test/java/com/m9d/sroom/util/ServiceTest.java @@ -1,17 +1,30 @@ package com.m9d.sroom.util; import com.m9d.sroom.common.entity.MemberEntity; +import com.m9d.sroom.course.CourseService; +import com.m9d.sroom.member.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; import java.sql.Timestamp; import java.util.UUID; -public class ServiceTest extends SroomTest{ +public class ServiceTest extends SroomTest { + + @Autowired + protected MemberService memberService; + + @Autowired + protected JdbcTemplate jdbcTemplate; + + @Autowired + protected CourseService courseService; protected MemberEntity getNewMember() { UUID uuid = UUID.randomUUID(); String memberCode = uuid.toString(); - return memberService.findOrCreateMemberByMemberCode(memberCode); + return memberService.findOrCreateMember(memberCode); } protected void saveVideo(String videoCode, int duration, String channel, String thumbnail, int accumulated_rating, int reviewCount, String title, String license, int viewCount) { diff --git a/src/test/java/com/m9d/sroom/util/SroomTest.java b/src/test/java/com/m9d/sroom/util/SroomTest.java index 100038b3..7a0ca892 100644 --- a/src/test/java/com/m9d/sroom/util/SroomTest.java +++ b/src/test/java/com/m9d/sroom/util/SroomTest.java @@ -2,73 +2,97 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.m9d.sroom.course.CourseService; -import com.m9d.sroom.dashboard.DashboardService; -import com.m9d.sroom.lecture.LectureService; -import com.m9d.sroom.member.MemberService; -import com.m9d.sroom.playlist.PlaylistService; -import com.m9d.sroom.search.SearchService; -import com.m9d.sroom.video.VideoService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; @SpringBootTest +@Disabled @AutoConfigureMockMvc +@Transactional @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class SroomTest { - @Autowired protected MockMvc mockMvc; @Autowired protected ObjectMapper objectMapper; - @Autowired - protected MemberService memberService; - - @Autowired - protected CourseService courseService; - - @Autowired - protected DashboardService dashboardService; - - @Autowired - protected JwtUtil jwtUtil; - @Autowired protected JdbcTemplate jdbcTemplate; - @Autowired - protected SearchService searchService; - - @Autowired - protected LectureService lectureService; - - @Autowired - protected VideoService videoService; - - @Autowired - protected PlaylistService playlistService; - - @Autowired - protected DateUtil dateUtil; - - @Autowired - protected YoutubeApi youtubeApi; - - @Autowired - protected YoutubeService youtubeService; - - protected static final String VIDEO_CODE = "Z9dvM7qgN9s"; - protected static final String PLAYLIST_CODE = "PLv2d7VI9OotQUUsgcTBHuy5vJkSgzVHL0"; - protected static final int PLAYLIST_VIDEO_COUNT = 5; - protected static final String LECTURETITLE = "손경식의 브이로그 - 소마센터 출퇴근편"; - protected static final String CHANNEL = "수경식"; - protected static final String LECTURE_DESCRIPTION = "잠실을 거쳐 선릉 소마센터까지"; - protected static final String THUMBNAIL = "https://i.ytimg.com/vi/Pc6n6HgWU5c/mqdefault.jpg"; + public static final String[] TABLE_NAMES = { + "COURSE", + "COURSE_DAILY_LOG", + "COURSEQUIZ", + "COURSEVIDEO", + "LECTURE", + "MATERIAL_FEEDBACK", + "MEMBER", + "PLAYLIST", + "PLAYLISTVIDEO", + "QUIZ", + "QUIZ_OPTION", + "RECOMMEND", + "REVIEW", + "SUMMARY", + "VIDEO" + }; + + public static final String[] TABLE_IDS = { + "course_id", + "course_daily_log_id", + "course_quiz_id", + "course_video_id", + "lecture_id", + "feedback_id", + "member_id", + "playlist_id", + "playlist_video_id", + "quiz_id", + "quiz_option_id", + "recommend_id", + "review_id", + "summary_id", + "video_id" + }; + + @AfterEach + public void afterEach() { + truncateTables(getTruncateQueries(), getResetIdQueries()); + } + + private List getTruncateQueries() { + List queries = new ArrayList<>(); + for (String tableName : TABLE_NAMES) { + queries.add("TRUNCATE TABLE " + tableName + ";"); + } + return queries; + } + + private List getResetIdQueries() { + List queries = new ArrayList<>(); + for (int i = 0; i < TABLE_NAMES.length; i++) { + queries.add("ALTER TABLE " + TABLE_NAMES[i] + " ALTER COLUMN " + TABLE_IDS[i] + " RESTART WITH 1;"); + } + return queries; + } + + private void truncateTables(final List truncateQueries, final List resetIdQueries) { + jdbcTemplate.execute("SET REFERENTIAL_INTEGRITY FALSE"); + truncateQueries.forEach(jdbcTemplate::execute); + resetIdQueries.forEach(jdbcTemplate::execute); + jdbcTemplate.execute("SET REFERENTIAL_INTEGRITY TRUE"); + } } diff --git a/src/test/java/com/m9d/sroom/util/TestConstant.java b/src/test/java/com/m9d/sroom/util/TestConstant.java new file mode 100644 index 00000000..c7ad8582 --- /dev/null +++ b/src/test/java/com/m9d/sroom/util/TestConstant.java @@ -0,0 +1,18 @@ +package com.m9d.sroom.util; + +import com.m9d.sroom.util.constant.ContentConstant; + +import java.sql.Timestamp; + +public class TestConstant { + + public static final String MEMBER_PROFILE = "멤버닉네임"; + public static final String PLAYLIST_CHANNEL = "정보처리기사 전문가 손경식"; + public static final String PLAYLIST_DESCRIPTION = "잠실을 거쳐 선릉 소마센터까지"; + public static final String THUMBNAIL = "https://i.ytimg.com/vi/Pc6n6HgWU5c/mqdefault.jpg"; + public static final String SUMMARY_CONTENT = "강의노트 원본"; + public static final Long VIDEO_VIEW_COUNT = 100000L; + public static final Timestamp PUBLISHED_AT = new Timestamp(System.currentTimeMillis() - 10 * 60 * 60 * 1000); + public static final String LANGUAGE_KO = "KO"; + public static final String LICENSE_YOUTUBE = "youtube"; +} diff --git a/src/test/java/com/m9d/sroom/util/constant/ContentConstant.java b/src/test/java/com/m9d/sroom/util/constant/ContentConstant.java new file mode 100644 index 00000000..4ef031c3 --- /dev/null +++ b/src/test/java/com/m9d/sroom/util/constant/ContentConstant.java @@ -0,0 +1,323 @@ +package com.m9d.sroom.util.constant; + +public class ContentConstant { + + public static final String PLAYLIST_CODE = "PLniy99c_7ZfpDRzBXv1ryJbW-KnHGp1Az"; + + public static final String PLAYLIST_INFO = "{\n" + + " \"pageInfo\": {\n" + + " \"totalResults\": 1,\n" + + " \"resultsPerPage\": 5\n" + + " },\n" + + " \"items\": [\n" + + " {\n" + + " \"id\": \"PLniy99c_7ZfpDRzBXv1ryJbW-KnHGp1Az\",\n" + + " \"snippet\": {\n" + + " \"publishedAt\": \"2022-04-28T13:28:58Z\",\n" + + " \"title\": \"정보처리기사 실기 기출해설\",\n" + + " \"description\": \"\",\n" + + " \"thumbnails\": {\n" + + " \"default\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/default.jpg\",\n" + + " \"width\": 120,\n" + + " \"height\": 90\n" + + " },\n" + + " \"medium\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/mqdefault.jpg\",\n" + + " \"width\": 320,\n" + + " \"height\": 180\n" + + " },\n" + + " \"high\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/hqdefault.jpg\",\n" + + " \"width\": 480,\n" + + " \"height\": 360\n" + + " },\n" + + " \"standard\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/sddefault.jpg\",\n" + + " \"width\": 640,\n" + + " \"height\": 480\n" + + " },\n" + + " \"maxres\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/maxresdefault.jpg\",\n" + + " \"width\": 1280,\n" + + " \"height\": 720\n" + + " }\n" + + " },\n" + + " \"channelTitle\": \"흥달쌤\"\n" + + " },\n" + + " \"status\": {\n" + + " \"privacyStatus\": \"public\"\n" + + " },\n" + + " \"contentDetails\": {\n" + + " \"itemCount\": 4\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + public static final String PLAYLIST_VIDEO_INFO = "{\n" + + " \"items\": [\n" + + " {\n" + + " \"snippet\": {\n" + + " \"title\": \"정보처리기사 실기 2020년 1회 - 기출해설특강\",\n" + + " \"thumbnails\": {\n" + + " \"default\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/default.jpg\",\n" + + " \"width\": 120,\n" + + " \"height\": 90\n" + + " },\n" + + " \"medium\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/mqdefault.jpg\",\n" + + " \"width\": 320,\n" + + " \"height\": 180\n" + + " },\n" + + " \"high\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/hqdefault.jpg\",\n" + + " \"width\": 480,\n" + + " \"height\": 360\n" + + " },\n" + + " \"standard\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/sddefault.jpg\",\n" + + " \"width\": 640,\n" + + " \"height\": 480\n" + + " },\n" + + " \"maxres\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/S7l1qX0WhqE/maxresdefault.jpg\",\n" + + " \"width\": 1280,\n" + + " \"height\": 720\n" + + " }\n" + + " },\n" + + " \"position\": 0,\n" + + " \"resourceId\": {\n" + + " \"kind\": \"youtube#video\",\n" + + " \"videoId\": \"S7l1qX0WhqE\"\n" + + " }\n" + + " },\n" + + " \"status\": {\n" + + " \"privacyStatus\": \"public\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"snippet\": {\n" + + " \"title\": \"정보처리기사 실기 2020년 2회 - 기출해설특강\",\n" + + " \"thumbnails\": {\n" + + " \"default\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/lv4-n_s5AvI/default.jpg\",\n" + + " \"width\": 120,\n" + + " \"height\": 90\n" + + " },\n" + + " \"medium\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/lv4-n_s5AvI/mqdefault.jpg\",\n" + + " \"width\": 320,\n" + + " \"height\": 180\n" + + " },\n" + + " \"high\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/lv4-n_s5AvI/hqdefault.jpg\",\n" + + " \"width\": 480,\n" + + " \"height\": 360\n" + + " },\n" + + " \"standard\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/lv4-n_s5AvI/sddefault.jpg\",\n" + + " \"width\": 640,\n" + + " \"height\": 480\n" + + " },\n" + + " \"maxres\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/lv4-n_s5AvI/maxresdefault.jpg\",\n" + + " \"width\": 1280,\n" + + " \"height\": 720\n" + + " }\n" + + " },\n" + + " \"position\": 1,\n" + + " \"resourceId\": {\n" + + " \"kind\": \"youtube#video\",\n" + + " \"videoId\": \"lv4-n_s5AvI\"\n" + + " }\n" + + " },\n" + + " \"status\": {\n" + + " \"privacyStatus\": \"public\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"snippet\": {\n" + + " \"title\": \"정보처리기사 실기 2020년 3회 - 기출해설특강\",\n" + + " \"thumbnails\": {\n" + + " \"default\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/eBu65lFAyjo/default.jpg\",\n" + + " \"width\": 120,\n" + + " \"height\": 90\n" + + " },\n" + + " \"medium\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/eBu65lFAyjo/mqdefault.jpg\",\n" + + " \"width\": 320,\n" + + " \"height\": 180\n" + + " },\n" + + " \"high\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/eBu65lFAyjo/hqdefault.jpg\",\n" + + " \"width\": 480,\n" + + " \"height\": 360\n" + + " },\n" + + " \"standard\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/eBu65lFAyjo/sddefault.jpg\",\n" + + " \"width\": 640,\n" + + " \"height\": 480\n" + + " },\n" + + " \"maxres\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/eBu65lFAyjo/maxresdefault.jpg\",\n" + + " \"width\": 1280,\n" + + " \"height\": 720\n" + + " }\n" + + " },\n" + + " \"position\": 2,\n" + + " \"resourceId\": {\n" + + " \"kind\": \"youtube#video\",\n" + + " \"videoId\": \"eBu65lFAyjo\"\n" + + " }\n" + + " },\n" + + " \"status\": {\n" + + " \"privacyStatus\": \"public\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"snippet\": {\n" + + " \"title\": \"정보처리기사 실기 2020년 4회 - 기출해설특강\",\n" + + " \"thumbnails\": {\n" + + " \"default\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/Kc1xRWy3-rs/default.jpg\",\n" + + " \"width\": 120,\n" + + " \"height\": 90\n" + + " },\n" + + " \"medium\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/Kc1xRWy3-rs/mqdefault.jpg\",\n" + + " \"width\": 320,\n" + + " \"height\": 180\n" + + " },\n" + + " \"high\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/Kc1xRWy3-rs/hqdefault.jpg\",\n" + + " \"width\": 480,\n" + + " \"height\": 360\n" + + " },\n" + + " \"standard\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/Kc1xRWy3-rs/sddefault.jpg\",\n" + + " \"width\": 640,\n" + + " \"height\": 480\n" + + " },\n" + + " \"maxres\": {\n" + + " \"url\": \"https://i.ytimg.com/vi/Kc1xRWy3-rs/maxresdefault.jpg\",\n" + + " \"width\": 1280,\n" + + " \"height\": 720\n" + + " }\n" + + " },\n" + + " \"position\": 3,\n" + + " \"resourceId\": {\n" + + " \"kind\": \"youtube#video\",\n" + + " \"videoId\": \"Kc1xRWy3-rs\"\n" + + " }\n" + + " },\n" + + " \"status\": {\n" + + " \"privacyStatus\": \"public\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"pageInfo\": {\n" + + " \"totalResults\": 4,\n" + + " \"resultsPerPage\": 50\n" + + " }\n" + + "}"; + + + public static final String VIDEO_TITLE = "정보처리기사 실기 2020년 1회 - 기출해설특강"; + public static final String PLAYLIST_TITLE = "정보처리기사 실기 기출해설"; + public static final String[] VIDEO_CODE_LIST = + {"S7l1qX0WhqE", "lv4-n_s5AvI", "eBu65lFAyjo", "Kc1xRWy3-rs"}; + + public static final String[] VIDEO_INFO = { + "{ \"items\": [ { \"id\": \"S7l1qX0WhqE\", \"snippet\": { \"publishedAt\": \"2022-07-22T12:49:31Z\", \"title\": \"정보처리기사 실기 2021년 3회 - 기출해설특강\", \"description\": \"정보처리 기사 2021년 3회 실기 기출해설 특강입니다.\\n\\n시험 끝나고 나시면 카페에서 문제복원 꼭 참여해주세요~\\n감사합니다. ^^\\n\\n#흥달 #흥달쌤 #정처기 #정보처리 #정보처리기사 #정보처리기출 #JAVA #C언어 #전산직 #계리직 #프로그래밍 #기출문제풀이\", \"thumbnails\": { \"default\": { \"url\": \"https://i.ytimg.com/vi/RAlRlle0hxg/default.jpg\", \"width\": 120, \"height\": 90 }, \"medium\": { \"url\": \"https://i.ytimg.com/vi/RAlRlle0hxg/mqdefault.jpg\", \"width\": 320, \"height\": 180 }, \"high\": { \"url\": \"https://i.ytimg.com/vi/RAlRlle0hxg/hqdefault.jpg\", \"width\": 480, \"height\": 360 }, \"standard\": { \"url\": \"https://i.ytimg.com/vi/RAlRlle0hxg/sddefault.jpg\", \"width\": 640, \"height\": 480 }, \"maxres\": { \"url\": \"https://i.ytimg.com/vi/RAlRlle0hxg/maxresdefault.jpg\", \"width\": 1280, \"height\": 720 } }, \"channelTitle\": \"흥달쌤\" }, \"contentDetails\": { \"duration\": \"PT1H13M5S\", \"dimension\": \"2d\" }, \"status\": { \"uploadStatus\": \"processed\", \"privacyStatus\": \"public\", \"license\": \"youtube\", \"embeddable\": true }, \"statistics\": { \"viewCount\": \"24789\" } } ], \"pageInfo\": { \"totalResults\": 1 }}", + "{ \"items\": [ { \"id\": \"lv4-n_s5AvI\", \"snippet\": { \"publishedAt\": \"2022-05-03T13:42:08Z\", \"title\": \"정보처리기사 실기 2021년 1회 - 기출해설특강\", \"description\": \"정보처리 기사 2021년 1회 실기 기출해설 특강입니다.\\n\\n시험 끝나고 나시면 카페에서 문제복원 꼭 참여해주세요~\\n감사합니다. ^^\\n\\n#흥달 #흥달쌤 #정처기 #정보처리 #정보처리기사 #정보처리기출 #JAVA #C언어 #전산직 #계리직 #프로그래밍 #기출문제풀이\", \"thumbnails\": { \"default\": { \"url\": \"https://i.ytimg.com/vi/HNU0A_JI_nY/default.jpg\", \"width\": 120, \"height\": 90 }, \"medium\": { \"url\": \"https://i.ytimg.com/vi/HNU0A_JI_nY/mqdefault.jpg\", \"width\": 320, \"height\": 180 }, \"high\": { \"url\": \"https://i.ytimg.com/vi/HNU0A_JI_nY/hqdefault.jpg\", \"width\": 480, \"height\": 360 }, \"standard\": { \"url\": \"https://i.ytimg.com/vi/HNU0A_JI_nY/sddefault.jpg\", \"width\": 640, \"height\": 480 }, \"maxres\": { \"url\": \"https://i.ytimg.com/vi/HNU0A_JI_nY/maxresdefault.jpg\", \"width\": 1280, \"height\": 720 } }, \"channelTitle\": \"흥달쌤\" }, \"contentDetails\": { \"duration\": \"PT50M1S\", \"dimension\": \"2d\" }, \"status\": { \"uploadStatus\": \"processed\", \"privacyStatus\": \"public\", \"license\": \"youtube\", \"embeddable\": true }, \"statistics\": { \"viewCount\": \"24275\" } } ], \"pageInfo\": { \"totalResults\": 1 }}", + "{ \"items\": [ { \"id\": \"eBu65lFAyjo\", \"snippet\": { \"publishedAt\": \"2023-04-18T17:19:22Z\", \"title\": \"정보처리기사 실기 2022년 3회 - 기출해설특강(1) 1~12번 문항\", \"description\": \"정보처리 기사 2022년 3회 실기 기출해설 특강입니다.\\n\\n시험 끝나고 나시면 카페에서 문제복원 꼭 참여해주세요~\\n감사합니다. ^^\\n\\n#흥달 #흥달쌤 #정처기 #정보처리 #정보처리기사 #정보처리기출 #JAVA #C언어 #전산직 #계리직 #프로그래밍 #기출문제풀이\", \"thumbnails\": { \"default\": { \"url\": \"https://i.ytimg.com/vi/8se_hUeN7XI/default.jpg\", \"width\": 120, \"height\": 90 }, \"medium\": { \"url\": \"https://i.ytimg.com/vi/8se_hUeN7XI/mqdefault.jpg\", \"width\": 320, \"height\": 180 }, \"high\": { \"url\": \"https://i.ytimg.com/vi/8se_hUeN7XI/hqdefault.jpg\", \"width\": 480, \"height\": 360 }, \"standard\": { \"url\": \"https://i.ytimg.com/vi/8se_hUeN7XI/sddefault.jpg\", \"width\": 640, \"height\": 480 }, \"maxres\": { \"url\": \"https://i.ytimg.com/vi/8se_hUeN7XI/maxresdefault.jpg\", \"width\": 1280, \"height\": 720 } }, \"channelTitle\": \"흥달쌤\" }, \"contentDetails\": { \"duration\": \"PT38M2S\", \"dimension\": \"2d\" }, \"status\": { \"uploadStatus\": \"processed\", \"privacyStatus\": \"public\", \"license\": \"youtube\", \"embeddable\": true }, \"statistics\": { \"viewCount\": \"19207\" } } ], \"pageInfo\": { \"totalResults\": 1 }}", + "{ \"items\": [ { \"id\": \"Kc1xRWy3-rs\", \"snippet\": { \"publishedAt\": \"2022-10-12T17:47:56Z\", \"title\": \"정보처리기사 실기 2022년 2회 - 기출해설특강(2) 11~20번 문항\", \"description\": \"정보처리 기사 2022년 2회 실기 기출해설 특강입니다.\\n\\n시험 끝나고 나시면 카페에서 문제복원 꼭 참여해주세요~\\n감사합니다. ^^\\n\\n#흥달 #흥달쌤 #정처기 #정보처리 #정보처리기사 #정보처리기출 #JAVA #C언어 #전산직 #계리직 #프로그래밍 #기출문제풀이\", \"thumbnails\": { \"default\": { \"url\": \"https://i.ytimg.com/vi/jeCbqu1XfcA/default.jpg\", \"width\": 120, \"height\": 90 }, \"medium\": { \"url\": \"https://i.ytimg.com/vi/jeCbqu1XfcA/mqdefault.jpg\", \"width\": 320, \"height\": 180 }, \"high\": { \"url\": \"https://i.ytimg.com/vi/jeCbqu1XfcA/hqdefault.jpg\", \"width\": 480, \"height\": 360 }, \"standard\": { \"url\": \"https://i.ytimg.com/vi/jeCbqu1XfcA/sddefault.jpg\", \"width\": 640, \"height\": 480 }, \"maxres\": { \"url\": \"https://i.ytimg.com/vi/jeCbqu1XfcA/maxresdefault.jpg\", \"width\": 1280, \"height\": 720 } }, \"channelTitle\": \"흥달쌤\" }, \"contentDetails\": { \"duration\": \"PT1H1M9S\", \"dimension\": \"2d\" }, \"status\": { \"uploadStatus\": \"processed\", \"privacyStatus\": \"public\", \"license\": \"youtube\", \"embeddable\": true }, \"statistics\": { \"viewCount\": \"22655\" } } ], \"pageInfo\": { \"totalResults\": 1 }}" + }; + + public static final String PLAYLIST_INSERT_SQL = "INSERT INTO PLAYLIST (playlist_code, channel, thumbnail, accumulated_rating, review_count, is_available, description, duration, updated_at, title, published_at, video_count, average_rating) VALUES (?, '흥달쌤', 'https://i.ytimg.com/vi/S7l1qX0WhqE/maxresdefault.jpg', 0, 0, 1, '', 46737, ?, '정보처리기사 실기 기출해설', '2022-04-28 22:28:58', 13, 0);"; + public static final String PLAYLIST_VIDEO_INSERT_SQL = "INSERT INTO PLAYLISTVIDEO (playlist_id, video_id, video_index) VALUES (?, ?, ?);"; + public static final String[] VIDEO_INSERT_SQL = { + "INSERT INTO VIDEO (video_code, duration, channel, thumbnail, accumulated_rating, review_count, summary_id, is_available, description, chapter_usage, title, language, license, updated_at, view_count, published_at, membership, material_status, average_rating) VALUES (?, 4130, '흥달쌤', 'https://i.ytimg.com/vi/S7l1qX0WhqE/maxresdefault.jpg', 0, 0, 1, 1, '정보처리 기사 2020년 1회 실기 기출해설 특강입니다', 0, '정보처리기사 실기 2020년 1회 - 기출해설특강', 'unknown', 'youtube', ?, 49742, '2022-04-28 23:20:46', 0, 1, 0);", + "INSERT INTO VIDEO (video_code, duration, channel, thumbnail, accumulated_rating, review_count, summary_id, is_available, description, chapter_usage, title, language, license, updated_at, view_count, published_at, membership, material_status, average_rating) VALUES (?, 3779, '흥달쌤', 'https://i.ytimg.com/vi/lv4-n_s5AvI/maxresdefault.jpg', 0, 0, 2, 1, '정보처리 기사 2020년 2회 실기 기출해설 특강입니다', 0, '정보처리기사 실기 2020년 2회 - 기출해설특강', 'unknown', 'youtube', ?, 19381, '2022-04-30 11:27:48', 0, 1, 0);", + "INSERT INTO VIDEO (video_code, duration, channel, thumbnail, accumulated_rating, review_count, summary_id, is_available, description, chapter_usage, title, language, license, updated_at, view_count, published_at, membership, material_status, average_rating) VALUES (?, 3449, '흥달쌤', 'https://i.ytimg.com/vi/eBu65lFAyjo/maxresdefault.jpg', 0, 0, 3, 1, '정보처리 기사 2020년 3회 실기 기출해설 특강입니다.', 0, '정보처리기사 실기 2020년 3회 - 기출해설특강', 'unknown', 'youtube', ?, 19323, '2022-05-01 23:30:09', 0, 1, 0);", + "INSERT INTO VIDEO (video_code, duration, channel, thumbnail, accumulated_rating, review_count, summary_id, is_available, description, chapter_usage, title, language, license, updated_at, view_count, published_at, membership, material_status, average_rating) VALUES (?, 3995, '흥달쌤', 'https://i.ytimg.com/vi/Kc1xRWy3-rs/maxresdefault.jpg', 0, 0, 4, 1, '정보처리 기사 2020년 4회 실기 기출해설 특강입니다.', 0, '정보처리기사 실기 2020년 4회 - 기출해설특강', 'unknown', 'youtube', ?, 20087, '2022-05-03 02:02:12', 0, 1, 0);" + }; + + public static final int[] VIDEO_DURATION_LIST = {4130, 3779, 3449, 3995}; + public static final int[] QUIZ_COUNT_LIST = {2, 2, 2, 2}; + public static final int[] QUIZ_OPTION_COUNT_LIST = {3, 3, 3, 3}; + + public static final String[] SUMMARY_INSERT_SQL = { + "INSERT INTO SUMMARY (video_id, content, updated_time, is_modified, positive_feedback_count, negative_feedback_count) VALUES (1, '\n" + + "### 데이터베이스 정규화 및 성능 향상\n" + + "- 개요: 이번 시간에는 2020년 도일 기출 문제를 풀어보고, 데이터베이스 모델링 과정 중 비정규와 정규화의 개념을 간략히 설명하였습니다.\n" + + "- 정규화 과정: 개념 설계, 논리 설계, 물리 설계로 나눠지며, 각 과정에서는 데이터베이스 설계를 진행하고 정규화를 수행합니다.\n" + + "- 성능 향상을 위한 반정규화: 데이터베이스의 성능을 향상시키기 위해 정규화에 위배되는 반정규화 기법을 사용합니다.\n" + + "- 이상 현상과 반정규화: 중복된 데이터가 존재하거나 이상한 현상이 생기는 경우, 반정규화를 통해 이상 현상을 없앱니다.\n" + + "- 정리: 데이터베이스 정규화를 통해 데이터를 체계적으로 모델링하고, 성능 향상을 위해 반정규화를 수행합니다.\n" + + " ', '2023-12-06 00:14:30', 0, 0, 0);", + "INSERT INTO SUMMARY (video_id, content, updated_time, is_modified, positive_feedback_count, negative_feedback_count) VALUES (2, '\n" + + "### 음악 정수 횡단 3\n" + + "이번 시간에는 2020년 이외 실기 기출 문제에 대해 진행해 보도록 하겠습니다. 실기 이외의 정도부터는 이제 합격률이 조금씩 올라가기 시작했습니다. 이외의 경우는 5%로 조금씩 증가하고 있으며, 20%에서 30%를 유지할 것으로 예상됩니다. 자격증은 보통 20%에서 30%를 유지해야 하는데, 정보처리기사 시험 2는 어떤 출제가 될지 알 수 없습니다. 교재에 나오지 않은 것들이 많이 있어 어떤 것들이 출제될지 모르겠지만, 열심히 준비해주셨으면 좋겠습니다.\n" + + " ', '2023-12-06 00:09:55', 0, 0, 0);", + "INSERT INTO SUMMARY (video_id, content, updated_time, is_modified, positive_feedback_count, negative_feedback_count) VALUES (3, '\n" + + "### 정석 인가 쓰입니다\n" + + "오늘은 2020년 3회 실기 기출 문제들을 풀어보도록 하겠습니다. 이번 강의는 소방헬기 소리가 마이크로 들어갈 수 있어서 조금 양해를 부탁드리겠습니다. 형상 통제에 대해 간략히 설명하자면, 형상 관리 절차는 형상식별, 형상통제, 형상감사, 형상기록으로 이루어져 있습니다. 형상 통제는 변경 요청 사항을 검토하고 승인하여 현재 베이스 라인에 반영하는 과정을 말합니다. 형상 감사는 형상의 정확성을 보는 것이고, 형상 기록은 기록하는 것입니다. 형상 통제는 형상 변경 요청을 승인하고 현재 베이스 라인에 반영하는 과정을 말합니다. 형상 관리 절차는 형상식별, 형상통제, 형상감사, 형상기록으로 이루어져 있으며, 이는 실무에서 중요한 개념입니다.\n" + + " ', '2023-12-06 00:13:55', 0, 0, 0);", + "INSERT INTO SUMMARY (video_id, content, updated_time, is_modified, positive_feedback_count, negative_feedback_count) VALUES (4, '<\n" + + "### 스크립트 정리\n" + + "\n" + + "- 2020년 4회 실기 기출문제를 풀기 위해서 공부하고 있다.\n" + + "- 코로나 검사를 했는데 음성이어서 안심했지만 여전히 걱정이 있다.\n" + + "- 목이 아프고, 시험이 끝날 때까지 건강하게 유지하고 싶다.\n" + + "- 백신을 맞으면 안 좋을 것 같아서 아직 맞지 않았다.\n" + + "- 주변 사람들도 조심하고, 걱정이 많다.\n" + + " ', '2023-12-06 00:13:45', 0, 0, 0);" + }; + + public static final String[] QUIZ_INSERT_SQL = { + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (1, 1, '데이터베이스 정규화의 개념은 무엇인가요?', null, 2, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (1, 1, '반정규화는 왜 사용하나요?', null, 2, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (2, 1, '정보처리기사 시험의 합격률은 어느 정도인가요?', null, 3, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (2, 1, '소프트웨어 개발 방법론 중 요구사항을 계속해서 받아들여 개발해나가는 방식은 무엇인가요?', null, 2, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (3, 1, '형상 통제는 무엇을 의미하나요?', null, 2, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (3, 1, 'EAI 구축 유형 중 어떤 유형들이 있나요?', null, 1, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (66, 1, '디자인 패턴은 무엇인가요?', null, 1, 0, 0);", + "INSERT INTO QUIZ (video_id, type, question, subjective_answer, choice_answer, positive_feedback_count, negative_feedback_count) VALUES (66, 1, '패키지 다이어그램은 무엇을 나타내는 것인가요?', null, 1, 0, 0);", + }; + public static final String[] QUIZ_OPTION_INSERT_SQL = { + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (1, '데이터베이스 설계를 진행하는 과정', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (1, '데이터를 체계적으로 모델링하는 과정', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (1, '데이터베이스의 성능을 향상시키는 과정', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (2, '데이터를 체계적으로 모델링하기 위해', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (2, '데이터베이스의 성능을 향상시키기 위해', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (2, '데이터의 중복을 없애기 위해', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (3, '20%', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (3, '30%', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (3, '5%', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (3, '1%', 4);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (4, '폭포수 모형', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (4, '애자일 방법론', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (4, '프로토타입 모형', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (4, '나선형 모형', 4);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (5, '형상식별, 형상통제, 형상감사, 형상기록', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (5, '형상 변경 요청을 승인하고 현재 베이스 라인에 반영하는 과정', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (5, '형상의 정확성을 보는 것', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (5, '기록하는 것', 4);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (6, 'P2P (Point-to-Point), 허브 앤 스포크', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (6, '허브 앤 스포크, 메시지 버스, 하이브리드', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (6, '메시지 버스, 하이브리드', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (6, 'P2P, 메시지 버스', 4);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (7, '객체지향 프로그래밍 설계를 할 때 사용하는 패턴들의 모음', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (7, '시스템을 구성하는 클래스들의 관계를 나타내는 다이어그램', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (7, '시스템의 기능과 사용자 사이의 관계를 나타내는 다이어그램', 3);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (8, '비슷한 것들을 묶어 놓은 것', 1);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (8, '객체지향 프로그래밍 설계를 할 때 사용하는 패턴들의 모음', 2);", + "INSERT INTO QUIZ_OPTION (quiz_id, option_text, option_index) VALUES (8, '시스템의 기능과 사용자 사이의 관계를 나타내는 다이어그램', 3);" + }; + + public static final String RECOMMEND_INSERT_SQL = "INSERT INTO RECOMMEND (source_code, is_playlist, domain) VALUES ('lv4-n_s5AvI', false, ?)"; +} diff --git a/src/test/java/com/m9d/sroom/util/constant/MaterialConstant.java b/src/test/java/com/m9d/sroom/util/constant/MaterialConstant.java new file mode 100644 index 00000000..2d21626f --- /dev/null +++ b/src/test/java/com/m9d/sroom/util/constant/MaterialConstant.java @@ -0,0 +1,911 @@ +package com.m9d.sroom.util.constant; + +public class MaterialConstant { + public static final String MATERIAL_STR_FROM_AI_SERVER = "{\n" + + " \"results\": [\n" + + " {\n" + + " \"video_id\": \"S7l1qX0WhqE\",\n" + + " \"is_valid\": 1,\n" + + " \"summary\": \"\\n### 데이터베이스 정규화 및 성능 향상\\n- 개요: 이번 시간에는 2020년 도일 기출 문제를 풀어보고, 데이터베이스 모델링 과정 중 비정규와 정규화의 개념을 간략히 설명하였습니다.\\n- 정규화 과정: 개념 설계, 논리 설계, 물리 설계로 나눠지며, 각 과정에서는 데이터베이스 설계를 진행하고 정규화를 수행합니다.\\n- 성능 향상을 위한 반정규화: 데이터베이스의 성능을 향상시키기 위해 정규화에 위배되는 반정규화 기법을 사용합니다.\\n- 이상 현상과 반정규화: 중복된 데이터가 존재하거나 이상한 현상이 생기는 경우, 반정규화를 통해 이상 현상을 없앱니다.\\n- 정리: 데이터베이스 정규화를 통해 데이터를 체계적으로 모델링하고, 성능 향상을 위해 반정규화를 수행합니다.\",\n" + + " \"quizzes\": [\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"데이터베이스 정규화의 개념은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터베이스 설계를 진행하는 과정\",\n" + + " \"데이터를 체계적으로 모델링하는 과정\",\n" + + " \"데이터베이스의 성능을 향상시키는 과정\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"반정규화는 왜 사용하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터를 체계적으로 모델링하기 위해\",\n" + + " \"데이터베이스의 성능을 향상시키기 위해\",\n" + + " \"데이터의 중복을 없애기 위해\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"정규화는 무엇을 위해 수행하는 작업인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"테이블 중복 제거\",\n" + + " \"컬럼 기반 분할\",\n" + + " \"컬럼 중복 제거\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"HTML은 어떤 종류의 마크업 언어인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"HTML\",\n" + + " \"SGML\",\n" + + " \"XML\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"HTML이란 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"마크업 언어\",\n" + + " \"프로그래밍 언어\",\n" + + " \"데이터 전송 방식\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"모듈의 독립성을 높이기 위해서는 어떤 것을 낮추고 높여야 하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"결합도\",\n" + + " \"응집도\",\n" + + " \"테스팅\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"결합도와 응집도는 무엇을 나타내는 지 알려주세요.\",\n" + + " \"quiz_select_options\": [\n" + + " \"모듈의 독립성\",\n" + + " \"모듈 간의 상호 의존 정도\",\n" + + " \"모듈 내부의 기능적인 집중 정도\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"결합도를 낮추고 응집도를 높이기 위해서는 어떻게 해야 할까요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"모듈 간의 상호 의존 정도를 높이기\",\n" + + " \"기능적인 집중 정도를 낮추기\",\n" + + " \"모듈 간의 상호 의존 정도를 낮추고 기능적인 집중 정도를 높이기\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"자기능 음식도란 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"모듈 내부에 있는 기능을 호출하는 것\",\n" + + " \"모듈 내부 기능들이 묶여 있는 것\",\n" + + " \"기능들이 순차적으로 정지하는 것\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"JSON은 무엇의 약자이며 어떤 용도로 사용되나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"Java Script Object Network, 데이터 전송에 사용됨\",\n" + + " \"Java Serialized Object Notation, 객체 직렬화에 사용됨\",\n" + + " \"JavaScript Object Notation, 구조화된 데이터 표현에 사용됨\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"셀렉트 구조에서 가져올 데이터를 선택하는 명령어는?\",\n" + + " \"quiz_select_options\": [\n" + + " \"셀렉트\",\n" + + " \"프롬\",\n" + + " \"웨어\",\n" + + " \"그룹 바이\",\n" + + " \"오더 바이\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"랜더 테이라는 공격 유형은 무엇인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"출발지 IP와 목적지 IP가 같은 패킷을 보내는 공격 방법\",\n" + + " \"수신자가 응답을 보낼 때 목적지 주소가 자기 자신이므로 계속해서 자기를 공격하는 방법\",\n" + + " \"방화벽에서 출발지와 목적지가 같은 패킷을 제거하는 방법\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"정보보안의 3요소 중 기밀성은 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터가 타인에게 노출되어서는 안 된다.\",\n" + + " \"데이터는 인가되지 않은 사람이 변경할 수 없다.\",\n" + + " \"데이터는 원하는 시점에 언제든지 사용 가능해야 한다.\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"디도스 공격은 무엇을 목적으로 하는 공격 방법인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"기밀성을 떨어뜨리기 위한 공격\",\n" + + " \"무결성을 떨어뜨리기 위한 공격\",\n" + + " \"가용성을 떨어뜨리기 위한 공격\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"계층 시켜 보는 것은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"프로토콜의 규약에 따라 데이터의 구문, 의미, 타이밍을 정의하는 것\",\n" + + " \"데이터를 암호화하는 것\",\n" + + " \"데이터를 쏠트하는 것\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"쏠트는 어떤 기법인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"암호에 고유한 값을 추가하여 레인보우테이블 공격을 피할 수 있게 하는 기법\",\n" + + " \"데이터를 계층 시켜 보는 기법\",\n" + + " \"암호를 해독하는 기법\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스케줄링 알고리즘 중 선점 스케줄링과 비선점 스케줄링의 차이점은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"선점 스케줄링은 실행 중인 프로세스를 중단시킬 수 있지만, 비선점 스케줄링은 실행 중인 프로세스를 중단시킬 수 없다.\",\n" + + " \"선점 스케줄링은 우선순위에 따라 프로세스를 스케줄링하지만, 비선점 스케줄링은 실행 시간을 기준으로 프로세스를 스케줄링한다.\",\n" + + " \"선점 스케줄링은 멀티프로세싱 환경에서 사용되고, 비선점 스케줄링은 싱글프로세싱 환경에서 사용된다.\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"트랜잭션의 '독립성'이란 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"트랜잭션은 다른 트랜잭션의 실행 결과에 영향을 받지 않는다.\",\n" + + " \"트랜잭션은 시스템이 중단되더라도 실행 결과가 유지된다.\",\n" + + " \"트랜잭션은 모든 데이터를 일관된 상태로 유지한다.\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"전송 계층은 어떤 프로토콜을 사용하여 데이터를 전송하는 역할을 하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"TCP\",\n" + + " \"UDP\",\n" + + " \"HTTP\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"네트워크 계층에서 사용되는 프로토콜로 IP, ICMP, ARP, RARP이 있습니다. ARP 프로토콜은 어떤 역할을 하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"IP 주소를 MAC 주소로 변환해주는 역할\",\n" + + " \"MAC 주소를 IP 주소로 변환해주는 역할\",\n" + + " \"패킷을 전송하는 역할\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스크립트 기간 계산을 위한 총 라인 수는 몇 라인인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"3천 라인\",\n" + + " \"3만 라인\",\n" + + " \"3백만 라인\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"시언어로 작성된 프로그램의 정렬된 결과는 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"50, 75, 85, 95, 100\",\n" + + " \"0, 1, 2, 3\",\n" + + " \"3\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " }\n" + + " ],\n" + + " \"tokens\": 33333\n" + + " },\n" + + " {\n" + + " \"video_id\": \"Kc1xRWy3-rs\",\n" + + " \"is_valid\": 1,\n" + + " \"summary\": \"\\n### 스크립트 정리\\n\\n- 2020년 4회 실기 기출문제를 풀기 위해서 공부하고 있다.\\n- 코로나 검사를 했는데 음성이어서 안심했지만 여전히 걱정이 있다.\\n- 목이 아프고, 시험이 끝날 때까지 건강하게 유지하고 싶다.\\n- 백신을 맞으면 안 좋을 것 같아서 아직 맞지 않았다.\\n- 주변 사람들도 조심하고, 걱정이 많다.\\n- 클래스 다이어그램과 유스케이스 다이어그램을 그리는 것에 대해 설명되었다.\\n- 디자인 패턴은 객체지향 프로그래밍 설계를 할 때 사용하는 패턴들의 모음이다.\\n- 디자인 패턴은 소스 코드의 모음으로 생각할 수 있다.\\n- 패키지 다이어그램, 구조 다이어그램, 행위 다이어그램 등이 있다.\\n- 클래스 다이어그램은 시스템을 구성하는 클래스들의 관계를 나타낸다.\\n- 패키지 다이어그램은 비슷한 것들을 묶어 놓은 것이다.\\n- 컴퍼넌트 다이어그램은 컴퍼넌트 구조 사이의 관계를 표현한다.\\n- 배치 다이어그램은 실행 시스템의 물리 구조를 표현한다.\\n- 유스케이스 다이어그램은 시스템의 기능과 사용자 사이의 관계를 나타낸다.\",\n" + + " \"quizzes\": [\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"디자인 패턴은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"객체지향 프로그래밍 설계를 할 때 사용하는 패턴들의 모음\",\n" + + " \"시스템을 구성하는 클래스들의 관계를 나타내는 다이어그램\",\n" + + " \"시스템의 기능과 사용자 사이의 관계를 나타내는 다이어그램\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"패키지 다이어그램은 무엇을 나타내는 것인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"비슷한 것들을 묶어 놓은 것\",\n" + + " \"객체지향 프로그래밍 설계를 할 때 사용하는 패턴들의 모음\",\n" + + " \"시스템의 기능과 사용자 사이의 관계를 나타내는 다이어그램\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"다이어그램 통신은 무엇을 시각화하는 도구인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"상호 작용\",\n" + + " \"테스트 오라클\",\n" + + " \"테스트 기법\",\n" + + " \"SQL 문\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"테스트 오라클 중 입력 값을 기대하는 결과와 비교하기 위해 사용되는 오라클은 무엇인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"참 오라클\",\n" + + " \"샘플링 오라클\",\n" + + " \"휴리스틱 오라클\",\n" + + " \"일관성 검사 오라클\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"학과로 그룹 파일을 하는 목적은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"결과값을 확인하기 위해\",\n" + + " \"학과별로 풀 수를 알아보기 위해\",\n" + + " \"학과별로 그룹핑하기 위해\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스니핑이란 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"패킷의 내용을 엿보는 행위\",\n" + + " \"네트워크상의 통과되는 패킷들을 분석하는 행위\",\n" + + " \"네트워크상의 통신을 가로채는 행위\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"데이터베이스 정규화의 목적은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"중복을 제거하고 데이터의 일관성과 무결성을 유지하기 위함\",\n" + + " \"데이터의 안정성을 보장하기 위함\",\n" + + " \"데이터의 저장 공간을 절약하기 위함\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"제 1 정규형에 대한 설명 중 옳은 것은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"부분 함수 종속을 제거하여 핵심 속성을 한 테이블에 포함\",\n" + + " \"도메인이 원자값이 되도록 테이블을 분리\",\n" + + " \"이행 함수 종속을 제거하여 조인 종속을 이용하거나 제거\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"지연 갱신과 즉시 갱신 중 어떤 것이 변경된 내용을 바로 데이터베이스에 반영하는 기법인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"지연 갱신\",\n" + + " \"즉시 갱신\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"리드와 언두 기법은 어떤 목적을 위해 수행되어야 하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터베이스 복구\",\n" + + " \"데이터 저장\",\n" + + " \"데이터 조회\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"IPv4와 IPv6의 차이점은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"주소 변환 기술\",\n" + + " \"디스패치 타임\",\n" + + " \"블록 메이크업\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"빅데이터 솔루션은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"운영체제: 유닉스\",\n" + + " \"정형 데이터 vs 비정형 데이터\",\n" + + " \"IPv4 주소 클래스 (A, B, C)\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"빅데이터와 데이터 마이닝에 대한 설명 중 맞는 것은?\",\n" + + " \"quiz_select_options\": [\n" + + " \"빅데이터는 많은 양의 데이터로부터 가치를 추출하고 결과를 분석하는 기술이다.\",\n" + + " \"데이터 마이닝은 대규모로 저장된 데이터 안에서 체계적이고 자동적으로 통계적 규칙이나 패턴을 찾아내는 것이다.\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"데이터 웨어하우스와 데이터 마트에 대한 설명 중 맞는 것은?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터 웨어하우스는 자기 간 시스템의 데이터베이스 축전처럼 데이터를 공통 형식으로 변환하여 관리하는 큰 데이터베이스이다.\",\n" + + " \"데이터 마트는 데이터 웨어하우스의 일부분으로, 특정 사용자가 관심을 갖는 작은 규모의 데이터 웨어하우스이다.\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"네트워크 주소 변환은 무엇을 변환하는 방식인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"내부 사설 IP 주소와 외부 공인 IP 주소\",\n" + + " \"내부 사설 IP 주소와 내부 사설 IP 주소\",\n" + + " \"외부 공인 IP 주소와 외부 공인 IP 주소\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"포인터 p가 10번지를 가리키고 있다. p가 가리키는 주소부터 끝까지 출력할 때, 예상되는 출력 결과는 무엇인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"korea와 2a\",\n" + + " \"korea와 10\",\n" + + " \"korea와 10번지의 문자열\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"다음 코드의 출력 결과는 무엇인가?\\n\\nfor(int i=1; i<=9; i++) {\\n for(int j=1; j<=5; j++) {\\n System.out.print(i*j + \\\" \\\");\\n }\\n System.out.println();\\n}\",\n" + + " \"quiz_select_options\": [\n" + + " \"1 2 3 4 5\\n2 4 6 8 10\\n3 6 9 12 15\",\n" + + " \"1 1 1 1 1\\n2 2 2 2 2\\n3 3 3 3 3\",\n" + + " \"1 2 3 4 5\\n2 4 6 8 10\\n3 9 12 15 18\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"j 값이 0보다 작고 영이 아닐 때, 배열 arr에 어떤 값을 넣어야 하는지 확인해보면 무엇이 들어가는지 알 수 있습니다.\",\n" + + " \"quiz_select_options\": [\n" + + " \"0\",\n" + + " \"1\",\n" + + " \"3\",\n" + + " \"4\"\n" + + " ],\n" + + " \"answer\": 4\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"i 값이 8보다 작을 때, 다음에 들어가야 하는 값은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"2\",\n" + + " \"3\",\n" + + " \"4\",\n" + + " \"6\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"같은 경우에는 아이가 a 점 랭스 라고 했잖아요. 랭스가 같은 경우에는 무엇을 구하는 건가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"길이\",\n" + + " \"높이\",\n" + + " \"너비\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"아이 값이 0일 때, 아이플러스플러스를 해서 몇이 되나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"-1\",\n" + + " \"0\",\n" + + " \"1\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"변수 c를 사용하여 계산을 진행한 후, 일이 1보다 작거나 같으면 1이 되는 상황에서 c의 값이 어떻게 변하게 될까요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"0\",\n" + + " \"1\",\n" + + " \"2\",\n" + + " \"3\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"주어진 파이썬 프로그램의 출력 결과는 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"1, 2, 3\",\n" + + " \"1, 2, 3, 7\",\n" + + " \"1, 2, 3, 4, 5, 6, 7, 8, 9\",\n" + + " \"7, 8, 9\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " }\n" + + " ],\n" + + " \"tokens\": 31437\n" + + " },\n" + + " {\n" + + " \"video_id\": \"eBu65lFAyjo\",\n" + + " \"is_valid\": 1,\n" + + " \"summary\": \"\\n### 정석 인가 쓰입니다\\n오늘은 2020년 3회 실기 기출 문제들을 풀어보도록 하겠습니다. 이번 강의는 소방헬기 소리가 마이크로 들어갈 수 있어서 조금 양해를 부탁드리겠습니다. 형상 통제에 대해 간략히 설명하자면, 형상 관리 절차는 형상식별, 형상통제, 형상감사, 형상기록으로 이루어져 있습니다. 형상 통제는 변경 요청 사항을 검토하고 승인하여 현재 베이스 라인에 반영하는 과정을 말합니다. 형상 감사는 형상의 정확성을 보는 것이고, 형상 기록은 기록하는 것입니다. 형상 통제는 형상 변경 요청을 승인하고 현재 베이스 라인에 반영하는 과정을 말합니다. 형상 관리 절차는 형상식별, 형상통제, 형상감사, 형상기록으로 이루어져 있으며, 이는 실무에서 중요한 개념입니다.\\n\\n### EAI 구축 유형에 대한 설명\\nEAI는 기업의 어플리케이션들을 통합하는 것을 말합니다. EAI 구축 유형에는 P2P (Point-to-Point), 허브 앤 스포크, 메시지 버스, 하이브리드가 있습니다. P2P는 미들웨어를 사용하지 않고 각 어플리케이션들을 직접 연결하는 방식입니다. 허브 앤 스포크는 중앙 시스템을 통해 데이터를 전송하는 방식이며, 확장 및 유지보수가 용이합니다. 하지만 변경 시 다시 개발해야 하므로 재사용이 어렵습니다. 허브 앤 스포크는 단일 접점을 통해 데이터를 전송하는 중앙 집중적인 방식입니다. 이번 문제에서는 P2P와 허브 앤 스포크가 정답입니다.\",\n" + + " \"quizzes\": [\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"형상 통제는 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"형상식별, 형상통제, 형상감사, 형상기록\",\n" + + " \"형상 변경 요청을 승인하고 현재 베이스 라인에 반영하는 과정\",\n" + + " \"형상의 정확성을 보는 것\",\n" + + " \"기록하는 것\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"EAI 구축 유형 중 어떤 유형들이 있나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"P2P (Point-to-Point), 허브 앤 스포크\",\n" + + " \"허브 앤 스포크, 메시지 버스, 하이브리드\",\n" + + " \"메시지 버스, 하이브리드\",\n" + + " \"P2P, 메시지 버스\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"버스를 통해 제공되는 서비스의 형태는 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"메세지 박스 방식\",\n" + + " \"웹 애플리케이션\",\n" + + " \"모바일 애플리케이션\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"UI 설계 원칙 중 '유연성'은 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"사용자의 목적을 정확하게 달성해야 함\",\n" + + " \"사용자가 기능을 쉽게 파악할 수 있어야 함\",\n" + + " \"사용자 요구사항을 최대한 수용하며 오류를 최소화해야 함\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스크립트에 대한 설명 중 옳은 것은?\",\n" + + " \"quiz_select_options\": [\n" + + " \"스크립트는 개별 조건 식에 대해 수행하는 테스트 커버리지이다.\",\n" + + " \"스크립트는 결정 포인트 내 모든 분기문에 대해 수행하는 테스트 커버리지이다.\",\n" + + " \"스크립트는 결정 포인트의 모든 개별 조건식 가능한 조합을 100% 보장해야 하는 커버리지이다.\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"다중 조건 커버리지에 대한 설명 중 옳은 것은?\",\n" + + " \"quiz_select_options\": [\n" + + " \"다중 조건 커버리지는 결정 포인트의 모든 개별 조건식 가능한 조합을 100% 보장해야 하는 커버리지이다.\",\n" + + " \"다중 조건 커버리지는 모든 결정 포인트 내에 개별 조건식을 적어도 한 번 틀어야 하는 커버리지이다.\",\n" + + " \"다중 조건 커버리지는 모든 개별 조건식에 대해 수행하는 커버리지를 말한다.\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"성적 테이블에서 과목별 평균 점수가 90점 이상인 과목의 이름, 최소 점수, 최대 점수를 출력하는 SQL 문은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"SELECT 과목이름, MIN(점수) AS 최소점수, MAX(점수) AS 최대점수 FROM 성적 GROUP BY 과목이름 HAVING AVG(점수) >= 90;\",\n" + + " \"SELECT 과목이름, MIN(점수) AS 최소점수, MAX(점수) AS 최대점수 FROM 성적 WHERE AVG(점수) >= 90 GROUP BY 과목이름;\",\n" + + " \"SELECT 과목이름, MIN(점수) AS 최소점수, MAX(점수) AS 최대점수 FROM 성적 WHERE AVG(점수) >= 90;\",\n" + + " \"SELECT 과목이름, MIN(점수) AS 최소점수, MAX(점수) AS 최대점수 FROM 성적 GROUP BY 과목이름 HAVING AVG(점수) > 90;\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"SQL 문에서 평균 점수가 90점 이상인 과목만 선택하기 위해 사용하는 절은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"SELECT\",\n" + + " \"FROM\",\n" + + " \"GROUP BY\",\n" + + " \"HAVING\"\n" + + " ],\n" + + " \"answer\": 4\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스키마는 무엇을 나타내는 것인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터베이스의 전체적인 구조와 제약 조건\",\n" + + " \"데이터베이스의 외부 구조\",\n" + + " \"데이터베이스의 개념적 구조\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"외부 스키마는 무엇인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"사용자가 보는 관점에서의 데이터베이스 구조\",\n" + + " \"데이터베이스의 전체적인 구조와 제약 조건\",\n" + + " \"데이터베이스의 개념적 구조\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스키마의 종류 중에서 외부 스키마에 영향을 주지 않는 변경을 무엇이라고 할까요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"논리적 독립성\",\n" + + " \"물리적 독립성\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"관계 대수 연산 중에서 릴레이션 A에서 릴레이션 B의 조건을 만족하지 않는 튜플을 제외한 후 프로젝션하는 연산은 무엇일까요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"선택(셀렉트)\",\n" + + " \"나누기(디비전)\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"서울에서 원주까지 오는 길을 결정하는 프로토콜은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"OSPF\",\n" + + " \"ICMP\",\n" + + " \"TCP\",\n" + + " \"UDP\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"라우터는 어떤 역할을 하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"데이터 전송\",\n" + + " \"데이터 암호화\",\n" + + " \"데이터 저장\",\n" + + " \"데이터 처리\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"헝가리안 표기법은 무엇을 명시하지 않는 코딩 규칙인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"변수나 함수의 인자 이름\",\n" + + " \"데이터 타입\",\n" + + " \"코딩 스타일\",\n" + + " \"주석\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"리팩토링의 주요 목적은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"코드의 구조를 조정하기\",\n" + + " \"기능을 개선하기\",\n" + + " \"코드를 깨끗하게 만들기\",\n" + + " \"성능 향상\"\n" + + " ],\n" + + " \"answer\": 4\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"베이크 클래스는 무엇을 상속받고 있습니까?\",\n" + + " \"quiz_select_options\": [\n" + + " \"카 클래스\",\n" + + " \"부모 클래스\",\n" + + " \"자식 클래스\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"베이크 클래스의 생성자는 어떤 역할을 합니까?\",\n" + + " \"quiz_select_options\": [\n" + + " \"객체를 삭제하는 역할\",\n" + + " \"객체를 생성할 때 초기값을 설정하는 역할\",\n" + + " \"객체를 복제하는 역할\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"생성자는 무엇을 초기화하는 목적으로 주로 사용되는 메서드인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"멤버 변수\",\n" + + " \"메소드\",\n" + + " \"상속\",\n" + + " \"객체\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"생성자는 객체가 생성될 때 언제 호출되나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"객체가 사용될 때\",\n" + + " \"객체를 생성할 때\",\n" + + " \"객체를 삭제할 때\",\n" + + " \"객체를 수정할 때\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " }\n" + + " ],\n" + + " \"tokens\": 28344\n" + + " },\n" + + " {\n" + + " \"video_id\": \"lv4-n_s5AvI\",\n" + + " \"is_valid\": 1,\n" + + " \"summary\": \"\\n### 음악 정수 횡단 3\\n이번 시간에는 2020년 이외 실기 기출 문제에 대해 진행해 보도록 하겠습니다. 실기 이외의 정도부터는 이제 합격률이 조금씩 올라가기 시작했습니다. 이외의 경우는 5%로 조금씩 증가하고 있으며, 20%에서 30%를 유지할 것으로 예상됩니다. 자격증은 보통 20%에서 30%를 유지해야 하는데, 정보처리기사 시험 2는 어떤 출제가 될지 알 수 없습니다. 교재에 나오지 않은 것들이 많이 있어 어떤 것들이 출제될지 모르겠지만, 열심히 준비해주셨으면 좋겠습니다.\",\n" + + " \"quizzes\": [\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"정보처리기사 시험의 합격률은 어느 정도인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"20%\",\n" + + " \"30%\",\n" + + " \"5%\",\n" + + " \"1%\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"소프트웨어 개발 방법론 중 요구사항을 계속해서 받아들여 개발해나가는 방식은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"폭포수 모형\",\n" + + " \"애자일 방법론\",\n" + + " \"프로토타입 모형\",\n" + + " \"나선형 모형\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"데이터베이스 설계의 순서는 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"논리 설계, 개념 설계, 물리 설계\",\n" + + " \"개념 설계, 물리 설계, 논리 설계\",\n" + + " \"물리 설계, 논리 설계, 개념 설계\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"소비와 레스트의 차이점은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"소비는 HTTP 프로토콜을 사용하고, 레스트는 XML을 기반으로 데이터를 전송한다.\",\n" + + " \"소비는 XML 형태로 데이터를 전송하고, 레스트는 헤더에 투시를 해가며 다양한 형태로 데이터를 전송한다.\",\n" + + " \"소비는 XML 형태로 데이터를 전송하고, 레스트는 HTTP 프로토콜을 사용하여 XML 기반의 메세지를 전달한다.\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"형상 관리란 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"소프트웨어 개발을 위한 도구\",\n" + + " \"문서 관리를 위한 도구\",\n" + + " \"형상 식별, 형상 통제 등을 위한 도구\",\n" + + " \"소프트웨어 개발 및 컴퓨터 시스템 관리를 위한 도구\"\n" + + " ],\n" + + " \"answer\": 4\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"형상 관리 도구의 분류 중 공유폴더 방식에 대한 설명으로 옳은 것은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"소스 코드를 중앙 서버에 저장하는 방식입니다.\",\n" + + " \"소스 코드를 공유 폴더에 저장하는 방식입니다.\",\n" + + " \"개인 컴퓨터와 원격지 컴퓨터에서 소스 코드를 관리하는 방식입니다.\",\n" + + " \"형상 관리에 대한 기록을 남기는 방식입니다.\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"UI 설계의 직관성에 대한 설명으로 옳은 것은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"사용자가 쉽게 이해하고 사용할 수 있어야 함\",\n" + + " \"목적을 정확하게 달성해야 함\",\n" + + " \"누구나 쉽게 배우고 익힐 수 있어야 함\",\n" + + " \"사용자의 요구사항을 잘 받아들여야 함\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"테스트 종류 중 정적 테스트에 대한 설명으로 옳은 것은 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"프로그램을 실행시켜 동작을 확인함\",\n" + + " \"소스 코드를 실행시키지 않고 내부 구조를 분석함\",\n" + + " \"개발자가 프로그램을 검증함\",\n" + + " \"사용자가 프로그램을 확인함\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"다음 중 소프트웨어 테스트의 종류로 맞는 것은?\",\n" + + " \"quiz_select_options\": [\n" + + " \"유닛 테스트\",\n" + + " \"백발백중 테스트\",\n" + + " \"모의 해킹 테스트\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"다음 중 SQL 인젝션에 대한 설명으로 틀린 것은?\",\n" + + " \"quiz_select_options\": [\n" + + " \"SQL 구문에 악의적으로 조작된 입력 값을 삽입하는 기법\",\n" + + " \"웹 프로그램의 취약점을 이용하여 데이터베이스를 공격하는 기법\",\n" + + " \"보안 취약점을 방어하기 위한 기법\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"안드로이드는 어떤 운영체제를 기반으로 동작하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"리눅스\",\n" + + " \"윈도우\",\n" + + " \"맥 OS\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"리눅스 또는 유닉스에서 파일의 권한을 변경하기 위해 사용하는 명령어는 무엇인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"chmod\",\n" + + " \"chown\",\n" + + " \"chdir\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"권한 표기 방식 중 8진수는 몇 자리로 표기되나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"1자리\",\n" + + " \"2자리\",\n" + + " \"3자리\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"권한 변경 명령어 'chmod 755 filename'에서 숫자 755는 무엇을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"사용자에게 읽기, 쓰기, 실행 권한 부여\",\n" + + " \"그룹에게 읽기, 실행 권한 부여\",\n" + + " \"아더에게 실행 권한 부여\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"스마트폰 앱에서 오픈 데이터는 무엇을 제공하는 것인가요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"저작권 없이 무료로 제공되는 데이터\",\n" + + " \"스마트폰 앱의 시간 제한\",\n" + + " \"스마트폰 앱의 보상금\",\n" + + " \"스마트폰 앱의 사용자 데이터 손실을 최소화하기 위한 백업\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"시맨틱 웹은 어떤 개념을 의미하나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"문서의 의미에 맞게 어플리케이션의 의미를 부여하는 것\",\n" + + " \"옵저버 패턴을 활용하는 것\",\n" + + " \"오픈 데이터를 활용하는 것\",\n" + + " \"링크드 오픈 데이터와 비슷한 개념\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"시멘틱 웹은 무엇을 위해 웹 페이지를 구조적으로 정리하고 있는가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"검색 엔진이 정보를 읽고 활용하기 위해\",\n" + + " \"웹 페이지의 시각적 디자인을 개선하기 위해\",\n" + + " \"웹 페이지의 보안을 강화하기 위해\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"레스트 풀은 어떤 아키텍처를 구현한 웹 서비스인가?\",\n" + + " \"quiz_select_options\": [\n" + + " \"마이크로서비스 아키텍처\",\n" + + " \"레이어드 아키텍처\",\n" + + " \"레스트 아키텍처\"\n" + + " ],\n" + + " \"answer\": 3\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"자바에서 상속을 통해 어떤 기능을 사용할 수 있나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"부모 클래스의 기능\",\n" + + " \"자식 클래스의 기능\",\n" + + " \"형제 클래스의 기능\"\n" + + " ],\n" + + " \"answer\": 1\n" + + " },\n" + + " {\n" + + " \"quiz_type\": 1,\n" + + " \"quiz_question\": \"자바에서 오버라이딩은 무엇을 할 수 있나요?\",\n" + + " \"quiz_select_options\": [\n" + + " \"부모 클래스의 메서드를 재정의\",\n" + + " \"자식 클래스의 메서드를 재정의\",\n" + + " \"형제 클래스의 메서드를 재정의\"\n" + + " ],\n" + + " \"answer\": 2\n" + + " }\n" + + " ],\n" + + " \"tokens\": 29206\n" + + " }\n" + + " ]\n" + + "}"; +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 9f5d4b66..08a9205e 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -22,9 +22,21 @@ spring: youtube: base-url: https://www.googleapis.com/youtube/v3 -gpt: - request-url : http://127.0.0.1:8000 - result-url: http://127.0.0.1:8000/results +ai: + request-url : unavailable + result-url: unavailable + +aws: + kms: + keyId: 5e54b60b-abd3-45ff-8fed-53f147aac32c + encryptionAlgorithm: "SYMMETRIC_DEFAULT" + +jwt: + secret: '{cipher}AQICAHiGKnLyWc0ZUra/HszDAQiLV/XwHEAQgwJdRl7/UiE1gAH7vLPg8WH9EnVY8w2miixbAAAArTCBqgYJKoZIhvcNAQcGoIGcMIGZAgEAMIGTBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDLU1yqELVYSmkGGH+wIBEIBmIaeZzCy1NVtiMBgB+UP4m6r573GMnHrZD+gKaygwTKtKQ8bO6kRGmxK5oLlL7FUwQVfXyUsNH5Bl97UUibztt27L/ZZcEI+2M2Q6G036Kuo6pHeS+xrH3s+MJqGhyo7kdy0Dn4jd' + +google: + cloud-api-key: unavailable + client-id: '{cipher}AQICAHiGKnLyWc0ZUra/HszDAQiLV/XwHEAQgwJdRl7/UiE1gAGe1mdXigBgHZxwI2CQ7StZAAAAqzCBqAYJKoZIhvcNAQcGoIGaMIGXAgEAMIGRBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDK01letLDt4MQMOakQIBEIBkVachLXH71fMO5/1eLctnr7j3EnecJXj9WFTe2ineYIPc9kG3z5JYvaAM5np3TqD9t0XRGo2DEffcpyH9/GgEuzTxT2G3h+bAcOzHR9Hp/n93XcldtBO3oRrf/JlnXhzckKVu7Q==' #Devtool devtools: