Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 구매자 로그인 #64

Merged
merged 31 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
943240a
[chore] CustomerFixture 주석 추가
kimhyun5u Aug 11, 2024
c09a6a3
[test] SignInCustomerServiceTest 추가
kimhyun5u Aug 11, 2024
1251252
[feat] SignInCustomerService 구현
kimhyun5u Aug 11, 2024
c61a434
[feat] SignInCustomerCommand 구현
kimhyun5u Aug 11, 2024
43d5329
[feat] CustomerRepository.findByEmail 추가
kimhyun5u Aug 11, 2024
fb7ba6a
[feat] AuthenticationException
kimhyun5u Aug 11, 2024
01cfcdf
[test] CustomerControllerTest.signIn 추가
kimhyun5u Aug 11, 2024
d56b533
[feat] CustomerController.signIn 구현
kimhyun5u Aug 11, 2024
627be32
[feat] ErrorCode.AUTH_INVALID_CREDENTIALS
kimhyun5u Aug 11, 2024
2395f85
[feat] SignInCustomerRequest
kimhyun5u Aug 11, 2024
c73ab20
Merge remote-tracking branch 'origin/main' into feature/7_kimhyun5u_구…
kimhyun5u Aug 13, 2024
9a4924a
[merge] remote-tracking branch 'origin/main' into feature/7_kimhyun5u…
kimhyun5u Aug 15, 2024
620fc67
[merge] remote-tracking branch 'origin/main' into feature/7_kimhyun5u…
kimhyun5u Aug 15, 2024
97d206b
[feat] CustomerAuthenticationException 구현
kimhyun5u Aug 15, 2024
915b6b4
[feat] CustomerErrorCode.AUTHENTICATION_FAILED 구현
kimhyun5u Aug 15, 2024
9f9434b
[feat] SignInCustomerService.signIn 에러 분기
kimhyun5u Aug 15, 2024
f812c5b
[feat] UnauthorizedException 생성자 추가
kimhyun5u Aug 15, 2024
2bee277
[fix] AuthenticationException 수정에 따른 반영
kimhyun5u Aug 15, 2024
a9fe4a5
[docs] SignInCustomerService 예외처리 명시
kimhyun5u Aug 15, 2024
50f4193
[feat] SignInCustomerService.signIn 반환 값 변경
kimhyun5u Aug 15, 2024
070fba7
[refactor] SignInCustomerRequest 패키지 변경
kimhyun5u Aug 15, 2024
8b53a13
[feat] CustomerRepository.findByEmail 반환값 변경
kimhyun5u Aug 15, 2024
dc6f581
[feat] Customer 로그인 인증 주체 변경
kimhyun5u Aug 15, 2024
c89d828
[test] CustomerApiController.login 테스트 추가
kimhyun5u Aug 15, 2024
945e0ca
[feat] CustomerApiController.login 추가
kimhyun5u Aug 15, 2024
f8c2793
[feat] CustomerExceptionHandler.handleCustomerAuthenticationException 추가
kimhyun5u Aug 15, 2024
ec02019
[feat] SignInCustomerResponse 구현
kimhyun5u Aug 15, 2024
bc21f9b
[refactor] CustomerErrorCode Status 처리
kimhyun5u Aug 15, 2024
ea691e9
[fix] SignInCustomerService.signIn 로직 수정
kimhyun5u Aug 15, 2024
2b8fbff
[fix] CustomerRepository.findByEmail 반환값 변경에 따른 수정
kimhyun5u Aug 15, 2024
6dbf9e4
[refactor] 중복 코드 추출
kimhyun5u Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ public class UnauthorizedException extends HttpStatusException {
public UnauthorizedException(ErrorCode errorCode) {
super(errorCode);
}

public UnauthorizedException(ErrorCode errorCode, String message) {
super(errorCode, message);
}
}
4 changes: 4 additions & 0 deletions src/main/java/camp/woowak/lab/customer/domain/Customer.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ public Customer(String name, String email, String password, String phone, PayAcc
this.phone = phone;
this.payAccount = payAccount;
}

public boolean validatePassword(String password, PasswordEncoder passwordEncoder) {
return passwordEncoder.matches(password, this.password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package camp.woowak.lab.customer.exception;

import camp.woowak.lab.common.exception.UnauthorizedException;

public class CustomerAuthenticationException extends UnauthorizedException {
public CustomerAuthenticationException(String message) {
super(CustomerErrorCode.AUTHENTICATION_FAILED, message);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
package camp.woowak.lab.customer.exception;

import org.springframework.http.HttpStatus;

import camp.woowak.lab.common.exception.ErrorCode;

public enum CustomerErrorCode implements ErrorCode {
INVALID_CREATION(400, "C1", "잘못된 요청입니다."),
DUPLICATE_EMAIL(400, "C2", "이미 존재하는 이메일입니다.");
INVALID_CREATION(HttpStatus.BAD_REQUEST, "C1", "잘못된 요청입니다."),
DUPLICATE_EMAIL(HttpStatus.BAD_REQUEST, "C2", "이미 존재하는 이메일입니다."),
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED, "C3", "이메일 또는 비밀번호가 올바르지 않습니다.");

private final int status;
private final HttpStatus status;
private final String errorCode;
private final String message;

CustomerErrorCode(int status, String errorCode, String message) {
CustomerErrorCode(HttpStatus status, String errorCode, String message) {
this.status = status;
this.errorCode = errorCode;
this.message = message;
}

@Override
public int getStatus() {
return status;
return status.value();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package camp.woowak.lab.customer.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import camp.woowak.lab.customer.domain.Customer;

public interface CustomerRepository extends JpaRepository<Customer, Long> {
Optional<Customer> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package camp.woowak.lab.customer.service;

import java.util.UUID;

import org.springframework.stereotype.Service;

import camp.woowak.lab.customer.domain.Customer;
import camp.woowak.lab.customer.exception.CustomerAuthenticationException;
import camp.woowak.lab.customer.repository.CustomerRepository;
import camp.woowak.lab.customer.service.command.SignInCustomerCommand;
import camp.woowak.lab.web.authentication.PasswordEncoder;

@Service
public class SignInCustomerService {
private final CustomerRepository customerRepository;
private final PasswordEncoder passwordEncoder;

public SignInCustomerService(CustomerRepository customerRepository, PasswordEncoder passwordEncoder) {
this.customerRepository = customerRepository;
this.passwordEncoder = passwordEncoder;
}

/**
* @throws CustomerAuthenticationException 이메일이 존재하지 않거나 비밀번호가 일치하지 않으면
*/
public UUID signIn(SignInCustomerCommand cmd) {
Customer byEmail = customerRepository.findByEmail(cmd.email())
.orElseThrow(() -> new CustomerAuthenticationException("email not found"));
if (!byEmail.validatePassword(cmd.password(), passwordEncoder)) {
throw new CustomerAuthenticationException("password not matched");
}

return byEmail.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package camp.woowak.lab.customer.service.command;

public record SignInCustomerCommand(
String email,
String password
) {
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
package camp.woowak.lab.web.api.customer;

import java.util.UUID;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import camp.woowak.lab.customer.service.SignInCustomerService;
import camp.woowak.lab.customer.service.SignUpCustomerService;
import camp.woowak.lab.customer.service.command.SignInCustomerCommand;
import camp.woowak.lab.customer.service.command.SignUpCustomerCommand;
import camp.woowak.lab.web.authentication.LoginCustomer;
import camp.woowak.lab.web.dto.request.customer.SignInCustomerRequest;
import camp.woowak.lab.web.dto.request.customer.SignUpCustomerRequest;
import camp.woowak.lab.web.dto.response.customer.SignInCustomerResponse;
import camp.woowak.lab.web.dto.response.customer.SignUpCustomerResponse;
import camp.woowak.lab.web.resolver.session.SessionConst;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;

@RestController
public class CustomerApiController {
private final SignUpCustomerService signUpCustomerService;
private final SignInCustomerService signInCustomerService;

public CustomerApiController(SignUpCustomerService signUpCustomerService) {
public CustomerApiController(SignUpCustomerService signUpCustomerService,
SignInCustomerService signInCustomerService) {
this.signUpCustomerService = signUpCustomerService;
this.signInCustomerService = signInCustomerService;
}

@PostMapping("/customers")
Expand All @@ -34,4 +46,16 @@ public SignUpCustomerResponse signUp(@Valid @RequestBody SignUpCustomerRequest r

return new SignUpCustomerResponse(registeredId);
}

@PostMapping("/customers/login")
@ResponseStatus(HttpStatus.NO_CONTENT)
public SignInCustomerResponse login(@RequestBody SignInCustomerRequest request, HttpSession session) {
SignInCustomerCommand command = new SignInCustomerCommand(request.email(), request.password());

UUID customerId = signInCustomerService.signIn(command);

session.setAttribute(SessionConst.SESSION_CUSTOMER_KEY, new LoginCustomer(customerId));

return new SignInCustomerResponse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import camp.woowak.lab.common.advice.DomainExceptionHandler;
import camp.woowak.lab.common.exception.BadRequestException;
import camp.woowak.lab.common.exception.HttpStatusException;
import camp.woowak.lab.customer.exception.CustomerAuthenticationException;
import camp.woowak.lab.customer.exception.DuplicateEmailException;
import camp.woowak.lab.customer.exception.InvalidCreationException;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -22,10 +24,7 @@ public class CustomerExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({InvalidCreationException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail handleBadRequestException(BadRequestException e) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST,
e.errorCode().getErrorCode());
problemDetail.setProperty("errorCode", e.errorCode().getErrorCode());
return problemDetail;
return getProblemDetail(e, HttpStatus.BAD_REQUEST);
}

/**
Expand All @@ -35,8 +34,17 @@ public ProblemDetail handleBadRequestException(BadRequestException e) {
@ExceptionHandler({DuplicateEmailException.class})
@ResponseStatus(HttpStatus.CONFLICT)
public ProblemDetail handleDuplicateEmailException(DuplicateEmailException e) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT,
e.errorCode().getMessage());
return getProblemDetail(e, HttpStatus.CONFLICT);
}

kimhyun5u marked this conversation as resolved.
Show resolved Hide resolved
@ExceptionHandler({CustomerAuthenticationException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ProblemDetail handleCustomerAuthenticationException(CustomerAuthenticationException e) {
return getProblemDetail(e, HttpStatus.UNAUTHORIZED);
}

private ProblemDetail getProblemDetail(HttpStatusException e, HttpStatus status) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, e.errorCode().getMessage());
problemDetail.setProperty("errorCode", e.errorCode().getErrorCode());
return problemDetail;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package camp.woowak.lab.web.dto.request.customer;

/**
* 이메일 비밀번호 조건을 알 수 없도록 모든 요청을 받을 수 있도록 구현
*/
public record SignInCustomerRequest(
String email,
String password
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package camp.woowak.lab.web.dto.response.customer;

public record SignInCustomerResponse() {
}
4 changes: 2 additions & 2 deletions src/main/java/camp/woowak/lab/web/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

public enum ErrorCode {
AUTH_DUPLICATE_EMAIL("a1", "이미 가입된 이메일 입니다."),
SIGNUP_INVALID_REQUEST("s1", "잘못된 요청입니다.");

SIGNUP_INVALID_REQUEST("s1", "잘못된 요청입니다."),
AUTH_INVALID_CREDENTIALS("a2", "이메일 또는 비밀번호가 올바르지 않습니다.");
private final String code;
private final String message;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package camp.woowak.lab.customer.service;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import camp.woowak.lab.customer.domain.Customer;
import camp.woowak.lab.customer.exception.CustomerAuthenticationException;
import camp.woowak.lab.customer.repository.CustomerRepository;
import camp.woowak.lab.customer.service.command.SignInCustomerCommand;
import camp.woowak.lab.fixture.CustomerFixture;
import camp.woowak.lab.payaccount.domain.PayAccount;
import camp.woowak.lab.payaccount.domain.TestPayAccount;
import camp.woowak.lab.web.authentication.PasswordEncoder;

/**
*
*/
@ExtendWith(MockitoExtension.class)
public class SignInCustomerServiceTest implements CustomerFixture {
@InjectMocks
private SignInCustomerService signInCustomerService;

@Mock
private CustomerRepository customerRepository;

@Mock
private PasswordEncoder passwordEncoder;

@Test
@DisplayName("로그인 성공")
void testSignIn() {
// given
PayAccount newPayAccount = new TestPayAccount(1L);
Customer customer = createCustomer(newPayAccount, passwordEncoder);
SignInCustomerCommand cmd = new SignInCustomerCommand(customer.getEmail(), customer.getPassword());
given(customerRepository.findByEmail(customer.getEmail())).willReturn(Optional.of(customer));
given(passwordEncoder.matches(cmd.password(), customer.getPassword())).willReturn(true);

// when & then
assertDoesNotThrow(() -> signInCustomerService.signIn(cmd));
verify(customerRepository).findByEmail(customer.getEmail());
verify(passwordEncoder).matches(cmd.password(), customer.getPassword());
}

@Test
@DisplayName("로그인 실패 - 이메일 없음")
void testSignInFailEmailNotFound() {
// given
PayAccount newPayAccount = new TestPayAccount(1L);
Customer customer = createCustomer(newPayAccount, passwordEncoder);
SignInCustomerCommand cmd = new SignInCustomerCommand("[email protected]", customer.getPassword());
given(customerRepository.findByEmail(cmd.email())).willReturn(Optional.empty());

// when & then
assertThrows(CustomerAuthenticationException.class, () -> signInCustomerService.signIn(cmd));
verify(customerRepository).findByEmail(cmd.email());
}

@Test
@DisplayName("로그인 실패 - 패스워드 불일치")
void testSignInFail() {
// given
PayAccount newPayAccount = new TestPayAccount(1L);
Customer customer = createCustomer(newPayAccount, passwordEncoder);
SignInCustomerCommand cmd = new SignInCustomerCommand(customer.getEmail(), customer.getPassword());
given(customerRepository.findByEmail(customer.getEmail())).willReturn(Optional.of(customer));
given(passwordEncoder.matches(cmd.password(), customer.getPassword())).willReturn(false);

// when & then
assertThrows(CustomerAuthenticationException.class, () -> signInCustomerService.signIn(cmd));
verify(customerRepository).findByEmail(customer.getEmail());
verify(passwordEncoder).matches(cmd.password(), customer.getPassword());
}
}
6 changes: 5 additions & 1 deletion src/test/java/camp/woowak/lab/fixture/CustomerFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
import camp.woowak.lab.payaccount.domain.PayAccount;
import camp.woowak.lab.web.authentication.PasswordEncoder;

/**
* CustomerFixture는 Customer와 관련된 테스트에서 공통적으로 사용되는 객체를 생성하는 인터페이스입니다.
*/
public interface CustomerFixture {
default PayAccount createPayAccount() {
return new PayAccount();
}

default Customer createCustomer(PayAccount payAccount, PasswordEncoder passwordEncoder) {
try {
return new Customer("vendorName", "[email protected]", "vendorPassword", "010-0000-0000", payAccount,
return new Customer("customerName", "[email protected]", "customerPassword", "010-0000-0000",
payAccount,
passwordEncoder);
} catch (InvalidCreationException e) {
throw new RuntimeException(e);
Expand Down
Loading