From 48e199e1d83b87de4860641022913647e6ca2905 Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:36:11 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[BE]=20feat:=20FestivalQueryInfo,=20StageQu?= =?UTF-8?q?eryInfo=EB=A5=BC=20=EC=9E=AC=EA=B0=B1=EC=8B=A0=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EC=96=B4=EB=93=9C=EB=AF=BC=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#933)=20(#937)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: StageQueryInfoEventListener 내 비즈니스 로직 Service 클래스로 이동 * feat: AdminQueryInfoRenewalService 추가 * feat: AdminQueryInfoRenewalV1Controller 추가 * fix: festivalIds -> festivalIds.size() 수정 --- .../AdminQueryInfoRenewalService.java | 38 ++++++ ...eryInfoRenewalFestivalPeriodV1Request.java | 11 ++ .../v1/AdminQueryInfoRenewalV1Controller.java | 38 ++++++ ...nFestivalIdResolverQueryDslRepository.java | 25 ++++ ...dminStageIdResolverQueryDslRepository.java | 33 +++++ .../StageQueryInfoEventListener.java | 42 +----- .../application/StageQueryInfoService.java | 56 ++++++++ ...AdminQueryInfoRenewalV1ControllerTest.java | 117 ++++++++++++++++ ...tivalIdResolverQueryDslRepositoryTest.java | 67 +++++++++ ...StageIdResolverQueryDslRepositoryTest.java | 94 +++++++++++++ .../DelimiterArtistsSerializer.java | 23 ++++ .../StageQueryInfoServiceTest.java | 129 ++++++++++++++++++ .../MemoryStageQueryInfoRepository.java | 24 ++++ .../support/fixture/FestivalFixture.java | 5 +- 14 files changed, 663 insertions(+), 39 deletions(-) create mode 100644 backend/src/main/java/com/festago/admin/application/AdminQueryInfoRenewalService.java create mode 100644 backend/src/main/java/com/festago/admin/dto/queryinfo/QueryInfoRenewalFestivalPeriodV1Request.java create mode 100644 backend/src/main/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1Controller.java create mode 100644 backend/src/main/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepository.java create mode 100644 backend/src/main/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepository.java create mode 100644 backend/src/main/java/com/festago/stage/application/StageQueryInfoService.java create mode 100644 backend/src/test/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1ControllerTest.java create mode 100644 backend/src/test/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepositoryTest.java create mode 100644 backend/src/test/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepositoryTest.java create mode 100644 backend/src/test/java/com/festago/artist/infrastructure/DelimiterArtistsSerializer.java create mode 100644 backend/src/test/java/com/festago/stage/application/StageQueryInfoServiceTest.java create mode 100644 backend/src/test/java/com/festago/stage/repository/MemoryStageQueryInfoRepository.java diff --git a/backend/src/main/java/com/festago/admin/application/AdminQueryInfoRenewalService.java b/backend/src/main/java/com/festago/admin/application/AdminQueryInfoRenewalService.java new file mode 100644 index 000000000..3d481ab51 --- /dev/null +++ b/backend/src/main/java/com/festago/admin/application/AdminQueryInfoRenewalService.java @@ -0,0 +1,38 @@ +package com.festago.admin.application; + +import com.festago.admin.repository.AdminFestivalIdResolverQueryDslRepository; +import com.festago.admin.repository.AdminStageIdResolverQueryDslRepository; +import com.festago.festival.application.FestivalQueryInfoArtistRenewService; +import com.festago.stage.application.StageQueryInfoService; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class AdminQueryInfoRenewalService { + + private final FestivalQueryInfoArtistRenewService festivalQueryInfoArtistRenewService; + private final StageQueryInfoService stageQueryInfoService; + private final AdminStageIdResolverQueryDslRepository adminStageIdResolverQueryDslRepository; + private final AdminFestivalIdResolverQueryDslRepository adminFestivalIdResolverQueryDslRepository; + + public void renewalByFestivalId(Long festivalId) { + festivalQueryInfoArtistRenewService.renewArtistInfo(festivalId); + adminStageIdResolverQueryDslRepository.findStageIdsByFestivalId(festivalId) + .forEach(stageQueryInfoService::renewalStageQueryInfo); + } + + public void renewalByFestivalStartDatePeriod(LocalDate to, LocalDate end) { + List festivalIds = adminFestivalIdResolverQueryDslRepository.findFestivalIdsByStartDatePeriod(to, end); + log.info("{}개의 축제에 대해 QueryInfo를 새로 갱신합니다.", festivalIds.size()); + festivalIds.forEach(festivalQueryInfoArtistRenewService::renewArtistInfo); + adminStageIdResolverQueryDslRepository.findStageIdsByFestivalIdIn(festivalIds) + .forEach(stageQueryInfoService::renewalStageQueryInfo); + } +} diff --git a/backend/src/main/java/com/festago/admin/dto/queryinfo/QueryInfoRenewalFestivalPeriodV1Request.java b/backend/src/main/java/com/festago/admin/dto/queryinfo/QueryInfoRenewalFestivalPeriodV1Request.java new file mode 100644 index 000000000..a35688d42 --- /dev/null +++ b/backend/src/main/java/com/festago/admin/dto/queryinfo/QueryInfoRenewalFestivalPeriodV1Request.java @@ -0,0 +1,11 @@ +package com.festago.admin.dto.queryinfo; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; + +public record QueryInfoRenewalFestivalPeriodV1Request( + @NotNull LocalDate to, + @NotNull LocalDate end +) { + +} diff --git a/backend/src/main/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1Controller.java b/backend/src/main/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1Controller.java new file mode 100644 index 000000000..24bb37a4a --- /dev/null +++ b/backend/src/main/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1Controller.java @@ -0,0 +1,38 @@ +package com.festago.admin.presentation.v1; + +import com.festago.admin.application.AdminQueryInfoRenewalService; +import com.festago.admin.dto.queryinfo.QueryInfoRenewalFestivalPeriodV1Request; +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/admin/api/v1/query-info/renewal") +@RequiredArgsConstructor +@Hidden +public class AdminQueryInfoRenewalV1Controller { + + private final AdminQueryInfoRenewalService queryInfoRenewalService; + + @PostMapping("/festival-id/{festivalId}") + public ResponseEntity renewalByFestivalId( + @PathVariable Long festivalId + ) { + queryInfoRenewalService.renewalByFestivalId(festivalId); + return ResponseEntity.ok().build(); + } + + @PostMapping("/festival-period") + public ResponseEntity renewalByFestivalStartDatePeriod( + @RequestBody @Valid QueryInfoRenewalFestivalPeriodV1Request request + ) { + queryInfoRenewalService.renewalByFestivalStartDatePeriod(request.to(), request.end()); + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepository.java b/backend/src/main/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepository.java new file mode 100644 index 000000000..da40e00c5 --- /dev/null +++ b/backend/src/main/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepository.java @@ -0,0 +1,25 @@ +package com.festago.admin.repository; + +import static com.festago.festival.domain.QFestival.festival; + +import com.festago.common.querydsl.QueryDslRepositorySupport; +import com.festago.festival.domain.Festival; +import java.time.LocalDate; +import java.util.List; +import org.springframework.stereotype.Repository; + +@Repository +public class AdminFestivalIdResolverQueryDslRepository extends QueryDslRepositorySupport { + + public AdminFestivalIdResolverQueryDslRepository() { + super(Festival.class); + } + + public List findFestivalIdsByStartDatePeriod(LocalDate to, LocalDate end) { + return select(festival.id) + .from(festival) + .where(festival.festivalDuration.startDate.goe(to) + .and(festival.festivalDuration.startDate.loe(end))) + .fetch(); + } +} diff --git a/backend/src/main/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepository.java b/backend/src/main/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepository.java new file mode 100644 index 000000000..bc94976cd --- /dev/null +++ b/backend/src/main/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepository.java @@ -0,0 +1,33 @@ +package com.festago.admin.repository; + +import static com.festago.festival.domain.QFestival.festival; +import static com.festago.stage.domain.QStage.stage; + +import com.festago.common.querydsl.QueryDslRepositorySupport; +import com.festago.stage.domain.Stage; +import java.util.List; +import org.springframework.stereotype.Repository; + +@Repository +public class AdminStageIdResolverQueryDslRepository extends QueryDslRepositorySupport { + + public AdminStageIdResolverQueryDslRepository() { + super(Stage.class); + } + + public List findStageIdsByFestivalId(Long festivalId) { + return select(stage.id) + .from(stage) + .innerJoin(festival).on(festival.id.eq(stage.festival.id)) + .where(festival.id.eq(festivalId)) + .fetch(); + } + + public List findStageIdsByFestivalIdIn(List festivalIds) { + return select(stage.id) + .from(stage) + .innerJoin(festival).on(festival.id.eq(stage.festival.id)) + .where(festival.id.in(festivalIds)) + .fetch(); + } +} diff --git a/backend/src/main/java/com/festago/stage/application/StageQueryInfoEventListener.java b/backend/src/main/java/com/festago/stage/application/StageQueryInfoEventListener.java index b48678ef1..bd599b38a 100644 --- a/backend/src/main/java/com/festago/stage/application/StageQueryInfoEventListener.java +++ b/backend/src/main/java/com/festago/stage/application/StageQueryInfoEventListener.java @@ -1,20 +1,9 @@ package com.festago.stage.application; -import com.festago.artist.domain.Artist; -import com.festago.artist.domain.ArtistsSerializer; -import com.festago.artist.repository.ArtistRepository; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.InternalServerException; -import com.festago.common.exception.NotFoundException; import com.festago.stage.domain.Stage; -import com.festago.stage.domain.StageQueryInfo; import com.festago.stage.dto.event.StageCreatedEvent; import com.festago.stage.dto.event.StageDeletedEvent; import com.festago.stage.dto.event.StageUpdatedEvent; -import com.festago.stage.repository.StageArtistRepository; -import com.festago.stage.repository.StageQueryInfoRepository; -import java.util.List; -import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; @@ -27,49 +16,26 @@ @Slf4j public class StageQueryInfoEventListener { - private final StageQueryInfoRepository stageQueryInfoRepository; - private final StageArtistRepository stageArtistRepository; - private final ArtistRepository artistRepository; - private final ArtistsSerializer serializer; + private final StageQueryInfoService stageQueryInfoService; @EventListener @Transactional(propagation = Propagation.MANDATORY) public void stageCreatedEventHandler(StageCreatedEvent event) { Stage stage = event.stage(); - Long stageId = stage.getId(); - List artists = getStageArtists(stageId); - StageQueryInfo stageQueryInfo = StageQueryInfo.of(stageId, artists, serializer); - stageQueryInfoRepository.save(stageQueryInfo); - } - - /** - * 해당 메서드를 사용하는 로직이 비동기로 처리된다면 예외 던지는 것을 다시 생각해볼것! (동기로 실행되면 ControllerAdvice에서 처리가 됨) - */ - private List getStageArtists(Long stageId) { - Set artistIds = stageArtistRepository.findAllArtistIdByStageId(stageId); - List artists = artistRepository.findByIdIn(artistIds); - if (artists.size() != artistIds.size()) { - log.error("StageArtist에 존재하지 않은 Artist가 있습니다. artistsIds=" + artistIds); - throw new InternalServerException(ErrorCode.ARTIST_NOT_FOUND); - } - return artists; + stageQueryInfoService.initialStageQueryInfo(stage.getId()); } @EventListener @Transactional(propagation = Propagation.MANDATORY) public void stageUpdatedEventHandler(StageUpdatedEvent event) { Stage stage = event.stage(); - Long stageId = stage.getId(); - List artists = getStageArtists(stageId); - StageQueryInfo stageQueryInfo = stageQueryInfoRepository.findByStageId(stageId) - .orElseThrow(() -> new NotFoundException(ErrorCode.STAGE_NOT_FOUND)); - stageQueryInfo.updateArtist(artists, serializer); + stageQueryInfoService.renewalStageQueryInfo(stage.getId()); } @EventListener @Transactional(propagation = Propagation.MANDATORY) public void stageDeletedEventHandler(StageDeletedEvent event) { Stage stage = event.stage(); - stageQueryInfoRepository.deleteByStageId(stage.getId()); + stageQueryInfoService.deleteStageQueryInfo(stage.getId()); } } diff --git a/backend/src/main/java/com/festago/stage/application/StageQueryInfoService.java b/backend/src/main/java/com/festago/stage/application/StageQueryInfoService.java new file mode 100644 index 000000000..3bad1f730 --- /dev/null +++ b/backend/src/main/java/com/festago/stage/application/StageQueryInfoService.java @@ -0,0 +1,56 @@ +package com.festago.stage.application; + +import com.festago.artist.domain.Artist; +import com.festago.artist.domain.ArtistsSerializer; +import com.festago.artist.repository.ArtistRepository; +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.InternalServerException; +import com.festago.common.exception.NotFoundException; +import com.festago.stage.domain.StageQueryInfo; +import com.festago.stage.repository.StageArtistRepository; +import com.festago.stage.repository.StageQueryInfoRepository; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class StageQueryInfoService { + + private final StageQueryInfoRepository stageQueryInfoRepository; + private final StageArtistRepository stageArtistRepository; + private final ArtistRepository artistRepository; + private final ArtistsSerializer serializer; + + public void initialStageQueryInfo(Long stageId) { + List artists = getStageArtists(stageId); + StageQueryInfo stageQueryInfo = StageQueryInfo.of(stageId, artists, serializer); + stageQueryInfoRepository.save(stageQueryInfo); + } + + private List getStageArtists(Long stageId) { + Set artistIds = stageArtistRepository.findAllArtistIdByStageId(stageId); + List artists = artistRepository.findByIdIn(artistIds); + if (artists.size() != artistIds.size()) { + log.error("StageArtist에 존재하지 않은 Artist가 있습니다. artistsIds=" + artistIds); + throw new InternalServerException(ErrorCode.ARTIST_NOT_FOUND); + } + return artists; + } + + public void renewalStageQueryInfo(Long stageId) { + StageQueryInfo stageQueryInfo = stageQueryInfoRepository.findByStageId(stageId) + .orElseThrow(() -> new NotFoundException(ErrorCode.STAGE_NOT_FOUND)); + List artists = getStageArtists(stageId); + stageQueryInfo.updateArtist(artists, serializer); + } + + public void deleteStageQueryInfo(Long stageId) { + stageQueryInfoRepository.deleteByStageId(stageId); + } +} diff --git a/backend/src/test/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1ControllerTest.java b/backend/src/test/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1ControllerTest.java new file mode 100644 index 000000000..6f9c8cc9f --- /dev/null +++ b/backend/src/test/java/com/festago/admin/presentation/v1/AdminQueryInfoRenewalV1ControllerTest.java @@ -0,0 +1,117 @@ +package com.festago.admin.presentation.v1; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.festago.admin.application.AdminQueryInfoRenewalService; +import com.festago.admin.dto.queryinfo.QueryInfoRenewalFestivalPeriodV1Request; +import com.festago.auth.domain.Role; +import com.festago.support.CustomWebMvcTest; +import com.festago.support.WithMockAuth; +import jakarta.servlet.http.Cookie; +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@CustomWebMvcTest +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminQueryInfoRenewalV1ControllerTest { + + private static final Cookie TOKEN_COOKIE = new Cookie("token", "token"); + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + AdminQueryInfoRenewalService adminQueryInfoRenewalService; + + @Nested + class QueryInfo_재갱신_by_festivalId { + + final String uri = "/admin/api/v1/query-info/renewal/festival-id/{festivalId}"; + + @Nested + @DisplayName("POST " + uri) + class 올바른_주소로 { + + private long festivalId = 1L; + + @Test + @WithMockAuth(role = Role.ADMIN) + void 요청을_보내면_200_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(post(uri, festivalId) + .cookie(TOKEN_COOKIE) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void 토큰_없이_보내면_401_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(post(uri, festivalId)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuth(role = Role.MEMBER) + void 토큰의_권한이_Admin이_아니면_404_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(post(uri, festivalId) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isNotFound()); + } + } + } + + @Nested + class QueryInfo_재갱신_by_festival_startDate_period { + + final String uri = "/admin/api/v1/query-info/renewal/festival-period"; + + @Nested + @DisplayName("POST " + uri) + class 올바른_주소로 { + + @Test + @WithMockAuth(role = Role.ADMIN) + void 요청을_보내면_200_응답이_반환된다() throws Exception { + // given + var request = new QueryInfoRenewalFestivalPeriodV1Request(LocalDate.now(), LocalDate.now()); + // when & then + mockMvc.perform(post(uri) + .content(objectMapper.writeValueAsString(request)) + .cookie(TOKEN_COOKIE) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void 토큰_없이_보내면_401_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(post(uri)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuth(role = Role.MEMBER) + void 토큰의_권한이_Admin이_아니면_404_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(post(uri) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isNotFound()); + } + } + } +} diff --git a/backend/src/test/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepositoryTest.java b/backend/src/test/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepositoryTest.java new file mode 100644 index 000000000..1246c09d4 --- /dev/null +++ b/backend/src/test/java/com/festago/admin/repository/AdminFestivalIdResolverQueryDslRepositoryTest.java @@ -0,0 +1,67 @@ +package com.festago.admin.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.festago.festival.domain.Festival; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; +import com.festago.school.repository.SchoolRepository; +import com.festago.support.ApplicationIntegrationTest; +import com.festago.support.fixture.FestivalFixture; +import com.festago.support.fixture.SchoolFixture; +import java.time.LocalDate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminFestivalIdResolverQueryDslRepositoryTest extends ApplicationIntegrationTest { + + @Autowired + AdminFestivalIdResolverQueryDslRepository adminFestivalIdResolverQueryDslRepository; + + @Autowired + FestivalRepository festivalRepository; + + @Autowired + SchoolRepository schoolRepository; + + School 테코대학교; + + LocalDate _6월_12일 = LocalDate.parse("2077-06-12"); + LocalDate _6월_13일 = LocalDate.parse("2077-06-13"); + LocalDate _6월_14일 = LocalDate.parse("2077-06-14"); + LocalDate _6월_15일 = LocalDate.parse("2077-06-15"); + + @BeforeEach + void setUp() { + 테코대학교 = schoolRepository.save(SchoolFixture.builder().name("테코대학교").build()); + } + + @Nested + class findFestivalIdsByWithinDates { + + @Test + void 축제의_시작일에_포함되는_축제의_식별자_목록을_반환한다() { + // given + Festival _6월_12일_축제 = festivalRepository.save( + FestivalFixture.builder().startDate(_6월_12일).school(테코대학교).build()); + Festival _6월_13일_축제 = festivalRepository.save( + FestivalFixture.builder().startDate(_6월_13일).school(테코대학교).build()); + Festival _6월_14일_축제 = festivalRepository.save( + FestivalFixture.builder().startDate(_6월_14일).school(테코대학교).build()); + Festival _6월_15일_축제 = festivalRepository.save( + FestivalFixture.builder().startDate(_6월_15일).school(테코대학교).build()); + + // when + var actual = adminFestivalIdResolverQueryDslRepository.findFestivalIdsByStartDatePeriod(_6월_13일, _6월_14일); + + // then + assertThat(actual).containsExactlyInAnyOrder(_6월_13일_축제.getId(), _6월_14일_축제.getId()); + } + } +} diff --git a/backend/src/test/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepositoryTest.java b/backend/src/test/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepositoryTest.java new file mode 100644 index 000000000..44cff8b19 --- /dev/null +++ b/backend/src/test/java/com/festago/admin/repository/AdminStageIdResolverQueryDslRepositoryTest.java @@ -0,0 +1,94 @@ +package com.festago.admin.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.festago.festival.domain.Festival; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; +import com.festago.school.repository.SchoolRepository; +import com.festago.stage.domain.Stage; +import com.festago.stage.repository.StageRepository; +import com.festago.support.ApplicationIntegrationTest; +import com.festago.support.fixture.FestivalFixture; +import com.festago.support.fixture.SchoolFixture; +import com.festago.support.fixture.StageFixture; +import java.util.List; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminStageIdResolverQueryDslRepositoryTest extends ApplicationIntegrationTest { + + @Autowired + AdminStageIdResolverQueryDslRepository adminStageIdResolverQueryDslRepository; + + @Autowired + FestivalRepository festivalRepository; + + @Autowired + StageRepository stageRepository; + + @Autowired + SchoolRepository schoolRepository; + + School 테코대학교; + + @BeforeEach + void setUp() { + 테코대학교 = schoolRepository.save(SchoolFixture.builder().name("테코대학교").build()); + } + + @Nested + class findStageIdsByFestivalId { + + @Test + void 축제_식별자로_공연의_식별자를_모두_조회한다() { + // given + Festival festival = festivalRepository.save(FestivalFixture.builder().school(테코대학교).build()); + List expect = IntStream.rangeClosed(1, 3) + .mapToObj(i -> stageRepository.save(StageFixture.builder().festival(festival).build())) + .map(Stage::getId) + .toList(); + + // when + List actual = adminStageIdResolverQueryDslRepository.findStageIdsByFestivalId(festival.getId()); + + // then + assertThat(actual).containsExactlyInAnyOrderElementsOf(expect); + } + } + + @Nested + class findStageIdsByFestivalIdIn { + + @Test + void 축제_식별자_목록으로_공연의_식별자를_모두_조회한다() { + // given + List festivals = IntStream.rangeClosed(1, 2) + .mapToObj(i -> festivalRepository.save(FestivalFixture.builder().school(테코대학교).build())) + .toList(); + List expect = festivals.stream() + .map(festival -> IntStream.rangeClosed(1, 3) + .mapToObj(j -> stageRepository.save(StageFixture.builder().festival(festival).build())) + .map(Stage::getId) + .toList()) + .flatMap(List::stream) + .toList(); + + // when + List festivalIds = festivals.stream() + .map(Festival::getId) + .toList(); + List actual = adminStageIdResolverQueryDslRepository.findStageIdsByFestivalIdIn(festivalIds); + + // then + assertThat(actual).containsExactlyInAnyOrderElementsOf(expect); + } + } +} diff --git a/backend/src/test/java/com/festago/artist/infrastructure/DelimiterArtistsSerializer.java b/backend/src/test/java/com/festago/artist/infrastructure/DelimiterArtistsSerializer.java new file mode 100644 index 000000000..92251d9f4 --- /dev/null +++ b/backend/src/test/java/com/festago/artist/infrastructure/DelimiterArtistsSerializer.java @@ -0,0 +1,23 @@ +package com.festago.artist.infrastructure; + +import static java.util.stream.Collectors.joining; + +import com.festago.artist.domain.Artist; +import com.festago.artist.domain.ArtistsSerializer; +import java.util.List; + +public class DelimiterArtistsSerializer implements ArtistsSerializer { + + private final String delimiter; + + public DelimiterArtistsSerializer(String delimiter) { + this.delimiter = delimiter; + } + + @Override + public String serialize(List artists) { + return artists.stream() + .map(Artist::getName) + .collect(joining(delimiter)); + } +} diff --git a/backend/src/test/java/com/festago/stage/application/StageQueryInfoServiceTest.java b/backend/src/test/java/com/festago/stage/application/StageQueryInfoServiceTest.java new file mode 100644 index 000000000..72e0572dd --- /dev/null +++ b/backend/src/test/java/com/festago/stage/application/StageQueryInfoServiceTest.java @@ -0,0 +1,129 @@ +package com.festago.stage.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.festago.artist.domain.Artist; +import com.festago.artist.domain.ArtistsSerializer; +import com.festago.artist.infrastructure.DelimiterArtistsSerializer; +import com.festago.artist.repository.ArtistRepository; +import com.festago.artist.repository.MemoryArtistRepository; +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.InternalServerException; +import com.festago.common.exception.NotFoundException; +import com.festago.stage.domain.StageQueryInfo; +import com.festago.stage.repository.MemoryStageArtistRepository; +import com.festago.stage.repository.MemoryStageQueryInfoRepository; +import com.festago.stage.repository.StageArtistRepository; +import com.festago.stage.repository.StageQueryInfoRepository; +import com.festago.support.fixture.ArtistFixture; +import com.festago.support.fixture.StageArtistFixture; +import com.festago.support.fixture.StageQueryInfoFixture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class StageQueryInfoServiceTest { + + private final Long stageId = 1L; + + StageQueryInfoService stageQueryInfoService; + + StageQueryInfoRepository stageQueryInfoRepository; + + StageArtistRepository stageArtistRepository; + + ArtistRepository artistRepository; + + ArtistsSerializer artistsSerializer = new DelimiterArtistsSerializer(","); + + Artist 뉴진스; + + @BeforeEach + void setUp() { + stageQueryInfoRepository = new MemoryStageQueryInfoRepository(); + stageArtistRepository = new MemoryStageArtistRepository(); + artistRepository = new MemoryArtistRepository(); + stageQueryInfoService = new StageQueryInfoService( + stageQueryInfoRepository, + stageArtistRepository, + artistRepository, + artistsSerializer + ); + 뉴진스 = artistRepository.save(ArtistFixture.builder().name("뉴진스").build()); + } + + @Nested + class initialStageQueryInfo { + + @Test + void Artist가_존재하지_않으면_예외() { + // given + stageArtistRepository.save(StageArtistFixture.builder(stageId, 4885L).build()); + + // when + assertThatThrownBy(() -> stageQueryInfoService.initialStageQueryInfo(stageId)) + .isInstanceOf(InternalServerException.class) + .hasMessage(ErrorCode.ARTIST_NOT_FOUND.getMessage()); + } + + @Test + void StageQueryInfo가_생성된다() { + // given + stageArtistRepository.save(StageArtistFixture.builder(stageId, 뉴진스.getId()).build()); + + // when + stageQueryInfoService.initialStageQueryInfo(stageId); + + // then + assertThat(stageQueryInfoRepository.findByStageId(stageId)).isPresent(); + } + } + + @Nested + class renewalStageQueryInfo { + + @Test + void Stage_식별자에_대한_StageQueryInfo가_없으면_예외() { + // when & then + assertThatThrownBy(() -> stageQueryInfoService.renewalStageQueryInfo(stageId)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorCode.STAGE_NOT_FOUND.getMessage()); + } + + @Test + void StageQueryInfo가_새롭게_갱신된다() { + // given + stageQueryInfoRepository.save( + StageQueryInfoFixture.builder().stageId(stageId).artistInfo("oldInfo").build()); + stageArtistRepository.save(StageArtistFixture.builder(stageId, 뉴진스.getId()).build()); + + // when + stageQueryInfoService.renewalStageQueryInfo(stageId); + + // then + StageQueryInfo stageQueryInfo = stageQueryInfoRepository.findByStageId(stageId).get(); + assertThat(stageQueryInfo.getArtistInfo()).isNotEqualTo("oldInfo"); + } + } + + @Nested + class deleteStageQueryInfo { + + @Test + void StageQueryInfo가_삭제된다() { + // given + stageQueryInfoRepository.save(StageQueryInfoFixture.builder().stageId(stageId).build()); + + // when + stageQueryInfoService.deleteStageQueryInfo(stageId); + + // then + assertThat(stageQueryInfoRepository.findByStageId(stageId)).isEmpty(); + } + } +} diff --git a/backend/src/test/java/com/festago/stage/repository/MemoryStageQueryInfoRepository.java b/backend/src/test/java/com/festago/stage/repository/MemoryStageQueryInfoRepository.java new file mode 100644 index 000000000..ce7fa4e46 --- /dev/null +++ b/backend/src/test/java/com/festago/stage/repository/MemoryStageQueryInfoRepository.java @@ -0,0 +1,24 @@ +package com.festago.stage.repository; + +import com.festago.stage.domain.StageQueryInfo; +import com.festago.support.AbstractMemoryRepository; +import java.util.Objects; +import java.util.Optional; + +public class MemoryStageQueryInfoRepository extends AbstractMemoryRepository implements + StageQueryInfoRepository { + + @Override + public Optional findByStageId(Long stageId) { + return memory.values() + .stream() + .filter(stageQueryInfo -> Objects.equals(stageQueryInfo.getStageId(), stageId)) + .findAny(); + } + + @Override + public void deleteByStageId(Long stageId) { + memory.entrySet() + .removeIf(entry -> Objects.equals(entry.getValue().getStageId(), stageId)); + } +} diff --git a/backend/src/test/java/com/festago/support/fixture/FestivalFixture.java b/backend/src/test/java/com/festago/support/fixture/FestivalFixture.java index 4e9da3f22..a677b27ba 100644 --- a/backend/src/test/java/com/festago/support/fixture/FestivalFixture.java +++ b/backend/src/test/java/com/festago/support/fixture/FestivalFixture.java @@ -10,7 +10,7 @@ public class FestivalFixture extends BaseFixture { private Long id; private String name; private LocalDate startDate = LocalDate.now(); - private LocalDate endDate = LocalDate.now().plusDays(3L); + private LocalDate endDate; private String posterImageUrl = "https://picsum.photos/536/354"; private School school = SchoolFixture.builder().build(); @@ -52,6 +52,9 @@ public FestivalFixture school(School school) { } public Festival build() { + if (endDate == null) { + endDate = startDate.plusDays(3L); + } return new Festival(id, uniqueValue("페스타고 대학교 축제", name), new FestivalDuration(startDate, endDate), posterImageUrl, school); } From 133b107e16d1aa2249d6607d75e5db9760291af7 Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:36:30 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[BE]=20refactor:=20CreateCommand,=20UpdateC?= =?UTF-8?q?ommand=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B9=8C=EB=8D=94=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?(#940)=20(#943)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Command 빌더 패턴 적용 (cherry picked from commit 9f34f2b82a8e1541306b33849a3aba14f5f88d41) * test: 테스트 코드에서 Command 생성 빌더 사용하도록 변경 (cherry picked from commit 7279977cd77c663a47361e387da821edc8d6603d) * refactor: SchoolCreateCommand, SchoolUpdateCommand 패키지 이동 및 검증 로직 제거 - 도메인 응집도 향상을 위해 DTO 검증 로직 제거 (cherry picked from commit ab06daaff355c56ac7e68d034f33333603eb814c) * test: 누락된 빌터 패턴 적용 추가 * fix: evnet -> event 패키지명 수정 --- .../dto/artist/ArtistV1CreateRequest.java | 10 +- .../dto/artist/ArtistV1UpdateRequest.java | 10 +- .../dto/festival/FestivalV1CreateRequest.java | 14 +- .../dto/festival/FestivalV1UpdateRequest.java | 12 +- .../dto/school/SchoolV1CreateRequest.java | 2 +- .../dto/school/SchoolV1UpdateRequest.java | 2 +- .../admin/dto/stage/StageV1CreateRequest.java | 12 +- .../admin/dto/stage/StageV1UpdateRequest.java | 10 +- .../dto/command/ArtistCreateCommand.java | 3 + .../dto/command/ArtistUpdateCommand.java | 3 + .../festago/auth/dto/AdminLoginV1Request.java | 5 +- .../auth/dto/AdminSignupV1Request.java | 5 +- .../auth/dto/command/AdminLoginCommand.java | 3 + .../auth/dto/command/AdminSignupCommand.java | 3 + .../dto/command/FestivalCreateCommand.java | 2 + .../dto/command/FestivalUpdateCommand.java | 2 + .../application/SchoolCommandService.java | 10 +- .../application/SchoolDeleteService.java | 2 +- .../school/dto/SchoolCreateCommand.java | 37 ----- .../school/dto/SchoolUpdateCommand.java | 21 --- .../dto/command/SchoolCreateCommand.java | 26 +++ .../dto/command/SchoolUpdateCommand.java | 14 ++ .../{evnet => event}/SchoolCreatedEvent.java | 2 +- .../{evnet => event}/SchoolDeletedEvent.java | 2 +- .../{evnet => event}/SchoolUpdatedEvent.java | 2 +- .../stage/dto/command/StageCreateCommand.java | 2 + .../stage/dto/command/StageUpdateCommand.java | 2 + ...UploadImagesStatusChangeEventListener.java | 6 +- .../steps/AdminStepDefinitions.java | 6 +- .../steps/FestivalStepDefinitions.java | 8 +- .../v1/AdminSchoolV1ControllerTest.java | 2 +- .../application/ArtistCommandServiceTest.java | 14 +- .../command/AdminAuthCommandServiceTest.java | 30 +++- ...FestivalV1QueryServiceIntegrationTest.java | 84 +++++----- .../command/FestivalCreateServiceTest.java | 28 ++-- .../command/FestivalUpdateServiceTest.java | 24 +-- ...alDetailV1QueryServiceIntegrationTest.java | 155 +++++++++++------- ...FestivalV1QueryServiceIntegrationTest.java | 106 ++++++------ ...FestivalV1QueryServiceIntegrationTest.java | 85 +++++----- .../application/SchoolCommandServiceTest.java | 4 +- .../SocialMediaCommandServiceTest.java | 68 ++++---- .../StageCommandServiceIntegrationTest.java | 116 ++++++------- .../command/StageCreateServiceTest.java | 79 +++++---- .../command/StageUpdateServiceTest.java | 70 ++++---- 44 files changed, 594 insertions(+), 509 deletions(-) delete mode 100644 backend/src/main/java/com/festago/school/dto/SchoolCreateCommand.java delete mode 100644 backend/src/main/java/com/festago/school/dto/SchoolUpdateCommand.java create mode 100644 backend/src/main/java/com/festago/school/dto/command/SchoolCreateCommand.java create mode 100644 backend/src/main/java/com/festago/school/dto/command/SchoolUpdateCommand.java rename backend/src/main/java/com/festago/school/dto/{evnet => event}/SchoolCreatedEvent.java (72%) rename backend/src/main/java/com/festago/school/dto/{evnet => event}/SchoolDeletedEvent.java (61%) rename backend/src/main/java/com/festago/school/dto/{evnet => event}/SchoolUpdatedEvent.java (72%) diff --git a/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1CreateRequest.java b/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1CreateRequest.java index b5dd0bea3..f59c5d30b 100644 --- a/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1CreateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1CreateRequest.java @@ -14,10 +14,10 @@ public record ArtistV1CreateRequest( ) { public ArtistCreateCommand toCommand() { - return new ArtistCreateCommand( - name, - profileImageUrl, - backgroundImageUrl - ); + return ArtistCreateCommand.builder() + .name(name) + .profileImageUrl(profileImageUrl) + .backgroundImageUrl(backgroundImageUrl) + .build(); } } diff --git a/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1UpdateRequest.java b/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1UpdateRequest.java index 42ea06955..46a51b4bc 100644 --- a/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1UpdateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/artist/ArtistV1UpdateRequest.java @@ -14,10 +14,10 @@ public record ArtistV1UpdateRequest( ) { public ArtistUpdateCommand toCommand() { - return new ArtistUpdateCommand( - name, - profileImageUrl, - backgroundImageUrl - ); + return ArtistUpdateCommand.builder() + .name(name) + .profileImageUrl(profileImageUrl) + .backgroundImageUrl(backgroundImageUrl) + .build(); } } diff --git a/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1CreateRequest.java b/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1CreateRequest.java index 95b1d0c98..90d1022d5 100644 --- a/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1CreateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1CreateRequest.java @@ -27,12 +27,12 @@ public record FestivalV1CreateRequest( ) { public FestivalCreateCommand toCommand() { - return new FestivalCreateCommand( - name, - startDate, - endDate, - posterImageUrl, - schoolId - ); + return FestivalCreateCommand.builder() + .name(name) + .startDate(startDate) + .endDate(endDate) + .posterImageUrl(posterImageUrl) + .schoolId(schoolId) + .build(); } } diff --git a/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1UpdateRequest.java b/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1UpdateRequest.java index dc1a92f05..26c37b68e 100644 --- a/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1UpdateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/festival/FestivalV1UpdateRequest.java @@ -24,11 +24,11 @@ public record FestivalV1UpdateRequest( ) { public FestivalUpdateCommand toCommand() { - return new FestivalUpdateCommand( - name, - startDate, - endDate, - posterImageUrl - ); + return FestivalUpdateCommand.builder() + .name(name) + .startDate(startDate) + .endDate(endDate) + .posterImageUrl(posterImageUrl) + .build(); } } diff --git a/backend/src/main/java/com/festago/admin/dto/school/SchoolV1CreateRequest.java b/backend/src/main/java/com/festago/admin/dto/school/SchoolV1CreateRequest.java index 43b0795d2..b2e2d9318 100644 --- a/backend/src/main/java/com/festago/admin/dto/school/SchoolV1CreateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/school/SchoolV1CreateRequest.java @@ -1,7 +1,7 @@ package com.festago.admin.dto.school; import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; +import com.festago.school.dto.command.SchoolCreateCommand; import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/festago/admin/dto/school/SchoolV1UpdateRequest.java b/backend/src/main/java/com/festago/admin/dto/school/SchoolV1UpdateRequest.java index 870cca5e5..14c1737c0 100644 --- a/backend/src/main/java/com/festago/admin/dto/school/SchoolV1UpdateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/school/SchoolV1UpdateRequest.java @@ -1,7 +1,7 @@ package com.festago.admin.dto.school; import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolUpdateCommand; +import com.festago.school.dto.command.SchoolUpdateCommand; import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/festago/admin/dto/stage/StageV1CreateRequest.java b/backend/src/main/java/com/festago/admin/dto/stage/StageV1CreateRequest.java index b34bd5785..d862d498a 100644 --- a/backend/src/main/java/com/festago/admin/dto/stage/StageV1CreateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/stage/StageV1CreateRequest.java @@ -20,11 +20,11 @@ public record StageV1CreateRequest( ) { public StageCreateCommand toCommand() { - return new StageCreateCommand( - festivalId, - startTime, - ticketOpenTime, - artistIds - ); + return StageCreateCommand.builder() + .festivalId(festivalId) + .startTime(startTime) + .ticketOpenTime(ticketOpenTime) + .artistIds(artistIds) + .build(); } } diff --git a/backend/src/main/java/com/festago/admin/dto/stage/StageV1UpdateRequest.java b/backend/src/main/java/com/festago/admin/dto/stage/StageV1UpdateRequest.java index cecf04b76..ce0b0f361 100644 --- a/backend/src/main/java/com/festago/admin/dto/stage/StageV1UpdateRequest.java +++ b/backend/src/main/java/com/festago/admin/dto/stage/StageV1UpdateRequest.java @@ -18,10 +18,10 @@ public record StageV1UpdateRequest( ) { public StageUpdateCommand toCommand() { - return new StageUpdateCommand( - startTime, - ticketOpenTime, - artistIds - ); + return StageUpdateCommand.builder() + .startTime(startTime) + .ticketOpenTime(ticketOpenTime) + .artistIds(artistIds) + .build(); } } diff --git a/backend/src/main/java/com/festago/artist/dto/command/ArtistCreateCommand.java b/backend/src/main/java/com/festago/artist/dto/command/ArtistCreateCommand.java index 949d586c2..16119e6de 100644 --- a/backend/src/main/java/com/festago/artist/dto/command/ArtistCreateCommand.java +++ b/backend/src/main/java/com/festago/artist/dto/command/ArtistCreateCommand.java @@ -1,5 +1,8 @@ package com.festago.artist.dto.command; +import lombok.Builder; + +@Builder public record ArtistCreateCommand( String name, String profileImageUrl, diff --git a/backend/src/main/java/com/festago/artist/dto/command/ArtistUpdateCommand.java b/backend/src/main/java/com/festago/artist/dto/command/ArtistUpdateCommand.java index 9ece595c0..c14d165d4 100644 --- a/backend/src/main/java/com/festago/artist/dto/command/ArtistUpdateCommand.java +++ b/backend/src/main/java/com/festago/artist/dto/command/ArtistUpdateCommand.java @@ -1,5 +1,8 @@ package com.festago.artist.dto.command; +import lombok.Builder; + +@Builder public record ArtistUpdateCommand( String name, String profileImageUrl, diff --git a/backend/src/main/java/com/festago/auth/dto/AdminLoginV1Request.java b/backend/src/main/java/com/festago/auth/dto/AdminLoginV1Request.java index 47fa8d59d..e5051f97e 100644 --- a/backend/src/main/java/com/festago/auth/dto/AdminLoginV1Request.java +++ b/backend/src/main/java/com/festago/auth/dto/AdminLoginV1Request.java @@ -11,6 +11,9 @@ public record AdminLoginV1Request( ) { public AdminLoginCommand toCommand() { - return new AdminLoginCommand(username, password); + return AdminLoginCommand.builder() + .username(username) + .password(password) + .build(); } } diff --git a/backend/src/main/java/com/festago/auth/dto/AdminSignupV1Request.java b/backend/src/main/java/com/festago/auth/dto/AdminSignupV1Request.java index 9e58dde72..bd630c314 100644 --- a/backend/src/main/java/com/festago/auth/dto/AdminSignupV1Request.java +++ b/backend/src/main/java/com/festago/auth/dto/AdminSignupV1Request.java @@ -11,6 +11,9 @@ public record AdminSignupV1Request( ) { public AdminSignupCommand toCommand() { - return new AdminSignupCommand(username, password); + return AdminSignupCommand.builder() + .username(username) + .password(password) + .build(); } } diff --git a/backend/src/main/java/com/festago/auth/dto/command/AdminLoginCommand.java b/backend/src/main/java/com/festago/auth/dto/command/AdminLoginCommand.java index 083d7047e..6b628d8ef 100644 --- a/backend/src/main/java/com/festago/auth/dto/command/AdminLoginCommand.java +++ b/backend/src/main/java/com/festago/auth/dto/command/AdminLoginCommand.java @@ -1,5 +1,8 @@ package com.festago.auth.dto.command; +import lombok.Builder; + +@Builder public record AdminLoginCommand( String username, String password diff --git a/backend/src/main/java/com/festago/auth/dto/command/AdminSignupCommand.java b/backend/src/main/java/com/festago/auth/dto/command/AdminSignupCommand.java index 215d2b948..8d2295a3f 100644 --- a/backend/src/main/java/com/festago/auth/dto/command/AdminSignupCommand.java +++ b/backend/src/main/java/com/festago/auth/dto/command/AdminSignupCommand.java @@ -1,5 +1,8 @@ package com.festago.auth.dto.command; +import lombok.Builder; + +@Builder public record AdminSignupCommand( String username, String password diff --git a/backend/src/main/java/com/festago/festival/dto/command/FestivalCreateCommand.java b/backend/src/main/java/com/festago/festival/dto/command/FestivalCreateCommand.java index 7db308a8a..f5f1b551f 100644 --- a/backend/src/main/java/com/festago/festival/dto/command/FestivalCreateCommand.java +++ b/backend/src/main/java/com/festago/festival/dto/command/FestivalCreateCommand.java @@ -4,7 +4,9 @@ import com.festago.festival.domain.FestivalDuration; import com.festago.school.domain.School; import java.time.LocalDate; +import lombok.Builder; +@Builder public record FestivalCreateCommand( String name, LocalDate startDate, diff --git a/backend/src/main/java/com/festago/festival/dto/command/FestivalUpdateCommand.java b/backend/src/main/java/com/festago/festival/dto/command/FestivalUpdateCommand.java index 57aa9f1de..6fadfcf8d 100644 --- a/backend/src/main/java/com/festago/festival/dto/command/FestivalUpdateCommand.java +++ b/backend/src/main/java/com/festago/festival/dto/command/FestivalUpdateCommand.java @@ -1,7 +1,9 @@ package com.festago.festival.dto.command; import java.time.LocalDate; +import lombok.Builder; +@Builder public record FestivalUpdateCommand( String name, LocalDate startDate, diff --git a/backend/src/main/java/com/festago/school/application/SchoolCommandService.java b/backend/src/main/java/com/festago/school/application/SchoolCommandService.java index 5f1cda3e8..afe34c449 100644 --- a/backend/src/main/java/com/festago/school/application/SchoolCommandService.java +++ b/backend/src/main/java/com/festago/school/application/SchoolCommandService.java @@ -3,10 +3,10 @@ import com.festago.common.exception.BadRequestException; import com.festago.common.exception.ErrorCode; import com.festago.school.domain.School; -import com.festago.school.dto.SchoolCreateCommand; -import com.festago.school.dto.SchoolUpdateCommand; -import com.festago.school.dto.evnet.SchoolCreatedEvent; -import com.festago.school.dto.evnet.SchoolUpdatedEvent; +import com.festago.school.dto.command.SchoolCreateCommand; +import com.festago.school.dto.command.SchoolUpdateCommand; +import com.festago.school.dto.event.SchoolCreatedEvent; +import com.festago.school.dto.event.SchoolUpdatedEvent; import com.festago.school.repository.SchoolRepository; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -24,7 +24,7 @@ public class SchoolCommandService { public Long createSchool(SchoolCreateCommand command) { validateCreate(command); - School school = schoolRepository.save(command.toDomain()); + School school = schoolRepository.save(command.toEntity()); eventPublisher.publishEvent(new SchoolCreatedEvent(school)); return school.getId(); } diff --git a/backend/src/main/java/com/festago/school/application/SchoolDeleteService.java b/backend/src/main/java/com/festago/school/application/SchoolDeleteService.java index 1d45a4719..db06eea83 100644 --- a/backend/src/main/java/com/festago/school/application/SchoolDeleteService.java +++ b/backend/src/main/java/com/festago/school/application/SchoolDeleteService.java @@ -1,7 +1,7 @@ package com.festago.school.application; import com.festago.school.domain.validator.SchoolDeleteValidator; -import com.festago.school.dto.evnet.SchoolDeletedEvent; +import com.festago.school.dto.event.SchoolDeletedEvent; import com.festago.school.repository.SchoolRepository; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/com/festago/school/dto/SchoolCreateCommand.java b/backend/src/main/java/com/festago/school/dto/SchoolCreateCommand.java deleted file mode 100644 index 6b266319a..000000000 --- a/backend/src/main/java/com/festago/school/dto/SchoolCreateCommand.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.festago.school.dto; - -import com.festago.common.util.Validator; -import com.festago.school.domain.School; -import com.festago.school.domain.SchoolRegion; -import lombok.Builder; - -@Builder -public record SchoolCreateCommand( - String name, - String domain, - SchoolRegion region, - String logoUrl, - String backgroundImageUrl -) { - - public SchoolCreateCommand { - Validator.notNull(name, "name"); - Validator.notNull(domain, "domain"); - Validator.notNull(region, "region"); - } - - /** - * TODO 도메인에도 빌더 패턴을 적용해야할까? - * 생성자에 같은 타입이 중복적으로 발생하여 버그 발생 가능성이 매우 높다. - */ - public School toDomain() { - return new School( - null, - domain, - name, - logoUrl, - backgroundImageUrl, - region - ); - } -} diff --git a/backend/src/main/java/com/festago/school/dto/SchoolUpdateCommand.java b/backend/src/main/java/com/festago/school/dto/SchoolUpdateCommand.java deleted file mode 100644 index 0dcbf4a62..000000000 --- a/backend/src/main/java/com/festago/school/dto/SchoolUpdateCommand.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.festago.school.dto; - -import com.festago.common.util.Validator; -import com.festago.school.domain.SchoolRegion; -import lombok.Builder; - -@Builder -public record SchoolUpdateCommand( - String name, - String domain, - SchoolRegion region, - String logoUrl, - String backgroundImageUrl -) { - - public SchoolUpdateCommand { - Validator.notNull(name, "name"); - Validator.notNull(domain, "domain"); - Validator.notNull(region, "region"); - } -} diff --git a/backend/src/main/java/com/festago/school/dto/command/SchoolCreateCommand.java b/backend/src/main/java/com/festago/school/dto/command/SchoolCreateCommand.java new file mode 100644 index 000000000..989ae928e --- /dev/null +++ b/backend/src/main/java/com/festago/school/dto/command/SchoolCreateCommand.java @@ -0,0 +1,26 @@ +package com.festago.school.dto.command; + +import com.festago.school.domain.School; +import com.festago.school.domain.SchoolRegion; +import lombok.Builder; + +@Builder +public record SchoolCreateCommand( + String name, + String domain, + SchoolRegion region, + String logoUrl, + String backgroundImageUrl +) { + + public School toEntity() { + return new School( + null, + domain, + name, + logoUrl, + backgroundImageUrl, + region + ); + } +} diff --git a/backend/src/main/java/com/festago/school/dto/command/SchoolUpdateCommand.java b/backend/src/main/java/com/festago/school/dto/command/SchoolUpdateCommand.java new file mode 100644 index 000000000..58f6235d1 --- /dev/null +++ b/backend/src/main/java/com/festago/school/dto/command/SchoolUpdateCommand.java @@ -0,0 +1,14 @@ +package com.festago.school.dto.command; + +import com.festago.school.domain.SchoolRegion; +import lombok.Builder; + +@Builder +public record SchoolUpdateCommand( + String name, + String domain, + SchoolRegion region, + String logoUrl, + String backgroundImageUrl +) { +} diff --git a/backend/src/main/java/com/festago/school/dto/evnet/SchoolCreatedEvent.java b/backend/src/main/java/com/festago/school/dto/event/SchoolCreatedEvent.java similarity index 72% rename from backend/src/main/java/com/festago/school/dto/evnet/SchoolCreatedEvent.java rename to backend/src/main/java/com/festago/school/dto/event/SchoolCreatedEvent.java index 48fbc5c2f..20836f105 100644 --- a/backend/src/main/java/com/festago/school/dto/evnet/SchoolCreatedEvent.java +++ b/backend/src/main/java/com/festago/school/dto/event/SchoolCreatedEvent.java @@ -1,4 +1,4 @@ -package com.festago.school.dto.evnet; +package com.festago.school.dto.event; import com.festago.school.domain.School; diff --git a/backend/src/main/java/com/festago/school/dto/evnet/SchoolDeletedEvent.java b/backend/src/main/java/com/festago/school/dto/event/SchoolDeletedEvent.java similarity index 61% rename from backend/src/main/java/com/festago/school/dto/evnet/SchoolDeletedEvent.java rename to backend/src/main/java/com/festago/school/dto/event/SchoolDeletedEvent.java index 1377a215a..444713afc 100644 --- a/backend/src/main/java/com/festago/school/dto/evnet/SchoolDeletedEvent.java +++ b/backend/src/main/java/com/festago/school/dto/event/SchoolDeletedEvent.java @@ -1,4 +1,4 @@ -package com.festago.school.dto.evnet; +package com.festago.school.dto.event; public record SchoolDeletedEvent( Long schoolId diff --git a/backend/src/main/java/com/festago/school/dto/evnet/SchoolUpdatedEvent.java b/backend/src/main/java/com/festago/school/dto/event/SchoolUpdatedEvent.java similarity index 72% rename from backend/src/main/java/com/festago/school/dto/evnet/SchoolUpdatedEvent.java rename to backend/src/main/java/com/festago/school/dto/event/SchoolUpdatedEvent.java index cfa48f01c..923a87c81 100644 --- a/backend/src/main/java/com/festago/school/dto/evnet/SchoolUpdatedEvent.java +++ b/backend/src/main/java/com/festago/school/dto/event/SchoolUpdatedEvent.java @@ -1,4 +1,4 @@ -package com.festago.school.dto.evnet; +package com.festago.school.dto.event; import com.festago.school.domain.School; diff --git a/backend/src/main/java/com/festago/stage/dto/command/StageCreateCommand.java b/backend/src/main/java/com/festago/stage/dto/command/StageCreateCommand.java index c7d5bcaee..f696c102c 100644 --- a/backend/src/main/java/com/festago/stage/dto/command/StageCreateCommand.java +++ b/backend/src/main/java/com/festago/stage/dto/command/StageCreateCommand.java @@ -2,7 +2,9 @@ import java.time.LocalDateTime; import java.util.List; +import lombok.Builder; +@Builder public record StageCreateCommand( Long festivalId, LocalDateTime startTime, diff --git a/backend/src/main/java/com/festago/stage/dto/command/StageUpdateCommand.java b/backend/src/main/java/com/festago/stage/dto/command/StageUpdateCommand.java index 46b452b8c..86b064d0b 100644 --- a/backend/src/main/java/com/festago/stage/dto/command/StageUpdateCommand.java +++ b/backend/src/main/java/com/festago/stage/dto/command/StageUpdateCommand.java @@ -2,7 +2,9 @@ import java.time.LocalDateTime; import java.util.List; +import lombok.Builder; +@Builder public record StageUpdateCommand( LocalDateTime startTime, LocalDateTime ticketOpenTime, diff --git a/backend/src/main/java/com/festago/upload/application/school/AsyncSchoolUploadImagesStatusChangeEventListener.java b/backend/src/main/java/com/festago/upload/application/school/AsyncSchoolUploadImagesStatusChangeEventListener.java index 663ee1ac1..184f72251 100644 --- a/backend/src/main/java/com/festago/upload/application/school/AsyncSchoolUploadImagesStatusChangeEventListener.java +++ b/backend/src/main/java/com/festago/upload/application/school/AsyncSchoolUploadImagesStatusChangeEventListener.java @@ -3,9 +3,9 @@ import static com.festago.upload.domain.FileOwnerType.SCHOOL; import com.festago.school.domain.School; -import com.festago.school.dto.evnet.SchoolCreatedEvent; -import com.festago.school.dto.evnet.SchoolDeletedEvent; -import com.festago.school.dto.evnet.SchoolUpdatedEvent; +import com.festago.school.dto.event.SchoolCreatedEvent; +import com.festago.school.dto.event.SchoolDeletedEvent; +import com.festago.school.dto.event.SchoolUpdatedEvent; import com.festago.upload.application.UploadFileStatusChangeService; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/backend/src/test/java/com/festago/acceptance/steps/AdminStepDefinitions.java b/backend/src/test/java/com/festago/acceptance/steps/AdminStepDefinitions.java index 582a738f7..c45f169eb 100644 --- a/backend/src/test/java/com/festago/acceptance/steps/AdminStepDefinitions.java +++ b/backend/src/test/java/com/festago/acceptance/steps/AdminStepDefinitions.java @@ -16,7 +16,11 @@ public class AdminStepDefinitions { @Given("어드민 계정으로 로그인한다.") public void loginAdmin() { - var adminLoginResult = adminAuthCommandService.login(new AdminLoginCommand("admin", "1234")); + AdminLoginCommand command = AdminLoginCommand.builder() + .username("admin") + .password("1234") + .build(); + var adminLoginResult = adminAuthCommandService.login(command); cucumberClient.setToken(adminLoginResult.accessToken()); } diff --git a/backend/src/test/java/com/festago/acceptance/steps/FestivalStepDefinitions.java b/backend/src/test/java/com/festago/acceptance/steps/FestivalStepDefinitions.java index b8c2c9dab..c620e726a 100644 --- a/backend/src/test/java/com/festago/acceptance/steps/FestivalStepDefinitions.java +++ b/backend/src/test/java/com/festago/acceptance/steps/FestivalStepDefinitions.java @@ -36,7 +36,13 @@ public class FestivalStepDefinitions { LocalDate startDate = LocalDate.parse(시작일, DATE_TIME_FORMATTER); LocalDate endDate = LocalDate.parse(종료일, DATE_TIME_FORMATTER); Long schoolId = schoolRepository.findByName(학교이름).get().getId(); - var command = new FestivalCreateCommand(축제이름, startDate, endDate, "https://image.com/image.png", schoolId); + var command = FestivalCreateCommand.builder() + .name(축제이름) + .startDate(startDate) + .endDate(endDate) + .posterImageUrl("https://image.com/image.png") + .schoolId(schoolId) + .build(); festivalCreateService.createFestival(command); } diff --git a/backend/src/test/java/com/festago/admin/presentation/v1/AdminSchoolV1ControllerTest.java b/backend/src/test/java/com/festago/admin/presentation/v1/AdminSchoolV1ControllerTest.java index 8020bac7d..3b29b51a0 100644 --- a/backend/src/test/java/com/festago/admin/presentation/v1/AdminSchoolV1ControllerTest.java +++ b/backend/src/test/java/com/festago/admin/presentation/v1/AdminSchoolV1ControllerTest.java @@ -21,7 +21,7 @@ import com.festago.school.application.SchoolCommandService; import com.festago.school.application.SchoolDeleteService; import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; +import com.festago.school.dto.command.SchoolCreateCommand; import com.festago.support.CustomWebMvcTest; import com.festago.support.WithMockAuth; import jakarta.servlet.http.Cookie; diff --git a/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java b/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java index f8ce4ef82..7f177fbe0 100644 --- a/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java +++ b/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java @@ -33,8 +33,11 @@ void setUp() { @Test void 아티스트를_저장한다() { // given - ArtistCreateCommand command = new ArtistCreateCommand("윤서연", "https://image.com/image.png", - "https://image.com/image.png"); + var command = ArtistCreateCommand.builder() + .name("윤서연") + .profileImageUrl("https://image.com/image.png") + .backgroundImageUrl("https://image.com/image.png") + .build(); // when Long artistId = artistCommandService.save(command); @@ -47,8 +50,11 @@ void setUp() { void 아티스트_정보를_변경한다() { // given Long artistId = artistRepository.save(ArtistFixture.builder().name("고윤하").build()).getId(); - ArtistUpdateCommand command = new ArtistUpdateCommand("윤하", "https://image.com/image2.png", - "https://image.com/image2.png"); + var command = ArtistUpdateCommand.builder() + .name("윤하") + .profileImageUrl("https://image.com/image2.png") + .backgroundImageUrl("https://image.com/image2.png") + .build(); // when artistCommandService.update(command, artistId); diff --git a/backend/src/test/java/com/festago/auth/application/command/AdminAuthCommandServiceTest.java b/backend/src/test/java/com/festago/auth/application/command/AdminAuthCommandServiceTest.java index c1838a14c..0fc53dfd4 100644 --- a/backend/src/test/java/com/festago/auth/application/command/AdminAuthCommandServiceTest.java +++ b/backend/src/test/java/com/festago/auth/application/command/AdminAuthCommandServiceTest.java @@ -56,7 +56,10 @@ class 로그인 { @Test void 계정이_없으면_예외() { // given - var command = new AdminLoginCommand("admin", "password"); + var command = AdminLoginCommand.builder() + .username("admin") + .password("password") + .build(); // when & then assertThatThrownBy(() -> adminAuthCommandService.login(command)) @@ -71,7 +74,10 @@ class 로그인 { .username("admin") .password("{noop}password") .build()); - var command = new AdminLoginCommand("admin", "admin"); + var command = AdminLoginCommand.builder() + .username("admin") + .password("admin") + .build(); // when & then assertThatThrownBy(() -> adminAuthCommandService.login(command)) @@ -86,7 +92,10 @@ class 로그인 { .username("admin") .password("{noop}password") .build()); - var command = new AdminLoginCommand("admin", "password"); + var command = AdminLoginCommand.builder() + .username("admin") + .password("password") + .build(); given(authTokenProvider.provide(any())) .willReturn(new TokenResponse("token", LocalDateTime.now().plusWeeks(1))); @@ -105,7 +114,10 @@ class 가입 { void 닉네임이_중복이면_예외() { // given Admin rootAdmin = adminRepository.save(Admin.createRootAdmin("{noop}password")); - var command = new AdminSignupCommand("admin", "password"); + var command = AdminSignupCommand.builder() + .username("admin") + .password("password") + .build(); // when & then Long rootAdminId = rootAdmin.getId(); @@ -121,7 +133,10 @@ class 가입 { .username("glen") .password("{noop}password") .build()); - var command = new AdminSignupCommand("newAdmin", "password"); + var command = AdminSignupCommand.builder() + .username("newAdmin") + .password("password") + .build(); // when & then Long adminId = admin.getId(); @@ -134,7 +149,10 @@ class 가입 { void 성공() { // given Admin rootAdmin = adminRepository.save(Admin.createRootAdmin("{noop}password")); - var command = new AdminSignupCommand("newAdmin", "password"); + var command = AdminSignupCommand.builder() + .username("newAdmin") + .password("password") + .build(); // when adminAuthCommandService.signup(rootAdmin.getId(), command); diff --git a/backend/src/test/java/com/festago/festival/application/QueryDslSchoolSearchRecentFestivalV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/festival/application/QueryDslSchoolSearchRecentFestivalV1QueryServiceIntegrationTest.java index 44b0ffb8d..909dba7f6 100644 --- a/backend/src/test/java/com/festago/festival/application/QueryDslSchoolSearchRecentFestivalV1QueryServiceIntegrationTest.java +++ b/backend/src/test/java/com/festago/festival/application/QueryDslSchoolSearchRecentFestivalV1QueryServiceIntegrationTest.java @@ -3,13 +3,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; -import com.festago.festival.application.command.FestivalCreateService; -import com.festago.festival.dto.command.FestivalCreateCommand; -import com.festago.school.application.SchoolCommandService; -import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; +import com.festago.school.repository.SchoolRepository; import com.festago.support.ApplicationIntegrationTest; import com.festago.support.TimeInstantProvider; +import com.festago.support.fixture.FestivalFixture; +import com.festago.support.fixture.SchoolFixture; import java.time.Clock; import java.time.LocalDate; import java.util.List; @@ -23,25 +23,21 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class QueryDslSchoolSearchRecentFestivalV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { - private static final String LOGO_URL = "https://image.com/logo.png"; - private static final String BACKGROUND_IMAGE_URL = "https://image.com/backgroundimage.png"; - private static final String POSTER_IMAGE_URL = "https://image.com/posterimage.png"; - @Autowired QueryDslSchoolUpcomingFestivalStartDateV1QueryService schoolUpcomingFestivalStartDateV1QueryService; @Autowired - SchoolCommandService schoolCommandService; + SchoolRepository schoolRepository; @Autowired - FestivalCreateService festivalCreateService; + FestivalRepository festivalRepository; @Autowired Clock clock; - Long 테코대학교_식별자; + School 테코대학교; - Long 우테대학교_식별자; + School 우테대학교; LocalDate _6월_14일 = LocalDate.parse("2077-06-14"); LocalDate _6월_15일 = LocalDate.parse("2077-06-15"); @@ -54,20 +50,28 @@ class QueryDslSchoolSearchRecentFestivalV1QueryServiceIntegrationTest extends Ap */ @BeforeEach void setUp() { - 테코대학교_식별자 = schoolCommandService.createSchool( - new SchoolCreateCommand("테코대학교", "teco.ac.kr", SchoolRegion.서울, LOGO_URL, BACKGROUND_IMAGE_URL) - ); - 우테대학교_식별자 = schoolCommandService.createSchool( - new SchoolCreateCommand("우테대학교", "wote.ac.kr", SchoolRegion.서울, LOGO_URL, BACKGROUND_IMAGE_URL) - ); - festivalCreateService.createFestival( - new FestivalCreateCommand("테코대학교 6월 15일 당일 축제", _6월_15일, _6월_15일, POSTER_IMAGE_URL, 테코대학교_식별자) + 테코대학교 = schoolRepository.save(SchoolFixture.builder().name("테코대학교").build()); + 우테대학교 = schoolRepository.save(SchoolFixture.builder().name("우테대학교").build()); + festivalRepository.save(FestivalFixture.builder() + .name("테코대학교 6월 15일 당일 축제") + .startDate(_6월_15일) + .endDate(_6월_15일) + .school(테코대학교) + .build() ); - festivalCreateService.createFestival( - new FestivalCreateCommand("테코대학교 6월 16일 당일 축제", _6월_16일, _6월_16일, POSTER_IMAGE_URL, 테코대학교_식별자) + festivalRepository.save(FestivalFixture.builder() + .name("테코대학교 6월 16일 당일 축제") + .startDate(_6월_16일) + .endDate(_6월_16일) + .school(테코대학교) + .build() ); - festivalCreateService.createFestival( - new FestivalCreateCommand("우테대학교 6월 16~17일 축제", _6월_16일, _6월_17일, POSTER_IMAGE_URL, 우테대학교_식별자) + festivalRepository.save(FestivalFixture.builder() + .name("우테대학교 6월 16~17일 축제") + .startDate(_6월_16일) + .endDate(_6월_17일) + .school(우테대학교) + .build() ); } @@ -79,12 +83,12 @@ void setUp() { // when var actual = schoolUpcomingFestivalStartDateV1QueryService.getSchoolIdToUpcomingFestivalStartDate( - List.of(테코대학교_식별자, 우테대학교_식별자) + List.of(테코대학교.getId(), 우테대학교.getId()) ); // then - assertThat(actual.get(테코대학교_식별자)).isEqualTo(_6월_15일); - assertThat(actual.get(우테대학교_식별자)).isEqualTo(_6월_16일); + assertThat(actual.get(테코대학교.getId())).isEqualTo(_6월_15일); + assertThat(actual.get(우테대학교.getId())).isEqualTo(_6월_16일); } @Test @@ -95,12 +99,12 @@ void setUp() { // when var actual = schoolUpcomingFestivalStartDateV1QueryService.getSchoolIdToUpcomingFestivalStartDate( - List.of(테코대학교_식별자, 우테대학교_식별자) + List.of(테코대학교.getId(), 우테대학교.getId()) ); // then - assertThat(actual.get(테코대학교_식별자)).isEqualTo(_6월_15일); - assertThat(actual.get(우테대학교_식별자)).isEqualTo(_6월_16일); + assertThat(actual.get(테코대학교.getId())).isEqualTo(_6월_15일); + assertThat(actual.get(우테대학교.getId())).isEqualTo(_6월_16일); } @Test @@ -111,12 +115,12 @@ void setUp() { // when var actual = schoolUpcomingFestivalStartDateV1QueryService.getSchoolIdToUpcomingFestivalStartDate( - List.of(테코대학교_식별자, 우테대학교_식별자) + List.of(테코대학교.getId(), 우테대학교.getId()) ); // then - assertThat(actual.get(테코대학교_식별자)).isEqualTo(_6월_16일); - assertThat(actual.get(우테대학교_식별자)).isEqualTo(_6월_16일); + assertThat(actual.get(테코대학교.getId())).isEqualTo(_6월_16일); + assertThat(actual.get(우테대학교.getId())).isEqualTo(_6월_16일); } @Test @@ -127,12 +131,12 @@ void setUp() { // when var actual = schoolUpcomingFestivalStartDateV1QueryService.getSchoolIdToUpcomingFestivalStartDate( - List.of(테코대학교_식별자, 우테대학교_식별자) + List.of(테코대학교.getId(), 우테대학교.getId()) ); // then - assertThat(actual.get(테코대학교_식별자)).isNull(); - assertThat(actual.get(우테대학교_식별자)).isEqualTo(_6월_16일); + assertThat(actual.get(테코대학교.getId())).isNull(); + assertThat(actual.get(우테대학교.getId())).isEqualTo(_6월_16일); } @Test @@ -143,11 +147,11 @@ void setUp() { // when var actual = schoolUpcomingFestivalStartDateV1QueryService.getSchoolIdToUpcomingFestivalStartDate( - List.of(테코대학교_식별자, 우테대학교_식별자) + List.of(테코대학교.getId(), 우테대학교.getId()) ); // then - assertThat(actual.get(테코대학교_식별자)).isNull(); - assertThat(actual.get(우테대학교_식별자)).isNull(); + assertThat(actual.get(테코대학교.getId())).isNull(); + assertThat(actual.get(우테대학교.getId())).isNull(); } } diff --git a/backend/src/test/java/com/festago/festival/application/integration/command/FestivalCreateServiceTest.java b/backend/src/test/java/com/festago/festival/application/integration/command/FestivalCreateServiceTest.java index 3074c91ba..feb784cdd 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/command/FestivalCreateServiceTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/command/FestivalCreateServiceTest.java @@ -64,13 +64,13 @@ void setUp() { LocalDate startDate = now.minusDays(1); LocalDate endDate = now.plusDays(3); String posterImageUrl = "https://image.com/image.png"; - var command = new FestivalCreateCommand( - festivalName, - startDate, - endDate, - posterImageUrl, - schoolId - ); + var command = FestivalCreateCommand.builder() + .name(festivalName) + .startDate(startDate) + .endDate(endDate) + .posterImageUrl(posterImageUrl) + .schoolId(schoolId) + .build(); // when & then assertThatThrownBy(() -> festivalCreateService.createFestival(command)) @@ -85,13 +85,13 @@ void setUp() { LocalDate startDate = now.plusDays(1); LocalDate endDate = now.plusDays(3); String posterImageUrl = "https://image.com/image.png"; - var command = new FestivalCreateCommand( - festivalName, - startDate, - endDate, - posterImageUrl, - schoolId - ); + var command = FestivalCreateCommand.builder() + .name(festivalName) + .startDate(startDate) + .endDate(endDate) + .posterImageUrl(posterImageUrl) + .schoolId(schoolId) + .build(); // when Long festivalId = festivalCreateService.createFestival(command); diff --git a/backend/src/test/java/com/festago/festival/application/integration/command/FestivalUpdateServiceTest.java b/backend/src/test/java/com/festago/festival/application/integration/command/FestivalUpdateServiceTest.java index 5ad33ee4f..b5357fb6d 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/command/FestivalUpdateServiceTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/command/FestivalUpdateServiceTest.java @@ -60,12 +60,12 @@ void setUp() { LocalDate newStartDate = now.minusDays(1); LocalDate newEndDate = now.plusDays(1); String newPosterImageUrl = "https://image.com/new-image.png"; - var command = new FestivalUpdateCommand( - newFestivalName, - newStartDate, - newEndDate, - newPosterImageUrl - ); + var command = FestivalUpdateCommand.builder() + .name(newFestivalName) + .startDate(newStartDate) + .endDate(newEndDate) + .posterImageUrl(newPosterImageUrl) + .build(); // when festivalUpdateService.updateFestival(festivalId, command); @@ -87,12 +87,12 @@ void setUp() { LocalDate newStartDate = now.plusDays(1); LocalDate newEndDate = now.plusDays(1); String newPosterImageUrl = "https://image.com/new-image.png"; - var command = new FestivalUpdateCommand( - newFestivalName, - newStartDate, - newEndDate, - newPosterImageUrl - ); + var command = FestivalUpdateCommand.builder() + .name(newFestivalName) + .startDate(newStartDate) + .endDate(newEndDate) + .posterImageUrl(newPosterImageUrl) + .build(); // when festivalUpdateService.updateFestival(festivalId, command); diff --git a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java index bdd1e587e..af1631c77 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java @@ -3,37 +3,39 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import com.festago.artist.application.ArtistCommandService; -import com.festago.artist.dto.command.ArtistCreateCommand; +import com.festago.artist.domain.Artist; +import com.festago.artist.repository.ArtistRepository; import com.festago.common.exception.ErrorCode; import com.festago.common.exception.NotFoundException; import com.festago.festival.application.FestivalDetailV1QueryService; -import com.festago.festival.application.command.FestivalCreateService; +import com.festago.festival.domain.Festival; import com.festago.festival.dto.SocialMediaV1Response; -import com.festago.festival.dto.command.FestivalCreateCommand; -import com.festago.school.application.SchoolCommandService; -import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; +import com.festago.school.repository.SchoolRepository; import com.festago.socialmedia.domain.OwnerType; import com.festago.socialmedia.domain.SocialMediaType; import com.festago.socialmedia.repository.SocialMediaRepository; import com.festago.stage.application.command.StageCreateService; -import com.festago.stage.dto.command.StageCreateCommand; +import com.festago.stage.domain.Stage; +import com.festago.stage.repository.StageArtistRepository; +import com.festago.stage.repository.StageRepository; import com.festago.support.ApplicationIntegrationTest; +import com.festago.support.fixture.ArtistFixture; +import com.festago.support.fixture.FestivalFixture; +import com.festago.support.fixture.SchoolFixture; import com.festago.support.fixture.SocialMediaFixture; +import com.festago.support.fixture.StageArtistFixture; +import com.festago.support.fixture.StageFixture; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") -@Transactional class FestivalDetailV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { @Autowired @@ -43,22 +45,28 @@ class FestivalDetailV1QueryServiceIntegrationTest extends ApplicationIntegration SocialMediaRepository socialMediaRepository; @Autowired - SchoolCommandService schoolCommandService; + SchoolRepository schoolRepository; @Autowired - FestivalCreateService festivalCreateService; + FestivalRepository festivalRepository; @Autowired StageCreateService stageCreateService; @Autowired - ArtistCommandService artistCommandService; + StageRepository stageRepository; + + @Autowired + StageArtistRepository stageArtistRepository; + + @Autowired + ArtistRepository artistRepository; LocalDate now = LocalDate.parse("2077-06-30"); - Long 테코대학교_축제_식별자; - Long 테코대학교_공연_없는_축제_식별자; - Long 우테대학교_축제_식별자; + Festival 테코대학교_축제; + Festival 테코대학교_공연_없는_축제; + Festival 우테대학교_축제; /** * 테코대학교 축제는 공연이 있는 3일 기간의 축제와 공연이 없는 당일 축제가 있다.
테코대학교는 소셜미디어에 인스타그램과 페이스북이 등록되어 있다.

우테대학교 축제는 공연이 @@ -66,37 +74,56 @@ class FestivalDetailV1QueryServiceIntegrationTest extends ApplicationIntegration */ @BeforeEach void setUp() { - Long 테코대학교_식별자 = createSchool("테코대학교", "teco.ac.kr"); - Long 우테대학교_식별자 = createSchool("우테대학교", "wote.ac.kr"); - - 테코대학교_축제_식별자 = festivalCreateService.createFestival(new FestivalCreateCommand( - "테코대학교 축제", now, now.plusDays(2), "https://school.com/image.com", 테코대학교_식별자 - )); - 테코대학교_공연_없는_축제_식별자 = festivalCreateService.createFestival(new FestivalCreateCommand( - "테코대학교 공연 없는 축제", now, now, "https://school.com/image.com", 테코대학교_식별자 - )); - 우테대학교_축제_식별자 = festivalCreateService.createFestival(new FestivalCreateCommand( - "우테대학교 축제", now, now, "https://school.com/image.com", 우테대학교_식별자 - )); - - Long 아티스트_식별자 = createArtist("아티스트A"); - - LocalDateTime ticketOpenTime = now.minusWeeks(1).atStartOfDay(); - stageCreateService.createStage(new StageCreateCommand( - 테코대학교_축제_식별자, now.atTime(18, 0), ticketOpenTime, List.of(아티스트_식별자) - )); - stageCreateService.createStage(new StageCreateCommand( - 테코대학교_축제_식별자, now.plusDays(1).atTime(18, 0), ticketOpenTime, List.of(아티스트_식별자) - )); - stageCreateService.createStage(new StageCreateCommand( - 테코대학교_축제_식별자, now.plusDays(2).atTime(18, 0), ticketOpenTime, List.of(아티스트_식별자) - )); - stageCreateService.createStage(new StageCreateCommand( - 우테대학교_축제_식별자, now.atTime(18, 0), ticketOpenTime, List.of(아티스트_식별자) - )); + School 테코대학교 = createSchool("테코대학교", "teco.ac.kr"); + School 우테대학교 = createSchool("우테대학교", "wote.ac.kr"); + + 테코대학교_축제 = festivalRepository.save(FestivalFixture.builder() + .startDate(now) + .endDate(now.plusDays(2)) + .school(테코대학교) + .build() + ); + 테코대학교_공연_없는_축제 = festivalRepository.save(FestivalFixture.builder() + .startDate(now) + .endDate(now) + .school(테코대학교) + .build() + ); + 우테대학교_축제 = festivalRepository.save(FestivalFixture.builder() + .startDate(now) + .endDate(now) + .school(우테대학교) + .build() + ); + Artist 아티스트A = createArtist("아티스트A"); + + Stage 테코대학교_축제_1일차_공연 = stageRepository.save(StageFixture.builder() + .festival(테코대학교_축제) + .startTime(now.atTime(18, 0)) + .build() + ); + Stage 테코대학교_축제_2일차_공연 = stageRepository.save(StageFixture.builder() + .festival(테코대학교_축제) + .startTime(now.plusDays(1).atTime(18, 0)) + .build() + ); + Stage 테코대학교_축제_3일차_공연 = stageRepository.save(StageFixture.builder() + .festival(테코대학교_축제) + .startTime(now.plusDays(2).atTime(18, 0)) + .build() + ); + Stage 우테대학교_축제_당일_공연 = stageRepository.save(StageFixture.builder() + .festival(우테대학교_축제) + .startTime(now.atTime(18, 0)) + .build() + ); + stageArtistRepository.save(StageArtistFixture.builder(테코대학교_축제_1일차_공연.getId(), 아티스트A.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(테코대학교_축제_2일차_공연.getId(), 아티스트A.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(테코대학교_축제_3일차_공연.getId(), 아티스트A.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(우테대학교_축제_당일_공연.getId(), 아티스트A.getId()).build()); socialMediaRepository.save(SocialMediaFixture.builder() - .ownerId(테코대학교_식별자) + .ownerId(테코대학교.getId()) .ownerType(OwnerType.SCHOOL) .mediaType(SocialMediaType.INSTAGRAM) .name("총학생회 인스타그램") @@ -104,7 +131,7 @@ void setUp() { .url("https://instagram.com/테코대학교_총학생회") .build()); socialMediaRepository.save(SocialMediaFixture.builder() - .ownerId(테코대학교_식별자) + .ownerId(테코대학교.getId()) .ownerType(OwnerType.SCHOOL) .mediaType(SocialMediaType.FACEBOOK) .name("총학생회 페이스북") @@ -114,29 +141,31 @@ void setUp() { ); } - private Long createArtist(String artistName) { - return artistCommandService.save(new ArtistCreateCommand( - artistName, "https://image.com/profileImage.png", "https://image.com/background.png" - )); + private Artist createArtist(String artistName) { + Artist artist = ArtistFixture.builder() + .name(artistName) + .build(); + return artistRepository.save(artist); } - private Long createSchool(String schoolName, String domain) { - return schoolCommandService.createSchool(new SchoolCreateCommand( - schoolName, domain, SchoolRegion.서울, "https://image.com/logo.png", "https://image.com/background.png" - )); + private School createSchool(String schoolName, String domain) { + School school = SchoolFixture.builder() + .name(schoolName) + .domain(domain) + .build(); + return schoolRepository.save(school); } @Test void 축제의_식별자로_축제의_상세_조회를_할_수_있다() { // when - var response = festivalDetailV1QueryService.findFestivalDetail(테코대학교_축제_식별자); + var response = festivalDetailV1QueryService.findFestivalDetail(테코대학교_축제.getId()); // then assertSoftly(softly -> { - softly.assertThat(response.id()).isEqualTo(테코대학교_축제_식별자); + softly.assertThat(response.id()).isEqualTo(테코대학교_축제.getId()); softly.assertThat(response.startDate()).isEqualTo("2077-06-30"); softly.assertThat(response.endDate()).isEqualTo("2077-07-02"); - softly.assertThat(response.posterImageUrl()).isEqualTo("https://school.com/image.com"); softly.assertThat(response.school().name()).isEqualTo("테코대학교"); softly.assertThat(response.socialMedias()) .map(SocialMediaV1Response::name) @@ -148,11 +177,11 @@ private Long createSchool(String schoolName, String domain) { @Test void 축제에_공연이_없으면_응답의_공연에는_비어있는_컬렉션이_반환된다() { // when - var response = festivalDetailV1QueryService.findFestivalDetail(테코대학교_공연_없는_축제_식별자); + var response = festivalDetailV1QueryService.findFestivalDetail(테코대학교_공연_없는_축제.getId()); // then assertSoftly(softly -> { - softly.assertThat(response.id()).isEqualTo(테코대학교_공연_없는_축제_식별자); + softly.assertThat(response.id()).isEqualTo(테코대학교_공연_없는_축제.getId()); softly.assertThat(response.stages()).isEmpty(); softly.assertThat(response.socialMedias()) .map(SocialMediaV1Response::name) @@ -163,11 +192,11 @@ private Long createSchool(String schoolName, String domain) { @Test void 축제에_속한_학교에_소셜미디어가_없으면_소셜미디어에는_비어있는_컬렉션이_반환된다() { // when - var response = festivalDetailV1QueryService.findFestivalDetail(우테대학교_축제_식별자); + var response = festivalDetailV1QueryService.findFestivalDetail(우테대학교_축제.getId()); // then assertSoftly(softly -> { - softly.assertThat(response.id()).isEqualTo(우테대학교_축제_식별자); + softly.assertThat(response.id()).isEqualTo(우테대학교_축제.getId()); softly.assertThat(response.socialMedias()).isEmpty(); softly.assertThat(response.stages()).hasSize(1); }); diff --git a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalV1QueryServiceIntegrationTest.java index c0394e17f..46b73d75b 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalV1QueryServiceIntegrationTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalV1QueryServiceIntegrationTest.java @@ -5,16 +5,20 @@ import static org.mockito.BDDMockito.given; import com.festago.festival.application.FestivalV1QueryService; -import com.festago.festival.application.command.FestivalCreateService; +import com.festago.festival.domain.Festival; import com.festago.festival.dto.FestivalV1QueryRequest; import com.festago.festival.dto.FestivalV1Response; -import com.festago.festival.dto.command.FestivalCreateCommand; import com.festago.festival.repository.FestivalFilter; -import com.festago.school.application.SchoolCommandService; +import com.festago.festival.repository.FestivalInfoRepository; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; +import com.festago.school.repository.SchoolRepository; import com.festago.support.ApplicationIntegrationTest; import com.festago.support.TimeInstantProvider; +import com.festago.support.fixture.FestivalFixture; +import com.festago.support.fixture.FestivalQueryInfoFixture; +import com.festago.support.fixture.SchoolFixture; import java.time.Clock; import java.time.LocalDate; import java.util.List; @@ -29,22 +33,22 @@ import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; -import org.springframework.transaction.annotation.Transactional; -// TODO Repository 사용하지 않고 Service로 데이터 세팅하도록 변경 @DisplayNameGeneration(ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") -@Transactional class FestivalV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { @Autowired FestivalV1QueryService festivalV1QueryService; @Autowired - FestivalCreateService festivalCreateService; + FestivalRepository festivalRepository; @Autowired - SchoolCommandService schoolCommandService; + SchoolRepository schoolRepository; + + @Autowired + FestivalInfoRepository festivalInfoRepository; @Autowired Clock clock; @@ -52,16 +56,16 @@ class FestivalV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { LocalDate now = LocalDate.parse("2077-07-10"); // 진행중 - Long 서울대학교_8일_12일_축제_식별자; - Long 서울대학교_6일_12일_축제_식별자; - Long 대구대학교_9일_12일_축제_식별자; - Long 부산대학교_6일_13일_축제_식별자; - Long 부산대학교_6일_12일_축제_식별자; + Festival 서울대학교_8일_12일_축제; + Festival 서울대학교_6일_12일_축제; + Festival 대구대학교_9일_12일_축제; + Festival 부산대학교_6일_13일_축제; + Festival 부산대학교_6일_12일_축제; // 진행 예정 - Long 대구대학교_13일_14일_축제_식별자; - Long 대구대학교_12일_14일_축제_식별자; - Long 부산대학교_12일_14일_축제_식별자; + Festival 대구대학교_13일_14일_축제; + Festival 대구대학교_12일_14일_축제; + Festival 부산대학교_12일_14일_축제; /** * 현재 시간

2023년 7월 10일

@@ -74,40 +78,40 @@ class FestivalV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { */ @BeforeEach void setting() { - Long 서울대학교_식별자 = createSchool("서울대학교", "seoul.ac.kr", SchoolRegion.서울); - Long 부산대학교_식별자 = createSchool("부산대학교", "busan.ac.kr", SchoolRegion.부산); - Long 대구대학교_식별자 = createSchool("대구대학교", "daegu.ac.kr", SchoolRegion.대구); + School 서울대학교 = createSchool("서울대학교", SchoolRegion.서울); + School 부산대학교 = createSchool("부산대학교", SchoolRegion.부산); + School 대구대학교 = createSchool("대구대학교", SchoolRegion.대구); // 진행 중 - 서울대학교_8일_12일_축제_식별자 = createFestival("서울대학교_8일_12일_축제", now.minusDays(2), now.plusDays(2), 서울대학교_식별자); - 서울대학교_6일_12일_축제_식별자 = createFestival("서울대학교_6일_12일_축제", now.minusDays(4), now.plusDays(2), 서울대학교_식별자); - 대구대학교_9일_12일_축제_식별자 = createFestival("대구대학교_9일_12일_축제", now.minusDays(1), now.plusDays(2), 대구대학교_식별자); - 부산대학교_6일_13일_축제_식별자 = createFestival("부산대학교_6일_13일_축제", now.minusDays(4), now.plusDays(3), 부산대학교_식별자); - 부산대학교_6일_12일_축제_식별자 = createFestival("부산대학교_6일_12일_축제", now.minusDays(4), now.plusDays(2), 부산대학교_식별자); + 서울대학교_8일_12일_축제 = createFestival("서울대학교_8일_12일_축제", now.minusDays(2), now.plusDays(2), 서울대학교); + 서울대학교_6일_12일_축제 = createFestival("서울대학교_6일_12일_축제", now.minusDays(4), now.plusDays(2), 서울대학교); + 대구대학교_9일_12일_축제 = createFestival("대구대학교_9일_12일_축제", now.minusDays(1), now.plusDays(2), 대구대학교); + 부산대학교_6일_13일_축제 = createFestival("부산대학교_6일_13일_축제", now.minusDays(4), now.plusDays(3), 부산대학교); + 부산대학교_6일_12일_축제 = createFestival("부산대학교_6일_12일_축제", now.minusDays(4), now.plusDays(2), 부산대학교); // 진행 예정 - 대구대학교_13일_14일_축제_식별자 = createFestival("대구대학교_13일_14일_축제", now.plusDays(3), now.plusDays(4), 대구대학교_식별자); - 부산대학교_12일_14일_축제_식별자 = createFestival("부산대학교_12일_14일_축제", now.plusDays(2), now.plusDays(4), 부산대학교_식별자); - 대구대학교_12일_14일_축제_식별자 = createFestival("대구대학교_12일_14일_축제", now.plusDays(2), now.plusDays(4), 대구대학교_식별자); + 대구대학교_13일_14일_축제 = createFestival("대구대학교_13일_14일_축제", now.plusDays(3), now.plusDays(4), 대구대학교); + 부산대학교_12일_14일_축제 = createFestival("부산대학교_12일_14일_축제", now.plusDays(2), now.plusDays(4), 부산대학교); + 대구대학교_12일_14일_축제 = createFestival("대구대학교_12일_14일_축제", now.plusDays(2), now.plusDays(4), 대구대학교); given(clock.instant()) .willReturn(TimeInstantProvider.from(now)); } - private Long createSchool(String schoolName, String domain, SchoolRegion region) { - return schoolCommandService.createSchool(new SchoolCreateCommand( - schoolName, - domain, - region, - "https://image.com/logo.png", - "https://image.com/background.png" - )); + private School createSchool(String schoolName, SchoolRegion region) { + return schoolRepository.save(SchoolFixture.builder().name(schoolName).region(region).build()); } - private Long createFestival(String festivalName, LocalDate startDate, LocalDate endDate, Long schoolId) { - return festivalCreateService.createFestival(new FestivalCreateCommand( - festivalName, startDate, endDate, "https://image.com/posterImage.png", schoolId - )); + private Festival createFestival(String festivalName, LocalDate startDate, LocalDate endDate, School school) { + Festival festival = festivalRepository.save(FestivalFixture.builder() + .name(festivalName) + .startDate(startDate) + .endDate(endDate) + .school(school) + .build() + ); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(festival.getId()).build()); + return festival; } @Nested @@ -170,9 +174,9 @@ class 지역_필터_미적용 { assertThat(response.getContent()) .map(FestivalV1Response::id) .containsExactly( - 부산대학교_12일_14일_축제_식별자, - 대구대학교_12일_14일_축제_식별자, - 대구대학교_13일_14일_축제_식별자 + 부산대학교_12일_14일_축제.getId(), + 대구대학교_12일_14일_축제.getId(), + 대구대학교_13일_14일_축제.getId() ); } @@ -188,11 +192,11 @@ class 지역_필터_미적용 { assertThat(response.getContent()) .map(FestivalV1Response::id) .containsExactly( - 대구대학교_9일_12일_축제_식별자, - 서울대학교_8일_12일_축제_식별자, - 서울대학교_6일_12일_축제_식별자, - 부산대학교_6일_13일_축제_식별자, - 부산대학교_6일_12일_축제_식별자 + 대구대학교_9일_12일_축제.getId(), + 서울대학교_8일_12일_축제.getId(), + 서울대학교_6일_12일_축제.getId(), + 부산대학교_6일_13일_축제.getId(), + 부산대학교_6일_12일_축제.getId() ); } @@ -216,9 +220,9 @@ class 지역_필터_미적용 { softly.assertThat(secondResponse.getContent()) .map(FestivalV1Response::id) .containsExactly( - 서울대학교_6일_12일_축제_식별자, - 부산대학교_6일_13일_축제_식별자, - 부산대학교_6일_12일_축제_식별자 + 서울대학교_6일_12일_축제.getId(), + 부산대학교_6일_13일_축제.getId(), + 부산대학교_6일_12일_축제.getId() ); }); } diff --git a/backend/src/test/java/com/festago/festival/application/integration/query/PopularFestivalV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/festival/application/integration/query/PopularFestivalV1QueryServiceIntegrationTest.java index d85f1ccaf..e19c11fe6 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/query/PopularFestivalV1QueryServiceIntegrationTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/query/PopularFestivalV1QueryServiceIntegrationTest.java @@ -3,14 +3,16 @@ import static org.assertj.core.api.Assertions.assertThat; import com.festago.festival.application.PopularFestivalV1QueryService; -import com.festago.festival.application.command.FestivalCreateService; +import com.festago.festival.domain.Festival; import com.festago.festival.dto.FestivalV1Response; -import com.festago.festival.dto.command.FestivalCreateCommand; -import com.festago.school.application.SchoolCommandService; -import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; +import com.festago.festival.repository.FestivalInfoRepository; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; +import com.festago.school.repository.SchoolRepository; import com.festago.support.ApplicationIntegrationTest; -import java.time.LocalDate; +import com.festago.support.fixture.FestivalFixture; +import com.festago.support.fixture.FestivalQueryInfoFixture; +import com.festago.support.fixture.SchoolFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; @@ -25,64 +27,61 @@ class PopularFestivalV1QueryServiceIntegrationTest extends ApplicationIntegratio PopularFestivalV1QueryService popularQueryService; @Autowired - FestivalCreateService festivalCreateService; + FestivalRepository festivalRepository; @Autowired - SchoolCommandService schoolCommandService; + FestivalInfoRepository festivalInfoRepository; - LocalDate now = LocalDate.parse("2077-06-30"); + @Autowired + SchoolRepository schoolRepository; - Long 대학교_식별자; + School 대학교; - Long 첫번째로_저장된_축제_식별자; - Long 두번째로_저장된_축제_식별자; - Long 세번째로_저장된_축제_식별자; - Long 네번째로_저장된_축제_식별자; - Long 다섯번째로_저장된_축제_식별자; - Long 여섯번째로_저장된_축제_식별자; - Long 일곱번째로_저장된_축제_식별자; - Long 여덟번째로_저장된_축제_식별자; + Festival 첫번째로_저장된_축제; + Festival 두번째로_저장된_축제; + Festival 세번째로_저장된_축제; + Festival 네번째로_저장된_축제; + Festival 다섯번째로_저장된_축제; + Festival 여섯번째로_저장된_축제; + Festival 일곱번째로_저장된_축제; + Festival 여덟번째로_저장된_축제; @BeforeEach void setUp() { - 대학교_식별자 = schoolCommandService.createSchool(new SchoolCreateCommand( - "테코대학교", - "teco.ac.kr", - SchoolRegion.서울, - "https://image.com/logo.png", - "https://image.com/background.png" - )); + 대학교 = schoolRepository.save(SchoolFixture.builder().build()); - 첫번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("첫번째로 저장된 축제")); - 두번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("두번째로 저장된 축제")); - 세번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("세번째로 저장된 축제")); - 네번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("네번째로 저장된 축제")); - 다섯번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("다섯번째로 저장된 축제")); - 여섯번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("여섯번째로 저장된 축제")); - 일곱번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("일곱번째로 저장된 축제")); - 여덟번째로_저장된_축제_식별자 = festivalCreateService.createFestival(getFestivalCreateCommand("여덟번째로 저장된 축제")); + 첫번째로_저장된_축제 = createFestival(); + 두번째로_저장된_축제 = createFestival(); + 세번째로_저장된_축제 = createFestival(); + 네번째로_저장된_축제 = createFestival(); + 다섯번째로_저장된_축제 = createFestival(); + 여섯번째로_저장된_축제 = createFestival(); + 일곱번째로_저장된_축제 = createFestival(); + 여덟번째로_저장된_축제 = createFestival(); } - private FestivalCreateCommand getFestivalCreateCommand(String schoolName) { - return new FestivalCreateCommand(schoolName, now, now, "https://image.com/posterImage.png", 대학교_식별자); + private Festival createFestival() { + Festival festival = festivalRepository.save(FestivalFixture.builder().school(대학교).build()); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(festival.getId()).build()); + return festival; } @Test void 인기_축제는_7개까지_반환되고_식별자의_내림차순으로_정렬되어_조회된다() { - // given && when + // when var expect = popularQueryService.findPopularFestivals().content(); // then assertThat(expect) .map(FestivalV1Response::id) .containsExactly( - 여덟번째로_저장된_축제_식별자, - 일곱번째로_저장된_축제_식별자, - 여섯번째로_저장된_축제_식별자, - 다섯번째로_저장된_축제_식별자, - 네번째로_저장된_축제_식별자, - 세번째로_저장된_축제_식별자, - 두번째로_저장된_축제_식별자 + 여덟번째로_저장된_축제.getId(), + 일곱번째로_저장된_축제.getId(), + 여섯번째로_저장된_축제.getId(), + 다섯번째로_저장된_축제.getId(), + 네번째로_저장된_축제.getId(), + 세번째로_저장된_축제.getId(), + 두번째로_저장된_축제.getId() ); } } diff --git a/backend/src/test/java/com/festago/school/application/SchoolCommandServiceTest.java b/backend/src/test/java/com/festago/school/application/SchoolCommandServiceTest.java index b67b5390b..3b8919481 100644 --- a/backend/src/test/java/com/festago/school/application/SchoolCommandServiceTest.java +++ b/backend/src/test/java/com/festago/school/application/SchoolCommandServiceTest.java @@ -10,8 +10,8 @@ import com.festago.common.exception.NotFoundException; import com.festago.school.domain.School; import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateCommand; -import com.festago.school.dto.SchoolUpdateCommand; +import com.festago.school.dto.command.SchoolCreateCommand; +import com.festago.school.dto.command.SchoolUpdateCommand; import com.festago.school.repository.MemorySchoolRepository; import com.festago.school.repository.SchoolRepository; import com.festago.support.fixture.SchoolFixture; diff --git a/backend/src/test/java/com/festago/socialmedia/application/SocialMediaCommandServiceTest.java b/backend/src/test/java/com/festago/socialmedia/application/SocialMediaCommandServiceTest.java index cbdc39304..8b6c32e5e 100644 --- a/backend/src/test/java/com/festago/socialmedia/application/SocialMediaCommandServiceTest.java +++ b/backend/src/test/java/com/festago/socialmedia/application/SocialMediaCommandServiceTest.java @@ -67,14 +67,14 @@ class createSocialMedia { .build()); // when & then - var command = new SocialMediaCreateCommand( - socialMedia.getOwnerId(), - socialMedia.getOwnerType(), - socialMedia.getMediaType(), - socialMedia.getName(), - socialMedia.getLogoUrl(), - socialMedia.getUrl() - ); + var command = SocialMediaCreateCommand.builder() + .ownerId(socialMedia.getId()) + .ownerType(socialMedia.getOwnerType()) + .socialMediaType(socialMedia.getMediaType()) + .name(socialMedia.getName()) + .logoUrl(socialMedia.getLogoUrl()) + .url(socialMedia.getUrl()) + .build(); assertThatThrownBy(() -> socialMediaCommandService.createSocialMedia(command)) .isInstanceOf(BadRequestException.class) .hasMessage(ErrorCode.DUPLICATE_SOCIAL_MEDIA.getMessage()); @@ -84,14 +84,14 @@ class createSocialMedia { @Test void 추가하려는_소셜미디어의_owner가_존재하지_않으면_예외() { // when & then - var command = new SocialMediaCreateCommand( - 4885L, - OwnerType.SCHOOL, - SocialMediaType.INSTAGRAM, - "테코대학교 인스타그램", - "https://image.com/logo.png", - "https://instagram.com/tecodaehak" - ); + var command = SocialMediaCreateCommand.builder() + .ownerId(4885L) + .ownerType(OwnerType.SCHOOL) + .socialMediaType(SocialMediaType.INSTAGRAM) + .name("테코대학교 인스타그램") + .logoUrl("https://image.com/logo.png") + .url("https://instagram.com/tecodaehak") + .build(); assertThatThrownBy(() -> socialMediaCommandService.createSocialMedia(command)) .isInstanceOf(NotFoundException.class) .hasMessage(ErrorCode.SCHOOL_NOT_FOUND.getMessage()); @@ -103,14 +103,14 @@ class createSocialMedia { School 테코대학교 = schoolRepository.save(SchoolFixture.builder().name("테코대학교").build()); // when - var command = new SocialMediaCreateCommand( - 테코대학교.getId(), - OwnerType.SCHOOL, - SocialMediaType.INSTAGRAM, - "테코대학교 인스타그램", - "https://image.com/logo.png", - "https://instagram.com/tecodaehak" - ); + var command = SocialMediaCreateCommand.builder() + .ownerId(테코대학교.getId()) + .ownerType(OwnerType.SCHOOL) + .socialMediaType(SocialMediaType.INSTAGRAM) + .name("테코대학교 인스타그램") + .logoUrl("https://image.com/logo.png") + .url("https://instagram.com/tecodaehak") + .build(); Long socialMediaId = socialMediaCommandService.createSocialMedia(command); // then @@ -124,11 +124,11 @@ class updateSocialMedia { @Test void 소셜미디어의_식별자에_대한_소셜미디어가_존재하지_않으면_예외() { // when & then - var command = new SocialMediaUpdateCommand( - "테코대학교 인스타그램", - "https://instagram.com/tecodaehak", - "https://image.com/logo.png" - ); + var command = SocialMediaUpdateCommand.builder() + .name("테코대학교 인스타그램") + .url("https://instagram.com/tecodaehak") + .logoUrl("https://image.com/logo.png") + .build(); assertThatThrownBy(() -> socialMediaCommandService.updateSocialMedia(4885L, command)) .isInstanceOf(NotFoundException.class) .hasMessage(ErrorCode.SOCIAL_MEDIA_NOT_FOUND.getMessage()); @@ -146,11 +146,11 @@ class updateSocialMedia { .build()); // when - var command = new SocialMediaUpdateCommand( - "테코대학교 인스타그램", - "https://instagram.com/tecodaehak", - "https://image.com/logo.png" - ); + var command = SocialMediaUpdateCommand.builder() + .name("테코대학교 인스타그램") + .url("https://instagram.com/tecodaehak") + .logoUrl("https://image.com/logo.png") + .build(); socialMediaCommandService.updateSocialMedia(socialMedia.getId(), command); // then diff --git a/backend/src/test/java/com/festago/stage/application/command/StageCommandServiceIntegrationTest.java b/backend/src/test/java/com/festago/stage/application/command/StageCommandServiceIntegrationTest.java index 3dbdf46b7..1973b438e 100644 --- a/backend/src/test/java/com/festago/stage/application/command/StageCommandServiceIntegrationTest.java +++ b/backend/src/test/java/com/festago/stage/application/command/StageCommandServiceIntegrationTest.java @@ -105,12 +105,12 @@ class createStage { @Test void 공연을_생성하면_StageQueryInfo가_저장된다() { // given - var command = new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자) - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자)) + .build(); // when Long stageId = stageCreateService.createStage(command); @@ -122,12 +122,12 @@ class createStage { @Test void 공연을_생성하면_FestivalQueryInfo가_갱신된다() { // given - var command = new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자) - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자)) + .build(); // when FestivalQueryInfo previosFestivalQueryInfo = festivalInfoRepository.findByFestivalId(테코대학교_축제_식별자).get(); @@ -144,18 +144,18 @@ class createStage { @Test void 공연이_여러_개_추가되면_FestivalQueryInfo에_추가된_공연의_ArtistInfo가_갱신된다() throws Exception { // given - var firstCommand = new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자) - ); - var secondCommand = new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.plusDays(1).atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(소녀시대_식별자) - ); + var firstCommand = StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자)) + .build(); + var secondCommand = StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.plusDays(1).atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(소녀시대_식별자)) + .build(); // when stageCreateService.createStage(firstCommand); @@ -174,18 +174,18 @@ class createStage { @Test void 공연이_여러_개_추가될때_공연에_중복된_아티스트가_있어도_FestivalQueryInfo에는_중복이_없다() throws Exception { // given - var firstCommand = new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자) - ); - var secondCommand = new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.plusDays(1).atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자) - ); + var firstCommand = StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자)) + .build(); + var secondCommand = StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.plusDays(1).atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자)) + .build(); // when stageCreateService.createStage(firstCommand); @@ -209,22 +209,22 @@ class updateStage { @BeforeEach void setUp() { - 테코대학교_축제_공연_식별자 = stageCreateService.createStage(new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.atTime(18, 0), - now.minusWeeks(1), - List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자) - )); + 테코대학교_축제_공연_식별자 = stageCreateService.createStage(StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자)) + .build()); } @Test void 공연을_수정하면_StageQueryInfo가_갱신된다() throws Exception { // given - var command = new StageUpdateCommand( - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자, 소녀시대_식별자) - ); + var command = StageUpdateCommand.builder() + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자)) + .build(); // when stageUpdateService.updateStage(테코대학교_축제_공연_식별자, command); @@ -242,11 +242,11 @@ void setUp() { @Test void 공연을_수정하면_FestivalQueryInfo가_갱신된다() throws Exception { // given - var command = new StageUpdateCommand( - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이_식별자, 소녀시대_식별자) - ); + var command = StageUpdateCommand.builder() + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자)) + .build(); // when stageUpdateService.updateStage(테코대학교_축제_공연_식별자, command); @@ -268,12 +268,12 @@ class deleteStage { @BeforeEach void setUp() { - 테코대학교_축제_공연_식별자 = stageCreateService.createStage(new StageCreateCommand( - 테코대학교_축제_식별자, - festivalStartDate.atTime(18, 0), - now.minusWeeks(1), - List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자) - )); + 테코대학교_축제_공연_식별자 = stageCreateService.createStage(StageCreateCommand.builder() + .festivalId(테코대학교_축제_식별자) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이_식별자, 소녀시대_식별자, 뉴진스_식별자)) + .build()); } @Test diff --git a/backend/src/test/java/com/festago/stage/application/command/StageCreateServiceTest.java b/backend/src/test/java/com/festago/stage/application/command/StageCreateServiceTest.java index 91f2618f5..e49fa58ff 100644 --- a/backend/src/test/java/com/festago/stage/application/command/StageCreateServiceTest.java +++ b/backend/src/test/java/com/festago/stage/application/command/StageCreateServiceTest.java @@ -36,13 +36,20 @@ class StageCreateServiceTest { StageRepository stageRepository; + FestivalRepository festivalRepository; + ArtistRepository artistRepository; + StageArtistRepository stageArtistRepository; + StageCreateService stageCreateService; + LocalDate festivalStartDate = LocalDate.parse("2077-06-30"); LocalDate festivalEndDate = LocalDate.parse("2077-07-02"); + Festival 테코대학교_축제; + Artist 에픽하이; Artist 소녀시대; Artist 뉴진스; @@ -80,12 +87,12 @@ class createStage { @Test void ArtistIds에_중복이_있으면_예외() { // given - var command = new StageCreateCommand( - 테코대학교_축제.getId(), - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이.getId(), 에픽하이.getId()) - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제.getId()) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이.getId(), 에픽하이.getId())) + .build(); // then // when & then @@ -100,12 +107,12 @@ class createStage { List artistIds = LongStream.rangeClosed(1, 11) .boxed() .toList(); - var command = new StageCreateCommand( - 테코대학교_축제.getId(), - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - artistIds - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제.getId()) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(artistIds) + .build(); // when & then assertThatThrownBy(() -> stageCreateService.createStage(command)) @@ -120,12 +127,12 @@ class createStage { .mapToObj(it -> artistRepository.save(ArtistFixture.builder().build())) .map(Artist::getId) .toList(); - var command = new StageCreateCommand( - 테코대학교_축제.getId(), - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - artistIds - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제.getId()) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(artistIds) + .build(); // when assertThatNoException().isThrownBy(() -> stageCreateService.createStage(command)); @@ -134,12 +141,12 @@ class createStage { @Test void Festival_식별자에_대한_Festival이_없으면_예외() { // given - var command = new StageCreateCommand( - 4885L, - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId()) - ); + var command = StageCreateCommand.builder() + .festivalId(4885L) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId())) + .build(); // when & then assertThatThrownBy(() -> stageCreateService.createStage(command)) @@ -150,12 +157,12 @@ class createStage { @Test void 아티스트_식별자_목록에_존재하지_않은_아티스트가_있으면_예외() { // given - var command = new StageCreateCommand( - 테코대학교_축제.getId(), - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId(), 4885L) - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제.getId()) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId(), 4885L)) + .build(); // when & then assertThatThrownBy(() -> stageCreateService.createStage(command)) @@ -166,12 +173,12 @@ class createStage { @Test void 성공하면_생성한_Stage에_대한_StageArtist가_저장된다() { // given - var command = new StageCreateCommand( - 테코대학교_축제.getId(), - festivalStartDate.atTime(18, 0), - festivalStartDate.minusWeeks(1).atStartOfDay(), - List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId()) - ); + var command = StageCreateCommand.builder() + .festivalId(테코대학교_축제.getId()) + .startTime(festivalStartDate.atTime(18, 0)) + .ticketOpenTime(festivalStartDate.minusWeeks(1).atStartOfDay()) + .artistIds(List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId())) + .build(); // when Long stageId = stageCreateService.createStage(command); diff --git a/backend/src/test/java/com/festago/stage/application/command/StageUpdateServiceTest.java b/backend/src/test/java/com/festago/stage/application/command/StageUpdateServiceTest.java index ddc8c8600..cf5e173ec 100644 --- a/backend/src/test/java/com/festago/stage/application/command/StageUpdateServiceTest.java +++ b/backend/src/test/java/com/festago/stage/application/command/StageUpdateServiceTest.java @@ -82,11 +82,11 @@ class updateStage { @Test void ArtistIds에_중복이_있으면_예외() { // given - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - List.of(에픽하이.getId(), 에픽하이.getId()) - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(List.of(에픽하이.getId(), 에픽하이.getId())) + .build(); // then // when & then @@ -101,11 +101,11 @@ class updateStage { List artistIds = LongStream.rangeClosed(1, 11) .boxed() .toList(); - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - artistIds - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(artistIds) + .build(); // when & then assertThatThrownBy(() -> stageUpdateService.updateStage(테코대학교_축제_공연.getId(), command)) @@ -120,11 +120,11 @@ class updateStage { .mapToObj(it -> artistRepository.save(ArtistFixture.builder().build())) .map(Artist::getId) .toList(); - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - artistIds - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(artistIds) + .build(); // when assertThatNoException() @@ -135,11 +135,11 @@ class updateStage { void Stage에_대한_식별자가_없으면_예외() { // given Long stageId = 4885L; - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId()) - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId())) + .build(); // when & then assertThatThrownBy(() -> stageUpdateService.updateStage(stageId, command)) @@ -150,11 +150,11 @@ class updateStage { @Test void 아티스트_식별자_목록에_존재하지_않은_아티스트가_있으면_예외() { // given - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId(), 4885L) - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId(), 4885L)) + .build(); // when & then assertThatThrownBy(() -> stageUpdateService.updateStage(테코대학교_축제_공연.getId(), command)) @@ -165,11 +165,11 @@ class updateStage { @Test void 성공하면_수정된_Stage에_반영된다() { // given - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId()) - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(List.of(에픽하이.getId(), 소녀시대.getId(), 뉴진스.getId())) + .build(); // when stageUpdateService.updateStage(테코대학교_축제_공연.getId(), command); @@ -183,11 +183,11 @@ class updateStage { @Test void 성공하면_수정된_Stage에_대한_StageArtist가_갱신된다() { // given - var command = new StageUpdateCommand( - stageStartTime.minusHours(1), - ticketOpenTime.minusDays(1), - List.of(에픽하이.getId()) - ); + var command = StageUpdateCommand.builder() + .startTime(stageStartTime.minusHours(1)) + .ticketOpenTime(ticketOpenTime.minusDays(1)) + .artistIds(List.of(에픽하이.getId())) + .build(); // when stageUpdateService.updateStage(테코대학교_축제_공연.getId(), command); From 20860b328a3d578526c2025edd6c46b4d8163bb4 Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:36:51 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[BE]=20feat:=20ImageFileUploadService=20jpe?= =?UTF-8?q?g=20=ED=99=95=EC=9E=A5=EC=9E=90=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#946)=20(#947)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ImageFileUploadService jpeg, webp 확장자 지원 추가 * fix: webp 확장자 지원 삭제 - iOS에서 webp 지원 불가 이슈 --- .../com/festago/upload/application/ImageFileUploadService.java | 2 +- .../src/main/java/com/festago/upload/domain/FileExtension.java | 1 + .../festago/upload/application/ImageFileUploadServiceTest.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/festago/upload/application/ImageFileUploadService.java b/backend/src/main/java/com/festago/upload/application/ImageFileUploadService.java index e42f66687..9f6391b70 100644 --- a/backend/src/main/java/com/festago/upload/application/ImageFileUploadService.java +++ b/backend/src/main/java/com/festago/upload/application/ImageFileUploadService.java @@ -25,7 +25,7 @@ public class ImageFileUploadService { private static final int MAX_FILE_SIZE = 2_000_000; // 2MB - private static final Set ALLOW_IMAGE_EXTENSION = EnumSet.of(FileExtension.JPG, FileExtension.PNG); + private static final Set ALLOW_IMAGE_EXTENSION = EnumSet.of(FileExtension.JPG, FileExtension.JPEG, FileExtension.PNG); private final StorageClient storageClient; private final UploadFileRepository uploadFileRepository; diff --git a/backend/src/main/java/com/festago/upload/domain/FileExtension.java b/backend/src/main/java/com/festago/upload/domain/FileExtension.java index ef511f693..b5387464c 100644 --- a/backend/src/main/java/com/festago/upload/domain/FileExtension.java +++ b/backend/src/main/java/com/festago/upload/domain/FileExtension.java @@ -8,6 +8,7 @@ @RequiredArgsConstructor public enum FileExtension { JPG(".jpg", MimeTypeUtils.IMAGE_JPEG), + JPEG(".jpeg", MimeTypeUtils.IMAGE_JPEG), PNG(".png", MimeTypeUtils.IMAGE_PNG), NONE("", MimeTypeUtils.APPLICATION_OCTET_STREAM), ; diff --git a/backend/src/test/java/com/festago/upload/application/ImageFileUploadServiceTest.java b/backend/src/test/java/com/festago/upload/application/ImageFileUploadServiceTest.java index 108de8663..60a752fbd 100644 --- a/backend/src/test/java/com/festago/upload/application/ImageFileUploadServiceTest.java +++ b/backend/src/test/java/com/festago/upload/application/ImageFileUploadServiceTest.java @@ -41,7 +41,7 @@ void setUp() { } @ParameterizedTest - @ValueSource(strings = {"image.png", "image.jpg"}) + @ValueSource(strings = {"image.png", "image.jpg", "image.jpeg"}) void 이미지를_업로드할때_JPG_PNG_확장자이면_성공한다(String filename) { // given MultipartFile multipartFile = new MockMultipartFile("image", filename, "image/png", From 20aef31a94ee02900cee04a44592251c8cf11c68 Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:37:07 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[BE]=20refactor:=20OpenIdUserInfoProvider?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20OpenIdClient=EC=97=90=20?= =?UTF-8?q?=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#950)=20(#951)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: OpenIdUserInfoProvider 삭제 및 OpenIdClient에 비즈니스 로직 구현 - 불필요한 depth 제거 --- .../auth/domain/OpenIdUserInfoProvider.java | 6 -- .../openid/KakaoOpenIdClient.java | 54 +++++++++++++-- .../openid/KakaoOpenIdUserInfoProvider.java | 65 ------------------- ...erTest.java => KakaoOpenIdClientTest.java} | 29 ++++----- 4 files changed, 64 insertions(+), 90 deletions(-) delete mode 100644 backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java delete mode 100644 backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdUserInfoProvider.java rename backend/src/test/java/com/festago/auth/infrastructure/{KakaoOpenIdUserInfoProviderTest.java => KakaoOpenIdClientTest.java} (85%) diff --git a/backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java b/backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java deleted file mode 100644 index f721d40d4..000000000 --- a/backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.festago.auth.domain; - -public interface OpenIdUserInfoProvider { - - UserInfo provide(String idToken); -} diff --git a/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java b/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java index 5c2fbd0be..09e1c4cd1 100644 --- a/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java +++ b/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java @@ -1,20 +1,66 @@ package com.festago.auth.infrastructure.openid; import com.festago.auth.domain.OpenIdClient; +import com.festago.auth.domain.OpenIdNonceValidator; import com.festago.auth.domain.SocialType; import com.festago.auth.domain.UserInfo; -import lombok.RequiredArgsConstructor; +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.UnauthorizedException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import java.time.Clock; +import java.util.Date; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +@Slf4j @Component -@RequiredArgsConstructor public class KakaoOpenIdClient implements OpenIdClient { - private final KakaoOpenIdUserInfoProvider kakaoIdTokenUserInfoProvider; + private static final String ISSUER = "https://kauth.kakao.com"; + private final OpenIdNonceValidator openIdNonceValidator; + private final OpenIdIdTokenParser idTokenParser; + private final Set appKeys; + + public KakaoOpenIdClient( + @Value("${festago.oauth2.kakao.rest-api-key}") String restApiKey, + @Value("${festago.oauth2.kakao.native-app-key}") String nativeAppKey, + KakaoOpenIdPublicKeyLocator kakaoOpenIdPublicKeyLocator, + OpenIdNonceValidator openIdNonceValidator, + Clock clock + ) { + this.appKeys = Set.of(restApiKey, nativeAppKey); + this.openIdNonceValidator = openIdNonceValidator; + this.idTokenParser = new OpenIdIdTokenParser(Jwts.parser() + .keyLocator(kakaoOpenIdPublicKeyLocator) + .requireIssuer(ISSUER) + .clock(() -> Date.from(clock.instant())) + .build()); + } @Override public UserInfo getUserInfo(String idToken) { - return kakaoIdTokenUserInfoProvider.provide(idToken); + Claims payload = idTokenParser.parse(idToken); + openIdNonceValidator.validate(payload.get("nonce", String.class), payload.getExpiration()); + validateAudience(payload.getAudience()); + return UserInfo.builder() + .socialType(SocialType.KAKAO) + .socialId(payload.getSubject()) + .nickname(payload.get("nickname", String.class)) + .profileImage(payload.get("picture", String.class)) + .build(); + } + + private void validateAudience(Set audiences) { + for (String audience : audiences) { + if (appKeys.contains(audience)) { + return; + } + } + log.info("허용되지 않는 id 토큰의 audience 값이 요청되었습니다. audiences={}", audiences); + throw new UnauthorizedException(ErrorCode.OPEN_ID_INVALID_TOKEN); } @Override diff --git a/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdUserInfoProvider.java b/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdUserInfoProvider.java deleted file mode 100644 index 30c81b05f..000000000 --- a/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdUserInfoProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.festago.auth.infrastructure.openid; - -import com.festago.auth.domain.OpenIdNonceValidator; -import com.festago.auth.domain.OpenIdUserInfoProvider; -import com.festago.auth.domain.SocialType; -import com.festago.auth.domain.UserInfo; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.UnauthorizedException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import java.time.Clock; -import java.util.Date; -import java.util.Set; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class KakaoOpenIdUserInfoProvider implements OpenIdUserInfoProvider { - - private static final String ISSUER = "https://kauth.kakao.com"; - private final OpenIdNonceValidator openIdNonceValidator; - private final OpenIdIdTokenParser idTokenParser; - private final Set appKeys; - - public KakaoOpenIdUserInfoProvider( - @Value("${festago.oauth2.kakao.rest-api-key}") String restApiKey, - @Value("${festago.oauth2.kakao.native-app-key}") String nativeAppKey, - KakaoOpenIdPublicKeyLocator kakaoOpenIdPublicKeyLocator, - OpenIdNonceValidator openIdNonceValidator, - Clock clock - ) { - this.appKeys = Set.of(restApiKey, nativeAppKey); - this.openIdNonceValidator = openIdNonceValidator; - this.idTokenParser = new OpenIdIdTokenParser(Jwts.parser() - .keyLocator(kakaoOpenIdPublicKeyLocator) - .requireIssuer(ISSUER) - .clock(() -> Date.from(clock.instant())) - .build()); - } - - @Override - public UserInfo provide(String idToken) { - Claims payload = idTokenParser.parse(idToken); - openIdNonceValidator.validate(payload.get("nonce", String.class), payload.getExpiration()); - validateAudience(payload.getAudience()); - return UserInfo.builder() - .socialType(SocialType.KAKAO) - .socialId(payload.getSubject()) - .nickname(payload.get("nickname", String.class)) - .profileImage(payload.get("picture", String.class)) - .build(); - } - - private void validateAudience(Set audiences) { - for (String audience : audiences) { - if (appKeys.contains(audience)) { - return; - } - } - log.info("허용되지 않는 id 토큰의 audience 값이 요청되었습니다. audiences={}", audiences); - throw new UnauthorizedException(ErrorCode.OPEN_ID_INVALID_TOKEN); - } -} diff --git a/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdUserInfoProviderTest.java b/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdClientTest.java similarity index 85% rename from backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdUserInfoProviderTest.java rename to backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdClientTest.java index 0f03442c9..b19c4a98f 100644 --- a/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdUserInfoProviderTest.java +++ b/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdClientTest.java @@ -1,14 +1,13 @@ package com.festago.auth.infrastructure; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.spy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import com.festago.auth.infrastructure.openid.KakaoOpenIdClient; import com.festago.auth.infrastructure.openid.KakaoOpenIdPublicKeyLocator; -import com.festago.auth.infrastructure.openid.KakaoOpenIdUserInfoProvider; import com.festago.auth.infrastructure.openid.NoopOpenIdNonceValidator; import com.festago.common.exception.ErrorCode; import com.festago.common.exception.UnauthorizedException; @@ -28,9 +27,9 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") -class KakaoOpenIdUserInfoProviderTest { +class KakaoOpenIdClientTest { - KakaoOpenIdUserInfoProvider kakaoOpenIdUserInfoProvider; + KakaoOpenIdClient kakaoOpenIdClient; KakaoOpenIdPublicKeyLocator keyLocator; @@ -42,7 +41,7 @@ class KakaoOpenIdUserInfoProviderTest { void setUp() { keyLocator = mock(); clock = spy(Clock.systemDefaultZone()); - kakaoOpenIdUserInfoProvider = new KakaoOpenIdUserInfoProvider( + kakaoOpenIdClient = new KakaoOpenIdClient( "restApiKey", "nativeAppKey", keyLocator, @@ -65,7 +64,7 @@ void setUp() { .compact(); // when & then - assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken)) + assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken)) .isInstanceOf(UnauthorizedException.class) .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage()); } @@ -84,7 +83,7 @@ void setUp() { .compact(); // when & then - assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken)) + assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken)) .isInstanceOf(UnauthorizedException.class) .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage()); } @@ -104,7 +103,7 @@ void setUp() { .compact(); // when & then - assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken)) + assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken)) .isInstanceOf(UnauthorizedException.class) .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage()); } @@ -124,7 +123,7 @@ void setUp() { .compact(); // when & then - assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken)) + assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken)) .isInstanceOf(UnauthorizedException.class) .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage()); } @@ -143,7 +142,7 @@ void setUp() { .compact(); // when & then - assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken)) + assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken)) .isInstanceOf(UnauthorizedException.class) .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage()); } @@ -164,7 +163,7 @@ void setUp() { .compact(); // when - var expect = kakaoOpenIdUserInfoProvider.provide(idToken); + var expect = kakaoOpenIdClient.getUserInfo(idToken); // then assertThat(expect.socialId()).isEqualTo(socialId); @@ -187,7 +186,7 @@ void setUp() { .compact(); // when - var expect = kakaoOpenIdUserInfoProvider.provide(idToken); + var expect = kakaoOpenIdClient.getUserInfo(idToken); // then assertThat(expect.socialId()).isEqualTo(socialId); From 13f731824e1bd68efca07827444749d1c493b8de Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:37:21 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[BE]=20feat:=20Uploaded=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A1=9C=20=EC=98=A4=EB=9E=98=EB=90=98=EA=B1=B0?= =?UTF-8?q?=EB=82=98=20Abandoned=20=EC=83=81=ED=83=9C=EC=9D=98=20UploadFil?= =?UTF-8?q?e=EC=9D=84=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#956)=20(#957)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: UploadFile 삭제 기능 추가 * chore: 로그 띄어쓰기 삭제 * feat: 업로드 파일 삭제 어드민 API 추가 * refactor: AdminUploadV1Controller -> AdminUploadImageV1Controller 클래스명 변경 - 더 명확한 의미를 가지도록 변경 --- ...eteAbandonedPeriodUploadFileV1Request.java | 11 ++ .../v1/AdminUploadFileDeleteV1Controller.java | 37 +++++ ...java => AdminUploadImageV1Controller.java} | 2 +- .../application/UploadFileDeleteService.java | 38 +++++ .../festago/upload/domain/StorageClient.java | 13 +- .../infrastructure/R2StorageClient.java | 43 ++++- .../repository/UploadFileRepository.java | 14 ++ ...AdminUploadFileDeleteV1ControllerTest.java | 114 ++++++++++++++ ... => AdminUploadImageV1ControllerTest.java} | 2 +- .../UploadFileDeleteServiceTest.java | 147 ++++++++++++++++++ .../infrastructure/FakeStorageClient.java | 6 + .../MemoryUploadFileRepository.java | 27 ++++ 12 files changed, 447 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/com/festago/admin/dto/upload/AdminDeleteAbandonedPeriodUploadFileV1Request.java create mode 100644 backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1Controller.java rename backend/src/main/java/com/festago/admin/presentation/v1/{AdminUploadV1Controller.java => AdminUploadImageV1Controller.java} (96%) create mode 100644 backend/src/main/java/com/festago/upload/application/UploadFileDeleteService.java create mode 100644 backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1ControllerTest.java rename backend/src/test/java/com/festago/admin/presentation/v1/{AdminUploadV1ControllerTest.java => AdminUploadImageV1ControllerTest.java} (98%) create mode 100644 backend/src/test/java/com/festago/upload/application/UploadFileDeleteServiceTest.java diff --git a/backend/src/main/java/com/festago/admin/dto/upload/AdminDeleteAbandonedPeriodUploadFileV1Request.java b/backend/src/main/java/com/festago/admin/dto/upload/AdminDeleteAbandonedPeriodUploadFileV1Request.java new file mode 100644 index 000000000..014e8c2f8 --- /dev/null +++ b/backend/src/main/java/com/festago/admin/dto/upload/AdminDeleteAbandonedPeriodUploadFileV1Request.java @@ -0,0 +1,11 @@ +package com.festago.admin.dto.upload; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; + +public record AdminDeleteAbandonedPeriodUploadFileV1Request( + @NotNull LocalDateTime startTime, + @NotNull LocalDateTime endTime +) { + +} diff --git a/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1Controller.java b/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1Controller.java new file mode 100644 index 000000000..2ca48930c --- /dev/null +++ b/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1Controller.java @@ -0,0 +1,37 @@ +package com.festago.admin.presentation.v1; + +import com.festago.admin.dto.upload.AdminDeleteAbandonedPeriodUploadFileV1Request; +import com.festago.upload.application.UploadFileDeleteService; +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/admin/api/v1/upload/delete") +@RequiredArgsConstructor +@Hidden +public class AdminUploadFileDeleteV1Controller { + + private final UploadFileDeleteService uploadFileDeleteService; + + @DeleteMapping("/abandoned-period") + public ResponseEntity deleteAbandonedWithPeriod( + @RequestBody @Valid AdminDeleteAbandonedPeriodUploadFileV1Request request + ) { + uploadFileDeleteService.deleteAbandonedStatusWithPeriod(request.startTime(), request.endTime()); + return ResponseEntity.ok() + .build(); + } + + @DeleteMapping("/old-uploaded") + public ResponseEntity deleteOldUploaded() { + uploadFileDeleteService.deleteOldUploadedStatus(); + return ResponseEntity.ok() + .build(); + } +} diff --git a/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadV1Controller.java b/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadImageV1Controller.java similarity index 96% rename from backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadV1Controller.java rename to backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadImageV1Controller.java index 9ae8b0cd2..3530aae0e 100644 --- a/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadV1Controller.java +++ b/backend/src/main/java/com/festago/admin/presentation/v1/AdminUploadImageV1Controller.java @@ -18,7 +18,7 @@ @RequestMapping("/admin/api/v1/upload/images") @RequiredArgsConstructor @Hidden -public class AdminUploadV1Controller { +public class AdminUploadImageV1Controller { private final ImageFileUploadService imageFileUploadService; diff --git a/backend/src/main/java/com/festago/upload/application/UploadFileDeleteService.java b/backend/src/main/java/com/festago/upload/application/UploadFileDeleteService.java new file mode 100644 index 000000000..05d4d6a80 --- /dev/null +++ b/backend/src/main/java/com/festago/upload/application/UploadFileDeleteService.java @@ -0,0 +1,38 @@ +package com.festago.upload.application; + +import com.festago.upload.domain.StorageClient; +import com.festago.upload.domain.UploadFile; +import com.festago.upload.domain.UploadStatus; +import com.festago.upload.repository.UploadFileRepository; +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class UploadFileDeleteService { + + private final StorageClient storageClient; + private final UploadFileRepository uploadFileRepository; + private final Clock clock; + + public void deleteAbandonedStatusWithPeriod(LocalDateTime startTime, LocalDateTime endTime) { + List uploadFiles = uploadFileRepository.findByCreatedAtBetweenAndStatus(startTime, endTime, UploadStatus.ABANDONED); + deleteUploadFiles(uploadFiles); + } + + private void deleteUploadFiles(List uploadFiles) { + storageClient.delete(uploadFiles); + uploadFileRepository.deleteByIn(uploadFiles); + } + + public void deleteOldUploadedStatus() { + LocalDateTime yesterday = LocalDateTime.now(clock).minusDays(1); + List uploadFiles = uploadFileRepository.findByCreatedAtBeforeAndStatus(yesterday, UploadStatus.UPLOADED); + deleteUploadFiles(uploadFiles); + } +} diff --git a/backend/src/main/java/com/festago/upload/domain/StorageClient.java b/backend/src/main/java/com/festago/upload/domain/StorageClient.java index 6cbc55a8d..65914f4cd 100644 --- a/backend/src/main/java/com/festago/upload/domain/StorageClient.java +++ b/backend/src/main/java/com/festago/upload/domain/StorageClient.java @@ -1,15 +1,24 @@ package com.festago.upload.domain; +import java.util.List; import org.springframework.web.multipart.MultipartFile; public interface StorageClient { /** - * MultipartFile을 보관(영속)하는 클래스
업로드 작업이 끝나면, 업로드한 파일의 정보를 가진 UploadStatus.UPLOADED 상태의 UploadFile를 반환해야 한다. - *
반환된 UploadFile을 영속하는 책임은 해당 클래스를 사용하는 클라이언트가 구현해야 한다.
+ * MultipartFile을 보관(영속)하는 메서드
업로드 작업이 끝나면, 업로드한 파일의 정보를 가진 UploadStatus.UPLOADED 상태의 UploadFile를 반환해야 한다. + *
반환된 UploadFile을 영속하는 책임은 해당 메서드를 사용하는 클라이언트가 구현해야 한다.
* * @param file 업로드 할 MultipartFile * @return UploadStatus.PENDING 상태의 영속되지 않은 UploadFile 엔티티 */ UploadFile storage(MultipartFile file); + + /** + * 업로드 파일을 삭제하는 메서드
삭제 작업이 끝나면, UploadFile이 가진 정보에 대한 업로드 된 파일이 없으므로, 인자로 들어온 UploadFiles를 삭제해야 한다.
삭제가 + * 끝나고 UploadFile을 삭제하는 책임은 해당 메서드를 사용하는 클라이언트가 구현해야 한다.
+ * + * @param uploadFiles 삭제하려는 업로드 된 파일의 정보가 담긴 UploadFile 목록 + */ + void delete(List uploadFiles); } diff --git a/backend/src/main/java/com/festago/upload/infrastructure/R2StorageClient.java b/backend/src/main/java/com/festago/upload/infrastructure/R2StorageClient.java index ec56b29e2..e7f9b383b 100644 --- a/backend/src/main/java/com/festago/upload/infrastructure/R2StorageClient.java +++ b/backend/src/main/java/com/festago/upload/infrastructure/R2StorageClient.java @@ -10,6 +10,7 @@ import java.net.URI; import java.time.Clock; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -20,7 +21,11 @@ import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse; +import software.amazon.awssdk.services.s3.model.ObjectIdentifier; import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Error; @Slf4j @Component @@ -71,12 +76,44 @@ private void upload(MultipartFile file, UploadFile uploadFile) { String mimeType = uploadFile.getMimeType().toString(); RequestBody requestBody = RequestBody.fromContentProvider(() -> inputStream, fileSize, mimeType); UUID uploadFileId = uploadFile.getId(); - log.info("파일 업로드 시작. id = {}, uploadUri={}, size={}", uploadFileId, uploadFile.getUploadUri(), fileSize); + log.info("파일 업로드 시작. id={}, uploadUri={}, size={}", uploadFileId, uploadFile.getUploadUri(), fileSize); s3Client.putObject(objectRequest, requestBody); - log.info("파일 업로드 완료. id = {}", uploadFileId); + log.info("파일 업로드 완료. id={}", uploadFileId); } catch (IOException e) { - log.warn("파일 업로드 중 문제가 발생했습니다. id = {}", uploadFile.getId()); + log.warn("파일 업로드 중 문제가 발생했습니다. id={}", uploadFile.getId()); throw new InternalServerException(ErrorCode.FILE_UPLOAD_ERROR, e); } } + + @Override + public void delete(List uploadFiles) { + if (uploadFiles.isEmpty()) { + log.info("삭제하려는 파일이 없습니다."); + return; + } + int fileSize = uploadFiles.size(); + UUID firstFileId = uploadFiles.get(0).getId(); + DeleteObjectsRequest deleteObjectsRequest = getDeleteObjectsRequest(uploadFiles); + + log.info("{}개 파일 삭제 시작. 첫 번째 파일 식별자={}", fileSize, firstFileId); + DeleteObjectsResponse response = s3Client.deleteObjects(deleteObjectsRequest); + log.info("{}개 파일 삭제 완료. 첫 번째 파일 식별자={}", fileSize, firstFileId); + + if (response.hasErrors()) { + List errors = response.errors(); + log.warn("{}개 파일 삭제 중 에러가 발생했습니다. 첫 번째 파일 식별자={}, 에러 개수={}", fileSize, firstFileId, errors.size()); + errors.forEach(error -> log.info("파일 삭제 중 에러가 발생했습니다. key={}, message={}", error.key(), error.message())); + } + } + + private DeleteObjectsRequest getDeleteObjectsRequest(List uploadFiles) { + List objectIdentifiers = uploadFiles.stream() + .map(UploadFile::getName) + .map(name -> ObjectIdentifier.builder().key(name).build()) + .toList(); + return DeleteObjectsRequest.builder() + .bucket(bucket) + .delete(builder -> builder.objects(objectIdentifiers).build()) + .build(); + } } diff --git a/backend/src/main/java/com/festago/upload/repository/UploadFileRepository.java b/backend/src/main/java/com/festago/upload/repository/UploadFileRepository.java index 5029aae61..b2eb159ab 100644 --- a/backend/src/main/java/com/festago/upload/repository/UploadFileRepository.java +++ b/backend/src/main/java/com/festago/upload/repository/UploadFileRepository.java @@ -2,11 +2,16 @@ import com.festago.upload.domain.FileOwnerType; import com.festago.upload.domain.UploadFile; +import com.festago.upload.domain.UploadStatus; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; public interface UploadFileRepository extends Repository { @@ -17,4 +22,13 @@ public interface UploadFileRepository extends Repository { List findAllByOwnerIdAndOwnerType(Long ownerId, FileOwnerType ownerType); List findByIdIn(Collection ids); + + List findByCreatedAtBetweenAndStatus(LocalDateTime startTime, LocalDateTime endTime, + UploadStatus status); + + List findByCreatedAtBeforeAndStatus(LocalDateTime createdAt, UploadStatus status); + + @Modifying + @Query("delete from UploadFile uf where uf in :uploadFiles") + void deleteByIn(@Param("uploadFiles") List uploadFiles); } diff --git a/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1ControllerTest.java b/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1ControllerTest.java new file mode 100644 index 000000000..e92023eb8 --- /dev/null +++ b/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadFileDeleteV1ControllerTest.java @@ -0,0 +1,114 @@ +package com.festago.admin.presentation.v1; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.festago.admin.dto.upload.AdminDeleteAbandonedPeriodUploadFileV1Request; +import com.festago.auth.domain.Role; +import com.festago.support.CustomWebMvcTest; +import com.festago.support.WithMockAuth; +import jakarta.servlet.http.Cookie; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@CustomWebMvcTest +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminUploadFileDeleteV1ControllerTest { + + private static final Cookie TOKEN_COOKIE = new Cookie("token", "token"); + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Nested + class ABANDONED_상태와_기간에_포함되는_파일_삭제 { + + final String uri = "/admin/api/v1/upload/delete/abandoned-period"; + + @Nested + @DisplayName("DELETE " + uri) + class 올바른_주소로 { + + @Test + @WithMockAuth(role = Role.ADMIN) + void 요청을_보내면_200_응답이_반환된다() throws Exception { + // given + LocalDateTime now = LocalDateTime.now(); + var request = new AdminDeleteAbandonedPeriodUploadFileV1Request(now, now); + + // when & then + mockMvc.perform(delete(uri) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isOk()); + } + + @Test + void 토큰_없이_보내면_401_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(delete(uri)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuth(role = Role.MEMBER) + void 토큰의_권한이_Admin이_아니면_404_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(delete(uri) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isNotFound()); + } + } + } + + @Nested + class 오래된_UPLOADED_상태_파일_삭제 { + + final String uri = "/admin/api/v1/upload/delete/old-uploaded"; + + @Nested + @DisplayName("DELETE " + uri) + class 올바른_주소로 { + + @Test + @WithMockAuth(role = Role.ADMIN) + void 요청을_보내면_200_응답이_반환된다() throws Exception { + // given + // when & then + mockMvc.perform(delete(uri) + .contentType(MediaType.APPLICATION_JSON) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isOk()); + } + + @Test + void 토큰_없이_보내면_401_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(delete(uri)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuth(role = Role.MEMBER) + void 토큰의_권한이_Admin이_아니면_404_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(delete(uri) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isNotFound()); + } + } + } +} diff --git a/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadV1ControllerTest.java b/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadImageV1ControllerTest.java similarity index 98% rename from backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadV1ControllerTest.java rename to backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadImageV1ControllerTest.java index 5476791e2..a230a1d26 100644 --- a/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadV1ControllerTest.java +++ b/backend/src/test/java/com/festago/admin/presentation/v1/AdminUploadImageV1ControllerTest.java @@ -28,7 +28,7 @@ @CustomWebMvcTest @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") -class AdminUploadV1ControllerTest { +class AdminUploadImageV1ControllerTest { private static final Cookie TOKEN_COOKIE = new Cookie("token", "token"); diff --git a/backend/src/test/java/com/festago/upload/application/UploadFileDeleteServiceTest.java b/backend/src/test/java/com/festago/upload/application/UploadFileDeleteServiceTest.java new file mode 100644 index 000000000..252a30880 --- /dev/null +++ b/backend/src/test/java/com/festago/upload/application/UploadFileDeleteServiceTest.java @@ -0,0 +1,147 @@ +package com.festago.upload.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.spy; + +import com.festago.support.TimeInstantProvider; +import com.festago.support.fixture.UploadFileFixture; +import com.festago.upload.domain.UploadFile; +import com.festago.upload.infrastructure.FakeStorageClient; +import com.festago.upload.repository.MemoryUploadFileRepository; +import com.festago.upload.repository.UploadFileRepository; +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class UploadFileDeleteServiceTest { + + UploadFileDeleteService uploadFileDeleteService; + + UploadFileRepository uploadFileRepository; + + Clock clock; + + @BeforeEach + void setUp() { + uploadFileRepository = new MemoryUploadFileRepository(); + clock = spy(Clock.systemDefaultZone()); + uploadFileDeleteService = new UploadFileDeleteService( + new FakeStorageClient(), + uploadFileRepository, + clock + ); + } + + @Nested + class deleteAbandonedStatusWithPeriod { + + LocalDateTime _6월_30일_18시_0분_0초 = LocalDateTime.parse("2077-06-30T18:00:00"); + LocalDateTime _6월_30일_18시_0분_1초 = LocalDateTime.parse("2077-06-30T18:00:01"); + LocalDateTime _6월_30일_18시_0분_2초 = LocalDateTime.parse("2077-06-30T18:00:02"); + + @Test + void 삭제되는_파일은_시작일에_포함되고_종료일에도_포함된다() { + // given + UploadFile _6월_30일_18시_0분_0초_생성된_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_30일_18시_0분_0초).buildAbandoned()); + UploadFile _6월_30일_18시_0분_1초_생성된_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_30일_18시_0분_1초).buildAbandoned()); + UploadFile _6월_30일_18시_0분_2초_생성된_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_30일_18시_0분_2초).buildAbandoned()); + + // when + uploadFileDeleteService.deleteAbandonedStatusWithPeriod(_6월_30일_18시_0분_0초, _6월_30일_18시_0분_1초); + + // then + var expect = uploadFileRepository.findByIdIn(List.of( + _6월_30일_18시_0분_0초_생성된_파일.getId(), + _6월_30일_18시_0분_1초_생성된_파일.getId(), + _6월_30일_18시_0분_2초_생성된_파일.getId() + )); + assertThat(expect) + .map(UploadFile::getId) + .containsExactly(_6월_30일_18시_0분_2초_생성된_파일.getId()); + } + + @Test + void ABANDONED_상태의_파일만_삭제된다() { + // given + UploadFile UPLOADED_상태_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_30일_18시_0분_0초).build()); + UploadFile ABANDONED_상태_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_30일_18시_0분_0초).buildAbandoned()); + + // when + uploadFileDeleteService.deleteAbandonedStatusWithPeriod(_6월_30일_18시_0분_0초, _6월_30일_18시_0분_0초); + + // then + assertThat(uploadFileRepository.findById(UPLOADED_상태_파일.getId())).isPresent(); + assertThat(uploadFileRepository.findById(ABANDONED_상태_파일.getId())).isEmpty(); + } + } + + @Nested + class deleteOldUploadedStatus { + + LocalDateTime _6월_29일_17시_59분_59초 = LocalDateTime.parse("2077-06-29T17:59:59"); + LocalDateTime _6월_29일_18시_0분_0초 = LocalDateTime.parse("2077-06-29T18:00:00"); + LocalDateTime _6월_29일_18시_0분_1초 = LocalDateTime.parse("2077-06-29T18:00:01"); + LocalDateTime _6월_30일_18시_0분_1초 = LocalDateTime.parse("2077-06-30T18:00:01"); + + @Test + void 생성된지_정확히_하루가_지난_파일만_삭제된다() { + // given + UploadFile _6월_29일_17시_59분_59초_생성된_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_29일_17시_59분_59초).build()); + UploadFile _6월_29일_18시_0분_0초_생성된_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_29일_18시_0분_0초).build()); + UploadFile _6월_29일_18시_0분_1초_생성된_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_29일_18시_0분_1초).build()); + + LocalDateTime now = _6월_30일_18시_0분_1초; + given(clock.instant()) + .willReturn(TimeInstantProvider.from(now)); + + // when + uploadFileDeleteService.deleteOldUploadedStatus(); + + // then + var expect = uploadFileRepository.findByIdIn(List.of( + _6월_29일_17시_59분_59초_생성된_파일.getId(), + _6월_29일_18시_0분_0초_생성된_파일.getId(), + _6월_29일_18시_0분_1초_생성된_파일.getId() + )); + assertThat(expect) + .map(UploadFile::getId) + .containsExactly(_6월_29일_18시_0분_1초_생성된_파일.getId()); + } + + @Test + void UPLOADED_상태의_파일만_삭제된다() { + // given + UploadFile UPLOADED_상태_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_29일_18시_0분_0초).build()); + UploadFile ABANDONED_상태_파일 = uploadFileRepository.save( + UploadFileFixture.builder().createdAt(_6월_29일_18시_0분_0초).buildAbandoned()); + + LocalDateTime now = _6월_30일_18시_0분_1초; + given(clock.instant()) + .willReturn(TimeInstantProvider.from(now)); + + // when + uploadFileDeleteService.deleteOldUploadedStatus(); + + // then + assertThat(uploadFileRepository.findById(UPLOADED_상태_파일.getId())).isEmpty(); + assertThat(uploadFileRepository.findById(ABANDONED_상태_파일.getId())).isPresent(); + } + } +} diff --git a/backend/src/test/java/com/festago/upload/infrastructure/FakeStorageClient.java b/backend/src/test/java/com/festago/upload/infrastructure/FakeStorageClient.java index dcc0ab55c..e57533708 100644 --- a/backend/src/test/java/com/festago/upload/infrastructure/FakeStorageClient.java +++ b/backend/src/test/java/com/festago/upload/infrastructure/FakeStorageClient.java @@ -3,6 +3,7 @@ import com.festago.support.fixture.UploadFileFixture; import com.festago.upload.domain.StorageClient; import com.festago.upload.domain.UploadFile; +import java.util.List; import org.springframework.web.multipart.MultipartFile; public class FakeStorageClient implements StorageClient { @@ -11,4 +12,9 @@ public class FakeStorageClient implements StorageClient { public UploadFile storage(MultipartFile file) { return UploadFileFixture.builder().build(); } + + @Override + public void delete(List uploadFiles) { + // NOOP + } } diff --git a/backend/src/test/java/com/festago/upload/repository/MemoryUploadFileRepository.java b/backend/src/test/java/com/festago/upload/repository/MemoryUploadFileRepository.java index f7a141061..82a3c8462 100644 --- a/backend/src/test/java/com/festago/upload/repository/MemoryUploadFileRepository.java +++ b/backend/src/test/java/com/festago/upload/repository/MemoryUploadFileRepository.java @@ -2,6 +2,8 @@ import com.festago.upload.domain.FileOwnerType; import com.festago.upload.domain.UploadFile; +import com.festago.upload.domain.UploadStatus; +import java.time.LocalDateTime; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -38,4 +40,29 @@ public List findByIdIn(Collection ids) { .filter(uploadFile -> ids.contains(uploadFile.getId())) .toList(); } + + @Override + public List findByCreatedAtBetweenAndStatus(LocalDateTime startTime, LocalDateTime endTime, + UploadStatus status) { + return memory.values().stream() + .filter(it -> it.getStatus() == status) + .filter(it -> it.getCreatedAt().isEqual(startTime) || it.getCreatedAt().isAfter(startTime)) + .filter(it -> it.getCreatedAt().isEqual(endTime) || it.getCreatedAt().isBefore(endTime)) + .toList(); + } + + @Override + public List findByCreatedAtBeforeAndStatus(LocalDateTime createdAt, UploadStatus status) { + return memory.values().stream() + .filter(it -> it.getStatus() == status) + .filter(it -> it.getCreatedAt().isBefore(createdAt)) + .toList(); + } + + @Override + public void deleteByIn(List uploadFiles) { + for (UploadFile uploadFile : uploadFiles) { + memory.remove(uploadFile.getId()); + } + } } From f0e6f46b1c69eb79deb8c8e5d2d94b716675ef9a Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:37:46 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[BE]=20feat:=20Artist=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=8B=9C=20=EC=A4=91=EB=B3=B5=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20(#963)=20(#964)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: Artist 생성 시 중복 이름 검증 추가 --- .../application/ArtistCommandService.java | 9 +++++++++ .../artist/repository/ArtistRepository.java | 2 ++ .../festago/common/exception/ErrorCode.java | 1 + .../application/ArtistCommandServiceTest.java | 19 +++++++++++++++++-- .../repository/MemoryArtistRepository.java | 7 +++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/festago/artist/application/ArtistCommandService.java b/backend/src/main/java/com/festago/artist/application/ArtistCommandService.java index ec4b82c22..094535c48 100644 --- a/backend/src/main/java/com/festago/artist/application/ArtistCommandService.java +++ b/backend/src/main/java/com/festago/artist/application/ArtistCommandService.java @@ -7,6 +7,8 @@ import com.festago.artist.dto.event.ArtistDeletedEvent; import com.festago.artist.dto.event.ArtistUpdatedEvent; import com.festago.artist.repository.ArtistRepository; +import com.festago.common.exception.BadRequestException; +import com.festago.common.exception.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -21,6 +23,7 @@ public class ArtistCommandService { private final ApplicationEventPublisher eventPublisher; public Long save(ArtistCreateCommand command) { + validateSave(command); Artist artist = artistRepository.save( new Artist(command.name(), command.profileImageUrl(), command.backgroundImageUrl()) ); @@ -28,6 +31,12 @@ public Long save(ArtistCreateCommand command) { return artist.getId(); } + private void validateSave(ArtistCreateCommand command) { + if (artistRepository.existsByName(command.name())) { + throw new BadRequestException(ErrorCode.DUPLICATE_ARTIST_NAME); + } + } + public void update(ArtistUpdateCommand command, Long artistId) { Artist artist = artistRepository.getOrThrow(artistId); artist.update(command.name(), command.profileImageUrl(), command.backgroundImageUrl()); diff --git a/backend/src/main/java/com/festago/artist/repository/ArtistRepository.java b/backend/src/main/java/com/festago/artist/repository/ArtistRepository.java index daaac169b..5c0d13687 100644 --- a/backend/src/main/java/com/festago/artist/repository/ArtistRepository.java +++ b/backend/src/main/java/com/festago/artist/repository/ArtistRepository.java @@ -26,4 +26,6 @@ default Artist getOrThrow(Long artistId) { List findByIdIn(Collection artistIds); boolean existsById(Long id); + + boolean existsByName(String name); } diff --git a/backend/src/main/java/com/festago/common/exception/ErrorCode.java b/backend/src/main/java/com/festago/common/exception/ErrorCode.java index b240c25b1..146d5d174 100644 --- a/backend/src/main/java/com/festago/common/exception/ErrorCode.java +++ b/backend/src/main/java/com/festago/common/exception/ErrorCode.java @@ -46,6 +46,7 @@ public enum ErrorCode { OPEN_ID_NOT_SUPPORTED_SOCIAL_TYPE("해당 OpenId 제공자는 지원되지 않습니다."), OPEN_ID_INVALID_TOKEN("잘못된 OpenID 토큰입니다."), NOT_SUPPORT_FILE_EXTENSION("해당 파일의 확장자는 허용되지 않습니다."), + DUPLICATE_ARTIST_NAME("이미 존재하는 아티스트의 이름입니다."), // 401 EXPIRED_AUTH_TOKEN("만료된 로그인 토큰입니다."), diff --git a/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java b/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java index 7f177fbe0..40423d4dc 100644 --- a/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java +++ b/backend/src/test/java/com/festago/artist/application/ArtistCommandServiceTest.java @@ -1,20 +1,22 @@ package com.festago.artist.application; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static org.mockito.BDDMockito.*; +import static org.mockito.BDDMockito.mock; import com.festago.artist.domain.Artist; import com.festago.artist.dto.command.ArtistCreateCommand; import com.festago.artist.dto.command.ArtistUpdateCommand; import com.festago.artist.repository.ArtistRepository; import com.festago.artist.repository.MemoryArtistRepository; +import com.festago.common.exception.BadRequestException; +import com.festago.common.exception.ErrorCode; import com.festago.support.fixture.ArtistFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; -import org.mockito.BDDMockito; @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -46,6 +48,19 @@ void setUp() { assertThat(artistRepository.findById(artistId)).isPresent(); } + @Test + void 중복된_이름의_아티스트가_저장되면_예외가_발생한다() { + // given + artistRepository.save(ArtistFixture.builder().name("윤서연").build()); + ArtistCreateCommand command = new ArtistCreateCommand("윤서연", "https://image.com/image.png", + "https://image.com/image.png"); + + // when & then + assertThatThrownBy(() -> artistCommandService.save(command)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorCode.DUPLICATE_ARTIST_NAME.getMessage()); + } + @Test void 아티스트_정보를_변경한다() { // given diff --git a/backend/src/test/java/com/festago/artist/repository/MemoryArtistRepository.java b/backend/src/test/java/com/festago/artist/repository/MemoryArtistRepository.java index 1eba7e9c6..3e12fb6c5 100644 --- a/backend/src/test/java/com/festago/artist/repository/MemoryArtistRepository.java +++ b/backend/src/test/java/com/festago/artist/repository/MemoryArtistRepository.java @@ -4,6 +4,7 @@ import com.festago.support.AbstractMemoryRepository; import java.util.Collection; import java.util.List; +import java.util.Objects; public class MemoryArtistRepository extends AbstractMemoryRepository implements ArtistRepository { @@ -20,4 +21,10 @@ public List findByIdIn(Collection artistIds) { .filter(artist -> artistIds.contains(artist.getId())) .toList(); } + + @Override + public boolean existsByName(String name) { + return memory.values().stream() + .anyMatch(it -> Objects.equals(it.getName(), name)); + } } From 75f69ebe71491b5ce801a92d2bff8fa8017693ef Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Wed, 15 May 2024 22:48:35 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[BE]=20fix:=20=EC=B6=95=EC=A0=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EA=B3=B5?= =?UTF-8?q?=EC=97=B0=20=EB=AA=A9=EB=A1=9D=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A0=AC=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#967)=20(#968)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 축제 상세 조회 시 공연 목록 정렬 조건 수정 - 식별자 -> 시작 시간 --- .../FestivalDetailV1QueryDslRepository.java | 2 +- ...stivalDetailV1QueryServiceIntegrationTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/festival/repository/FestivalDetailV1QueryDslRepository.java b/backend/src/main/java/com/festago/festival/repository/FestivalDetailV1QueryDslRepository.java index ddb1eb2ae..73380c8ec 100644 --- a/backend/src/main/java/com/festago/festival/repository/FestivalDetailV1QueryDslRepository.java +++ b/backend/src/main/java/com/festago/festival/repository/FestivalDetailV1QueryDslRepository.java @@ -64,7 +64,7 @@ public Optional findFestivalDetail(Long festivalId) { stage.id, stage.startTime, stageQueryInfo.artistInfo - ).skipNulls(), Comparator.comparingLong(StageV1Response::id)) + ).skipNulls(), Comparator.comparing(StageV1Response::startDateTime)) ) ) ); diff --git a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java index af1631c77..2c087b700 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalDetailV1QueryServiceIntegrationTest.java @@ -1,5 +1,6 @@ package com.festago.festival.application.integration.query; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -10,6 +11,7 @@ import com.festago.festival.application.FestivalDetailV1QueryService; import com.festago.festival.domain.Festival; import com.festago.festival.dto.SocialMediaV1Response; +import com.festago.festival.dto.StageV1Response; import com.festago.festival.repository.FestivalRepository; import com.festago.school.domain.School; import com.festago.school.repository.SchoolRepository; @@ -28,6 +30,7 @@ import com.festago.support.fixture.StageArtistFixture; import com.festago.support.fixture.StageFixture; import java.time.LocalDate; +import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -174,6 +177,18 @@ private School createSchool(String schoolName, String domain) { }); } + @Test + void 공연_목록은_공연의_시작_시간_기준으로_정렬된다() { + // when + var response = festivalDetailV1QueryService.findFestivalDetail(테코대학교_축제.getId()); + + // then + assertThat(response.stages()) + .map(StageV1Response::startDateTime) + .map(LocalDateTime::toLocalDate) + .containsExactly(now, now.plusDays(1), now.plusDays(2)); + } + @Test void 축제에_공연이_없으면_응답의_공연에는_비어있는_컬렉션이_반환된다() { // when