From 425166a05b2f5571dcc0be5f947f5e49a5f42ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=82=99=ED=97=8C?= <95845037+nak-honest@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:00:02 +0900 Subject: [PATCH] =?UTF-8?q?[Feature]=20-=20=EC=97=AC=ED=96=89=EA=B8=B0=20?= =?UTF-8?q?=EB=82=98=EB=9D=BC=20=EA=B4=80=EB=A0=A8=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20(#586)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: CountryCode 내부로 검증 로직 이동 * refactor: 통일성을 위해 SearchType 의 from 메소드 수정 * feat: TravelogueCountry 검증 로직 추가 * style: 통일성을 위해 "나라"를 "국가" 로 변경 * feat: 여행기 장소 생성시 국가 코드가 NONE 인지 검증하는 로직 추가 * feat: 여행기 생성 시 국가 코드가 NONE 인 여행기 장소가 생성되지 않는 기능 구현 * feat: 태그 생성 기능 삭제 * sytle: 통일성을 위해 괄호 수정 Co-authored-by: eunjungL <62099953+eunjungL@users.noreply.github.com> * feat: TravelogueCountry 에 NONE 국가 코드 허용 * feat: 존재하지 않는 국가로 검색 시 빈 여행기 목록을 반환하는 기능 구현 * refactor: 비즈니스 로직을 facade 가 아닌 서비스에서 수행하도록 변경 * test: 존재하지 않는 국가 검색 테스트 코드 추가 --------- Co-authored-by: eunjungL <62099953+eunjungL@users.noreply.github.com> --- .../touroot/tag/controller/TagController.java | 27 --- .../kr/touroot/tag/dto/TagCreateRequest.java | 15 -- .../touroot/tag/repository/TagRepository.java | 2 - .../kr/touroot/tag/service/TagService.java | 17 -- .../travelogue/domain/TravelogueCountry.java | 22 +++ .../travelogue/domain/TraveloguePlace.java | 8 +- .../travelogue/domain/search/CountryCode.java | 9 +- .../domain/search/SearchCondition.java | 4 + .../travelogue/domain/search/SearchType.java | 11 +- .../service/TravelogueCountryService.java | 24 +-- .../service/TravelogueFacadeService.java | 1 + .../travelogue/service/TravelogueService.java | 7 + .../travelplan/domain/TravelPlanPlace.java | 4 +- .../kr/touroot/tag/TagControllerTest.java | 56 ------- .../kr/touroot/tag/fixture/TagFixture.java | 5 - .../kr/touroot/tag/helper/TagTestHelper.java | 27 --- .../touroot/tag/service/TagServiceTest.java | 53 ------ .../domain/TravelogueCountryTest.java | 55 +++++++ .../domain/TraveloguePlaceTest.java | 2 +- .../domain/search/CountryCodeTest.java | 22 ++- .../fixture/TraveloguePlaceFixture.java | 2 +- .../fixture/TravelogueRequestFixture.java | 12 ++ .../helper/TravelogueTestHelper.java | 26 +++ .../service/TravelogueCountryServiceTest.java | 154 ++++++++++++++++++ .../service/TravelogueFacadeServiceTest.java | 22 ++- .../service/TravelogueServiceTest.java | 17 ++ .../domain/TravelPlanPlaceTest.java | 2 +- 27 files changed, 374 insertions(+), 232 deletions(-) delete mode 100644 backend/src/main/java/kr/touroot/tag/dto/TagCreateRequest.java delete mode 100644 backend/src/test/java/kr/touroot/tag/TagControllerTest.java delete mode 100644 backend/src/test/java/kr/touroot/tag/helper/TagTestHelper.java delete mode 100644 backend/src/test/java/kr/touroot/tag/service/TagServiceTest.java create mode 100644 backend/src/test/java/kr/touroot/travelogue/domain/TravelogueCountryTest.java create mode 100644 backend/src/test/java/kr/touroot/travelogue/service/TravelogueCountryServiceTest.java diff --git a/backend/src/main/java/kr/touroot/tag/controller/TagController.java b/backend/src/main/java/kr/touroot/tag/controller/TagController.java index 3dffd2e6..d1ed5e50 100644 --- a/backend/src/main/java/kr/touroot/tag/controller/TagController.java +++ b/backend/src/main/java/kr/touroot/tag/controller/TagController.java @@ -1,23 +1,15 @@ package kr.touroot.tag.controller; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import java.net.URI; import java.util.List; -import kr.touroot.global.exception.dto.ExceptionResponse; -import kr.touroot.tag.dto.TagCreateRequest; import kr.touroot.tag.dto.TagResponse; import kr.touroot.tag.service.TagService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -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; @@ -29,25 +21,6 @@ public class TagController { private final TagService tagService; - @Operation(summary = "태그 생성") - @ApiResponses(value = { - @ApiResponse( - responseCode = "201", - description = "태그가 생성이 정상적으로 성공했을 때" - ), - @ApiResponse( - responseCode = "400", - description = "Body에 유효하지 않은 값이 존재하거나 중복된 태그가 존재할 때", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - }) - @PostMapping - public ResponseEntity createTag(@Valid @RequestBody TagCreateRequest request) { - TagResponse data = tagService.createTag(request); - return ResponseEntity.created(URI.create("/api/v1/tags/" + data.id())) - .body(data); - } - @Operation(summary = "모든 태그 조회") @ApiResponses(value = { @ApiResponse( diff --git a/backend/src/main/java/kr/touroot/tag/dto/TagCreateRequest.java b/backend/src/main/java/kr/touroot/tag/dto/TagCreateRequest.java deleted file mode 100644 index 5c97f88f..00000000 --- a/backend/src/main/java/kr/touroot/tag/dto/TagCreateRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package kr.touroot.tag.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import kr.touroot.tag.domain.Tag; - -public record TagCreateRequest( - @Schema(description = "태그 이름", example = "강아지와 함께") - @NotEmpty(message = "태그는 비어있을 수 없습니다.") String tag -) { - - public Tag toTag() { - return new Tag(tag); - } -} diff --git a/backend/src/main/java/kr/touroot/tag/repository/TagRepository.java b/backend/src/main/java/kr/touroot/tag/repository/TagRepository.java index 23430a27..c5bfd6b2 100644 --- a/backend/src/main/java/kr/touroot/tag/repository/TagRepository.java +++ b/backend/src/main/java/kr/touroot/tag/repository/TagRepository.java @@ -4,6 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface TagRepository extends JpaRepository { - - boolean existsByTag(String tag); } diff --git a/backend/src/main/java/kr/touroot/tag/service/TagService.java b/backend/src/main/java/kr/touroot/tag/service/TagService.java index 645dd53c..4a95ff97 100644 --- a/backend/src/main/java/kr/touroot/tag/service/TagService.java +++ b/backend/src/main/java/kr/touroot/tag/service/TagService.java @@ -1,9 +1,6 @@ package kr.touroot.tag.service; import java.util.List; -import kr.touroot.global.exception.BadRequestException; -import kr.touroot.tag.domain.Tag; -import kr.touroot.tag.dto.TagCreateRequest; import kr.touroot.tag.dto.TagResponse; import kr.touroot.tag.repository.TagRepository; import lombok.RequiredArgsConstructor; @@ -19,20 +16,6 @@ public class TagService { private final TagRepository tagRepository; - @Transactional - public TagResponse createTag(TagCreateRequest tagCreateRequest) { - validateDuplicated(tagCreateRequest); - Tag savedTag = tagRepository.save(tagCreateRequest.toTag()); - - return TagResponse.from(savedTag); - } - - private void validateDuplicated(TagCreateRequest tagCreateRequest) { - if (tagRepository.existsByTag(tagCreateRequest.tag())) { - throw new BadRequestException("이미 존재하는 태그입니다."); - } - } - @Cacheable(cacheNames = "tag") @Transactional(readOnly = true) public List readTags() { diff --git a/backend/src/main/java/kr/touroot/travelogue/domain/TravelogueCountry.java b/backend/src/main/java/kr/touroot/travelogue/domain/TravelogueCountry.java index 503e0b60..a6a641d0 100644 --- a/backend/src/main/java/kr/touroot/travelogue/domain/TravelogueCountry.java +++ b/backend/src/main/java/kr/touroot/travelogue/domain/TravelogueCountry.java @@ -10,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import kr.touroot.global.exception.BadRequestException; import kr.touroot.travelogue.domain.search.CountryCode; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -24,6 +25,8 @@ @Entity public class TravelogueCountry { + private static final int MIN_COUNT = 1; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -40,8 +43,27 @@ public class TravelogueCountry { private Integer count; public TravelogueCountry(Travelogue travelogue, CountryCode countryCode, Integer count) { + validate(travelogue, countryCode, count); this.travelogue = travelogue; this.countryCode = countryCode; this.count = count; } + + + private void validate(Travelogue travelogue, CountryCode countryCode, Integer count) { + validateNotNull(travelogue, countryCode, count); + validateCount(count); + } + + private void validateNotNull(Travelogue travelogue, CountryCode countryCode, Integer count) { + if (travelogue == null || countryCode == null || count == null) { + throw new BadRequestException("여행기와 국가 코드, 국가 코드의 count 는 null 일 수 없습니다."); + } + } + + private void validateCount(Integer count) { + if (count < MIN_COUNT) { + throw new BadRequestException(String.format("국가 코드의 개수는 %d 보다 커야합니다.", MIN_COUNT)); + } + } } diff --git a/backend/src/main/java/kr/touroot/travelogue/domain/TraveloguePlace.java b/backend/src/main/java/kr/touroot/travelogue/domain/TraveloguePlace.java index 680a8791..bec2e4ac 100644 --- a/backend/src/main/java/kr/touroot/travelogue/domain/TraveloguePlace.java +++ b/backend/src/main/java/kr/touroot/travelogue/domain/TraveloguePlace.java @@ -84,7 +84,7 @@ public TraveloguePlace( this.name = name; this.position = position; this.travelogueDay = travelogueDay; - this.countryCode = CountryCode.valueOfIgnoreCase(countryCode); + this.countryCode = CountryCode.from(countryCode); } public TraveloguePlace( @@ -147,11 +147,7 @@ private void validatePlaceNameLength(String placeName) { } private void validateCountryCode(String countryCode) { - try { - CountryCode.valueOfIgnoreCase(countryCode); - } catch (IllegalArgumentException e) { - throw new BadRequestException("존재하지 않는 국가 코드입니다"); - } + CountryCode.from(countryCode); } public void addPhoto(TraveloguePhoto photo) { diff --git a/backend/src/main/java/kr/touroot/travelogue/domain/search/CountryCode.java b/backend/src/main/java/kr/touroot/travelogue/domain/search/CountryCode.java index eaf797e9..d5ef161d 100644 --- a/backend/src/main/java/kr/touroot/travelogue/domain/search/CountryCode.java +++ b/backend/src/main/java/kr/touroot/travelogue/domain/search/CountryCode.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.Set; +import kr.touroot.global.exception.BadRequestException; public enum CountryCode { @@ -259,7 +260,11 @@ public static CountryCode findByName(String name) { .orElse(NONE); } - public static CountryCode valueOfIgnoreCase(String name) { - return CountryCode.valueOf(name.toUpperCase()); + public static CountryCode from(String code) { + try { + return CountryCode.valueOf(code.toUpperCase()); + } catch (IllegalArgumentException exception) { + throw new BadRequestException("존재하지 않는 국가 코드입니다."); + } } } diff --git a/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchCondition.java b/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchCondition.java index af30d379..dc466a49 100644 --- a/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchCondition.java +++ b/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchCondition.java @@ -16,4 +16,8 @@ public SearchCondition(String keyword, SearchType searchType) { public boolean isEmptyCondition() { return keyword == null && searchType == null; } + + public boolean isNoneCountry() { + return searchType == SearchType.COUNTRY && CountryCode.findByName(keyword) == CountryCode.NONE; + } } diff --git a/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchType.java b/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchType.java index 804bc632..bfd84557 100644 --- a/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchType.java +++ b/backend/src/main/java/kr/touroot/travelogue/domain/search/SearchType.java @@ -1,14 +1,15 @@ package kr.touroot.travelogue.domain.search; -import java.util.Arrays; +import kr.touroot.global.exception.BadRequestException; public enum SearchType { TITLE, AUTHOR, COUNTRY; public static SearchType from(String searchType) { - return Arrays.stream(SearchType.values()) - .filter(type -> searchType.equalsIgnoreCase(type.name())) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 검색 키워드 종류입니다.")); + try { + return SearchType.valueOf(searchType.toUpperCase()); + } catch (IllegalArgumentException exception) { + throw new BadRequestException("존재하지 않는 검색 키워드 종류입니다."); + } } } diff --git a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueCountryService.java b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueCountryService.java index 62b6de82..b28f4212 100644 --- a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueCountryService.java +++ b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueCountryService.java @@ -19,30 +19,34 @@ public class TravelogueCountryService { private final TravelogueCountryRepository travelogueCountryRepository; - @Transactional(readOnly = true) - public List readCountryByTravelogue(Travelogue travelogue) { - return travelogueCountryRepository.findAllByTravelogue(travelogue); - } - @Transactional - public void createTravelogueCountries(Travelogue travelogue, TravelogueRequest request) { + public List createTravelogueCountries(Travelogue travelogue, TravelogueRequest request) { Map countryCounts = countCountries(request); - countryCounts.forEach((countryCode, count) -> travelogueCountryRepository.save( - new TravelogueCountry(travelogue, countryCode, count.intValue()))); + return countryCounts.entrySet().stream() + .map(entry -> travelogueCountryRepository.save( + new TravelogueCountry(travelogue, entry.getKey(), entry.getValue().intValue())) + ) + .toList(); } private Map countCountries(TravelogueRequest request) { return request.days().stream() .flatMap(day -> day.places().stream()) .map(place -> CountryCode.valueOf(place.countryCode())) + .filter(countryCode -> countryCode != CountryCode.NONE) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); } + @Transactional(readOnly = true) + public List getTravelogueCountryByTravelogue(Travelogue travelogue) { + return travelogueCountryRepository.findAllByTravelogue(travelogue); + } + @Transactional - public void updateTravelogueCountries(Travelogue travelogue, TravelogueRequest request) { + public List updateTravelogueCountries(Travelogue travelogue, TravelogueRequest request) { deleteAllByTravelogue(travelogue); - createTravelogueCountries(travelogue, request); + return createTravelogueCountries(travelogue, request); } @Transactional diff --git a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueFacadeService.java b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueFacadeService.java index c8727479..cae32a9c 100644 --- a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueFacadeService.java +++ b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueFacadeService.java @@ -69,6 +69,7 @@ public Page findSimpleTravelogues( ) { TravelogueFilterCondition filter = filterRequest.toFilterCondition(); SearchCondition searchCondition = searchRequest.toSearchCondition(); + Page travelogues = travelogueService.findAll(searchCondition, filter, pageable); return travelogues.map(this::getTravelogueSimpleResponse); diff --git a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueService.java b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueService.java index 48cf73f3..df8ce340 100644 --- a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueService.java +++ b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueService.java @@ -45,6 +45,9 @@ public Page findAllByMember(Member member, Pageable pageable) { public Page findByKeyword(TravelogueSearchRequest request, Pageable pageable) { SearchType searchType = SearchType.from(request.searchType()); SearchCondition searchCondition = new SearchCondition(request.keyword(), searchType); + if (searchCondition.isNoneCountry()) { + return Page.empty(); + } return travelogueQueryRepository.findAllBySearchCondition(searchCondition, pageable); } @@ -55,6 +58,10 @@ public Page findAll( TravelogueFilterCondition filter, Pageable pageable ) { + if (searchCondition.isNoneCountry()) { + return Page.empty(); + } + if (filter.isEmptyCondition() && searchCondition.isEmptyCondition()) { return travelogueRepository.findAll(pageable); } diff --git a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java index 11af2c9f..16106840 100644 --- a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java +++ b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java @@ -71,7 +71,7 @@ public TravelPlanPlace(Long id, Integer order, TravelPlanDay day, String name, P this.day = day; this.name = name; this.position = position; - this.countryCode = CountryCode.valueOfIgnoreCase(countryCode); + this.countryCode = CountryCode.from(countryCode); } public TravelPlanPlace(Integer order, TravelPlanDay day, String name, String latitude, String longitude, @@ -115,7 +115,7 @@ private void validatePlaceNameLength(String placeName) { private void validateCountryCode(String countryCode) { try { - CountryCode.valueOfIgnoreCase(countryCode); + CountryCode.from(countryCode); } catch (IllegalArgumentException e) { throw new BadRequestException("존재하지 않는 국가 코드입니다"); } diff --git a/backend/src/test/java/kr/touroot/tag/TagControllerTest.java b/backend/src/test/java/kr/touroot/tag/TagControllerTest.java deleted file mode 100644 index 53bfbfc0..00000000 --- a/backend/src/test/java/kr/touroot/tag/TagControllerTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package kr.touroot.tag; - -import static org.hamcrest.Matchers.is; - -import io.restassured.RestAssured; -import io.restassured.http.ContentType; -import kr.touroot.global.AcceptanceTest; -import kr.touroot.tag.dto.TagCreateRequest; -import kr.touroot.tag.fixture.TagFixture; -import kr.touroot.tag.helper.TagTestHelper; -import kr.touroot.utils.DatabaseCleaner; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.web.server.LocalServerPort; - -@DisplayName("태그 컨트롤러") -@AcceptanceTest -class TagControllerTest { - - private final DatabaseCleaner databaseCleaner; - private final TagTestHelper testHelper; - - @LocalServerPort - private int port; - - @Autowired - public TagControllerTest(DatabaseCleaner databaseCleaner, TagTestHelper testHelper) { - this.databaseCleaner = databaseCleaner; - this.testHelper = testHelper; - } - - @BeforeEach - void setUp() { - RestAssured.port = port; - databaseCleaner.executeTruncate(); - } - - @DisplayName("태그 컨트롤러는 태그 생성 요청 시 201을 응답한다.") - @Test - void createTag() { - // given - TagCreateRequest request = TagFixture.TAG_1.getCreateRequest(); - - // when & then - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(request) - .when().log().all() - .post("/api/v1/tags") - .then().log().all() - .statusCode(201) - .header("Location", is("/api/v1/tags/1")); - } -} diff --git a/backend/src/test/java/kr/touroot/tag/fixture/TagFixture.java b/backend/src/test/java/kr/touroot/tag/fixture/TagFixture.java index dcd3e47d..b506c705 100644 --- a/backend/src/test/java/kr/touroot/tag/fixture/TagFixture.java +++ b/backend/src/test/java/kr/touroot/tag/fixture/TagFixture.java @@ -1,7 +1,6 @@ package kr.touroot.tag.fixture; import kr.touroot.tag.domain.Tag; -import kr.touroot.tag.dto.TagCreateRequest; import kr.touroot.tag.dto.TagResponse; public enum TagFixture { @@ -21,10 +20,6 @@ public Tag get() { return new Tag(tag); } - public TagCreateRequest getCreateRequest() { - return new TagCreateRequest(tag); - } - public TagResponse getResponse(Long id) { return new TagResponse(id, tag); } diff --git a/backend/src/test/java/kr/touroot/tag/helper/TagTestHelper.java b/backend/src/test/java/kr/touroot/tag/helper/TagTestHelper.java deleted file mode 100644 index 169ae524..00000000 --- a/backend/src/test/java/kr/touroot/tag/helper/TagTestHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -package kr.touroot.tag.helper; - -import kr.touroot.tag.domain.Tag; -import kr.touroot.tag.fixture.TagFixture; -import kr.touroot.tag.repository.TagRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class TagTestHelper { - - private final TagRepository tagRepository; - - @Autowired - public TagTestHelper(TagRepository tagRepository) { - this.tagRepository = tagRepository; - } - - public Tag initTagData() { - Tag tag = TagFixture.TAG_1.get();; - return tagRepository.save(tag); - } - - public Tag initTagData(Tag tag) { - return tagRepository.save(tag); - } -} diff --git a/backend/src/test/java/kr/touroot/tag/service/TagServiceTest.java b/backend/src/test/java/kr/touroot/tag/service/TagServiceTest.java deleted file mode 100644 index 5e30d5d0..00000000 --- a/backend/src/test/java/kr/touroot/tag/service/TagServiceTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package kr.touroot.tag.service; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import kr.touroot.global.ServiceTest; -import kr.touroot.global.config.TestQueryDslConfig; -import kr.touroot.global.exception.BadRequestException; -import kr.touroot.tag.domain.Tag; -import kr.touroot.tag.dto.TagCreateRequest; -import kr.touroot.tag.fixture.TagFixture; -import kr.touroot.tag.helper.TagTestHelper; -import kr.touroot.utils.DatabaseCleaner; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; - -@DisplayName("태그 서비스") -@Import(value = {TagService.class, TagTestHelper.class, TestQueryDslConfig.class}) -@ServiceTest -class TagServiceTest { - - private final DatabaseCleaner databaseCleaner; - private final TagTestHelper testHelper; - private final TagService tagService; - - @Autowired - public TagServiceTest(DatabaseCleaner databaseCleaner, TagTestHelper testHelper, TagService tagService) { - this.databaseCleaner = databaseCleaner; - this.testHelper = testHelper; - this.tagService = tagService; - } - - @BeforeEach - void setUp() { - databaseCleaner.executeTruncate(); - } - - @DisplayName("태그 서비스는 중복된 태그 생성 요청시 예외가 발생한다.") - @Test - void validateDuplicated() { - // given - Tag tag = TagFixture.TAG_1.get(); - testHelper.initTagData(tag); - TagCreateRequest request = new TagCreateRequest(tag.getTag()); - - // when & then - assertThatThrownBy(() -> tagService.createTag(request)) - .isInstanceOf(BadRequestException.class) - .hasMessage("이미 존재하는 태그입니다."); - } -} diff --git a/backend/src/test/java/kr/touroot/travelogue/domain/TravelogueCountryTest.java b/backend/src/test/java/kr/touroot/travelogue/domain/TravelogueCountryTest.java new file mode 100644 index 00000000..118aa893 --- /dev/null +++ b/backend/src/test/java/kr/touroot/travelogue/domain/TravelogueCountryTest.java @@ -0,0 +1,55 @@ +package kr.touroot.travelogue.domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import kr.touroot.global.exception.BadRequestException; +import kr.touroot.travelogue.domain.search.CountryCode; +import kr.touroot.travelogue.fixture.TravelogueFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class TravelogueCountryTest { + + @DisplayName("검증 규칙에 어긋나지 않는 여행기 생성 시 예외가 발생하지 않는다") + @Test + void createTravelogueCountryWithValidData() { + assertThatCode(() -> new TravelogueCountry(TravelogueFixture.TRAVELOGUE.get(), CountryCode.KR, 1)) + .doesNotThrowAnyException(); + } + + @DisplayName("여행기가 null인 경우 여행기 국가 생성 시 예외가 발생한다") + @Test + void createTravelogueCountryWithNullTravelogue() { + assertThatThrownBy(() -> new TravelogueCountry(null, CountryCode.KR, 1)) + .isInstanceOf(BadRequestException.class) + .hasMessage("여행기와 국가 코드, 국가 코드의 count 는 null 일 수 없습니다."); + } + + @DisplayName("국가 코드가 null인 경우 여행기 국가 생성 시 예외가 발생한다") + @Test + void createTravelogueCountryWithNullCountryCode() { + assertThatThrownBy(() -> new TravelogueCountry(TravelogueFixture.TRAVELOGUE.get(), null, 1)) + .isInstanceOf(BadRequestException.class) + .hasMessage("여행기와 국가 코드, 국가 코드의 count 는 null 일 수 없습니다."); + } + + @DisplayName("count가 null인 경우 여행기 국가 생성 시 예외가 발생한다") + @Test + void createTravelogueCountryWithNullCount() { + assertThatThrownBy(() -> new TravelogueCountry(TravelogueFixture.TRAVELOGUE.get(), CountryCode.KR, null)) + .isInstanceOf(BadRequestException.class) + .hasMessage("여행기와 국가 코드, 국가 코드의 count 는 null 일 수 없습니다."); + } + + @DisplayName("count가 1보다 작은 경우 여행기 국가 생성 시 예외가 발생한다") + @ValueSource(ints = {0, -1}) + @ParameterizedTest + void createTravelogueCountryWithLessThanMinCount(int count) { + assertThatThrownBy(() -> new TravelogueCountry(TravelogueFixture.TRAVELOGUE.get(), CountryCode.KR, count)) + .isInstanceOf(BadRequestException.class) + .hasMessage("국가 코드의 개수는 1 보다 커야합니다."); + } +} diff --git a/backend/src/test/java/kr/touroot/travelogue/domain/TraveloguePlaceTest.java b/backend/src/test/java/kr/touroot/travelogue/domain/TraveloguePlaceTest.java index 395d92ab..19ff4b64 100644 --- a/backend/src/test/java/kr/touroot/travelogue/domain/TraveloguePlaceTest.java +++ b/backend/src/test/java/kr/touroot/travelogue/domain/TraveloguePlaceTest.java @@ -130,7 +130,7 @@ void createPlaceWithInvalidCountryCode() { "SAM-572") ) .isInstanceOf(BadRequestException.class) - .hasMessage("존재하지 않는 국가 코드입니다"); + .hasMessage("존재하지 않는 국가 코드입니다."); } @DisplayName("장소 사진을 추가할 수 있다") diff --git a/backend/src/test/java/kr/touroot/travelogue/domain/search/CountryCodeTest.java b/backend/src/test/java/kr/touroot/travelogue/domain/search/CountryCodeTest.java index 3f4825b4..429110da 100644 --- a/backend/src/test/java/kr/touroot/travelogue/domain/search/CountryCodeTest.java +++ b/backend/src/test/java/kr/touroot/travelogue/domain/search/CountryCodeTest.java @@ -1,7 +1,9 @@ package kr.touroot.travelogue.domain.search; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import kr.touroot.global.exception.BadRequestException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -19,7 +21,7 @@ void findByName(String name) { .isEqualTo(CountryCode.KR); } - @DisplayName("없는 나라 이름으로 찾으면 NONE을 반환한다.") + @DisplayName("없는 국가 이름으로 찾으면 NONE을 반환한다.") @Test void findByNonCountryName() { CountryCode code = CountryCode.findByName("미역국"); @@ -27,4 +29,22 @@ void findByNonCountryName() { assertThat(code) .isEqualTo(CountryCode.NONE); } + + @DisplayName("대소문자와 관계없이 국가코드로 찾을 수 있다.") + @ValueSource(strings = {"KR", "kr", "Kr"}) + @ParameterizedTest + void from(String code) { + CountryCode countryCode = CountryCode.from(code); + + assertThat(countryCode) + .isEqualTo(CountryCode.KR); + } + + @DisplayName("존재하지 않는 국가 코드로 찾으면 예외가 발생한다.") + @Test + void fromNotExists() { + assertThatThrownBy(() -> CountryCode.from("WOO")) + .isInstanceOf(BadRequestException.class) + .hasMessage("존재하지 않는 국가 코드입니다."); + } } diff --git a/backend/src/test/java/kr/touroot/travelogue/fixture/TraveloguePlaceFixture.java b/backend/src/test/java/kr/touroot/travelogue/fixture/TraveloguePlaceFixture.java index dc1c359e..d7fa730c 100644 --- a/backend/src/test/java/kr/touroot/travelogue/fixture/TraveloguePlaceFixture.java +++ b/backend/src/test/java/kr/touroot/travelogue/fixture/TraveloguePlaceFixture.java @@ -10,7 +10,7 @@ public enum TraveloguePlaceFixture { TRAVELOGUE_PLACE(1, "에메랄드 빛 해변", "함덕 해수욕장", "34.54343", "126.66977", TRAVELOGUE_DAY.get(), "KR"), - ; + TRAVELOGUE_PLACE_WITH_NONE_COUNTRY_CODE(1, "해변", "함덕", "34.54343", "126.66977", TRAVELOGUE_DAY.get(), "NONE"); private final int order; private final String description; diff --git a/backend/src/test/java/kr/touroot/travelogue/fixture/TravelogueRequestFixture.java b/backend/src/test/java/kr/touroot/travelogue/fixture/TravelogueRequestFixture.java index d38b3bc8..43ed8d60 100644 --- a/backend/src/test/java/kr/touroot/travelogue/fixture/TravelogueRequestFixture.java +++ b/backend/src/test/java/kr/touroot/travelogue/fixture/TravelogueRequestFixture.java @@ -59,6 +59,18 @@ public static List getTraveloguePlaceRequests(List getTraveloguePlaceRequestsWithNoneCountryCode( + List photos) { + return List.of(new TraveloguePlaceRequest( + "함덕 해수욕장", + getTraveloguePositionRequest(), + "에메랄드 빛 해변", + photos, + "NONE" + )); + } + + public static List getUpdateTraveloguePlaceRequests(List photos) { return List.of(new TraveloguePlaceRequest( "함덕 해수욕장", diff --git a/backend/src/test/java/kr/touroot/travelogue/helper/TravelogueTestHelper.java b/backend/src/test/java/kr/touroot/travelogue/helper/TravelogueTestHelper.java index 60a157a9..23a6b08c 100644 --- a/backend/src/test/java/kr/touroot/travelogue/helper/TravelogueTestHelper.java +++ b/backend/src/test/java/kr/touroot/travelogue/helper/TravelogueTestHelper.java @@ -5,6 +5,7 @@ import static kr.touroot.travelogue.fixture.TravelogueFixture.TRAVELOGUE; import static kr.touroot.travelogue.fixture.TraveloguePhotoFixture.TRAVELOGUE_PHOTO; import static kr.touroot.travelogue.fixture.TraveloguePlaceFixture.TRAVELOGUE_PLACE; +import static kr.touroot.travelogue.fixture.TraveloguePlaceFixture.TRAVELOGUE_PLACE_WITH_NONE_COUNTRY_CODE; import java.util.List; import kr.touroot.member.domain.LoginType; @@ -79,6 +80,15 @@ public Travelogue initTravelogueTestData() { return initTravelogueTestData(author); } + public Travelogue initTravelogueTestDataWithoutCountryCode() { + Member author = persistMember(); + Travelogue travelogue = persistTravelogue(author); + TravelogueDay day = persistTravelogueDay(travelogue); + TraveloguePlace place = persistTraveloguePlace(day); + persistTraveloguePhoto(place); + return travelogue; + } + public Travelogue initTravelogueTestDataWithSeveralDays() { Member author = persistMember(); return initTravelogueTestDataWithSeveralDays(author); @@ -135,6 +145,16 @@ public Travelogue initTravelogueTestDataWithLike(Member liker) { return travelogue; } + public Travelogue initTravelogueTestDataWithNoneCountryCode() { + Member author = persistMember(); + Travelogue travelogue = persistTravelogue(author); + TravelogueDay day = persistTravelogueDay(travelogue); + TraveloguePlace place = persistTraveloguePlaceWithNoneCountryCode(day); + persistTraveloguePhoto(place); + + return travelogue; + } + private void persisTravelogueTag(Travelogue travelogue, Tag tag) { Tag savedTag = initTagTestData(tag); travelogueTagRepository.save(new TravelogueTag(travelogue, savedTag)); @@ -164,6 +184,12 @@ public TraveloguePlace persistTraveloguePlace(TravelogueDay day) { return traveloguePlaceRepository.save(place); } + public TraveloguePlace persistTraveloguePlaceWithNoneCountryCode(TravelogueDay day) { + TraveloguePlace place = TRAVELOGUE_PLACE_WITH_NONE_COUNTRY_CODE.create(day); + + return traveloguePlaceRepository.save(place); + } + public TravelogueCountry persistTravelogueCountry(Travelogue travelogue) { TravelogueCountry travelogueCountry = TRAVELOGUE_COUNTRY.create(travelogue); diff --git a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueCountryServiceTest.java b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueCountryServiceTest.java new file mode 100644 index 00000000..9107a627 --- /dev/null +++ b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueCountryServiceTest.java @@ -0,0 +1,154 @@ +package kr.touroot.travelogue.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import kr.touroot.global.ServiceTest; +import kr.touroot.travelogue.domain.Travelogue; +import kr.touroot.travelogue.domain.TravelogueCountry; +import kr.touroot.travelogue.domain.search.CountryCode; +import kr.touroot.travelogue.dto.request.TravelogueDayRequest; +import kr.touroot.travelogue.dto.request.TraveloguePhotoRequest; +import kr.touroot.travelogue.dto.request.TraveloguePlaceRequest; +import kr.touroot.travelogue.dto.request.TravelogueRequest; +import kr.touroot.travelogue.fixture.TravelogueRequestFixture; +import kr.touroot.travelogue.helper.TravelogueTestHelper; +import kr.touroot.utils.DatabaseCleaner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +@DisplayName("여행기 국가 서비스") +@Import(value = {TravelogueCountryService.class, TravelogueTestHelper.class}) +@ServiceTest +class TravelogueCountryServiceTest { + + public static final int BASIC_PAGE_SIZE = 5; + + private final TravelogueCountryService travelogueCountryService; + private final DatabaseCleaner databaseCleaner; + private final TravelogueTestHelper testHelper; + + @Autowired + public TravelogueCountryServiceTest( + TravelogueCountryService travelogueCountryService, + DatabaseCleaner databaseCleaner, + TravelogueTestHelper testHelper + ) { + this.travelogueCountryService = travelogueCountryService; + this.databaseCleaner = databaseCleaner; + this.testHelper = testHelper; + } + + @BeforeEach + void setUp() { + databaseCleaner.executeTruncate(); + } + + @DisplayName("여행기 국가들을 생성할 수 있다.") + @Test + void createTravelogueCountries() { + // given + Travelogue travelogue = testHelper.initTravelogueTestDataWithoutCountryCode(); + TravelogueRequest travelogueRequest = getTravelogueRequest(); + + // when + List travelogueCountries = + travelogueCountryService.createTravelogueCountries(travelogue, travelogueRequest); + + // then + assertAll( + () -> assertThat(travelogueCountries).hasSize(1), + () -> assertThat(travelogueCountries.get(0).getTravelogue().getId()).isEqualTo(1L), + () -> assertThat(travelogueCountries.get(0).getCount()).isEqualTo(1), + () -> assertThat(travelogueCountries.get(0).getCountryCode()).isEqualTo(CountryCode.KR) + ); + } + + @DisplayName("여행기 장소의 국가 코드가 None 이면 여행기 국가가 생성되지 않는다.") + @Test + void createTravelogueCountriesWithNoneCountryCode() { + // given + Travelogue travelogue = testHelper.initTravelogueTestDataWithNoneCountryCode(); + TravelogueRequest travelogueRequest = getTravelogueRequestWithNoneCountryCode(); + + // when + List travelogueCountries = + travelogueCountryService.createTravelogueCountries(travelogue, travelogueRequest); + + // then + assertThat(travelogueCountries).isEmpty(); + } + + @DisplayName("여행기의 여행기 국가를 조회할 수 있다.") + @Test + void getTravelogueCountryByTravelogue() { + // given + Travelogue travelogue = testHelper.initTravelogueTestDataWithoutCountryCode(); + TravelogueRequest travelogueRequest = getTravelogueRequest(); + travelogueCountryService.createTravelogueCountries(travelogue, travelogueRequest); + + // when + List travelogueCountries = travelogueCountryService.getTravelogueCountryByTravelogue( + travelogue); + + // then + assertAll( + () -> assertThat(travelogueCountries).hasSize(1), + () -> assertThat(travelogueCountries.get(0).getTravelogue().getId()).isEqualTo(1L), + () -> assertThat(travelogueCountries.get(0).getCount()).isEqualTo(1), + () -> assertThat(travelogueCountries.get(0).getCountryCode()).isEqualTo(CountryCode.KR) + ); + } + + @DisplayName("여행기 국가를 업데이트 할 수 있다.") + @Test + void updateTravelogueCountries() { + // given + Travelogue travelogue = testHelper.initTravelogueTestDataWithoutCountryCode(); + TravelogueRequest travelogueRequest = getTravelogueRequest(); + Travelogue newTravelogue = testHelper.initTravelogueTestDataWithNoneCountryCode(); + TravelogueRequest newTravelogueRequest = getTravelogueRequestWithNoneCountryCode(); + travelogueCountryService.createTravelogueCountries(travelogue, travelogueRequest); + + // when + List travelogueCountries = + travelogueCountryService.updateTravelogueCountries(newTravelogue, newTravelogueRequest); + + // then + assertThat(travelogueCountries).isEmpty(); + } + + @DisplayName("여행기 국가를 업데이트 할 수 있다.") + @Test + void deleteAllByTravelogue() { + // given + Travelogue travelogue = testHelper.initTravelogueTestDataWithoutCountryCode(); + TravelogueRequest travelogueRequest = getTravelogueRequest(); + travelogueCountryService.createTravelogueCountries(travelogue, travelogueRequest); + + // when + travelogueCountryService.deleteAllByTravelogue(travelogue); + + // then + assertThat(travelogueCountryService.getTravelogueCountryByTravelogue(travelogue)).isEmpty(); + } + + private TravelogueRequest getTravelogueRequest() { + List photos = TravelogueRequestFixture.getTraveloguePhotoRequests(); + List places = TravelogueRequestFixture.getTraveloguePlaceRequests(photos); + List days = TravelogueRequestFixture.getTravelogueDayRequests(places); + return TravelogueRequestFixture.getTravelogueRequest(days); + } + + private TravelogueRequest getTravelogueRequestWithNoneCountryCode() { + List photos = TravelogueRequestFixture.getTraveloguePhotoRequests(); + List places = + TravelogueRequestFixture.getTraveloguePlaceRequestsWithNoneCountryCode(photos); + List days = TravelogueRequestFixture.getTravelogueDayRequests(places); + return TravelogueRequestFixture.getTravelogueRequest(days); + } +} diff --git a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueFacadeServiceTest.java b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueFacadeServiceTest.java index ffb2c195..65becd7e 100644 --- a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueFacadeServiceTest.java +++ b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueFacadeServiceTest.java @@ -234,12 +234,32 @@ void findTraveloguesByCountryCodeKeyword() { PageRequest pageRequest = PageRequest.of(0, 5, Sort.by("id")); // when - Page searchResults = service.findSimpleTravelogues(filterRequest, searchRequest, pageRequest); + Page searchResults = service.findSimpleTravelogues(filterRequest, searchRequest, + pageRequest); // then assertThat(searchResults).containsAll(responses); } + @DisplayName("존재하지 않는 국가를 기반으로 여행기 목록을 조회하면 빈 결과를 반환한다.") + @Test + void findTraveloguesByNoneCountryCodeKeyword() { + // given + testHelper.initAllTravelogueTestData(); + Page responses = TravelogueResponseFixture.getTravelogueSimpleResponses(); + + TravelogueSearchRequest searchRequest = new TravelogueSearchRequest("미역국", "country"); + TravelogueFilterRequest filterRequest = new TravelogueFilterRequest(null, null); + PageRequest pageRequest = PageRequest.of(0, 5, Sort.by("id")); + + // when + Page searchResults = service.findSimpleTravelogues(filterRequest, searchRequest, + pageRequest); + + // then + assertThat(searchResults).isEmpty(); + } + @DisplayName("여행기를 수정할 수 있다.") @Test void updateTravelogue() { diff --git a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueServiceTest.java b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueServiceTest.java index 20409b85..5099d030 100644 --- a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueServiceTest.java +++ b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueServiceTest.java @@ -125,6 +125,23 @@ void findByKeywordWithNotExistRequest() { assertThat(actual).isEmpty(); } + @DisplayName("존재하지 않는 국가로 여행기를 조회하면 빈 페이지가 반환된다.") + @Test + void findByKeywordWithNotExistCountryRequest() { + // given + testHelper.initTravelogueTestData(); + + SearchCondition searchCondition = new SearchCondition("미역국", SearchType.TITLE); + TravelogueFilterCondition filter = new TravelogueFilterCondition(null, null); + PageRequest pageRequest = PageRequest.of(0, 5, Sort.by("createdAt")); + + // when + Page actual = travelogueService.findAll(searchCondition, filter, pageRequest); + + // then + assertThat(actual).isEmpty(); + } + @DisplayName("여행기를 수정할 수 있다.") @Test void updateTravelogue() { diff --git a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java index 7c7e1cc7..2aa2b8ba 100644 --- a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java +++ b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java @@ -102,7 +102,7 @@ void createPlaceWithInvalidCountryCode() { assertThatThrownBy( () -> new TravelPlanPlace(VALID_ORDER, VALID_DAY, VALID_NAME, VALID_LAT, VALID_LNG, "CONCODE")) .isInstanceOf(BadRequestException.class) - .hasMessage("존재하지 않는 국가 코드입니다"); + .hasMessage("존재하지 않는 국가 코드입니다."); } @DisplayName("Todo를 추가할 수 있다")