diff --git a/README.md b/README.md index 0bfd97eb0..0f80a7464 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,42 @@ ## 요구사항 ### 기능 요구사항 -- [x] 깃허브를 이용한 로그인 구현(토큰 발행) +>- [x] 깃허브를 이용한 로그인 구현(토큰 발행) > - [x] `AuthAcceptanceTest` 테스트 만들기 -- [x] 가입이 되어있지 않은 경우 회원 가입으로 진행 후 토큰 발행 +>- [x] 가입이 되어있지 않은 경우 회원 가입으로 진행 후 토큰 발행 ### 프로그래밍 요구사항 >- [x] GitHub 로그인을 검증할 수 있는 인수 테스트 구현(실제 GitHub에 요청을 하지 않아도 됨) + +### 리뷰 요구사항 +>- [x] 인수테스트 추가하기 +>- [x] api호출 로직과 db호출로직 분리 +>- [x] 에러 메세지 변경 +--- + + +# 🚀 3단계 - 즐겨찾기 기능 구현 + +## 요구사항 +### 기능 요구사항 +>- [x] 요구사항 설명에서 제공되는 추가된 요구사항을 기반으로 즐겨 찾기 기능을 리팩터링하세요. +> - [x] 생성 +> - [x] 조회 +> - [x] 삭제 +>- 추가된 요구사항을 정의한 인수 조건을 도출하세요. +> - [x] 내 정보 관리 / 즐겨 찾기 기능은 로그인 된 상태에서만 가능 +> - [x] `FavoriteAcceptanceTest` 인수 테스트 만들기 +> - 예외 케이스에 대한 검증도 포함하세요. +> - [x] 로그인이 필요한 API 요청 시 유효하지 않은 경우 401 응답 내려주기 + +### 프로그래밍 요구사항 +>- [x] 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. +> - 요구사항 설명을 참고하여 인수 조건을 정의 +> - 인수 조건을 검증하는 인수 테스트 작성 +> - 인수 테스트를 충족하는 기능 구현 +>- [x] 인수 조건은 인수 테스트 메서드 상단에 주석으로 작성하세요. +> - 뼈대 코드의 인수 테스트를 참고 +>- [x] 인수 테스트 이후 기능 구현은 TDD로 진행하세요. +> - [x] 도메인 레이어 테스트는 필수 +> - [ ] 서비스 레이어 테스트는 선택 +성 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/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/FavoriteController.java b/src/main/java/nextstep/favorite/FavoriteController.java new file mode 100644 index 000000000..eec253ec2 --- /dev/null +++ b/src/main/java/nextstep/favorite/FavoriteController.java @@ -0,0 +1,50 @@ +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; + +@RestController +@RequestMapping("/favorites") +public class FavoriteController { + private final FavoriteService favoriteService; + + public FavoriteController(FavoriteService favoriteService) { + this.favoriteService = favoriteService; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + 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 + 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 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 new file mode 100644 index 000000000..c10489255 --- /dev/null +++ b/src/main/java/nextstep/favorite/application/FavoriteService.java @@ -0,0 +1,57 @@ +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; + +import java.util.List; + +@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 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); + 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); + } + + @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); + } +} 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..c91ca01ea --- /dev/null +++ b/src/main/java/nextstep/favorite/domain/Favorite.java @@ -0,0 +1,66 @@ +package nextstep.favorite.domain; + +import nextstep.exception.SubwayIllegalArgumentException; +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) { + validateStation(source, target); + validateEqualStartAndDestination(source, target); + 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; + } + + 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); +} 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/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/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 84b2a2ffb..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, @@ -44,7 +50,7 @@ public String getAccessTokenFromGithub(String code) { .getBody(); if (response == null) { - throw new RuntimeException("아무것도 없엉..."); + throw new IllegalStateException("Github에서 토큰정보를 가져오는데 실패했습니다."); } return response.getAccessToken(); } @@ -61,7 +67,7 @@ public GithubProfileResponse getGithubProfileFromGithub(String accessToken) { .exchange(profileUrl, HttpMethod.GET, httpEntity, GithubProfileResponse.class) .getBody(); } catch (HttpClientErrorException e) { - throw new RuntimeException(); + throw new IllegalStateException("GitHub 에서 회원의 프로필을 가져오는데 실패했습니다."); } } } 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 +} diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index acf4b790a..6eab2d615 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -7,9 +7,10 @@ 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.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; class AuthAcceptanceTest extends AcceptanceTest { @@ -32,4 +33,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/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java new file mode 100644 index 000000000..5c0e41527 --- /dev/null +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -0,0 +1,131 @@ +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 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 강남역; + 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()); + } + + /** + * 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 new file mode 100644 index 000000000..330ad34dc --- /dev/null +++ b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java @@ -0,0 +1,44 @@ +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(); + } + + 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(); + } +} 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(); } } 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); 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("출발역과, 도착역 둘다 입력해줘야 합니다."); + } +}