From 19faf78967a44b8b0e69ded52f5149203d5da906 Mon Sep 17 00:00:00 2001 From: Seokjin Jeon Date: Tue, 5 Mar 2024 23:53:05 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=B6=95=EC=A0=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#745)=20(#749)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 관리자 축제 조회 기능 추가 * feat: 관리자 축제 조회 API 추가 --- .../AdminFestivalV1QueryService.java | 21 ++ .../admin/dto/AdminFestivalV1Response.java | 18 ++ .../v1/AdminFestivalV1Controller.java | 21 ++ .../AdminFestivalV1QueryDslRepository.java | 97 +++++++ .../common/querydsl/OrderSpecifierUtils.java | 20 ++ .../common/querydsl/QueryDslHelper.java | 39 +++ ...FestivalV1QueryServiceIntegrationTest.java | 269 ++++++++++++++++++ .../v1/AdminFestivalV1ControllerTest.java | 55 ++++ 8 files changed, 540 insertions(+) create mode 100644 backend/src/main/java/com/festago/admin/application/AdminFestivalV1QueryService.java create mode 100644 backend/src/main/java/com/festago/admin/dto/AdminFestivalV1Response.java create mode 100644 backend/src/main/java/com/festago/admin/repository/AdminFestivalV1QueryDslRepository.java create mode 100644 backend/src/main/java/com/festago/common/querydsl/OrderSpecifierUtils.java create mode 100644 backend/src/main/java/com/festago/common/querydsl/QueryDslHelper.java create mode 100644 backend/src/test/java/com/festago/admin/application/integration/AdminFestivalV1QueryServiceIntegrationTest.java diff --git a/backend/src/main/java/com/festago/admin/application/AdminFestivalV1QueryService.java b/backend/src/main/java/com/festago/admin/application/AdminFestivalV1QueryService.java new file mode 100644 index 000000000..01d8430b5 --- /dev/null +++ b/backend/src/main/java/com/festago/admin/application/AdminFestivalV1QueryService.java @@ -0,0 +1,21 @@ +package com.festago.admin.application; + +import com.festago.admin.dto.AdminFestivalV1Response; +import com.festago.admin.repository.AdminFestivalV1QueryDslRepository; +import com.festago.common.querydsl.SearchCondition; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class AdminFestivalV1QueryService { + + private final AdminFestivalV1QueryDslRepository adminFestivalV1QueryDslRepository; + + public Page findAll(SearchCondition searchCondition) { + return adminFestivalV1QueryDslRepository.findAll(searchCondition); + } +} diff --git a/backend/src/main/java/com/festago/admin/dto/AdminFestivalV1Response.java b/backend/src/main/java/com/festago/admin/dto/AdminFestivalV1Response.java new file mode 100644 index 000000000..593844918 --- /dev/null +++ b/backend/src/main/java/com/festago/admin/dto/AdminFestivalV1Response.java @@ -0,0 +1,18 @@ +package com.festago.admin.dto; + +import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDate; + +public record AdminFestivalV1Response( + Long id, + String name, + String schoolName, + LocalDate startDate, + LocalDate endDate, + long stageCount +) { + + @QueryProjection + public AdminFestivalV1Response { + } +} diff --git a/backend/src/main/java/com/festago/admin/presentation/v1/AdminFestivalV1Controller.java b/backend/src/main/java/com/festago/admin/presentation/v1/AdminFestivalV1Controller.java index c77cfdd50..a467ade4f 100644 --- a/backend/src/main/java/com/festago/admin/presentation/v1/AdminFestivalV1Controller.java +++ b/backend/src/main/java/com/festago/admin/presentation/v1/AdminFestivalV1Controller.java @@ -1,19 +1,28 @@ package com.festago.admin.presentation.v1; +import com.festago.admin.application.AdminFestivalV1QueryService; +import com.festago.admin.dto.AdminFestivalV1Response; import com.festago.admin.dto.FestivalV1CreateRequest; import com.festago.admin.dto.FestivalV1UpdateRequest; +import com.festago.common.aop.ValidPageable; +import com.festago.common.querydsl.SearchCondition; import com.festago.festival.application.command.FestivalCommandFacadeService; import io.swagger.v3.oas.annotations.Hidden; import jakarta.validation.Valid; import java.net.URI; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -22,8 +31,20 @@ @Hidden public class AdminFestivalV1Controller { + private final AdminFestivalV1QueryService adminFestivalV1QueryService; private final FestivalCommandFacadeService festivalCommandFacadeService; + @ValidPageable(maxSize = 50) + @GetMapping + public ResponseEntity> findAll( + @RequestParam(defaultValue = "") String searchFilter, + @RequestParam(defaultValue = "") String searchKeyword, + @PageableDefault(size = 10) Pageable pageable + ) { + return ResponseEntity.ok() + .body(adminFestivalV1QueryService.findAll(new SearchCondition(searchFilter, searchKeyword, pageable))); + } + @PostMapping public ResponseEntity createFestival( @RequestBody @Valid FestivalV1CreateRequest request diff --git a/backend/src/main/java/com/festago/admin/repository/AdminFestivalV1QueryDslRepository.java b/backend/src/main/java/com/festago/admin/repository/AdminFestivalV1QueryDslRepository.java new file mode 100644 index 000000000..f4bfdb79e --- /dev/null +++ b/backend/src/main/java/com/festago/admin/repository/AdminFestivalV1QueryDslRepository.java @@ -0,0 +1,97 @@ +package com.festago.admin.repository; + +import static com.festago.festival.domain.QFestival.festival; +import static com.festago.school.domain.QSchool.school; +import static com.festago.stage.domain.QStage.stage; + +import com.festago.admin.dto.AdminFestivalV1Response; +import com.festago.admin.dto.QAdminFestivalV1Response; +import com.festago.common.querydsl.OrderSpecifierUtils; +import com.festago.common.querydsl.QueryDslHelper; +import com.festago.common.querydsl.SearchCondition; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +@Repository +@RequiredArgsConstructor +public class AdminFestivalV1QueryDslRepository { + + private final QueryDslHelper queryDslHelper; + + public Page findAll(SearchCondition searchCondition) { + Pageable pageable = searchCondition.pageable(); + String searchFilter = searchCondition.searchFilter(); + String searchKeyword = searchCondition.searchKeyword(); + return queryDslHelper.applyPagination(pageable, + queryFactory -> queryFactory.select( + new QAdminFestivalV1Response( + festival.id, + festival.name, + school.name, + festival.startDate, + festival.endDate, + stage.count() + )) + .from(festival) + .innerJoin(school).on(school.id.eq(festival.school.id)) + .leftJoin(stage).on(stage.festival.id.eq(festival.id)) + .where(applySearchFilter(searchFilter, searchKeyword)) + .groupBy(festival.id) + .orderBy(getOrderSpecifier(pageable.getSort())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()), + queryFactory -> queryFactory.select(festival.count()) + .from(festival) + .where(applySearchFilter(searchFilter, searchKeyword))); + } + + private BooleanExpression applySearchFilter(String searchFilter, String searchKeyword) { + return switch (searchFilter) { + case "id" -> eqId(searchKeyword); + case "name" -> containsName(searchKeyword); + case "schoolName" -> containsSchoolName(searchKeyword); + default -> null; + }; + } + + private BooleanExpression eqId(String searchKeyword) { + if (StringUtils.hasText(searchKeyword)) { + return festival.id.stringValue().eq(searchKeyword); + } + return null; + } + + private BooleanExpression containsName(String searchKeyword) { + if (StringUtils.hasText(searchKeyword)) { + return festival.name.contains(searchKeyword); + } + return null; + } + + private BooleanExpression containsSchoolName(String searchKeyword) { + if (StringUtils.hasText(searchKeyword)) { + return school.name.contains(searchKeyword); + } + return null; + } + + private OrderSpecifier getOrderSpecifier(Sort sort) { + return sort.stream() + .findFirst() + .map(it -> switch (it.getProperty()) { + case "id" -> OrderSpecifierUtils.of(it.getDirection(), festival.id); + case "name" -> OrderSpecifierUtils.of(it.getDirection(), festival.name); + case "schoolName" -> OrderSpecifierUtils.of(it.getDirection(), school.name); + case "startDate" -> OrderSpecifierUtils.of(it.getDirection(), festival.startDate); + case "endDate" -> OrderSpecifierUtils.of(it.getDirection(), festival.endDate); + default -> OrderSpecifierUtils.NULL; + }) + .orElse(OrderSpecifierUtils.NULL); + } +} diff --git a/backend/src/main/java/com/festago/common/querydsl/OrderSpecifierUtils.java b/backend/src/main/java/com/festago/common/querydsl/OrderSpecifierUtils.java new file mode 100644 index 000000000..8c9befb5a --- /dev/null +++ b/backend/src/main/java/com/festago/common/querydsl/OrderSpecifierUtils.java @@ -0,0 +1,20 @@ +package com.festago.common.querydsl; + +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.NullExpression; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import org.springframework.data.domain.Sort; + +public class OrderSpecifierUtils { + + public static final OrderSpecifier NULL = new OrderSpecifier(Order.ASC, NullExpression.DEFAULT, + OrderSpecifier.NullHandling.Default); + + private OrderSpecifierUtils() { + } + + public static OrderSpecifier of(Sort.Direction direction, Expression target) { + return new OrderSpecifier(direction.isAscending() ? Order.ASC : Order.DESC, target); + } +} diff --git a/backend/src/main/java/com/festago/common/querydsl/QueryDslHelper.java b/backend/src/main/java/com/festago/common/querydsl/QueryDslHelper.java new file mode 100644 index 000000000..bc47fdc3e --- /dev/null +++ b/backend/src/main/java/com/festago/common/querydsl/QueryDslHelper.java @@ -0,0 +1,39 @@ +package com.festago.common.querydsl; + +import com.querydsl.core.types.Expression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class QueryDslHelper { + + private final JPAQueryFactory queryFactory; + + public JPAQuery select(Expression expr) { + return queryFactory.select(expr); + } + + public Optional fetchOne(Function> queryFunction) { + JPAQuery query = queryFunction.apply(queryFactory); + return Optional.ofNullable(query.fetchOne()); + } + + public Page applyPagination( + Pageable pageable, + Function> contentQueryFunction, + Function> countQueryFunction + ) { + List content = contentQueryFunction.apply(queryFactory).fetch(); + JPAQuery countQuery = countQueryFunction.apply(queryFactory); + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } +} diff --git a/backend/src/test/java/com/festago/admin/application/integration/AdminFestivalV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/admin/application/integration/AdminFestivalV1QueryServiceIntegrationTest.java new file mode 100644 index 000000000..180cde1f3 --- /dev/null +++ b/backend/src/test/java/com/festago/admin/application/integration/AdminFestivalV1QueryServiceIntegrationTest.java @@ -0,0 +1,269 @@ +package com.festago.admin.application.integration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import com.festago.admin.application.AdminFestivalV1QueryService; +import com.festago.admin.dto.AdminFestivalV1Response; +import com.festago.common.querydsl.SearchCondition; +import com.festago.festival.domain.Festival; +import com.festago.festival.repository.FestivalRepository; +import com.festago.school.domain.School; +import com.festago.school.domain.SchoolRegion; +import com.festago.school.repository.SchoolRepository; +import com.festago.stage.domain.Stage; +import com.festago.stage.repository.StageRepository; +import com.festago.support.ApplicationIntegrationTest; +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; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminFestivalV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { + + @Autowired + AdminFestivalV1QueryService adminFestivalV1QueryService; + + @Autowired + FestivalRepository festivalRepository; + + @Autowired + SchoolRepository schoolRepository; + + @Autowired + StageRepository stageRepository; + + LocalDate now = LocalDate.parse("2077-06-30"); + LocalDate tomorrow = now.plusDays(1); + + Festival 테코대학교_축제; + Festival 테코대학교_공연_없는_축제; + Festival 우테대학교_축제; + Stage 테코대학교_공연; + Stage 우테대학교_첫째날_공연; + Stage 우테대학교_둘째날_공연; + + @BeforeEach + void setUp() { + LocalDateTime ticketOpenTime = now.atStartOfDay().minusWeeks(1); + School 테코대학교 = schoolRepository.save(new School("teco.ac.kr", "테코대학교", SchoolRegion.서울)); + School 우테대학교 = schoolRepository.save(new School("wote.ac.kr", "우테대학교", SchoolRegion.서울)); + 테코대학교_축제 = festivalRepository.save(new Festival("테코대학교 축제", now, now, 테코대학교)); + 테코대학교_공연_없는_축제 = festivalRepository.save(new Festival("테코대학교 공연 없는 축제", tomorrow, tomorrow, 테코대학교)); + 우테대학교_축제 = festivalRepository.save(new Festival("우테대학교 축제", now, tomorrow, 우테대학교)); + 테코대학교_공연 = stageRepository.save(new Stage(now.atTime(18, 0), ticketOpenTime, 테코대학교_축제)); + 우테대학교_첫째날_공연 = stageRepository.save(new Stage(now.atTime(18, 0), ticketOpenTime, 우테대학교_축제)); + 우테대학교_둘째날_공연 = stageRepository.save(new Stage(tomorrow.atTime(18, 0), ticketOpenTime, 우테대학교_축제)); + } + + @Nested + class findAll { + + @Test + void 페이지네이션이_적용되어야_한다() { + // given + Pageable pageable = PageRequest.ofSize(2); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + assertSoftly(softly -> { + softly.assertThat(response.getSize()).isEqualTo(2); + softly.assertThat(response.getTotalPages()).isEqualTo(2); + softly.assertThat(response.getTotalElements()).isEqualTo(3); + }); + } + + @Test + void 공연의_수가_정확하게_반환되어야_한다() { + // given + Pageable pageable = PageRequest.ofSize(10); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + assertThat(response.getContent()) + .map(AdminFestivalV1Response::stageCount) + .containsExactly(1L, 0L, 2L); + } + + @Nested + class 정렬 { + + @Test + void 축제의_식별자로_정렬_되어야_한다() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "id")); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(우테대학교_축제.getId(), 테코대학교_공연_없는_축제.getId(), 테코대학교_축제.getId()); + } + + @Test + void 축제의_이름으로_정렬이_되어야_한다() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "name")); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + assertThat(response.getContent()) + .map(AdminFestivalV1Response::name) + .containsExactly(우테대학교_축제.getName(), 테코대학교_공연_없는_축제.getName(), 테코대학교_축제.getName()); + } + + @Test + void 학교의_이름으로_정렬이_되어야_한다() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "schoolName")); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(우테대학교_축제.getId(), 테코대학교_축제.getId(), 테코대학교_공연_없는_축제.getId()); + } + + @Test + void 축제의_시작일으로_정렬이_되어야_한다() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "startDate")); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(테코대학교_축제.getId(), 우테대학교_축제.getId(), 테코대학교_공연_없는_축제.getId()); + } + + @Test + void 축제의_종료일으로_정렬이_되어야_한다() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "endDate")); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + System.out.println(response.getContent()); + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(테코대학교_공연_없는_축제.getId(), 우테대학교_축제.getId(), 테코대학교_축제.getId()); + } + + @Test + void 정렬_조건에_없으면_식별자의_오름차순으로_정렬이_되어야_한다() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "foo")); + SearchCondition searchCondition = new SearchCondition("", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + // then + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(테코대학교_축제.getId(), 테코대학교_공연_없는_축제.getId(), 우테대학교_축제.getId()); + } + } + + @Nested + class 검색 { + + @Test + void 축제의_식별자로_검색이_되어야_한다() { + // given + Pageable pageable = Pageable.ofSize(10); + SearchCondition searchCondition = new SearchCondition("id", 테코대학교_축제.getId().toString(), pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(테코대학교_축제.getId()); + } + + @Test + void 축제의_이름이_포함된_검색이_되어야_한다() { + // given + Pageable pageable = Pageable.ofSize(10); + SearchCondition searchCondition = new SearchCondition("name", "테코대학교", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(테코대학교_축제.getId(), 테코대학교_공연_없는_축제.getId()); + } + + @Test + void 학교의_이름이_포함된_검색이_되어야_한다() { + // given + Pageable pageable = Pageable.ofSize(10); + SearchCondition searchCondition = new SearchCondition("schoolName", "우테대학교", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + assertThat(response.getContent()) + .map(AdminFestivalV1Response::id) + .containsExactly(우테대학교_축제.getId()); + } + + @Test + void 검색_필터가_비어있으면_필터링이_적용되지_않는다() { + // given + Pageable pageable = Pageable.ofSize(10); + SearchCondition searchCondition = new SearchCondition("", "글렌", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + assertThat(response.getContent()) + .hasSize(3); + } + + @Test + void 검색어가_비어있으면_필터링이_적용되지_않는다() { + // given + Pageable pageable = Pageable.ofSize(10); + SearchCondition searchCondition = new SearchCondition("id", "", pageable); + + // when + var response = adminFestivalV1QueryService.findAll(searchCondition); + + assertThat(response.getContent()) + .hasSize(3); + } + } + } +} diff --git a/backend/src/test/java/com/festago/admin/presentation/v1/AdminFestivalV1ControllerTest.java b/backend/src/test/java/com/festago/admin/presentation/v1/AdminFestivalV1ControllerTest.java index e43585c07..15a9545bd 100644 --- a/backend/src/test/java/com/festago/admin/presentation/v1/AdminFestivalV1ControllerTest.java +++ b/backend/src/test/java/com/festago/admin/presentation/v1/AdminFestivalV1ControllerTest.java @@ -3,14 +3,19 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import com.festago.admin.application.AdminFestivalV1QueryService; +import com.festago.admin.dto.AdminFestivalV1Response; import com.festago.admin.dto.FestivalV1UpdateRequest; import com.festago.auth.domain.Role; +import com.festago.common.querydsl.SearchCondition; import com.festago.festival.application.command.FestivalCommandFacadeService; import com.festago.festival.dto.FestivalCreateRequest; import com.festago.festival.dto.command.FestivalCreateCommand; @@ -18,12 +23,14 @@ import com.festago.support.WithMockAuth; import jakarta.servlet.http.Cookie; import java.time.LocalDate; +import java.util.List; 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.data.domain.PageImpl; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -44,6 +51,9 @@ class AdminFestivalV1ControllerTest { @Autowired FestivalCommandFacadeService festivalCommandFacadeService; + @Autowired + AdminFestivalV1QueryService adminFestivalV1QueryService; + @Nested class 축제_생성 { @@ -172,4 +182,49 @@ class 올바른_주소로 { } } } + + @Nested + class 모든_축제_정보_조회 { + + final String uri = "/admin/api/v1/festivals"; + + @Nested + @DisplayName("GET " + uri) + class 올바른_주소로 { + + @Test + @WithMockAuth(role = Role.ADMIN) + void 요청을_하면_200_응답과_학교_정보_목록이_반환된다() throws Exception { + // given + var expected = List.of( + new AdminFestivalV1Response(1L, "테코대학교 축제", "테코대학교", LocalDate.now(), LocalDate.now(), 0) + ); + given(adminFestivalV1QueryService.findAll(any(SearchCondition.class))) + .willReturn(new PageImpl<>(expected)); + + // when & then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content.size()").value(1)); + } + + @Test + void 토큰_없이_보내면_401_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(get(uri)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuth(role = Role.MEMBER) + void 토큰의_권한이_Admin이_아니면_404_응답이_반환된다() throws Exception { + // when & then + mockMvc.perform(get(uri) + .cookie(TOKEN_COOKIE)) + .andExpect(status().isNotFound()); + } + } + } }