From b4ae7fddd164e06ef6ef1fdab11efc1dede4414a Mon Sep 17 00:00:00 2001 From: boradol2 Date: Thu, 13 Apr 2023 22:20:54 +0900 Subject: [PATCH 01/16] =?UTF-8?q?docs(README.md):=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0bfd97eb0..2b93b421a 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,8 @@ ### 프로그래밍 요구사항 >- [x] GitHub 로그인을 검증할 수 있는 인수 테스트 구현(실제 GitHub에 요청을 하지 않아도 됨) + +### 리뷰 요구사항 +>- [ ] 인수테스트 추가하기 +>- [ ] api호출 로직과 db호출로직 분리 +>- [ ] 에러 메세지 변경 From 3ed9cd7a91f3902be93302abbf17472b0b09c2f8 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Thu, 13 Apr 2023 22:28:50 +0900 Subject: [PATCH 02/16] =?UTF-8?q?refactor(Exception):=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/nextstep/member/application/GithubClient.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2b93b421a..36691f5e6 100644 --- a/README.md +++ b/README.md @@ -46,4 +46,4 @@ ### 리뷰 요구사항 >- [ ] 인수테스트 추가하기 >- [ ] api호출 로직과 db호출로직 분리 ->- [ ] 에러 메세지 변경 +>- [x] 에러 메세지 변경 diff --git a/src/main/java/nextstep/member/application/GithubClient.java b/src/main/java/nextstep/member/application/GithubClient.java index 84b2a2ffb..75376bb14 100644 --- a/src/main/java/nextstep/member/application/GithubClient.java +++ b/src/main/java/nextstep/member/application/GithubClient.java @@ -44,7 +44,7 @@ public String getAccessTokenFromGithub(String code) { .getBody(); if (response == null) { - throw new RuntimeException("아무것도 없엉..."); + throw new IllegalStateException("Github에서 토큰정보를 가져오는데 실패했습니다."); } return response.getAccessToken(); } @@ -61,7 +61,7 @@ public GithubProfileResponse getGithubProfileFromGithub(String accessToken) { .exchange(profileUrl, HttpMethod.GET, httpEntity, GithubProfileResponse.class) .getBody(); } catch (HttpClientErrorException e) { - throw new RuntimeException(); + throw new IllegalStateException("GitHub 에서 회원의 프로필을 가져오는데 실패했습니다."); } } } From 7a972ae5b2439c6e33e54b7c71b91fa33036a7bd Mon Sep 17 00:00:00 2001 From: boradol2 Date: Thu, 13 Apr 2023 22:43:22 +0900 Subject: [PATCH 03/16] =?UTF-8?q?refactor(AuthService):=20api=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/nextstep/member/application/AuthService.java | 9 +++++---- .../java/nextstep/member/application/GithubClient.java | 6 ++++++ src/test/java/nextstep/subway/unit/AuthServiceTest.java | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 36691f5e6..a12f7bb37 100644 --- a/README.md +++ b/README.md @@ -45,5 +45,5 @@ ### 리뷰 요구사항 >- [ ] 인수테스트 추가하기 ->- [ ] api호출 로직과 db호출로직 분리 +>- [x] api호출 로직과 db호출로직 분리 >- [x] 에러 메세지 변경 diff --git a/src/main/java/nextstep/member/application/AuthService.java b/src/main/java/nextstep/member/application/AuthService.java index 813db4a17..a56c1ea2b 100644 --- a/src/main/java/nextstep/member/application/AuthService.java +++ b/src/main/java/nextstep/member/application/AuthService.java @@ -9,6 +9,7 @@ import nextstep.member.domain.Member; import nextstep.member.domain.MemberRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class AuthService { @@ -31,12 +32,12 @@ public TokenResponse login(final TokenRequest tokenRequest) { return new TokenResponse(jwtTokenProvider.createToken(member.getEmail(), member.getRoles())); } + @Transactional public TokenResponse loginByGithub(final LoginRequest loginRequest) { - String accessTokenFromGithub = githubClient.getAccessTokenFromGithub(loginRequest.getCode()); - GithubProfileResponse githubProfileFromGithub = githubClient.getGithubProfileFromGithub(accessTokenFromGithub); + GithubProfileResponse profile = githubClient.callLoginApi(loginRequest); - Member member = memberRepository.findByEmail(githubProfileFromGithub.getEmail()) - .orElse(memberRepository.save(new Member(githubProfileFromGithub.getEmail(), "password", 20))); + Member member = memberRepository.findByEmail(profile.getEmail()) + .orElse(memberRepository.save(new Member(profile.getEmail(), "password", 20))); return new TokenResponse(jwtTokenProvider.createToken(member.getEmail(), member.getRoles())); } diff --git a/src/main/java/nextstep/member/application/GithubClient.java b/src/main/java/nextstep/member/application/GithubClient.java index 75376bb14..22e6591f6 100644 --- a/src/main/java/nextstep/member/application/GithubClient.java +++ b/src/main/java/nextstep/member/application/GithubClient.java @@ -3,6 +3,7 @@ import nextstep.member.application.dto.GithubAccessTokenRequest; import nextstep.member.application.dto.GithubAccessTokenResponse; import nextstep.member.application.dto.GithubProfileResponse; +import nextstep.member.application.dto.LoginRequest; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -24,6 +25,11 @@ public class GithubClient { @Value("${github.url.profile}") private String profileUrl; + public GithubProfileResponse callLoginApi(LoginRequest loginRequest) { + String accessTokenFromGithub = getAccessTokenFromGithub(loginRequest.getCode()); + return getGithubProfileFromGithub(accessTokenFromGithub); + } + public String getAccessTokenFromGithub(String code) { GithubAccessTokenRequest githubAccessTokenRequest = new GithubAccessTokenRequest( code, diff --git a/src/test/java/nextstep/subway/unit/AuthServiceTest.java b/src/test/java/nextstep/subway/unit/AuthServiceTest.java index 3b3da33a0..fca3f1675 100644 --- a/src/test/java/nextstep/subway/unit/AuthServiceTest.java +++ b/src/test/java/nextstep/subway/unit/AuthServiceTest.java @@ -55,7 +55,7 @@ void loginTest() { @ParameterizedTest @CsvSource({"member@test.com, password", "member2@test.com, password"}) @DisplayName("DB에 등록되지 않은 멤버면 에러를 발생한다.") - void test2(String newEmail, String password) { + void unregisterMemberLoginTest(String newEmail, String password) { // given TokenRequest tokenRequest = new TokenRequest(newEmail, password); @@ -69,7 +69,7 @@ void test2(String newEmail, String password) { @ParameterizedTest @CsvSource({"admin@test.com, 1234password", "admin@test.com, @#$123",}) @DisplayName("비밀번호가 일치하지 않을 경우 에러를 발생한다.") - void test3(String email, String wrongPassword) { + void mismatchPasswordLoginTest(String email, String wrongPassword) { // given TokenRequest tokenRequest = new TokenRequest(email, wrongPassword); From deecd6337cd47bd106506d8c605bf4cd6817f10a Mon Sep 17 00:00:00 2001 From: boradol2 Date: Thu, 13 Apr 2023 23:41:48 +0900 Subject: [PATCH 04/16] =?UTF-8?q?refactor(AuthAcceptanceTest):=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../subway/acceptance/AuthAcceptanceTest.java | 17 +++++++++++++++-- .../nextstep/subway/acceptance/MemberSteps.java | 16 +++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a12f7bb37..a1a4dafe3 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,6 @@ >- [x] GitHub 로그인을 검증할 수 있는 인수 테스트 구현(실제 GitHub에 요청을 하지 않아도 됨) ### 리뷰 요구사항 ->- [ ] 인수테스트 추가하기 +>- [x] 인수테스트 추가하기 >- [x] api호출 로직과 db호출로직 분리 >- [x] 에러 메세지 변경 diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index acf4b790a..38f8dde68 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -2,15 +2,19 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import nextstep.exception.MemberInvalidException; import nextstep.subway.utils.GithubResponses; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; -import static nextstep.subway.acceptance.MemberSteps.깃헙_로그인_요청; -import static nextstep.subway.acceptance.MemberSteps.베어러_인증_로그인_요청; +import static nextstep.subway.acceptance.MemberSteps.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class AuthAcceptanceTest extends AcceptanceTest { private static final String EMAIL = "admin@email.com"; @@ -32,4 +36,13 @@ void githubAuth(GithubResponses user) { assertThat(response.jsonPath().getString("accessToken")).isNotBlank(); } + + @DisplayName("Github Auth Fail") + @ValueSource(strings = {"832ovnq039hfjz", "abc", "zzzzzz", "코드"}) + @ParameterizedTest + void githubAuth2(String code) { + ExtractableResponse response = 깃헙_로그인_실패(code); + + assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } } diff --git a/src/test/java/nextstep/subway/acceptance/MemberSteps.java b/src/test/java/nextstep/subway/acceptance/MemberSteps.java index 8bf847b2d..5fee4510f 100644 --- a/src/test/java/nextstep/subway/acceptance/MemberSteps.java +++ b/src/test/java/nextstep/subway/acceptance/MemberSteps.java @@ -109,6 +109,20 @@ public class MemberSteps { .body(params) .when().post("/login/github") .then().log().all() - .statusCode(HttpStatus.OK.value()).extract(); + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + public static ExtractableResponse 깃헙_로그인_실패(String code) { + Map params = new HashMap<>(); + params.put("code", code); + + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(params) + .when().post("/login/github") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); } } From d770a3f9a14097da25bc7c658bac49545b8b7a67 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Thu, 13 Apr 2023 23:55:10 +0900 Subject: [PATCH 05/16] =?UTF-8?q?docs(README.md):=203=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=AA=85=EC=84=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a1a4dafe3..dc1e80901 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ ## 요구사항 ### 기능 요구사항 -- [x] 깃허브를 이용한 로그인 구현(토큰 발행) +>- [x] 깃허브를 이용한 로그인 구현(토큰 발행) > - [x] `AuthAcceptanceTest` 테스트 만들기 -- [x] 가입이 되어있지 않은 경우 회원 가입으로 진행 후 토큰 발행 +>- [x] 가입이 되어있지 않은 경우 회원 가입으로 진행 후 토큰 발행 ### 프로그래밍 요구사항 >- [x] GitHub 로그인을 검증할 수 있는 인수 테스트 구현(실제 GitHub에 요청을 하지 않아도 됨) @@ -47,3 +47,30 @@ >- [x] 인수테스트 추가하기 >- [x] api호출 로직과 db호출로직 분리 >- [x] 에러 메세지 변경 +--- + + +# 🚀 3단계 - 즐겨찾기 기능 구현 + +## 요구사항 +### 기능 요구사항 +>- [ ] 요구사항 설명에서 제공되는 추가된 요구사항을 기반으로 즐겨 찾기 기능을 리팩터링하세요. +> - [ ] 생성 +> - [ ] 조회 +> - [ ] 삭제 +>- [ ] 추가된 요구사항을 정의한 인수 조건을 도출하세요. +> - [ ] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 +>- [ ] `FavoriteAcceptanceTest` 인수 테스트 만들기 +> - 예외 케이스에 대한 검증도 포함하세요. +> - [ ] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 + +### 프로그래밍 요구사항 +>- [ ] 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. +> - [ ] 요구사항 설명을 참고하여 인수 조건을 정의 +> - [ ] 인수 조건을 검증하는 인수 테스트 작성 +> - [ ] 인수 테스트를 충족하는 기능 구현 +>- [ ] 인수 조건은 인수 테스트 메서드 상단에 주석으로 작성하세요. +> - [ ] 뼈대 코드의 인수 테스트를 참고 +>- [ ] 인수 테스트 이후 기능 구현은 TDD로 진행하세요. +> - [ ] 도메인 레이어 테스트는 필수 +> - [ ] 서비스 레이어 테스트는 선택 From daae3c58ca8cce9f01ee5df081c9bf8557be8f57 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Mon, 17 Apr 2023 03:33:32 +0900 Subject: [PATCH 06/16] =?UTF-8?q?refactor(FavoriteController):=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=EC=97=90=20=EC=95=8C=EB=A7=9E?= =?UTF-8?q?=EC=9D=80=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1.=20-=20=EC=9D=B8=EC=A6=9D=EB=90=9C=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 생성 2. 리스트조회 3. 삭제 --- .../nextstep/favorite/FavoriteController.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/nextstep/favorite/FavoriteController.java diff --git a/src/main/java/nextstep/favorite/FavoriteController.java b/src/main/java/nextstep/favorite/FavoriteController.java new file mode 100644 index 000000000..9c5652908 --- /dev/null +++ b/src/main/java/nextstep/favorite/FavoriteController.java @@ -0,0 +1,42 @@ +package nextstep.favorite; + +import nextstep.favorite.application.FavoriteService; +import nextstep.favorite.dto.FavoriteRequestDto; +import nextstep.favorite.dto.FavoriteResponseDto; +import nextstep.filter.PreAuthorize; +import nextstep.member.application.dto.MemberResponse; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/favorites") +public class FavoriteController { + private final FavoriteService favoriteService; + + public FavoriteController(FavoriteService favoriteService) { + this.favoriteService = favoriteService; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public void createFavorite(@PreAuthorize MemberResponse member, @RequestBody FavoriteRequestDto favoriteRequestDto) { + favoriteService.create(member.getEmail(), favoriteRequestDto); + } + + @GetMapping + public List getListFavorite(@PreAuthorize MemberResponse member) { + return favoriteService.getList(member.getEmail()) + .stream() + .map(FavoriteResponseDto::of) + .collect(Collectors.toUnmodifiableList()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteFavorite(@PreAuthorize MemberResponse member, @PathVariable Long id) { + favoriteService.deleteById(member.getEmail(), id); + } +} From 08ba1c090f277b69acd05d23493e768ba3cc8fc9 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Mon, 17 Apr 2023 03:35:59 +0900 Subject: [PATCH 07/16] =?UTF-8?q?refactor(Favorite):=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=9E=91=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. member와 연관관계 생각. 2. source와 target은 Station과 연관관계 생각. --- .../nextstep/favorite/domain/Favorite.java | 57 +++++++++++++++++++ .../favorite/domain/FavoriteRepository.java | 12 ++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/nextstep/favorite/domain/Favorite.java create mode 100644 src/main/java/nextstep/favorite/domain/FavoriteRepository.java diff --git a/src/main/java/nextstep/favorite/domain/Favorite.java b/src/main/java/nextstep/favorite/domain/Favorite.java new file mode 100644 index 000000000..417aedcc9 --- /dev/null +++ b/src/main/java/nextstep/favorite/domain/Favorite.java @@ -0,0 +1,57 @@ +package nextstep.favorite.domain; + +import nextstep.member.domain.Member; +import nextstep.subway.domain.Station; + +import javax.persistence.*; + +@Entity +public class Favorite { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "source_id") + private Station source; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "target_id") + private Station target; + + public Favorite() { + } + + public Favorite(Member member, Station source, Station target) { + this.member = member; + this.source = source; + this.target = target; + } + + public Favorite(Long id, Member member, Station source, Station target) { + this.id = id; + this.member = member; + this.source = source; + this.target = target; + } + + public Long getId() { + return id; + } + + public Member getMember() { + return member; + } + + public Station getSource() { + return source; + } + + public Station getTarget() { + return target; + } +} diff --git a/src/main/java/nextstep/favorite/domain/FavoriteRepository.java b/src/main/java/nextstep/favorite/domain/FavoriteRepository.java new file mode 100644 index 000000000..cb59f090a --- /dev/null +++ b/src/main/java/nextstep/favorite/domain/FavoriteRepository.java @@ -0,0 +1,12 @@ +package nextstep.favorite.domain; + + +import nextstep.member.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface FavoriteRepository extends JpaRepository { + List findAllByMember(Member member); + boolean existsByIdAndMember(Long id, Member member); +} From d03413ea8e465d8983f9f9bd6e06d0d78c0c8ceb Mon Sep 17 00:00:00 2001 From: boradol2 Date: Mon, 17 Apr 2023 03:39:26 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor(FavoriteService):=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../favorite/application/FavoriteService.java | 37 +++++++++++++++++++ .../favorite/dto/FavoriteRequestDto.java | 19 ++++++++++ .../member/application/MemberService.java | 4 +- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/main/java/nextstep/favorite/application/FavoriteService.java create mode 100644 src/main/java/nextstep/favorite/dto/FavoriteRequestDto.java diff --git a/README.md b/README.md index dc1e80901..25769f088 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ ## 요구사항 ### 기능 요구사항 >- [ ] 요구사항 설명에서 제공되는 추가된 요구사항을 기반으로 즐겨 찾기 기능을 리팩터링하세요. -> - [ ] 생성 +> - [x] 생성 > - [ ] 조회 > - [ ] 삭제 >- [ ] 추가된 요구사항을 정의한 인수 조건을 도출하세요. diff --git a/src/main/java/nextstep/favorite/application/FavoriteService.java b/src/main/java/nextstep/favorite/application/FavoriteService.java new file mode 100644 index 000000000..e87994620 --- /dev/null +++ b/src/main/java/nextstep/favorite/application/FavoriteService.java @@ -0,0 +1,37 @@ +package nextstep.favorite.application; + +import nextstep.favorite.domain.Favorite; +import nextstep.favorite.domain.FavoriteRepository; +import nextstep.favorite.dto.FavoriteRequestDto; +import nextstep.member.application.MemberService; +import nextstep.member.domain.Member; +import nextstep.subway.applicaion.StationService; +import nextstep.subway.domain.Station; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class FavoriteService { + private final FavoriteRepository favoriteRepository; + private final MemberService memberService; + private final StationService stationService; + + public FavoriteService(FavoriteRepository favoriteRepository, MemberService memberService, StationService stationService) { + this.favoriteRepository = favoriteRepository; + this.memberService = memberService; + this.stationService = stationService; + } + + @Transactional + public void create(String email, FavoriteRequestDto favoriteRequestDto) { + Member member = findMember(email); + Station sourceStation = stationService.findById(favoriteRequestDto.getSource()); + Station targetStation = stationService.findById(favoriteRequestDto.getTarget()); + Favorite favorite = new Favorite(member, sourceStation, targetStation); + favoriteRepository.save(favorite); + } + + private Member findMember(String email) { + return memberService.findByEmail(email); + } +} diff --git a/src/main/java/nextstep/favorite/dto/FavoriteRequestDto.java b/src/main/java/nextstep/favorite/dto/FavoriteRequestDto.java new file mode 100644 index 000000000..03a206662 --- /dev/null +++ b/src/main/java/nextstep/favorite/dto/FavoriteRequestDto.java @@ -0,0 +1,19 @@ +package nextstep.favorite.dto; + +public class FavoriteRequestDto { + private final Long source; + private final Long target; + + public FavoriteRequestDto(Long source, Long target) { + this.source = source; + this.target = target; + } + + public Long getSource() { + return source; + } + + public Long getTarget() { + return target; + } +} diff --git a/src/main/java/nextstep/member/application/MemberService.java b/src/main/java/nextstep/member/application/MemberService.java index e85e30a1a..ec65b0e88 100644 --- a/src/main/java/nextstep/member/application/MemberService.java +++ b/src/main/java/nextstep/member/application/MemberService.java @@ -38,8 +38,8 @@ public MemberResponse findMemberByEmail(String email) { return MemberResponse.of(findByEmail(email)); } - private Member findByEmail(String email) { + public Member findByEmail(String email) { return memberRepository.findByEmail(email) .orElseThrow(MemberNotFoundException::new); } -} \ No newline at end of file +} From 93c93932919494a27e609aaec0f12cabd8221e87 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Mon, 17 Apr 2023 03:43:30 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat(WebConfig):=20=EB=82=B4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B4=80=EB=A6=AC=20/=20=EC=A6=90=EA=B2=A8=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=EC=9D=80=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=90=9C=20=EC=83=81=ED=83=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20Con?= =?UTF-8?q?fig=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/main/java/nextstep/config/WebConfig.java | 3 ++- .../java/nextstep/subway/acceptance/AuthAcceptanceTest.java | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 25769f088..737280d7f 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ > - [x] 생성 > - [ ] 조회 > - [ ] 삭제 ->- [ ] 추가된 요구사항을 정의한 인수 조건을 도출하세요. -> - [ ] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 +>- 추가된 요구사항을 정의한 인수 조건을 도출하세요. +> - [x] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 >- [ ] `FavoriteAcceptanceTest` 인수 테스트 만들기 > - 예외 케이스에 대한 검증도 포함하세요. > - [ ] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 diff --git a/src/main/java/nextstep/config/WebConfig.java b/src/main/java/nextstep/config/WebConfig.java index d6b0f4dfa..bc02bd92c 100644 --- a/src/main/java/nextstep/config/WebConfig.java +++ b/src/main/java/nextstep/config/WebConfig.java @@ -22,7 +22,8 @@ public WebConfig(AuthenticationInterceptor authenticationInterceptor, AuthMember @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor) - .addPathPatterns("/members/me"); + .addPathPatterns("/members/me") + .addPathPatterns("/favorites/**"); } @Override diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index 38f8dde68..6eab2d615 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -2,19 +2,16 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; -import nextstep.exception.MemberInvalidException; import nextstep.subway.utils.GithubResponses; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import static nextstep.subway.acceptance.MemberSteps.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; class AuthAcceptanceTest extends AcceptanceTest { private static final String EMAIL = "admin@email.com"; From e73a7bd06302dbec8c6d9405889196af590952e3 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 00:57:55 +0900 Subject: [PATCH 10/16] =?UTF-8?q?test(FavoriteAcceptanceTest):=20=EC=A6=90?= =?UTF-8?q?=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EC=9D=B8=EC=88=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 즐겨찾기 추가 test 코드 작성 2. 즐겨찾기 추가 시 유효하지 않은 토큰에 대한 test 코드 작성 --- README.md | 2 +- .../nextstep/favorite/FavoriteController.java | 10 ++- .../favorite/application/FavoriteService.java | 4 +- .../favorite/dto/FavoriteResponseDto.java | 39 ++++++++++ .../acceptance/FavoriteAcceptanceTest.java | 75 +++++++++++++++++++ .../subway/acceptance/FavoriteSteps.java | 27 +++++++ 6 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/main/java/nextstep/favorite/dto/FavoriteResponseDto.java create mode 100644 src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java create mode 100644 src/test/java/nextstep/subway/acceptance/FavoriteSteps.java diff --git a/README.md b/README.md index 737280d7f..9ecda209d 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ > - [x] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 >- [ ] `FavoriteAcceptanceTest` 인수 테스트 만들기 > - 예외 케이스에 대한 검증도 포함하세요. -> - [ ] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 +> - [x] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 ### 프로그래밍 요구사항 >- [ ] 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. diff --git a/src/main/java/nextstep/favorite/FavoriteController.java b/src/main/java/nextstep/favorite/FavoriteController.java index 9c5652908..f4e2f4e89 100644 --- a/src/main/java/nextstep/favorite/FavoriteController.java +++ b/src/main/java/nextstep/favorite/FavoriteController.java @@ -1,13 +1,16 @@ package nextstep.favorite; import nextstep.favorite.application.FavoriteService; +import nextstep.favorite.domain.Favorite; import nextstep.favorite.dto.FavoriteRequestDto; import nextstep.favorite.dto.FavoriteResponseDto; import nextstep.filter.PreAuthorize; import nextstep.member.application.dto.MemberResponse; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.net.URI; import java.util.List; import java.util.stream.Collectors; @@ -22,8 +25,11 @@ public FavoriteController(FavoriteService favoriteService) { @PostMapping @ResponseStatus(HttpStatus.CREATED) - public void createFavorite(@PreAuthorize MemberResponse member, @RequestBody FavoriteRequestDto favoriteRequestDto) { - favoriteService.create(member.getEmail(), favoriteRequestDto); + public ResponseEntity createFavorite(@PreAuthorize MemberResponse member, @RequestBody FavoriteRequestDto favoriteRequestDto) { + Favorite favorite = favoriteService.create(member.getEmail(), favoriteRequestDto); + return ResponseEntity + .created(URI.create("/favorites/" + favorite.getId())) + .build(); } @GetMapping diff --git a/src/main/java/nextstep/favorite/application/FavoriteService.java b/src/main/java/nextstep/favorite/application/FavoriteService.java index e87994620..c55baf78f 100644 --- a/src/main/java/nextstep/favorite/application/FavoriteService.java +++ b/src/main/java/nextstep/favorite/application/FavoriteService.java @@ -23,12 +23,12 @@ public FavoriteService(FavoriteRepository favoriteRepository, MemberService memb } @Transactional - public void create(String email, FavoriteRequestDto favoriteRequestDto) { + public Favorite create(String email, FavoriteRequestDto favoriteRequestDto) { Member member = findMember(email); Station sourceStation = stationService.findById(favoriteRequestDto.getSource()); Station targetStation = stationService.findById(favoriteRequestDto.getTarget()); Favorite favorite = new Favorite(member, sourceStation, targetStation); - favoriteRepository.save(favorite); + return favoriteRepository.save(favorite); } private Member findMember(String email) { diff --git a/src/main/java/nextstep/favorite/dto/FavoriteResponseDto.java b/src/main/java/nextstep/favorite/dto/FavoriteResponseDto.java new file mode 100644 index 000000000..3da9bc0be --- /dev/null +++ b/src/main/java/nextstep/favorite/dto/FavoriteResponseDto.java @@ -0,0 +1,39 @@ +package nextstep.favorite.dto; + +import nextstep.favorite.domain.Favorite; +import nextstep.subway.applicaion.dto.StationResponse; + +public class FavoriteResponseDto { + private Long id; + private StationResponse source; + private StationResponse target; + + protected FavoriteResponseDto() { + } + + public FavoriteResponseDto(Long id, StationResponse source, StationResponse target) { + this.id = id; + this.source = source; + this.target = target; + } + + public static FavoriteResponseDto of(Favorite favorite) { + return new FavoriteResponseDto( + favorite.getId(), + StationResponse.of(favorite.getSource()), + StationResponse.of(favorite.getTarget()) + ); + } + + public Long getId() { + return id; + } + + public StationResponse getSource() { + return source; + } + + public StationResponse getTarget() { + return target; + } +} diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java new file mode 100644 index 000000000..0905f90b2 --- /dev/null +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -0,0 +1,75 @@ +package nextstep.subway.acceptance; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import nextstep.subway.utils.GithubResponses; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.http.HttpStatus; + +import static nextstep.subway.acceptance.FavoriteSteps.즐겨찾기_추가_요청; +import static nextstep.subway.acceptance.MemberSteps.깃헙_로그인_요청; +import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class FavoriteAcceptanceTest extends AcceptanceTest { + private Long 강남역; + private Long 양재역; + private Long 판교역; + private String accessToken; + + @BeforeEach + public void setUp() { + super.setUp(); + GithubResponses user = GithubResponses.사용자1; + accessToken = 깃헙_로그인_요청(user.getCode()).jsonPath().getString("accessToken"); + + 강남역 = 지하철역_생성_요청("강남역").jsonPath().getLong("id"); + 양재역 = 지하철역_생성_요청("양재역").jsonPath().getLong("id"); + 판교역 = 지하철역_생성_요청("판교역").jsonPath().getLong("id"); + } + + /** + * Given + * 1) 지하철 역을 생성한다. + * 2) 깃허브 로그인을 한다. + * + * When 즐겨찾기에 추가할 지하철역 2개(출발역, 도착역)를 넣어 즐겨찾기를 생성한다. + * + * Then 즐겨찾기를 조회할 수 있다. + * + */ + @Test + @DisplayName("즐겨찾기를 추가에 성공한다.") + void addFavorite() { + // when + ExtractableResponse response = 즐겨찾기_추가_요청(accessToken, 강남역, 판교역); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + } + + /** + * Given 유효하지 않은 accessToken이 생성된다. + * + * When 즐겨찾기에 추가 API를 호출한다. + * + * Then 401 error 발생한다. + * + */ + @EmptySource + @ValueSource(strings = {"abc", "123"}) + @ParameterizedTest + @DisplayName("즐겨찾기를 추가시 잘못된 엑세스 토큰으로 API를 호출하면 401 ERROR가 발생한다.") + void addFavoriteByInvalidUser(String invalidToken) { + // when + ExtractableResponse response = 즐겨찾기_추가_요청(invalidToken, 강남역, 판교역); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); + } +} diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java new file mode 100644 index 000000000..57a9d5e7c --- /dev/null +++ b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java @@ -0,0 +1,27 @@ +package nextstep.subway.acceptance; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.MediaType; + +import java.util.HashMap; +import java.util.Map; + +public class FavoriteSteps { + + + public static ExtractableResponse 즐겨찾기_추가_요청(String accessToken, Long source, Long target) { + Map params = new HashMap<>(); + params.put("source", source); + params.put("target", target); + + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .auth().oauth2(accessToken) + .body(params) + .when().post("/favorites") + .then().log().all() + .extract(); + } +} From 6bcdad3de2ba05a988eff80d7116fb606ff76542 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 01:02:23 +0900 Subject: [PATCH 11/16] =?UTF-8?q?feat(Favorite):=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=98=EA=B8=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 즐겨찾기 리스트 조회하기 API작성 2. controller ResponseEntity 변경 --- README.md | 2 +- src/main/java/nextstep/favorite/FavoriteController.java | 8 +++++--- .../nextstep/favorite/application/FavoriteService.java | 8 ++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ecda209d..7e35ee6b2 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ ### 기능 요구사항 >- [ ] 요구사항 설명에서 제공되는 추가된 요구사항을 기반으로 즐겨 찾기 기능을 리팩터링하세요. > - [x] 생성 -> - [ ] 조회 +> - [x] 조회 > - [ ] 삭제 >- 추가된 요구사항을 정의한 인수 조건을 도출하세요. > - [x] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 diff --git a/src/main/java/nextstep/favorite/FavoriteController.java b/src/main/java/nextstep/favorite/FavoriteController.java index f4e2f4e89..eec253ec2 100644 --- a/src/main/java/nextstep/favorite/FavoriteController.java +++ b/src/main/java/nextstep/favorite/FavoriteController.java @@ -33,16 +33,18 @@ public ResponseEntity createFavorite(@PreAuthorize MemberResponse member, } @GetMapping - public List getListFavorite(@PreAuthorize MemberResponse member) { - return favoriteService.getList(member.getEmail()) + public ResponseEntity> getListFavorite(@PreAuthorize MemberResponse member) { + List favorites = favoriteService.getList(member.getEmail()) .stream() .map(FavoriteResponseDto::of) .collect(Collectors.toUnmodifiableList()); + return ResponseEntity.ok(favorites); } @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteFavorite(@PreAuthorize MemberResponse member, @PathVariable Long id) { + public ResponseEntity deleteFavorite(@PreAuthorize MemberResponse member, @PathVariable Long id) { favoriteService.deleteById(member.getEmail(), id); + return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/nextstep/favorite/application/FavoriteService.java b/src/main/java/nextstep/favorite/application/FavoriteService.java index c55baf78f..62873033e 100644 --- a/src/main/java/nextstep/favorite/application/FavoriteService.java +++ b/src/main/java/nextstep/favorite/application/FavoriteService.java @@ -10,6 +10,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service public class FavoriteService { private final FavoriteRepository favoriteRepository; @@ -31,6 +33,12 @@ public Favorite create(String email, FavoriteRequestDto favoriteRequestDto) { return favoriteRepository.save(favorite); } + @Transactional(readOnly = true) + public List getList(String email) { + Member member = findMember(email); + return favoriteRepository.findAllByMember(member); + } + private Member findMember(String email) { return memberService.findByEmail(email); } From 9ffbf494407a3583a10f5544eebe9d3fd8680ded Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 01:31:58 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat(Favorite):=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=8B=9D?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EA=B8=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 즐겨찾기 리스트 조회하기 API작성 2. controller ResponseEntity 변경 --- README.md | 4 ++-- .../favorite/application/FavoriteService.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e35ee6b2..b8936f09a 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,10 @@ ## 요구사항 ### 기능 요구사항 ->- [ ] 요구사항 설명에서 제공되는 추가된 요구사항을 기반으로 즐겨 찾기 기능을 리팩터링하세요. +>- [x] 요구사항 설명에서 제공되는 추가된 요구사항을 기반으로 즐겨 찾기 기능을 리팩터링하세요. > - [x] 생성 > - [x] 조회 -> - [ ] 삭제 +> - [x] 삭제 >- 추가된 요구사항을 정의한 인수 조건을 도출하세요. > - [x] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 >- [ ] `FavoriteAcceptanceTest` 인수 테스트 만들기 diff --git a/src/main/java/nextstep/favorite/application/FavoriteService.java b/src/main/java/nextstep/favorite/application/FavoriteService.java index 62873033e..e2c1fb086 100644 --- a/src/main/java/nextstep/favorite/application/FavoriteService.java +++ b/src/main/java/nextstep/favorite/application/FavoriteService.java @@ -42,4 +42,18 @@ public List getList(String email) { private Member findMember(String email) { return memberService.findByEmail(email); } + + + + @Transactional + public void deleteById(String email, Long id) { + Member member = findMember(email); + if(existsByIdAndMember(id, member)){ + favoriteRepository.deleteById(id); + } + } + + private boolean existsByIdAndMember(Long id, Member member) { + return favoriteRepository.existsByIdAndMember(id, member); + } } From be186273f34085634ea60bd1b52fe50fe9e855f4 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 01:33:44 +0900 Subject: [PATCH 13/16] =?UTF-8?q?test(FavoriteAcceptanceTest):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 즐겨찾기 리스트 조회 인수테스트 작성 2. 즐겨찾기 삭제 인수테스트 작성 --- README.md | 6 +- .../acceptance/FavoriteAcceptanceTest.java | 58 ++++++++++++++++++- .../subway/acceptance/FavoriteSteps.java | 19 +++++- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b8936f09a..fc61f47fb 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ > - [x] 삭제 >- 추가된 요구사항을 정의한 인수 조건을 도출하세요. > - [x] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 ->- [ ] `FavoriteAcceptanceTest` 인수 테스트 만들기 -> - 예외 케이스에 대한 검증도 포함하세요. -> - [x] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 +> - [x] `FavoriteAcceptanceTest` 인수 테스트 만들기 +> - 예외 케이스에 대한 검증도 포함하세요. +> - [x] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 ### 프로그래밍 요구사항 >- [ ] 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java index 0905f90b2..5c0e41527 100644 --- a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -11,10 +11,13 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.http.HttpStatus; -import static nextstep.subway.acceptance.FavoriteSteps.즐겨찾기_추가_요청; +import java.util.Arrays; + +import static nextstep.subway.acceptance.FavoriteSteps.*; import static nextstep.subway.acceptance.MemberSteps.깃헙_로그인_요청; import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; class FavoriteAcceptanceTest extends AcceptanceTest { private Long 강남역; @@ -72,4 +75,57 @@ void addFavoriteByInvalidUser(String invalidToken) { // then assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); } + + /** + * Given 2개이상의 즐겨찾기를 추가한다. 이 때, 유효한 토큰을 넣어야 해당 멤버가 추가 할 수 있다. + * + * When 즐겨찾기 목록을 조회한다. + * + * Then 자신의 즐겨찾기만 조회할 수 있음. + * + */ + @Test + @DisplayName("해당 유저의 즐겨찾기를 목록을 조회한다.") + void getFavoriteBydUserId() { + // given + 즐겨찾기_추가_요청(accessToken, 강남역, 판교역); + 즐겨찾기_추가_요청(accessToken, 양재역, 판교역); + + // when + ExtractableResponse response = 즐겨찾기_목록_조회_요청(accessToken); + + // then + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> assertThat(response.jsonPath().getList("source.id", Long.class)).isEqualTo(Arrays.asList(강남역, 양재역)), + () -> assertThat(response.jsonPath().getList("target.id", Long.class)).isEqualTo(Arrays.asList(판교역, 판교역)) + ); + } + + /** + * Given 2개이상의 즐겨찾기를 추가한다. 이 때, 유효한 토큰을 넣어야 해당 멤버가 추가 할 수 있다. + * + * When 즐겨찾기 하나를 삭제한다. + * + * Then 즐겨찾기 목록을 조회하면 해당 즐겨찾기는 목록에서 사라진다. + * + */ + @Test + @DisplayName("해당 유저의 즐겨찾기를 삭제한다.") + void deleteFavoriteBydUserId() { + // given + 즐겨찾기_추가_요청(accessToken, 강남역, 판교역); + 즐겨찾기_추가_요청(accessToken, 양재역, 판교역); + + // when + ExtractableResponse response = 즐겨찾기_삭제_요청(accessToken, 1L); + + // then + ExtractableResponse responseList = 즐겨찾기_목록_조회_요청(accessToken); + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()), + () -> assertThat(responseList.jsonPath().getList("source.id", Long.class)).isEqualTo(Arrays.asList(양재역)), + () -> assertThat(responseList.jsonPath().getList("target.id", Long.class)).isEqualTo(Arrays.asList(판교역)) + ); + } } diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java index 57a9d5e7c..330ad34dc 100644 --- a/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java +++ b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java @@ -10,7 +10,6 @@ public class FavoriteSteps { - public static ExtractableResponse 즐겨찾기_추가_요청(String accessToken, Long source, Long target) { Map params = new HashMap<>(); params.put("source", source); @@ -24,4 +23,22 @@ public class FavoriteSteps { .then().log().all() .extract(); } + + public static ExtractableResponse 즐겨찾기_목록_조회_요청(String accessToken) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .auth().oauth2(accessToken) + .when().get("/favorites") + .then().log().all() + .extract(); + } + + public static ExtractableResponse 즐겨찾기_삭제_요청(String accessToken, Long id) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .auth().oauth2(accessToken) + .when().delete("/favorites/{id}", id) + .then().log().all() + .extract(); + } } From 6b9ad2681b06776d2cf6f2e5edfe90871a03252d Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 01:35:02 +0900 Subject: [PATCH 14/16] =?UTF-8?q?docs(Readme.md):=20=EC=9D=B8=EC=88=98?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B2=B4=ED=81=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fc61f47fb..af892c3b7 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,12 @@ > - [x] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 ### 프로그래밍 요구사항 ->- [ ] 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. -> - [ ] 요구사항 설명을 참고하여 인수 조건을 정의 -> - [ ] 인수 조건을 검증하는 인수 테스트 작성 -> - [ ] 인수 테스트를 충족하는 기능 구현 ->- [ ] 인수 조건은 인수 테스트 메서드 상단에 주석으로 작성하세요. -> - [ ] 뼈대 코드의 인수 테스트를 참고 +>- [x] 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. +> - 요구사항 설명을 참고하여 인수 조건을 정의 +> - 인수 조건을 검증하는 인수 테스트 작성 +> - 인수 테스트를 충족하는 기능 구현 +>- [x] 인수 조건은 인수 테스트 메서드 상단에 주석으로 작성하세요. +> - 뼈대 코드의 인수 테스트를 참고 >- [ ] 인수 테스트 이후 기능 구현은 TDD로 진행하세요. > - [ ] 도메인 레이어 테스트는 필수 > - [ ] 서비스 레이어 테스트는 선택 From 6741d4f90410319ee554431fcaff84c03ba45d18 Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 01:51:56 +0900 Subject: [PATCH 15/16] =?UTF-8?q?feat(Favorite):=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EC=98=88=EC=99=B8=EC=83=81=ED=99=A9=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A0=88=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EC=97=90=20=EC=9E=91=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubwayIllegalArgumentException.java | 11 +++++++++++ .../nextstep/favorite/domain/Favorite.java | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/main/java/nextstep/exception/SubwayIllegalArgumentException.java diff --git a/src/main/java/nextstep/exception/SubwayIllegalArgumentException.java b/src/main/java/nextstep/exception/SubwayIllegalArgumentException.java new file mode 100644 index 000000000..4ece9d5c2 --- /dev/null +++ b/src/main/java/nextstep/exception/SubwayIllegalArgumentException.java @@ -0,0 +1,11 @@ +package nextstep.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class SubwayIllegalArgumentException extends IllegalArgumentException { + public SubwayIllegalArgumentException(String message){ + super(message); + } +} diff --git a/src/main/java/nextstep/favorite/domain/Favorite.java b/src/main/java/nextstep/favorite/domain/Favorite.java index 417aedcc9..271b5f563 100644 --- a/src/main/java/nextstep/favorite/domain/Favorite.java +++ b/src/main/java/nextstep/favorite/domain/Favorite.java @@ -1,5 +1,6 @@ package nextstep.favorite.domain; +import nextstep.exception.SubwayIllegalArgumentException; import nextstep.member.domain.Member; import nextstep.subway.domain.Station; @@ -27,18 +28,26 @@ public Favorite() { } public Favorite(Member member, Station source, Station target) { + validateStation(source, target); + validateEqualStartAndDestination(source, target); this.member = member; this.source = source; this.target = target; } - public Favorite(Long id, Member member, Station source, Station target) { - this.id = id; - this.member = member; - this.source = source; - this.target = target; + private void validateEqualStartAndDestination(Station source, Station target) { + if (source == target) { + throw new SubwayIllegalArgumentException("출발역과 도착역이 같을 수 없습니다."); + } } + private void validateStation(Station source, Station target) { + if (source == null && target == null) { + throw new SubwayIllegalArgumentException("출발역과, 도착역 둘다 입력해줘야 합니다."); + } + } + + public Long getId() { return id; } From cea33b062e4e62a132629590d9c7f925b2ae92cd Mon Sep 17 00:00:00 2001 From: boradol2 Date: Tue, 18 Apr 2023 02:10:47 +0900 Subject: [PATCH 16/16] =?UTF-8?q?test(FavoriteTest):=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9E=91?= =?UTF-8?q?=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- .../favorite/application/FavoriteService.java | 2 - .../nextstep/favorite/domain/Favorite.java | 2 +- .../nextstep/subway/unit/FavoriteTest.java | 69 +++++++++++++++++++ 4 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 src/test/java/nextstep/subway/unit/FavoriteTest.java diff --git a/README.md b/README.md index af892c3b7..0f80a7464 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ > - 인수 테스트를 충족하는 기능 구현 >- [x] 인수 조건은 인수 테스트 메서드 상단에 주석으로 작성하세요. > - 뼈대 코드의 인수 테스트를 참고 ->- [ ] 인수 테스트 이후 기능 구현은 TDD로 진행하세요. -> - [ ] 도메인 레이어 테스트는 필수 +>- [x] 인수 테스트 이후 기능 구현은 TDD로 진행하세요. +> - [x] 도메인 레이어 테스트는 필수 > - [ ] 서비스 레이어 테스트는 선택 +성 diff --git a/src/main/java/nextstep/favorite/application/FavoriteService.java b/src/main/java/nextstep/favorite/application/FavoriteService.java index e2c1fb086..c10489255 100644 --- a/src/main/java/nextstep/favorite/application/FavoriteService.java +++ b/src/main/java/nextstep/favorite/application/FavoriteService.java @@ -43,8 +43,6 @@ private Member findMember(String email) { return memberService.findByEmail(email); } - - @Transactional public void deleteById(String email, Long id) { Member member = findMember(email); diff --git a/src/main/java/nextstep/favorite/domain/Favorite.java b/src/main/java/nextstep/favorite/domain/Favorite.java index 271b5f563..c91ca01ea 100644 --- a/src/main/java/nextstep/favorite/domain/Favorite.java +++ b/src/main/java/nextstep/favorite/domain/Favorite.java @@ -42,7 +42,7 @@ private void validateEqualStartAndDestination(Station source, Station target) { } private void validateStation(Station source, Station target) { - if (source == null && target == null) { + if (source == null || target == null) { throw new SubwayIllegalArgumentException("출발역과, 도착역 둘다 입력해줘야 합니다."); } } diff --git a/src/test/java/nextstep/subway/unit/FavoriteTest.java b/src/test/java/nextstep/subway/unit/FavoriteTest.java new file mode 100644 index 000000000..5b0c828e9 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/FavoriteTest.java @@ -0,0 +1,69 @@ +package nextstep.subway.unit; + +import nextstep.exception.SubwayIllegalArgumentException; +import nextstep.favorite.domain.Favorite; +import nextstep.member.domain.Member; +import nextstep.subway.domain.Station; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("즐겨찾기 테스트") +class FavoriteTest { + private Member member; + private Station 강남역; + private Station 판교역; + @BeforeEach + void setUp() { + member = new Member("test@test.com", "password", 33); + 강남역 = new Station("강남역"); + 판교역 = new Station("판교역"); + } + + @Test + @DisplayName("즐겨찾기 생성에 성공한다") + void 즐겨찾기_생성에_성공한다() { + + // when + Favorite favorite = new Favorite(member, 강남역, 판교역); + + // then + assertAll( + () -> assertThat(favorite.getMember()).isEqualTo(member), + () -> assertThat(favorite.getSource()).isEqualTo(강남역), + () -> assertThat(favorite.getTarget()).isEqualTo(판교역) + ); + } + + @Test + @DisplayName("즐겨찾기 생성시 출발역과 도착역이 같을경우 예외가 발생한다") + void 즐겨찾기_생성시_출발역과_도착역이_같을경우_예외가_발생한다() { + assertThatThrownBy(() -> new Favorite(member, 강남역, 강남역)) + .isInstanceOf(SubwayIllegalArgumentException.class) + .hasMessage("출발역과 도착역이 같을 수 없습니다."); + } + + @NullSource + @ParameterizedTest + @DisplayName("즐겨찾기 생성시 출발역을 지정하지 않으면 예외가 발생한다") + void 즐겨찾기_생성시_출발역을_지정하지_않으면_예외가_발생한다(Station source) { + assertThatThrownBy(() -> new Favorite(member, source, 판교역)) + .isInstanceOf(SubwayIllegalArgumentException.class) + .hasMessage("출발역과, 도착역 둘다 입력해줘야 합니다."); + } + + @NullSource + @ParameterizedTest + @DisplayName("즐겨찾기 생성시 도착역을 지정하지 않으면 예외가 발생한다") + void 즐겨찾기_생성시_도착역을_지정하지_않으면_예외가_발생한다(Station target) { + assertThatThrownBy(() -> new Favorite(member, 강남역, target)) + .isInstanceOf(SubwayIllegalArgumentException.class) + .hasMessage("출발역과, 도착역 둘다 입력해줘야 합니다."); + } +}