diff --git a/src/main/java/ita/tinybite/domain/user/controller/UserController.java b/src/main/java/ita/tinybite/domain/user/controller/UserController.java index 5e5ca01..d1d72a6 100644 --- a/src/main/java/ita/tinybite/domain/user/controller/UserController.java +++ b/src/main/java/ita/tinybite/domain/user/controller/UserController.java @@ -9,7 +9,7 @@ import static ita.tinybite.global.response.APIResponse.success; @RestController -@RequestMapping("/api/v1/user/me") +@RequestMapping("/api/v1/user") public class UserController { private final UserService userService; @@ -18,27 +18,33 @@ public UserController(UserService userService) { this.userService = userService; } - @GetMapping + @GetMapping("/me") public APIResponse getUser() { return success(userService.getUser()); } - @PatchMapping + @PatchMapping("/me") public APIResponse updateUser(@Valid @RequestBody UpdateUserReqDto req) { userService.updateUser(req); return success(); } - @PatchMapping("/location") + @PatchMapping("/me/location") public APIResponse updateLocation(@RequestParam(defaultValue = "37.3623504988728") String latitude, @RequestParam(defaultValue = "127.117057453619") String longitude) { userService.updateLocation(latitude, longitude); return success(); } - @DeleteMapping + @DeleteMapping("/me") public APIResponse deleteUser() { userService.deleteUser(); return success(); } + + @GetMapping("/nickname/check") + public APIResponse validateNickname(@RequestParam String nickname) { + userService.validateNickname(nickname); + return success(); + } } diff --git a/src/main/java/ita/tinybite/domain/user/entity/User.java b/src/main/java/ita/tinybite/domain/user/entity/User.java index f817069..8672f0c 100644 --- a/src/main/java/ita/tinybite/domain/user/entity/User.java +++ b/src/main/java/ita/tinybite/domain/user/entity/User.java @@ -18,13 +18,16 @@ public class User extends BaseEntity { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Comment("uid") private Long userId; @Column(nullable = false, length = 50, unique = true) private String email; + @Column(nullable = false, length = 30, unique = true) + private String nickname; + @Column(length = 50) private String phone; @@ -36,9 +39,6 @@ public class User extends BaseEntity { @Column(nullable = false) private UserStatus status; - @Column(nullable = false, length = 100) - private String nickname; - @Column(nullable = false, length = 100) private String location; diff --git a/src/main/java/ita/tinybite/domain/user/service/UserService.java b/src/main/java/ita/tinybite/domain/user/service/UserService.java index 5b04ee6..a71130e 100644 --- a/src/main/java/ita/tinybite/domain/user/service/UserService.java +++ b/src/main/java/ita/tinybite/domain/user/service/UserService.java @@ -5,6 +5,8 @@ import ita.tinybite.domain.user.dto.res.UserResDto; import ita.tinybite.domain.user.entity.User; import ita.tinybite.domain.user.repository.UserRepository; +import ita.tinybite.global.exception.BusinessException; +import ita.tinybite.global.exception.errorcode.AuthErrorCode; import ita.tinybite.global.location.LocationService; import org.springframework.stereotype.Service; @@ -43,4 +45,8 @@ public void deleteUser() { userRepository.delete(securityProvider.getCurrentUser()); } + public void validateNickname(String nickname) { + if(userRepository.existsByNickname(nickname)) + throw BusinessException.of(AuthErrorCode.DUPLICATED_NICKNAME); + } } diff --git a/src/main/java/ita/tinybite/global/config/SwaggerConfig.java b/src/main/java/ita/tinybite/global/config/SwaggerConfig.java index a120ca4..6c6fdc8 100644 --- a/src/main/java/ita/tinybite/global/config/SwaggerConfig.java +++ b/src/main/java/ita/tinybite/global/config/SwaggerConfig.java @@ -18,7 +18,6 @@ public class SwaggerConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() - .servers(List.of(new Server().url("https://growinserver.shop"))) .addSecurityItem(new SecurityRequirement().addList("BearerAuth")) .components( new Components() diff --git a/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java b/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java index 5618914..00c7180 100644 --- a/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java +++ b/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java @@ -7,7 +7,11 @@ public enum AuthErrorCode implements ErrorCode { INVALID_PHONE_NUMBER(HttpStatus.BAD_REQUEST, "INVALID_PHONE_NUMBER", "유효하지 않은 번호입니다."), - INVALID_AUTHCODE(HttpStatus.UNAUTHORIZED, "INVALID_AUTHCODE", "인증코드가 만료되었거나, 일치하지 않습니다.") + INVALID_AUTHCODE(HttpStatus.UNAUTHORIZED, "INVALID_AUTHCODE", "인증코드가 일치하지 않습니다."), + EXPIRED_AUTH_CODE(HttpStatus.BAD_REQUEST, "EXPIRED_AUTH_CODE", "인증시간이 만료되었습니다."), + + DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "DUPLICATED_NICKNAME", "중복된 닉네임입니다."), + ; private final HttpStatus httpStatus; diff --git a/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java b/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java index 57d84c9..eba0fa5 100644 --- a/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java +++ b/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java @@ -1,7 +1,5 @@ package ita.tinybite.global.sms.controller; -import ita.tinybite.global.exception.errorcode.AuthErrorCode; -import ita.tinybite.global.exception.errorcode.TaskErrorCode; import ita.tinybite.global.response.APIResponse; import ita.tinybite.global.sms.dto.req.CheckReqDto; import ita.tinybite.global.sms.dto.req.SendReqDto; @@ -11,6 +9,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static ita.tinybite.global.response.APIResponse.success; + @RestController @RequestMapping("/api/v1/auth/sms") public class SmsAuthController { @@ -24,12 +24,12 @@ public SmsAuthController(SmsAuthService smsAuthService) { @PostMapping("/send") public APIResponse send(@RequestBody SendReqDto req) { smsAuthService.send(req.phone()); - return APIResponse.success(); + return success(); } @PostMapping("/check") public APIResponse check(@RequestBody CheckReqDto req) { - if(smsAuthService.check(req)) return APIResponse.success(); - return APIResponse.businessError(AuthErrorCode.INVALID_AUTHCODE); + smsAuthService.check(req); + return success(); } } diff --git a/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java b/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java index 781ecc0..66daf10 100644 --- a/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java +++ b/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java @@ -27,7 +27,7 @@ public SmsAuthService(SmsService smsService, RedisTemplate redis /** * 1. 인증코드 생성
* 2. 주어진 폰번호로 인증코드 전송
- * 3. DB에 {번호, 인증코드}쌍으로 저장 or 메모리에 저장 (ttl 설정 고려하기)
+ * 3. redis에 {번호, 인증코드}쌍으로 저장 (ttl 설정 고려)
*/ public void send(String phone) { validatePhoneNumber(phone); @@ -40,14 +40,17 @@ public void send(String phone) { /** * req.phone으로 redis 조회
* 조회한 authCode와 요청받은 authcode를 비교
- * 같으면 true, 다르면 false
+ * 조회된 authCode가 없을 시, 만료 혹은 요청 X
*/ - public boolean check(CheckReqDto req) { + public void check(CheckReqDto req) { validatePhoneNumber(req.phone()); String authCode = redisTemplate.opsForValue().get(req.phone()); - if(authCode == null) return false; - return authCode.equals(req.authCode()); + if(authCode == null) + throw BusinessException.of(AuthErrorCode.EXPIRED_AUTH_CODE); + + if(!authCode.equals(req.authCode())) + throw BusinessException.of(AuthErrorCode.INVALID_AUTHCODE); } private void validatePhoneNumber(String phone) { diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 580242a..8410dca 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -16,7 +16,7 @@ spring: data: redis: - host: localhost + host: redis port: 6379 kakao: @@ -25,9 +25,4 @@ kakao: naver: client-id: ${NAVER_CLIENT_ID} - secret: ${NAVER_CLIENT_SECRET} - -jwt: - secret: ${JWT_SECRET} - access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY} - refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY} + secret: ${NAVER_CLIENT_SECRET} \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index f607a37..9de545a 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -33,11 +33,6 @@ naver: client-id: ${NAVER_CLIENT_ID} secret: ${NAVER_CLIENT_SECRET} -jwt: - secret: ${JWT_SECRET} - access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY} - refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY} - logging: level: org.hibernate.SQL: debug \ No newline at end of file diff --git a/src/test/java/ita/tinybite/TinyBiteApplicationTests.java b/src/test/java/ita/tinybite/TinyBiteApplicationTests.java deleted file mode 100644 index 4030bf9..0000000 --- a/src/test/java/ita/tinybite/TinyBiteApplicationTests.java +++ /dev/null @@ -1,15 +0,0 @@ -package ita.tinybite; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("test") -class TinyBiteApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java b/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java new file mode 100644 index 0000000..ae14b5b --- /dev/null +++ b/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java @@ -0,0 +1,97 @@ +package ita.tinybite.domain.user.service; + +import ita.tinybite.domain.user.constant.LoginType; +import ita.tinybite.domain.user.constant.UserStatus; +import ita.tinybite.domain.user.dto.req.UpdateUserReqDto; +import ita.tinybite.domain.user.dto.res.UserResDto; +import ita.tinybite.domain.user.entity.User; +import ita.tinybite.domain.user.repository.UserRepository; +import ita.tinybite.domain.user.service.fake.FakeLocationService; +import ita.tinybite.domain.user.service.fake.FakeSecurityProvider; +import ita.tinybite.global.exception.BusinessException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.assertj.core.api.Assertions.*; + +@DataJpaTest +class UserServiceTest { + + @Autowired + private UserRepository userRepository; + + // Fake 객체 + private FakeSecurityProvider securityProvider; + private FakeLocationService locationService; + + // 테스트 객체 + private UserService userService; + + @BeforeEach + void setUp() { + securityProvider = new FakeSecurityProvider(userRepository); + locationService = new FakeLocationService(); + userService = new UserService(securityProvider, userRepository, locationService); + + User user = User.builder() + .email("yyytir777@gmail.com") + .nickname("임원재") + .location("분당구 정자동") + .status(UserStatus.ACTIVE) + .type(LoginType.KAKAO) + .build(); + + userRepository.save(user); + securityProvider.setCurrentUser(user); + } + + @Test + void getUser() { + UserResDto user = userService.getUser(); + assertThat(user).isNotNull(); + } + + @Test + void updateUser() { + // given + UpdateUserReqDto req = new UpdateUserReqDto("updated_nickname"); + + // when + userService.updateUser(req); + + // then + assertThat(securityProvider.getCurrentUser().getNickname()).isEqualTo("updated_nickname"); + } + + @Test + void updateLocation() { + // given + String latitude = "12.123145"; String longitude = "123.123123"; + + // when + userService.updateLocation(latitude, longitude); + + // then + assertThat(securityProvider.getCurrentUser().getLocation()).isEqualTo(locationService.getLocation(latitude, longitude)); + } + + @Test + void deleteUser() { + // when + User currentUser = securityProvider.getCurrentUser(); + userService.deleteUser(); + + // then + assertThat(userRepository.findById(currentUser.getUserId())).isEmpty(); + } + + @Test + void validateNickname() { + assertThatThrownBy(() -> userService.validateNickname("임원재")) + .isInstanceOf(BusinessException.class); + + assertThatNoException().isThrownBy(() -> userService.validateNickname("임원재1")); + } +} \ No newline at end of file diff --git a/src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java b/src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java new file mode 100644 index 0000000..d959117 --- /dev/null +++ b/src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java @@ -0,0 +1,11 @@ +package ita.tinybite.domain.user.service.fake; + +import ita.tinybite.global.location.LocationService; + +public class FakeLocationService extends LocationService { + + @Override + public String getLocation(String latitude, String longitude) { + return latitude + " " + longitude; + } +} diff --git a/src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java b/src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java new file mode 100644 index 0000000..e368543 --- /dev/null +++ b/src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java @@ -0,0 +1,23 @@ +package ita.tinybite.domain.user.service.fake; + +import ita.tinybite.domain.auth.service.SecurityProvider; +import ita.tinybite.domain.user.entity.User; +import ita.tinybite.domain.user.repository.UserRepository; + +public class FakeSecurityProvider extends SecurityProvider { + + private User currentUser; + + public FakeSecurityProvider(UserRepository userRepository) { + super(userRepository); + } + + public void setCurrentUser(User user) { + currentUser = user; + } + + @Override + public User getCurrentUser() { + return currentUser; + } +} diff --git a/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java b/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java index 407877a..eb0a4bc 100644 --- a/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java +++ b/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java @@ -1,10 +1,12 @@ package ita.tinybite.global.sms; import ita.tinybite.global.exception.BusinessException; +import ita.tinybite.global.sms.dto.req.CheckReqDto; import ita.tinybite.global.sms.fake.FakeRedisTemplate; import ita.tinybite.global.sms.fake.FakeSmsService; import ita.tinybite.global.sms.service.SmsAuthService; import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.Assertions.*; @@ -35,4 +37,10 @@ void should_fail_when_smsAuth_send_with_invalid_phone() { .isInstanceOf(BusinessException.class); } } + + @Test + void should_fail_when_smsAuth_send_with_expired_auth_code() { + assertThatThrownBy(() -> smsAuthService.check(new CheckReqDto(SUCCESS_PHONE_NUMBER, "123456"))) + .isInstanceOf(BusinessException.class); + } } diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 9ade865..58872b9 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -3,9 +3,12 @@ spring: activate: on-profile: "test" -# h2 사용하기? -# datasource: -# driver-class-name: com.mysql.cj.jdbc.Driver -# url: ${TEST_DB_URL} -# username: ${TEST_DB_USERNAME} -# password: ${TEST_DB_PASSWORD} \ No newline at end of file + profiles: + active: "test" + +# h2 사용 + datasource: + url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1 + driver-class-name: org.h2.Driver + username: sa + password: \ No newline at end of file