diff --git a/src/main/java/play/pluv/base/BaseEntity.java b/src/main/java/play/pluv/base/BaseEntity.java index 1192e51..acaf556 100644 --- a/src/main/java/play/pluv/base/BaseEntity.java +++ b/src/main/java/play/pluv/base/BaseEntity.java @@ -16,9 +16,9 @@ public abstract class BaseEntity { @Column(updatable = false, nullable = false) @CreatedDate - private LocalDateTime createdAt; + protected LocalDateTime createdAt; @Column(nullable = false) @LastModifiedDate - private LocalDateTime updatedAt; + protected LocalDateTime updatedAt; } diff --git a/src/main/java/play/pluv/history/application/HistoryReader.java b/src/main/java/play/pluv/history/application/HistoryReader.java new file mode 100644 index 0000000..e5f84dc --- /dev/null +++ b/src/main/java/play/pluv/history/application/HistoryReader.java @@ -0,0 +1,38 @@ +package play.pluv.history.application; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import play.pluv.history.domain.History; +import play.pluv.history.domain.TransferFailMusic; +import play.pluv.history.domain.TransferredMusic; +import play.pluv.history.domain.repository.HistoryRepository; +import play.pluv.history.domain.repository.TransferFailMusicRepository; +import play.pluv.history.domain.repository.TransferredMusicRepository; + +@Component +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class HistoryReader { + + private final HistoryRepository historyRepository; + private final TransferredMusicRepository transferredMusicRepository; + private final TransferFailMusicRepository transferFailMusicRepository; + + public List readByMemberId(final Long memberId) { + return historyRepository.findByMemberId(memberId); + } + + public History readById(final Long historyId) { + return historyRepository.readById(historyId); + } + + public List readTransferredMusics(final Long historyId) { + return transferredMusicRepository.findByHistoryId(historyId); + } + + public List readTransferFailMusics(final Long historyId) { + return transferFailMusicRepository.findByHistoryId(historyId); + } +} diff --git a/src/main/java/play/pluv/history/application/HistoryService.java b/src/main/java/play/pluv/history/application/HistoryService.java new file mode 100644 index 0000000..9b77273 --- /dev/null +++ b/src/main/java/play/pluv/history/application/HistoryService.java @@ -0,0 +1,38 @@ +package play.pluv.history.application; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import play.pluv.history.domain.History; +import play.pluv.history.domain.TransferFailMusic; +import play.pluv.history.domain.TransferredMusic; + +@Service +@RequiredArgsConstructor +public class HistoryService { + + private final HistoryReader historyReader; + + @Transactional(readOnly = true) + public List findHistories(final Long memberId) { + return historyReader.readByMemberId(memberId); + } + + @Transactional(readOnly = true) + public History findHistory(final Long historyId, final Long memberId) { + final History history = historyReader.readById(historyId); + history.validateOwner(memberId); + return history; + } + + @Transactional(readOnly = true) + public List findTransferFailMusics(final Long historyId, final Long memberId) { + return historyReader.readTransferFailMusics(historyId); + } + + @Transactional(readOnly = true) + public List findTransferredMusics(final Long historyId, final Long memberId) { + return historyReader.readTransferredMusics(historyId); + } +} diff --git a/src/main/java/play/pluv/history/application/dto/HistoryDetailResponse.java b/src/main/java/play/pluv/history/application/dto/HistoryDetailResponse.java new file mode 100644 index 0000000..3ccb27b --- /dev/null +++ b/src/main/java/play/pluv/history/application/dto/HistoryDetailResponse.java @@ -0,0 +1,26 @@ +package play.pluv.history.application.dto; + +import play.pluv.history.domain.History; + +public record HistoryDetailResponse( + Long id, + Integer totalSongCount, + Integer transferredSongCount, + String title, + String imageUrl, + String source, + String destination +) { + + public static HistoryDetailResponse from(final History history) { + return new HistoryDetailResponse( + history.getId(), + history.getTotalSongCount(), + history.getTransferredSongCount(), + history.getTitle(), + history.getThumbNailUrl(), + history.getSource().getName(), + history.getDestination().getName() + ); + } +} diff --git a/src/main/java/play/pluv/history/application/dto/HistoryListResponse.java b/src/main/java/play/pluv/history/application/dto/HistoryListResponse.java new file mode 100644 index 0000000..802d600 --- /dev/null +++ b/src/main/java/play/pluv/history/application/dto/HistoryListResponse.java @@ -0,0 +1,30 @@ +package play.pluv.history.application.dto; + +import static java.util.Comparator.comparing; + +import java.time.LocalDate; +import java.util.List; +import play.pluv.history.domain.History; + +public record HistoryListResponse( + Long id, + Integer transferredSongCount, + LocalDate transferredDate, + String title, + String imageUrl +) { + + public static List createList(final List histories) { + return histories.stream() + .map(HistoryListResponse::from) + .sorted(comparing(HistoryListResponse::transferredDate).reversed()) + .toList(); + } + + private static HistoryListResponse from(final History history) { + return new HistoryListResponse( + history.getId(), history.getTransferredSongCount(), history.getCreatedAt().toLocalDate(), + history.getTitle(), history.getThumbNailUrl() + ); + } +} diff --git a/src/main/java/play/pluv/history/application/dto/HistoryMusicResponse.java b/src/main/java/play/pluv/history/application/dto/HistoryMusicResponse.java new file mode 100644 index 0000000..2a6c45f --- /dev/null +++ b/src/main/java/play/pluv/history/application/dto/HistoryMusicResponse.java @@ -0,0 +1,42 @@ +package play.pluv.history.application.dto; + +import java.util.List; +import play.pluv.history.domain.TransferFailMusic; +import play.pluv.history.domain.TransferredMusic; + +public record HistoryMusicResponse( + String title, + String imageUrl, + String artistNames +) { + + public static HistoryMusicResponse from(final TransferredMusic transferredMusic) { + return new HistoryMusicResponse( + transferredMusic.getTitle(), transferredMusic.getImageUrl(), + transferredMusic.getArtistNames() + ); + } + + public static HistoryMusicResponse from(final TransferFailMusic transferFailMusic) { + return new HistoryMusicResponse( + transferFailMusic.getTitle(), transferFailMusic.getImageUrl(), + transferFailMusic.getArtistNames() + ); + } + + public static List createListFromTransferFail( + final List transferFailMusics + ) { + return transferFailMusics.stream() + .map(HistoryMusicResponse::from) + .toList(); + } + + public static List createListFromTransferred( + final List transferredMusics + ) { + return transferredMusics.stream() + .map(HistoryMusicResponse::from) + .toList(); + } +} diff --git a/src/main/java/play/pluv/history/controller/HistoryController.java b/src/main/java/play/pluv/history/controller/HistoryController.java new file mode 100644 index 0000000..dce2d8b --- /dev/null +++ b/src/main/java/play/pluv/history/controller/HistoryController.java @@ -0,0 +1,59 @@ +package play.pluv.history.controller; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import play.pluv.base.BaseResponse; +import play.pluv.history.application.HistoryService; +import play.pluv.history.application.dto.HistoryDetailResponse; +import play.pluv.history.application.dto.HistoryListResponse; +import play.pluv.history.application.dto.HistoryMusicResponse; +import play.pluv.security.JwtMemberId; + +@RestController +@RequiredArgsConstructor +public class HistoryController { + + private final HistoryService historyService; + + @GetMapping("/history/me") + public BaseResponse> getHistories(final JwtMemberId jwtMemberId) { + final var histories = historyService.findHistories(jwtMemberId.memberId()); + final List historyListResponses = HistoryListResponse + .createList(histories); + return BaseResponse.ok(historyListResponses); + } + + @GetMapping("/history/{id}") + public BaseResponse getHistory( + final JwtMemberId jwtMemberId, @PathVariable final Long id + ) { + final var history = historyService.findHistory(id, jwtMemberId.memberId()); + final HistoryDetailResponse response = HistoryDetailResponse.from(history); + return BaseResponse.ok(response); + } + + @GetMapping("/history/{id}/music/fail") + public BaseResponse> getTransferFailMusics( + final JwtMemberId jwtMemberId, @PathVariable final Long id + ) { + final var transferFailMusics = historyService.findTransferFailMusics( + id, jwtMemberId.memberId() + ); + final List responses = HistoryMusicResponse + .createListFromTransferFail(transferFailMusics); + return BaseResponse.ok(responses); + } + + @GetMapping("/history/{id}/music/success") + public BaseResponse> getTransferredMusics( + final JwtMemberId jwtMemberId, @PathVariable final Long id + ) { + final var transferredMusics = historyService.findTransferredMusics(id, jwtMemberId.memberId()); + final List responses = HistoryMusicResponse + .createListFromTransferred(transferredMusics); + return BaseResponse.ok(responses); + } +} diff --git a/src/main/java/play/pluv/history/domain/History.java b/src/main/java/play/pluv/history/domain/History.java index 993205b..ac63014 100644 --- a/src/main/java/play/pluv/history/domain/History.java +++ b/src/main/java/play/pluv/history/domain/History.java @@ -2,21 +2,25 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import static play.pluv.history.exception.HistoryExceptionType.HISTORY_NOT_OWNER; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import java.time.LocalDateTime; +import java.util.Objects; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import play.pluv.base.BaseEntity; +import play.pluv.history.exception.HistoryException; import play.pluv.playlist.domain.MusicStreaming; @Entity -@NoArgsConstructor(access = PROTECTED) @Getter +@NoArgsConstructor(access = PROTECTED) public class History extends BaseEntity { @Id @@ -32,6 +36,22 @@ public class History extends BaseEntity { @Enumerated(EnumType.STRING) private MusicStreaming destination; + public History(final Long id, final String title, final String thumbNailUrl, + final Integer transferredSongCount, + final Integer totalSongCount, final Long memberId, final MusicStreaming source, + final MusicStreaming destination, final LocalDateTime createdAt) { + this.id = id; + this.title = title; + this.thumbNailUrl = thumbNailUrl; + this.transferredSongCount = transferredSongCount; + this.totalSongCount = totalSongCount; + this.memberId = memberId; + this.source = source; + this.destination = destination; + this.createdAt = createdAt; + this.updatedAt = createdAt; + } + @Builder public History(final String title, final String thumbNailUrl, final Integer transferredSongCount, final Integer totalSongCount, final Long memberId, final MusicStreaming source, @@ -44,4 +64,10 @@ public History(final String title, final String thumbNailUrl, final Integer tran this.source = source; this.destination = destination; } + + public void validateOwner(final Long memberId) { + if (!Objects.equals(memberId, this.memberId)) { + throw new HistoryException(HISTORY_NOT_OWNER); + } + } } diff --git a/src/main/java/play/pluv/history/domain/TransferFailMusic.java b/src/main/java/play/pluv/history/domain/TransferFailMusic.java index 9e70006..a1f5026 100644 --- a/src/main/java/play/pluv/history/domain/TransferFailMusic.java +++ b/src/main/java/play/pluv/history/domain/TransferFailMusic.java @@ -7,6 +7,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import lombok.Builder; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import play.pluv.base.BaseEntity; @@ -20,8 +21,11 @@ public class TransferFailMusic extends BaseEntity { @GeneratedValue(strategy = IDENTITY) private Long id; private Long historyId; + @Getter private String title; + @Getter private String imageUrl; + @Getter private String artistNames; @Builder diff --git a/src/main/java/play/pluv/history/domain/TransferredMusic.java b/src/main/java/play/pluv/history/domain/TransferredMusic.java index 86f1647..44ea39c 100644 --- a/src/main/java/play/pluv/history/domain/TransferredMusic.java +++ b/src/main/java/play/pluv/history/domain/TransferredMusic.java @@ -8,6 +8,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import lombok.Builder; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import play.pluv.base.BaseEntity; @@ -21,8 +22,11 @@ public class TransferredMusic extends BaseEntity { @GeneratedValue(strategy = IDENTITY) private Long id; private Long historyId; + @Getter private String title; + @Getter private String imageUrl; + @Getter private String artistNames; @Embedded private HistoryMusicId musicId; diff --git a/src/main/java/play/pluv/history/domain/repository/HistoryRepository.java b/src/main/java/play/pluv/history/domain/repository/HistoryRepository.java index a918d05..417af86 100644 --- a/src/main/java/play/pluv/history/domain/repository/HistoryRepository.java +++ b/src/main/java/play/pluv/history/domain/repository/HistoryRepository.java @@ -2,12 +2,15 @@ import static play.pluv.history.exception.HistoryExceptionType.HISTORY_NOT_FOUND; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import play.pluv.history.domain.History; import play.pluv.history.exception.HistoryException; public interface HistoryRepository extends JpaRepository { + List findByMemberId(final Long memberId); + default History readById(final Long id) { return findById(id).orElseThrow(() -> new HistoryException(HISTORY_NOT_FOUND)); } diff --git a/src/main/java/play/pluv/history/exception/HistoryExceptionType.java b/src/main/java/play/pluv/history/exception/HistoryExceptionType.java index f3c1435..c29ec27 100644 --- a/src/main/java/play/pluv/history/exception/HistoryExceptionType.java +++ b/src/main/java/play/pluv/history/exception/HistoryExceptionType.java @@ -1,5 +1,6 @@ package play.pluv.history.exception; +import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; import lombok.Getter; @@ -11,6 +12,7 @@ @RequiredArgsConstructor public enum HistoryExceptionType implements BaseExceptionType { + HISTORY_NOT_OWNER(FORBIDDEN, "요청한 히스토리는 유저의 소유가 아닙니다"), HISTORY_NOT_FOUND(NOT_FOUND, "요청한 이전 내역을 찾을 수 없습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/play/pluv/music/controller/MusicController.java b/src/main/java/play/pluv/music/controller/MusicController.java index 2b45516..d3a3f52 100644 --- a/src/main/java/play/pluv/music/controller/MusicController.java +++ b/src/main/java/play/pluv/music/controller/MusicController.java @@ -29,8 +29,7 @@ public class MusicController { public BaseResponse> searchSpotifyMusics( @Valid @RequestBody final MusicSearchRequest musicSearchRequest ) { - final var responses = musicService - .searchMusics(SPOTIFY, musicSearchRequest); + final var responses = musicService.searchMusics(SPOTIFY, musicSearchRequest); return BaseResponse.ok(responses); } @@ -38,8 +37,7 @@ public BaseResponse> searchSpotifyMusics( public BaseResponse> searchYoutubeMusics( @Valid @RequestBody final MusicSearchRequest musicSearchRequest ) { - final var responses = musicService - .searchMusics(YOUTUBE, musicSearchRequest); + final var responses = musicService.searchMusics(YOUTUBE, musicSearchRequest); return BaseResponse.ok(responses); } @@ -47,8 +45,7 @@ public BaseResponse> searchYoutubeMusics( public BaseResponse> searchAppleMusics( @Valid @RequestBody final MusicSearchRequest musicSearchRequest ) { - final var responses = musicService - .searchMusics(APPLE, musicSearchRequest); + final var responses = musicService.searchMusics(APPLE, musicSearchRequest); return BaseResponse.ok(responses); } diff --git a/src/test/java/play/pluv/api/HistoryApiTest.java b/src/test/java/play/pluv/api/HistoryApiTest.java new file mode 100644 index 0000000..4e8fcaa --- /dev/null +++ b/src/test/java/play/pluv/api/HistoryApiTest.java @@ -0,0 +1,163 @@ +package play.pluv.api; + + +import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static play.pluv.fixture.HistoryEntityFixture.이전실패한_음악들; +import static play.pluv.fixture.HistoryEntityFixture.이전한_음악들; +import static play.pluv.fixture.HistoryEntityFixture.히스토리_1; +import static play.pluv.fixture.HistoryEntityFixture.히스토리들; + +import org.junit.jupiter.api.Test; +import play.pluv.support.ApiTest; + +public class HistoryApiTest extends ApiTest { + + @Test + void 히스토리_목록을_조회한다() throws Exception { + final Long memberId = 10L; + final String token = "access Token"; + final var histories = 히스토리들(memberId); + + setAccessToken(token, memberId); + when(historyService.findHistories(memberId)).thenReturn(histories); + + mockMvc.perform(get("/history/me") + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isOk()) + .andDo(document("history-list", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer Token") + ), + responseFields( + fieldWithPath("code").type(NUMBER).description("상태 코드"), + fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"), + fieldWithPath("data[]").type(ARRAY).description("히스토리 목록"), + fieldWithPath("data[].id").type(NUMBER).description("히스토리의 Id"), + fieldWithPath("data[].transferredSongCount").type(NUMBER).description("이전한 곡수"), + fieldWithPath("data[].title").type(STRING).description("이전한 음악 타이틀"), + fieldWithPath("data[].imageUrl").type(STRING).description("이미지 url"), + fieldWithPath("data[].transferredDate").type(STRING).description("이전된 날짜") + ) + )); + } + + @Test + void 히스토리_상세조회한다() throws Exception { + final Long memberId = 10L; + final Long historyId = 3L; + final String token = "access Token"; + final var history = 히스토리_1(memberId); + + setAccessToken(token, memberId); + when(historyService.findHistory(historyId, memberId)).thenReturn(history); + + mockMvc.perform(get("/history/{id}", historyId) + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isOk()) + .andDo(document("history-detail", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("id").description("히스토리의 식별자") + ), + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer Token") + ), + responseFields( + fieldWithPath("code").type(NUMBER).description("상태 코드"), + fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"), + fieldWithPath("data.id").type(NUMBER).description("히스토리의 Id"), + fieldWithPath("data.totalSongCount").type(NUMBER).description("이전하려 했던 곡수"), + fieldWithPath("data.transferredSongCount").type(NUMBER).description("이전한 곡수"), + fieldWithPath("data.title").type(STRING).description("이전한 음악 타이틀"), + fieldWithPath("data.imageUrl").type(STRING).description("이미지 url"), + fieldWithPath("data.source").type(STRING).description("이전하려했던 플레이리스트 출처"), + fieldWithPath("data.destination").type(STRING).description("이전한 플레이리스트 목적지") + ) + )); + } + + @Test + void 이전_실패한_음악을_조회한다() throws Exception { + final Long memberId = 10L; + final Long historyId = 3L; + final String token = "access Token"; + final var transferFailMusics = 이전실패한_음악들(historyId); + + setAccessToken(token, memberId); + when(historyService.findTransferFailMusics(historyId, memberId)).thenReturn(transferFailMusics); + + mockMvc.perform(get("/history/{id}/music/fail", historyId) + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isOk()) + .andDo(document("history-transfer-fail", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("id").description("히스토리의 식별자") + ), + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer Token") + ), + responseFields( + fieldWithPath("code").type(NUMBER).description("상태 코드"), + fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"), + fieldWithPath("data[]").type(ARRAY).description("이전하지 못한 음악들"), + fieldWithPath("data[].title").type(STRING).description("음악 이름"), + fieldWithPath("data[].imageUrl").type(STRING).description("음악 이미지 url"), + fieldWithPath("data[].artistNames").type(STRING).description("가수들") + ) + )); + } + + @Test + void 이전_성공한_음악을_조회한다() throws Exception { + final Long memberId = 10L; + final Long historyId = 3L; + final String token = "access Token"; + final var transferredMusics = 이전한_음악들(historyId); + + setAccessToken(token, memberId); + when(historyService.findTransferredMusics(historyId, memberId)).thenReturn(transferredMusics); + + mockMvc.perform(get("/history/{id}/music/success", historyId) + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isOk()) + .andDo(document("history-transfer-success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("id").description("히스토리의 식별자") + ), + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer Token") + ), + responseFields( + fieldWithPath("code").type(NUMBER).description("상태 코드"), + fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"), + fieldWithPath("data[]").type(ARRAY).description("이전한 음악들"), + fieldWithPath("data[].title").type(STRING).description("음악 이름"), + fieldWithPath("data[].imageUrl").type(STRING).description("음악 이미지 url"), + fieldWithPath("data[].artistNames").type(STRING).description("가수들") + ) + )); + } +} diff --git a/src/test/java/play/pluv/fixture/HistoryEntityFixture.java b/src/test/java/play/pluv/fixture/HistoryEntityFixture.java new file mode 100644 index 0000000..ae0d389 --- /dev/null +++ b/src/test/java/play/pluv/fixture/HistoryEntityFixture.java @@ -0,0 +1,47 @@ +package play.pluv.fixture; + +import static play.pluv.playlist.domain.MusicStreaming.APPLE; +import static play.pluv.playlist.domain.MusicStreaming.SPOTIFY; +import static play.pluv.playlist.domain.MusicStreaming.YOUTUBE; + +import java.time.LocalDateTime; +import java.util.List; +import play.pluv.history.domain.History; +import play.pluv.history.domain.HistoryMusicId; +import play.pluv.history.domain.TransferFailMusic; +import play.pluv.history.domain.TransferredMusic; + +public class HistoryEntityFixture { + + public static History 히스토리_1(final Long memberId) { + return new History( + 1L, "히스토리 1", "thumbNailurl", 7, 10, memberId, SPOTIFY, YOUTUBE, LocalDateTime.now() + ); + } + + public static History 히스토리_2(final Long memberId) { + return new History( + 1L, "히스토리 2", "thumbNailurl", 10, 10, memberId, APPLE, SPOTIFY, LocalDateTime.now() + ); + } + + public static List 히스토리들(final Long memberId) { + return List.of(히스토리_1(memberId), 히스토리_2(memberId)); + } + + public static List 이전실패한_음악들(final Long historyId) { + return List.of( + new TransferFailMusic(historyId, "이전 실패한 음악 1", "imagerUrl", "이전 실패한 음악1의 가수 이름"), + new TransferFailMusic(historyId, "이전 실패한 음악 2", "imagerUrl", "이전 실패한 음악2의 가수 이름") + ); + } + + public static List 이전한_음악들(final Long historyId) { + return List.of( + new TransferredMusic(historyId, "다시 사랑한다 말할까", "imagerUrl", "김동률", + new HistoryMusicId(SPOTIFY, "ab")), + new TransferredMusic(historyId, "오래된 노래", "imagerUrl", "김동률", + new HistoryMusicId(SPOTIFY, "cd")) + ); + } +} diff --git a/src/test/java/play/pluv/fixture/HistoryFixture.java b/src/test/java/play/pluv/fixture/HistoryFixture.java index 83d0ca4..4559654 100644 --- a/src/test/java/play/pluv/fixture/HistoryFixture.java +++ b/src/test/java/play/pluv/fixture/HistoryFixture.java @@ -1,9 +1,115 @@ package play.pluv.fixture; +import static play.pluv.playlist.domain.MusicStreaming.APPLE; +import static play.pluv.playlist.domain.MusicStreaming.SPOTIFY; +import static play.pluv.playlist.domain.MusicStreaming.YOUTUBE; + import java.util.List; +import play.pluv.history.domain.History; +import play.pluv.history.domain.HistoryMusicId; import play.pluv.history.domain.TransferFailMusic; -import play.pluv.transfer_context.domain.TransferFailMusicInContext; +import play.pluv.history.domain.TransferredMusic; +import play.pluv.history.domain.repository.HistoryRepository; +import play.pluv.history.domain.repository.TransferFailMusicRepository; +import play.pluv.history.domain.repository.TransferredMusicRepository; public class HistoryFixture { + public static History 히스토리_1(final Long memberId) { + return History.builder() + .memberId(memberId) + .title("히스토리 1") + .source(SPOTIFY) + .destination(APPLE) + .transferredSongCount(10) + .totalSongCount(15) + .thumbNailUrl("thumbNailUrl") + .build(); + } + + public static History 저장된_히스토리_1(final HistoryRepository historyRepository, final Long memberId) { + return historyRepository.save(히스토리_1(memberId)); + } + + public static History 저장된_히스토리_2(final Long memberId) { + return History.builder() + .memberId(memberId) + .title("히스토리 2") + .source(YOUTUBE) + .destination(SPOTIFY) + .transferredSongCount(10) + .totalSongCount(10) + .thumbNailUrl("thumbNailUrl") + .build(); + } + + public static History 저장된_히스토리_2(final HistoryRepository historyRepository, final Long memberId) { + return historyRepository.save(저장된_히스토리_2(memberId)); + } + + public static TransferFailMusic 저장된_이전실패한_음악1( + final TransferFailMusicRepository repository, final Long historyId + ) { + return repository.save( + TransferFailMusic.builder() + .title("이전 실패한 음악_1") + .historyId(historyId) + .artistNames("이전실패음악_가수") + .imageUrl("imageUrl") + .build() + ); + } + + public static TransferredMusic 저장된_이전한_음악1( + final TransferredMusicRepository repository, final Long historyId + ) { + return repository.save( + TransferredMusic.builder() + .title("이전한 음악_1") + .historyId(historyId) + .artistNames("이전가수") + .imageUrl("imageUrl") + .musicId(new HistoryMusicId(SPOTIFY, "ab")) + .build() + ); + } + + public static TransferFailMusic 저장된_이전실패한_음악2( + final TransferFailMusicRepository repository, final Long historyId + ) { + return repository.save( + TransferFailMusic.builder() + .title("이전 실패한 음악 2") + .historyId(historyId) + .artistNames("이전실패음악 가수2") + .imageUrl("imageUrl") + .build() + ); + } + + public static TransferredMusic 저장된_이전한_음악2( + final TransferredMusicRepository repository, final Long historyId + ) { + return repository.save( + TransferredMusic.builder() + .title("이전한 음악 2") + .historyId(historyId) + .artistNames("이전한 음악 가수 2") + .imageUrl("imageUrl") + .musicId(new HistoryMusicId(SPOTIFY, "cd")) + .build() + ); + } + + public static List 저장된_이전실패한_음악들( + final TransferFailMusicRepository repository, final Long historyId + ) { + return List.of(저장된_이전실패한_음악1(repository, historyId), 저장된_이전실패한_음악2(repository, historyId)); + } + + public static List 저장된_이전한_음악들( + final TransferredMusicRepository repository, final Long historyId + ) { + return List.of(저장된_이전한_음악1(repository, historyId), 저장된_이전한_음악2(repository, historyId)); + } } diff --git a/src/test/java/play/pluv/history/application/HistoryServiceTest.java b/src/test/java/play/pluv/history/application/HistoryServiceTest.java new file mode 100644 index 0000000..ce8eb55 --- /dev/null +++ b/src/test/java/play/pluv/history/application/HistoryServiceTest.java @@ -0,0 +1,110 @@ +package play.pluv.history.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static play.pluv.fixture.HistoryFixture.저장된_이전실패한_음악들; +import static play.pluv.fixture.HistoryFixture.저장된_이전한_음악들; +import static play.pluv.fixture.HistoryFixture.저장된_히스토리_1; +import static play.pluv.fixture.HistoryFixture.저장된_히스토리_2; +import static play.pluv.fixture.MemberEntityFixture.멤버_홍혁준; +import static play.pluv.history.exception.HistoryExceptionType.HISTORY_NOT_OWNER; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import play.pluv.history.domain.History; +import play.pluv.history.domain.TransferFailMusic; +import play.pluv.history.domain.TransferredMusic; +import play.pluv.history.domain.repository.HistoryRepository; +import play.pluv.history.domain.repository.TransferFailMusicRepository; +import play.pluv.history.domain.repository.TransferredMusicRepository; +import play.pluv.history.exception.HistoryException; +import play.pluv.member.domain.Member; +import play.pluv.member.domain.repository.MemberRepository; +import play.pluv.support.ApplicationTest; + +class HistoryServiceTest extends ApplicationTest { + + @Autowired + private HistoryService historyService; + @Autowired + private MemberRepository memberRepository; + @Autowired + private HistoryRepository historyRepository; + @Autowired + private TransferFailMusicRepository transferFailMusicRepository; + @Autowired + private TransferredMusicRepository transferredMusicRepository; + + @Test + void 히스토리_목록을_조회한다() { + final Member member = 멤버_홍혁준(memberRepository); + final History history1 = 저장된_히스토리_1(historyRepository, member.getId()); + final History history2 = 저장된_히스토리_2(historyRepository, member.getId()); + + final List actual = historyService.findHistories(member.getId()); + + assertThat(actual) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder(history1, history2); + } + + @Test + void 히스토리를_하나_조회한다() { + final Member member = 멤버_홍혁준(memberRepository); + final History history = 저장된_히스토리_1(historyRepository, member.getId()); + + final History actual = historyService.findHistory(history.getId(), member.getId()); + + assertThat(actual) + .usingRecursiveComparison() + .isEqualTo(history); + } + + @Test + void 히스토리를_하나_조회하는_대상이_소유자가_아니면_예외를_던진다() { + final Member member = 멤버_홍혁준(memberRepository); + final Long invalidMemberId = member.getId() + 1; + final History history = 저장된_히스토리_1(historyRepository, member.getId()); + + assertThatThrownBy(() -> historyService.findHistory(history.getId(), invalidMemberId)) + .isInstanceOf(HistoryException.class) + .hasMessage(HISTORY_NOT_OWNER.getMessage()); + } + + @Test + void 이전실패한_음악을_반환한다() { + final Member member = 멤버_홍혁준(memberRepository); + final History history = 저장된_히스토리_1(historyRepository, member.getId()); + final List transferFailMusics = 저장된_이전실패한_음악들( + transferFailMusicRepository, + history.getId() + ); + + final List actual = historyService.findTransferFailMusics( + history.getId(), member.getId() + ); + + assertThat(actual) + .usingRecursiveFieldByFieldElementComparator() + .isEqualTo(transferFailMusics); + } + + @Test + void 이전한_음악들을_반환한다() { + final Member member = 멤버_홍혁준(memberRepository); + final History history = 저장된_히스토리_1(historyRepository, member.getId()); + final List transferFailMusics = 저장된_이전한_음악들( + transferredMusicRepository, + history.getId() + ); + + final List actual = historyService.findTransferredMusics( + history.getId(), member.getId() + ); + + assertThat(actual) + .usingRecursiveFieldByFieldElementComparator() + .isEqualTo(transferFailMusics); + } +} \ No newline at end of file diff --git a/src/test/java/play/pluv/support/ApiTest.java b/src/test/java/play/pluv/support/ApiTest.java index e8887f4..c297df0 100644 --- a/src/test/java/play/pluv/support/ApiTest.java +++ b/src/test/java/play/pluv/support/ApiTest.java @@ -23,6 +23,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import play.pluv.base.LocalDateTimeProvider; +import play.pluv.history.application.HistoryService; import play.pluv.security.JwtProvider; import play.pluv.login.application.LoginService; import play.pluv.member.application.MemberService; @@ -56,6 +57,8 @@ public abstract class ApiTest { @MockBean protected ProgressService progressService; @MockBean + protected HistoryService historyService; + @MockBean protected JwtProvider jwtProvider; @BeforeEach