diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 7f9d6a5d..c5678b63 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,5 @@
-
+
-
\ No newline at end of file
+
diff --git a/src/main/java/com/backendoori/ootw/avatar/controller/AvatarItemController.java b/src/main/java/com/backendoori/ootw/avatar/controller/AvatarItemController.java
index d66ad894..6050fc13 100644
--- a/src/main/java/com/backendoori/ootw/avatar/controller/AvatarItemController.java
+++ b/src/main/java/com/backendoori/ootw/avatar/controller/AvatarItemController.java
@@ -3,27 +3,28 @@
import com.backendoori.ootw.avatar.dto.AvatarItemRequest;
import com.backendoori.ootw.avatar.dto.AvatarItemResponse;
import com.backendoori.ootw.avatar.service.AvatarItemService;
+import com.backendoori.ootw.common.validation.Image;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
-@RequestMapping("/api/v1/image")
+@RequestMapping("/api/v1/avatar-items")
@RequiredArgsConstructor
public class AvatarItemController {
private final AvatarItemService appearanceService;
@PostMapping
- public ResponseEntity uploadImage(@RequestPart("file") MultipartFile file,
- @RequestBody AvatarItemRequest requestDto) {
- AvatarItemResponse avatarItem = appearanceService.uploadItem(file, requestDto);
+ public ResponseEntity uploadImage(@RequestPart @Image MultipartFile file,
+ @RequestPart @Valid AvatarItemRequest request) {
+ AvatarItemResponse avatarItem = appearanceService.uploadItem(file, request);
return ResponseEntity.status(HttpStatus.CREATED).body(avatarItem);
}
diff --git a/src/main/java/com/backendoori/ootw/avatar/domain/AvatarItem.java b/src/main/java/com/backendoori/ootw/avatar/domain/AvatarItem.java
index d4bbd125..fcd6b6da 100644
--- a/src/main/java/com/backendoori/ootw/avatar/domain/AvatarItem.java
+++ b/src/main/java/com/backendoori/ootw/avatar/domain/AvatarItem.java
@@ -29,14 +29,14 @@ public class AvatarItem {
@Column(name = "type", nullable = false, columnDefinition = "varchar(30)")
@Enumerated(EnumType.STRING)
- private Type type;
+ private ItemType itemType;
@Column(name = "sex", nullable = false, columnDefinition = "tinyint")
private boolean sex;
private AvatarItem(String image, String type, boolean sex) {
this.image = image;
- this.type = Type.valueOf(type);
+ this.itemType = ItemType.valueOf(type);
this.sex = sex;
}
diff --git a/src/main/java/com/backendoori/ootw/avatar/domain/Type.java b/src/main/java/com/backendoori/ootw/avatar/domain/ItemType.java
similarity index 80%
rename from src/main/java/com/backendoori/ootw/avatar/domain/Type.java
rename to src/main/java/com/backendoori/ootw/avatar/domain/ItemType.java
index 274757f6..ee2d5f60 100644
--- a/src/main/java/com/backendoori/ootw/avatar/domain/Type.java
+++ b/src/main/java/com/backendoori/ootw/avatar/domain/ItemType.java
@@ -1,5 +1,5 @@
package com.backendoori.ootw.avatar.domain;
-public enum Type {
+public enum ItemType {
HAIR, TOP, PANTS, ACCESSORY, SHOES, BACKGROUND
}
diff --git a/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemRequest.java b/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemRequest.java
index 9baed1a7..0a5f2bf2 100644
--- a/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemRequest.java
+++ b/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemRequest.java
@@ -1,7 +1,13 @@
package com.backendoori.ootw.avatar.dto;
+import com.backendoori.ootw.avatar.domain.ItemType;
+import com.backendoori.ootw.common.validation.Enum;
+import jakarta.validation.constraints.NotNull;
+
public record AvatarItemRequest(
+ @Enum(enumClass = ItemType.class)
String type,
+ @NotNull
boolean sex
) {
diff --git a/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemResponse.java b/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemResponse.java
index 2b32b5ba..b69bc3df 100644
--- a/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemResponse.java
+++ b/src/main/java/com/backendoori/ootw/avatar/dto/AvatarItemResponse.java
@@ -9,7 +9,7 @@ public record AvatarItemResponse(
) {
public static AvatarItemResponse from(AvatarItem avatarItem) {
- return new AvatarItemResponse(avatarItem.getType().name(),
+ return new AvatarItemResponse(avatarItem.getItemType().name(),
avatarItem.isSex(),
avatarItem.getImage());
}
diff --git a/src/main/java/com/backendoori/ootw/common/AssertUtil.java b/src/main/java/com/backendoori/ootw/common/AssertUtil.java
new file mode 100644
index 00000000..e083b4e2
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/common/AssertUtil.java
@@ -0,0 +1,33 @@
+package com.backendoori.ootw.common;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AssertUtil extends Assert {
+
+ public static void notBlank(@Nullable String string, String message) {
+ if (string == null || string.isBlank()) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ public static void hasPattern(@Nullable String string, @Nullable String pattern, String message) {
+ notNull(string, message);
+
+ if (!string.matches(Objects.requireNonNull(pattern))) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ public static void throwIf(boolean state, Supplier exceptionSupplier) {
+ if (state) {
+ throw exceptionSupplier.get();
+ }
+ }
+
+}
diff --git a/src/main/java/com/backendoori/ootw/common/image/MiniOImageServiceImpl.java b/src/main/java/com/backendoori/ootw/common/image/MiniOImageServiceImpl.java
index 7446ce32..0f3bb5e0 100644
--- a/src/main/java/com/backendoori/ootw/common/image/MiniOImageServiceImpl.java
+++ b/src/main/java/com/backendoori/ootw/common/image/MiniOImageServiceImpl.java
@@ -4,18 +4,19 @@
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import com.backendoori.ootw.config.MiniOConfig;
+import com.backendoori.ootw.exception.ImageUploadException;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Controller;
+import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
-@Controller
+@Service
@RequiredArgsConstructor
public class MiniOImageServiceImpl implements ImageService {
@@ -39,7 +40,7 @@ public String uploadImage(MultipartFile file) {
.build();
minioClient.putObject(args);
} catch (Exception e) {
- log.warn("Exception occurred while saving contents : {}", e.getMessage(), e);
+ throw new ImageUploadException();
}
return getUrl();
@@ -56,7 +57,7 @@ private String getUrl() {
.expiry(DURATION, TimeUnit.HOURS)
.build());
} catch (Exception e) {
- log.warn("Exception Occurred while getting: {}", e.getMessage(), e);
+ throw new ImageUploadException();
}
return url;
diff --git a/src/main/java/com/backendoori/ootw/common/validation/Enum.java b/src/main/java/com/backendoori/ootw/common/validation/Enum.java
new file mode 100644
index 00000000..de8c6e93
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/common/validation/Enum.java
@@ -0,0 +1,20 @@
+package com.backendoori.ootw.common.validation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+@Target(value = {ElementType.PARAMETER, ElementType.FIELD})
+@Retention(value = RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = EnumValidator.class)
+public @interface Enum {
+
+ String message() default "유효하지 않은 값입니다 다시 입력해주세요";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+ Class extends java.lang.Enum>> enumClass();
+
+}
diff --git a/src/main/java/com/backendoori/ootw/common/validation/EnumValidator.java b/src/main/java/com/backendoori/ootw/common/validation/EnumValidator.java
new file mode 100644
index 00000000..0f3fc64b
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/common/validation/EnumValidator.java
@@ -0,0 +1,26 @@
+package com.backendoori.ootw.common.validation;
+
+import java.util.Arrays;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+public class EnumValidator implements ConstraintValidator {
+
+ private Enum annotation;
+
+ @Override
+ public void initialize(Enum constraintAnnotation) {
+ this.annotation = constraintAnnotation;
+ }
+
+ @Override
+ public boolean isValid(String type, ConstraintValidatorContext context) {
+ if (type == null) {
+ return false;
+ }
+
+ return Arrays.stream(this.annotation.enumClass().getEnumConstants())
+ .anyMatch(e -> e.name().equals(type));
+ }
+
+}
diff --git a/src/main/java/com/backendoori/ootw/common/validation/Image.java b/src/main/java/com/backendoori/ootw/common/validation/Image.java
new file mode 100644
index 00000000..6376e9ce
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/common/validation/Image.java
@@ -0,0 +1,23 @@
+package com.backendoori.ootw.common.validation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+@Target(value = ElementType.PARAMETER)
+@Retention(value = RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = ImageValidator.class)
+public @interface Image {
+
+ String message = "유효하지 않은 이미지를 업로드하였습니다. 다른 이미지를 업로드 해주세요";
+
+ String message() default message;
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+}
diff --git a/src/main/java/com/backendoori/ootw/common/validation/ImageValidator.java b/src/main/java/com/backendoori/ootw/common/validation/ImageValidator.java
new file mode 100644
index 00000000..778c713f
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/common/validation/ImageValidator.java
@@ -0,0 +1,25 @@
+package com.backendoori.ootw.common.validation;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.springframework.web.multipart.MultipartFile;
+
+public class ImageValidator implements ConstraintValidator {
+
+ @Override
+ public boolean isValid(MultipartFile img, ConstraintValidatorContext context) {
+ if(img == null || img.isEmpty()){
+ return false;
+ }
+ if(img.getSize() > 10_000_000){
+ return false;
+ }
+ String contentType = img.getContentType();
+ if(!contentType.startsWith("image")){
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/backendoori/ootw/config/MiniOConfig.java b/src/main/java/com/backendoori/ootw/config/MiniOConfig.java
index c317d8de..358ce4c9 100644
--- a/src/main/java/com/backendoori/ootw/config/MiniOConfig.java
+++ b/src/main/java/com/backendoori/ootw/config/MiniOConfig.java
@@ -5,9 +5,9 @@
import io.minio.MinioClient;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
-import org.springframework.stereotype.Component;
+import org.springframework.context.annotation.Configuration;
-@Component
+@Configuration
@RequiredArgsConstructor
public class MiniOConfig {
diff --git a/src/main/java/com/backendoori/ootw/exception/ExceptionResponse.java b/src/main/java/com/backendoori/ootw/exception/ExceptionResponse.java
deleted file mode 100644
index 242cfd29..00000000
--- a/src/main/java/com/backendoori/ootw/exception/ExceptionResponse.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.backendoori.ootw.exception;
-
-import java.util.List;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-
-public record ExceptionResponse(
- T error
-) {
-
- public static ExceptionResponse> from(
- MethodArgumentNotValidException e) {
- List errors = e.getBindingResult()
- .getFieldErrors()
- .stream()
- .map(fieldError ->
- new FieldErrorDetail(fieldError.getField(), fieldError.getDefaultMessage()))
- .toList();
-
- return new ExceptionResponse<>(errors);
- }
-
- public static ExceptionResponse from(E e) {
- return new ExceptionResponse<>(e.getMessage());
- }
-
- @RequiredArgsConstructor(access = AccessLevel.PROTECTED)
- @Getter
- public static class FieldErrorDetail {
-
- private final String field;
- private final String defaultMessage;
-
- }
-
-}
diff --git a/src/main/java/com/backendoori/ootw/exception/GlobalControllerAdvice.java b/src/main/java/com/backendoori/ootw/exception/GlobalControllerAdvice.java
index 40cf0a38..cf184ae0 100644
--- a/src/main/java/com/backendoori/ootw/exception/GlobalControllerAdvice.java
+++ b/src/main/java/com/backendoori/ootw/exception/GlobalControllerAdvice.java
@@ -1,11 +1,16 @@
package com.backendoori.ootw.exception;
+import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
@@ -14,6 +19,44 @@
@RestControllerAdvice
public class GlobalControllerAdvice {
+ public static final String DEFAULT_MESSAGE = "유효하지 않은 요청 입니다.";
+
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) {
+ ErrorResponse errorResponse = new ErrorResponse(e.getMessage());
+
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ .body(errorResponse);
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+ List errors = e.getFieldErrors();
+ String message = errors.stream()
+ .map(DefaultMessageSourceResolvable::getDefaultMessage)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(DEFAULT_MESSAGE);
+
+ ErrorResponse errorResponse = new ErrorResponse(message);
+
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ .body(errorResponse);
+ }
+
+ @ExceptionHandler(HandlerMethodValidationException.class)
+ public ResponseEntity handlerMethodValidationException(HandlerMethodValidationException e) {
+ String errorMessage = e.getAllValidationResults()
+ .get(0)
+ .getResolvableErrors()
+ .get(0)
+ .getDefaultMessage();
+ ErrorResponse errorResponse = new ErrorResponse(errorMessage);
+
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ .body(errorResponse);
+ }
+
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity handleAuthenticationException(AuthenticationException e) {
ErrorResponse errorResponse = new ErrorResponse(e.getMessage());
@@ -38,12 +81,11 @@ public ResponseEntity handleDuplicateKeyException(DuplicateKeyExc
.body(errorResponse);
}
- @ExceptionHandler(HandlerMethodValidationException.class)
- public ResponseEntity handleHandlerMethodValidationException(
- HandlerMethodValidationException e) {
- ErrorResponse errorResponse = new ErrorResponse(e.getReason());
+ @ExceptionHandler(ImageUploadException.class)
+ public ResponseEntity handleImageUploadException(ImageUploadException e) {
+ ErrorResponse errorResponse = new ErrorResponse(e.getMessage());
- return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
.body(errorResponse);
}
diff --git a/src/main/java/com/backendoori/ootw/exception/ImageUploadException.java b/src/main/java/com/backendoori/ootw/exception/ImageUploadException.java
new file mode 100644
index 00000000..b9646bc0
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/exception/ImageUploadException.java
@@ -0,0 +1,12 @@
+package com.backendoori.ootw.exception;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public class ImageUploadException extends RuntimeException{
+
+ private final String message = "이미지 업로드 중 예외가 발생했습니다.";
+
+}
diff --git a/src/main/java/com/backendoori/ootw/post/controller/PostController.java b/src/main/java/com/backendoori/ootw/post/controller/PostController.java
index 17e82ea2..5c3ba93a 100644
--- a/src/main/java/com/backendoori/ootw/post/controller/PostController.java
+++ b/src/main/java/com/backendoori/ootw/post/controller/PostController.java
@@ -2,9 +2,6 @@
import java.net.URI;
import java.util.List;
-import java.util.NoSuchElementException;
-import com.backendoori.ootw.exception.ExceptionResponse;
-import com.backendoori.ootw.exception.ExceptionResponse.FieldErrorDetail;
import com.backendoori.ootw.post.dto.PostReadResponse;
import com.backendoori.ootw.post.dto.PostSaveRequest;
import com.backendoori.ootw.post.dto.PostSaveResponse;
@@ -13,8 +10,6 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -55,34 +50,4 @@ public ResponseEntity> readAll() {
.body(postService.getAll());
}
- @ExceptionHandler(Exception.class)
- public ResponseEntity> handleException(Exception e) {
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
- .body(ExceptionResponse.from(e));
- }
-
- @ExceptionHandler(IllegalArgumentException.class)
- public ResponseEntity> handleIllegalArgumentException(
- IllegalArgumentException e
- ) {
- return ResponseEntity.status(HttpStatus.BAD_REQUEST)
- .body(ExceptionResponse.from(e));
- }
-
- @ExceptionHandler(NoSuchElementException.class)
- public ResponseEntity> handleNoSuchElementException(
- NoSuchElementException e
- ) {
- return ResponseEntity.status(HttpStatus.NOT_FOUND)
- .body(ExceptionResponse.from(e));
- }
-
- @ExceptionHandler(MethodArgumentNotValidException.class)
- public ResponseEntity>> handleMethodArgumentNotValidException(
- MethodArgumentNotValidException e
- ) {
- return ResponseEntity.status(HttpStatus.BAD_REQUEST)
- .body(ExceptionResponse.from(e));
- }
-
}
diff --git a/src/main/java/com/backendoori/ootw/user/controller/UserController.java b/src/main/java/com/backendoori/ootw/user/controller/UserController.java
index fc97f46d..bf9eb119 100644
--- a/src/main/java/com/backendoori/ootw/user/controller/UserController.java
+++ b/src/main/java/com/backendoori/ootw/user/controller/UserController.java
@@ -6,6 +6,7 @@
import com.backendoori.ootw.user.dto.TokenDto;
import com.backendoori.ootw.user.dto.UserDto;
import com.backendoori.ootw.user.service.UserService;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -23,7 +24,7 @@ public class UserController {
private final UserService userService;
@PostMapping("/signup")
- public ResponseEntity signup(@RequestBody SignupDto signupDto) {
+ public ResponseEntity signup(@RequestBody @Valid SignupDto signupDto) {
UserDto userDto = userService.signup(signupDto);
return ResponseEntity.status(HttpStatus.CREATED)
@@ -31,7 +32,7 @@ public ResponseEntity signup(@RequestBody SignupDto signupDto) {
}
@PostMapping("/login")
- public ResponseEntity login(@RequestBody LoginDto loginDto) {
+ public ResponseEntity login(@RequestBody @Valid LoginDto loginDto) {
TokenDto tokenDto = userService.login(loginDto);
HttpHeaders httpHeaders = new HttpHeaders();
diff --git a/src/main/java/com/backendoori/ootw/user/domain/User.java b/src/main/java/com/backendoori/ootw/user/domain/User.java
index 6fdecfff..35f4a99a 100644
--- a/src/main/java/com/backendoori/ootw/user/domain/User.java
+++ b/src/main/java/com/backendoori/ootw/user/domain/User.java
@@ -1,6 +1,9 @@
package com.backendoori.ootw.user.domain;
+import com.backendoori.ootw.common.AssertUtil;
import com.backendoori.ootw.common.BaseEntity;
+import com.backendoori.ootw.user.validation.Message;
+import com.backendoori.ootw.user.validation.RFC5322;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@@ -8,7 +11,6 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -18,7 +20,6 @@
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
-@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseEntity {
@Id
@@ -38,4 +39,16 @@ public class User extends BaseEntity {
@Column(name = "image")
private String image;
+ public User(Long id, String email, String password, String nickname, String image) {
+ AssertUtil.hasPattern(email, RFC5322.REGEX, Message.INVALID_EMAIL);
+ AssertUtil.notBlank(password, Message.BLANK_PASSWORD);
+ AssertUtil.notBlank(nickname, Message.BLANK_NICKNAME);
+
+ this.id = id;
+ this.email = email;
+ this.password = password;
+ this.nickname = nickname;
+ this.image = image;
+ }
+
}
diff --git a/src/main/java/com/backendoori/ootw/user/dto/LoginDto.java b/src/main/java/com/backendoori/ootw/user/dto/LoginDto.java
index 1add5ec0..a2667b18 100644
--- a/src/main/java/com/backendoori/ootw/user/dto/LoginDto.java
+++ b/src/main/java/com/backendoori/ootw/user/dto/LoginDto.java
@@ -1,7 +1,19 @@
package com.backendoori.ootw.user.dto;
+import com.backendoori.ootw.user.validation.Password;
+import com.backendoori.ootw.user.validation.RFC5322;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
public record LoginDto(
+ @NotNull
+ @NotBlank
+ @Email(regexp = RFC5322.REGEX)
String email,
+
+ @NotNull
+ @Password
String password
) {
diff --git a/src/main/java/com/backendoori/ootw/user/dto/SignupDto.java b/src/main/java/com/backendoori/ootw/user/dto/SignupDto.java
index fea29628..59b368c3 100644
--- a/src/main/java/com/backendoori/ootw/user/dto/SignupDto.java
+++ b/src/main/java/com/backendoori/ootw/user/dto/SignupDto.java
@@ -1,10 +1,25 @@
package com.backendoori.ootw.user.dto;
+import com.backendoori.ootw.user.validation.Message;
+import com.backendoori.ootw.user.validation.Password;
+import com.backendoori.ootw.user.validation.RFC5322;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
public record SignupDto(
+ @NotNull
+ @NotBlank
+ @Email(regexp = RFC5322.REGEX)
String email,
+
+ @NotNull
+ @Password
String password,
- String nickname,
- String image
+
+ @NotNull
+ @NotBlank(message = Message.BLANK_NICKNAME)
+ String nickname
) {
}
diff --git a/src/main/java/com/backendoori/ootw/exception/AlreadyExistEmailException.java b/src/main/java/com/backendoori/ootw/user/exception/AlreadyExistEmailException.java
similarity index 87%
rename from src/main/java/com/backendoori/ootw/exception/AlreadyExistEmailException.java
rename to src/main/java/com/backendoori/ootw/user/exception/AlreadyExistEmailException.java
index 16ae8ab6..d8ec3ae4 100644
--- a/src/main/java/com/backendoori/ootw/exception/AlreadyExistEmailException.java
+++ b/src/main/java/com/backendoori/ootw/user/exception/AlreadyExistEmailException.java
@@ -1,4 +1,4 @@
-package com.backendoori.ootw.exception;
+package com.backendoori.ootw.user.exception;
import org.springframework.dao.DuplicateKeyException;
diff --git a/src/main/java/com/backendoori/ootw/exception/IncorrectPasswordException.java b/src/main/java/com/backendoori/ootw/user/exception/IncorrectPasswordException.java
similarity index 87%
rename from src/main/java/com/backendoori/ootw/exception/IncorrectPasswordException.java
rename to src/main/java/com/backendoori/ootw/user/exception/IncorrectPasswordException.java
index cea57a9b..474f996e 100644
--- a/src/main/java/com/backendoori/ootw/exception/IncorrectPasswordException.java
+++ b/src/main/java/com/backendoori/ootw/user/exception/IncorrectPasswordException.java
@@ -1,4 +1,4 @@
-package com.backendoori.ootw.exception;
+package com.backendoori.ootw.user.exception;
import org.springframework.security.core.AuthenticationException;
diff --git a/src/main/java/com/backendoori/ootw/user/service/UserService.java b/src/main/java/com/backendoori/ootw/user/service/UserService.java
index 72707bc9..082615bc 100644
--- a/src/main/java/com/backendoori/ootw/user/service/UserService.java
+++ b/src/main/java/com/backendoori/ootw/user/service/UserService.java
@@ -1,7 +1,6 @@
package com.backendoori.ootw.user.service;
-import com.backendoori.ootw.exception.AlreadyExistEmailException;
-import com.backendoori.ootw.exception.IncorrectPasswordException;
+import com.backendoori.ootw.common.AssertUtil;
import com.backendoori.ootw.exception.UserNotFoundException;
import com.backendoori.ootw.security.jwt.TokenProvider;
import com.backendoori.ootw.user.domain.User;
@@ -9,11 +8,16 @@
import com.backendoori.ootw.user.dto.SignupDto;
import com.backendoori.ootw.user.dto.TokenDto;
import com.backendoori.ootw.user.dto.UserDto;
+import com.backendoori.ootw.user.exception.AlreadyExistEmailException;
+import com.backendoori.ootw.user.exception.IncorrectPasswordException;
import com.backendoori.ootw.user.repository.UserRepository;
+import com.backendoori.ootw.user.validation.Message;
+import com.backendoori.ootw.user.validation.Password;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
@Service
@RequiredArgsConstructor
@@ -28,16 +32,10 @@ public UserDto signup(SignupDto signupDto) {
boolean isAlreadyExistEmail = userRepository.findByEmail(signupDto.email())
.isPresent();
- if (isAlreadyExistEmail) {
- throw new AlreadyExistEmailException();
- }
+ AssertUtil.throwIf(isAlreadyExistEmail, AlreadyExistEmailException::new);
+ AssertUtil.isTrue(isValidPassword(signupDto.password()), Message.INVALID_PASSWORD);
- User user = User.builder()
- .email(signupDto.email())
- .password(passwordEncoder.encode(signupDto.password()))
- .nickname(signupDto.nickname())
- .image(signupDto.image())
- .build();
+ User user = buildUser(signupDto);
userRepository.save(user);
@@ -47,14 +45,29 @@ public UserDto signup(SignupDto signupDto) {
public TokenDto login(LoginDto loginDto) {
User user = userRepository.findByEmail(loginDto.email())
.orElseThrow(UserNotFoundException::new);
+ boolean isIncorrectPassword = !matchPassword(loginDto.password(), user.getPassword());
- if (!passwordEncoder.matches(loginDto.password(), user.getPassword())) {
- throw new IncorrectPasswordException();
- }
+ AssertUtil.throwIf(isIncorrectPassword, IncorrectPasswordException::new);
String token = tokenProvider.createToken(user.getId());
return new TokenDto(token);
}
+ private User buildUser(SignupDto signupDto) {
+ return User.builder()
+ .email(signupDto.email())
+ .password(passwordEncoder.encode(signupDto.password()))
+ .nickname(signupDto.nickname())
+ .build();
+ }
+
+ private boolean matchPassword(String decrypted, String encrypted) {
+ return passwordEncoder.matches(decrypted, encrypted);
+ }
+
+ private boolean isValidPassword(String password) {
+ return StringUtils.hasLength(password) && password.matches(Password.REGEX);
+ }
+
}
diff --git a/src/main/java/com/backendoori/ootw/user/validation/Message.java b/src/main/java/com/backendoori/ootw/user/validation/Message.java
new file mode 100644
index 00000000..22e4f08b
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/user/validation/Message.java
@@ -0,0 +1,18 @@
+package com.backendoori.ootw.user.validation;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class Message {
+
+ public static final String INVALID_EMAIL = "이메일 형식이 올바르지 않습니다.";
+
+ public static final String INVALID_PASSWORD = "비밀번호는 숫자, 영문자, 특수문자를 포함한 "
+ + Password.MIN_SIZE + "자 이상, "
+ + Password.MAX_SIZE + "자 이내의 문자여야 합니다.";
+
+ public static final String BLANK_PASSWORD = "비밀번호는 공백일 수 없습니다.";
+ public static final String BLANK_NICKNAME = "닉네임은 공백일 수 없습니다.";
+
+}
diff --git a/src/main/java/com/backendoori/ootw/user/validation/Password.java b/src/main/java/com/backendoori/ootw/user/validation/Password.java
new file mode 100644
index 00000000..764f3095
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/user/validation/Password.java
@@ -0,0 +1,28 @@
+package com.backendoori.ootw.user.validation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+@Documented
+@Constraint(validatedBy = PasswordValidator.class)
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Password {
+
+ int MIN_SIZE = 8;
+ int MAX_SIZE = 30;
+ String REGEX = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&^])[A-Za-z\\d@$!%*#?&^]" +
+ "{" + MIN_SIZE + "," + MAX_SIZE + "}$";
+
+ String message() default Message.INVALID_PASSWORD;
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+}
diff --git a/src/main/java/com/backendoori/ootw/user/validation/PasswordValidator.java b/src/main/java/com/backendoori/ootw/user/validation/PasswordValidator.java
new file mode 100644
index 00000000..2fb653e8
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/user/validation/PasswordValidator.java
@@ -0,0 +1,30 @@
+package com.backendoori.ootw.user.validation;
+
+import java.util.Objects;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+public class PasswordValidator implements ConstraintValidator {
+
+ @Override
+ public boolean isValid(String password, ConstraintValidatorContext context) {
+ if (Objects.isNull(password) || password.isBlank()) {
+ return violateWithMessage(context, Message.BLANK_PASSWORD);
+ }
+
+ if (!password.matches(Password.REGEX)) {
+ return violateWithMessage(context, Message.INVALID_PASSWORD);
+ }
+
+ return true;
+ }
+
+ private boolean violateWithMessage(ConstraintValidatorContext context, String message) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(message)
+ .addConstraintViolation();
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/backendoori/ootw/user/validation/RFC5322.java b/src/main/java/com/backendoori/ootw/user/validation/RFC5322.java
new file mode 100644
index 00000000..1180e8f2
--- /dev/null
+++ b/src/main/java/com/backendoori/ootw/user/validation/RFC5322.java
@@ -0,0 +1,11 @@
+package com.backendoori.ootw.user.validation;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class RFC5322 {
+
+ public static final String REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$";
+
+}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 68b54916..358b9b3a 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -8,6 +8,10 @@ spring:
hibernate:
ddl-auto: validate
open-in-view: false
+ servlet:
+ multipart:
+ max-file-size: 10MB
+ maxRequestSize: 10MB
minio:
url: ${MINIO_URL}
bucket: ${MINIO_BUCKET}
diff --git a/src/test/java/com/backendoori/ootw/avatar/controller/AvatarItemControllerTest.java b/src/test/java/com/backendoori/ootw/avatar/controller/AvatarItemControllerTest.java
index d1702f51..f24167e1 100644
--- a/src/test/java/com/backendoori/ootw/avatar/controller/AvatarItemControllerTest.java
+++ b/src/test/java/com/backendoori/ootw/avatar/controller/AvatarItemControllerTest.java
@@ -3,11 +3,16 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.nio.charset.StandardCharsets;
import com.backendoori.ootw.avatar.dto.AvatarItemRequest;
import com.backendoori.ootw.avatar.service.AvatarItemService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
@@ -17,6 +22,7 @@
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
+@WithMockUser
@AutoConfigureMockMvc
@SpringBootTest
class AvatarItemControllerTest {
@@ -31,23 +37,86 @@ class AvatarItemControllerTest {
ObjectMapper objectMapper;
@Test
- @WithMockUser
- @DisplayName("아바타 이미지 업로드 api 테스트")
+ @DisplayName("아바타 이미지를 정상적으로 등록한다.")
public void imageUploadTest() throws Exception {
//given
MockMultipartFile file = new MockMultipartFile("file", "filename.txt",
- "text/plain", "some xml".getBytes());
- AvatarItemRequest request = new AvatarItemRequest("HAIR", true);
- String requestJson = objectMapper.writeValueAsString(request);
+ "image/jpeg", "some xml".getBytes());
+ AvatarItemRequest requestDto = new AvatarItemRequest("HAIR", true);
+ MockMultipartFile request = new MockMultipartFile("request", "filename.txt",
+ "application/json", objectMapper.writeValueAsBytes(requestDto));
//when, then
- mockMvc.perform(multipart("/api/v1/image")
+ mockMvc.perform(multipart("/api/v1/avatar-items")
.file(file)
- .contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
- .content(requestJson)
- .contentType(MediaType.APPLICATION_JSON)
- .characterEncoding("utf-8"))
+ .file(request)
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .accept(MediaType.APPLICATION_JSON)
+ .characterEncoding(StandardCharsets.UTF_8))
.andExpect(status().isCreated());
}
+ @ParameterizedTest(name = "[{index}] content-type 이 {0}인 경우")
+ @ValueSource(strings = {"text/plain", "application/json"})
+ @DisplayName("아바타 이미지 업로드 시 파일의 유형이 이미지가 아닌 경우 예외가 발생한다.")
+ public void imageUpLoadWithInvalidContentType(String contentType) throws Exception {
+ //given
+ MockMultipartFile file = new MockMultipartFile("file", "filename.txt",
+ contentType, "some xml".getBytes());
+ AvatarItemRequest dto = new AvatarItemRequest("HAIR", true);
+ MockMultipartFile requestDto = new MockMultipartFile("request", "filename.json",
+ MediaType.APPLICATION_JSON_VALUE, objectMapper.writeValueAsBytes(dto));
+
+ //when, then
+ mockMvc.perform(multipart("/api/v1/avatar-items")
+ .file(file)
+ .file(requestDto)
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .accept(MediaType.APPLICATION_JSON)
+ .characterEncoding(StandardCharsets.UTF_8))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ @DisplayName("아바타 이미지 업로드 시 이미지 파일이 없는 경우 예외가 발생한다.")
+ public void noImageUpLoad() throws Exception {
+ //given
+ MockMultipartFile file = new MockMultipartFile("file", "", "image/png", new byte[0]);
+ AvatarItemRequest dto = new AvatarItemRequest("HAIR", true);
+ MockMultipartFile requestDto = new MockMultipartFile("request", "filename.json",
+ MediaType.APPLICATION_JSON_VALUE, objectMapper.writeValueAsBytes(dto));
+
+
+ //when, then
+ mockMvc.perform(multipart("/api/v1/avatar-items")
+ .file(file)
+ .file(requestDto)
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .accept(MediaType.APPLICATION_JSON)
+ .characterEncoding(StandardCharsets.UTF_8))
+ .andExpect(status().isBadRequest());
+ }
+
+ @ParameterizedTest(name = "[{index}] item type으로 {0}가 들어오는 경우")
+ @ValueSource(strings = {"afsee", "hair"})
+ @NullAndEmptySource
+ @DisplayName("아바타 이미지 업로드 시 아이템 타입이 존재하지 않는 경우 예외가 발생한다.")
+ public void UpLoadWithInvalidRequest(String type) throws Exception {
+ //given
+ MockMultipartFile file = new MockMultipartFile("file", "filename.txt",
+ "image/jpeg", "some xml".getBytes());
+ AvatarItemRequest requestDto = new AvatarItemRequest(type, true);
+ MockMultipartFile request = new MockMultipartFile("request", "filename.txt",
+ "application/json", objectMapper.writeValueAsBytes(requestDto));
+
+
+ //when, then
+ mockMvc.perform(multipart("/api/v1/avatar-items")
+ .file(file)
+ .file(request)
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .accept(MediaType.APPLICATION_JSON)
+ .characterEncoding(StandardCharsets.UTF_8))
+ .andExpect(status().isBadRequest());
+ }
}
diff --git a/src/test/java/com/backendoori/ootw/avatar/domain/AvatarItemTest.java b/src/test/java/com/backendoori/ootw/avatar/domain/AvatarItemTest.java
index 9a262312..b282fe2a 100644
--- a/src/test/java/com/backendoori/ootw/avatar/domain/AvatarItemTest.java
+++ b/src/test/java/com/backendoori/ootw/avatar/domain/AvatarItemTest.java
@@ -20,7 +20,7 @@ public void createTest() throws Exception {
//then
- assertThat(request.type()).isEqualTo(avatarItem.getType().name());
+ assertThat(request.type()).isEqualTo(avatarItem.getItemType().name());
assertThat(request.sex()).isEqualTo(avatarItem.isSex());
}
diff --git a/src/test/java/com/backendoori/ootw/common/image/MiniOImageServiceImplTest.java b/src/test/java/com/backendoori/ootw/common/image/MiniOImageServiceImplTest.java
index 7ce4a909..7eba159f 100644
--- a/src/test/java/com/backendoori/ootw/common/image/MiniOImageServiceImplTest.java
+++ b/src/test/java/com/backendoori/ootw/common/image/MiniOImageServiceImplTest.java
@@ -2,7 +2,6 @@
import static org.assertj.core.api.Assertions.assertThatCode;
-import com.backendoori.ootw.common.image.ImageService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/test/java/com/backendoori/ootw/post/controller/PostControllerTest.java b/src/test/java/com/backendoori/ootw/post/controller/PostControllerTest.java
index 6e3c236a..f82979a1 100644
--- a/src/test/java/com/backendoori/ootw/post/controller/PostControllerTest.java
+++ b/src/test/java/com/backendoori/ootw/post/controller/PostControllerTest.java
@@ -3,15 +3,15 @@
import static com.backendoori.ootw.security.jwt.JwtAuthenticationFilter.TOKEN_HEADER;
import static com.backendoori.ootw.security.jwt.JwtAuthenticationFilter.TOKEN_PREFIX;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.nio.charset.StandardCharsets;
import java.util.List;
-import com.backendoori.ootw.exception.ExceptionResponse;
-import com.backendoori.ootw.exception.ExceptionResponse.FieldErrorDetail;
import com.backendoori.ootw.post.dto.PostReadResponse;
import com.backendoori.ootw.post.dto.PostSaveRequest;
import com.backendoori.ootw.post.dto.PostSaveResponse;
@@ -173,16 +173,10 @@ void saveFailByMethodArgumentNotValidException() throws Exception {
.accept(MediaType.APPLICATION_JSON);
// then
- String response = mockMvc.perform(requestBuilder)
+ mockMvc.perform(requestBuilder)
.andExpect(status().isBadRequest())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andReturn()
- .getResponse()
- .getContentAsString(StandardCharsets.UTF_8);
-
- ExceptionResponse> exceptionResponse =
- objectMapper.readValue(response, ExceptionResponse.class);
- assertThat(exceptionResponse.error()).hasSize(1);
+ .andExpect(jsonPath("$.message", instanceOf(String.class)))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
@Test
diff --git a/src/test/java/com/backendoori/ootw/post/service/PostServiceTest.java b/src/test/java/com/backendoori/ootw/post/service/PostServiceTest.java
index 46471aa3..d81912bc 100644
--- a/src/test/java/com/backendoori/ootw/post/service/PostServiceTest.java
+++ b/src/test/java/com/backendoori/ootw/post/service/PostServiceTest.java
@@ -109,9 +109,6 @@ void saveFailUserNotFound() {
// given
setAuthentication(user.getId() + 1);
- System.out.println(user.getId() + 1);
- System.out.println(userRepository.findAll().stream().map(User::getId).toList());
-
WeatherDto weatherDto =
new WeatherDto(0.0, -10.0, 10.0, 1, 1);
PostSaveRequest postSaveRequest =
diff --git a/src/test/java/com/backendoori/ootw/user/controller/UserControllerTest.java b/src/test/java/com/backendoori/ootw/user/controller/UserControllerTest.java
index a7e42092..3280063e 100644
--- a/src/test/java/com/backendoori/ootw/user/controller/UserControllerTest.java
+++ b/src/test/java/com/backendoori/ootw/user/controller/UserControllerTest.java
@@ -10,20 +10,27 @@
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
-import com.backendoori.ootw.exception.AlreadyExistEmailException;
-import com.backendoori.ootw.exception.IncorrectPasswordException;
+import java.util.stream.Stream;
import com.backendoori.ootw.exception.UserNotFoundException;
import com.backendoori.ootw.security.jwt.TokenProvider;
import com.backendoori.ootw.user.dto.LoginDto;
import com.backendoori.ootw.user.dto.SignupDto;
import com.backendoori.ootw.user.dto.TokenDto;
import com.backendoori.ootw.user.dto.UserDto;
+import com.backendoori.ootw.user.exception.AlreadyExistEmailException;
+import com.backendoori.ootw.user.exception.IncorrectPasswordException;
import com.backendoori.ootw.user.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.datafaker.Faker;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -78,6 +85,68 @@ void created() throws Exception {
.andExpect(jsonPath("$.updatedAt", startsWith(removeMills(userDto.updatedAt()))));
}
+ @DisplayName("잘못된 형식의 email일 경우 400 status를 반환한다")
+ @NullAndEmptySource
+ @ArgumentsSource(InvalidEmailProvider.class)
+ @ParameterizedTest
+ void badRequestInvalidEmail(String email) throws Exception {
+ // given
+ String password = faker.internet().password(8, 30, true, true, true);
+ String nickname = faker.internet().username();
+ SignupDto signupDto = new SignupDto(email, password, nickname);
+
+ // when
+ ResultActions actions = mockMvc.perform(
+ post("/api/v1/auth/signup")
+ .with(csrf())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(signupDto)));
+
+ // then
+ actions.andExpect(status().isBadRequest());
+ }
+
+ @DisplayName("잘못된 형식의 비밀번호의 경우 400 status를 반환한다")
+ @NullAndEmptySource
+ @ArgumentsSource(InvalidPasswordProvider.class)
+ @ParameterizedTest
+ void badRequestInvalidPassword(String password) throws Exception {
+ // given
+ String email = faker.internet().emailAddress();
+ String nickname = faker.internet().username();
+ SignupDto signupDto = new SignupDto(email, password, nickname);
+
+ // when
+ ResultActions actions = mockMvc.perform(
+ post("/api/v1/auth/signup")
+ .with(csrf())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(signupDto)));
+
+ // then
+ actions.andExpect(status().isBadRequest());
+ }
+
+ @DisplayName("닉네임이 공백일 경우 400 status를 반환한다")
+ @NullAndEmptySource
+ @ParameterizedTest
+ void badRequestBlankNickname(String nickname) throws Exception {
+ // given
+ String email = faker.internet().emailAddress();
+ String password = faker.internet().password(8, 30, true, true, true);
+ SignupDto signupDto = new SignupDto(email, password, nickname);
+
+ // when
+ ResultActions actions = mockMvc.perform(
+ post("/api/v1/auth/signup")
+ .with(csrf())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(signupDto)));
+
+ // then
+ actions.andExpect(status().isBadRequest());
+ }
+
@DisplayName("이미 등록된 email일 경우 409 status를 반환한다")
@Test
void unauthorizedAlreadyExistEmail() throws Exception {
@@ -124,6 +193,26 @@ void created() throws Exception {
.andExpect(jsonPath("$.token", is(tokenDto.token())));
}
+ @DisplayName("잘못된 형식의 email일 경우 400 status를 반환한다")
+ @NullAndEmptySource
+ @ArgumentsSource(InvalidEmailProvider.class)
+ @ParameterizedTest
+ void badRequestInvalidEmail(String email) throws Exception {
+ // given
+ String password = faker.internet().password(8, 30, true, true, true);
+ LoginDto loginDto = new LoginDto(email, password);
+
+ // when
+ ResultActions actions = mockMvc.perform(
+ post("/api/v1/auth/login")
+ .with(csrf())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(loginDto)));
+
+ // then
+ actions.andExpect(status().isBadRequest());
+ }
+
@DisplayName("email이 일치하는 사용자가 없으면 404 status를 반환한다")
@Test
void unauthorizedNotExistUser() throws Exception {
@@ -143,6 +232,26 @@ void unauthorizedNotExistUser() throws Exception {
actions.andExpect(status().isNotFound());
}
+ @DisplayName("잘못된 형식의 비밀번호의 경우 400 status를 반환한다")
+ @NullAndEmptySource
+ @ArgumentsSource(InvalidPasswordProvider.class)
+ @ParameterizedTest
+ void badRequestInvalidPassword(String password) throws Exception {
+ // given
+ String email = faker.internet().emailAddress();
+ LoginDto loginDto = new LoginDto(email, password);
+
+ // when
+ ResultActions actions = mockMvc.perform(
+ post("/api/v1/auth/login")
+ .with(csrf())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(loginDto)));
+
+ // then
+ actions.andExpect(status().isBadRequest());
+ }
+
@DisplayName("비밀번호가 일치하지 않으면 401 status를 반환한다")
@Test
void unauthorizedIncorrectPassword() throws Exception {
@@ -166,11 +275,10 @@ void unauthorizedIncorrectPassword() throws Exception {
private SignupDto generateSignupDto() {
String email = faker.internet().emailAddress();
- String password = faker.internet().password();
+ String password = faker.internet().password(8, 30, true, true, true);
String nickname = faker.internet().username();
- String image = faker.internet().url();
- return new SignupDto(email, password, nickname, image);
+ return new SignupDto(email, password, nickname);
}
private UserDto createUser(SignupDto signupDto) {
@@ -187,7 +295,7 @@ private UserDto createUser(SignupDto signupDto) {
private LoginDto generateLoginDto() {
String email = faker.internet().emailAddress();
- String password = faker.internet().password();
+ String password = faker.internet().password(8, 30, true, true, true);
return new LoginDto(email, password);
}
@@ -196,4 +304,34 @@ private String removeMills(LocalDateTime localDateTime) {
return localDateTime.truncatedTo(ChronoUnit.SECONDS).toString();
}
+ static class InvalidEmailProvider implements ArgumentsProvider {
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
+ return Stream.of(
+ Arguments.of(faker.app().name()),
+ Arguments.of(faker.name().fullName()),
+ Arguments.of(faker.internet().url()),
+ Arguments.of(faker.internet().domainName()),
+ Arguments.of(faker.internet().webdomain()),
+ Arguments.of(faker.internet().botUserAgentAny())
+ );
+ }
+
+ }
+
+ static class InvalidPasswordProvider implements ArgumentsProvider {
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
+ return Stream.of(
+ Arguments.of(faker.internet().password(1, 7, true, true, true)),
+ Arguments.of(faker.internet().password(31, 50, true, true, true)),
+ Arguments.of(faker.internet().password(8, 30, true, false, true)),
+ Arguments.of(faker.internet().password(8, 30, true, true, false))
+ );
+ }
+
+ }
+
}
diff --git a/src/test/java/com/backendoori/ootw/user/domain/UserTest.java b/src/test/java/com/backendoori/ootw/user/domain/UserTest.java
new file mode 100644
index 00000000..3f1d58d4
--- /dev/null
+++ b/src/test/java/com/backendoori/ootw/user/domain/UserTest.java
@@ -0,0 +1,118 @@
+package com.backendoori.ootw.user.domain;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+import java.util.stream.Stream;
+import com.backendoori.ootw.user.validation.Message;
+import net.datafaker.Faker;
+import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
+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.MethodSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+
+class UserTest {
+
+ static final Faker faker = new Faker();
+
+ Long id;
+ String email;
+ String password;
+ String nickname;
+ String image;
+
+ @BeforeEach
+ void setup() {
+ id = (long) faker.number().positive();
+ email = faker.internet().emailAddress();
+ password = faker.internet().password();
+ nickname = faker.internet().username();
+ image = faker.internet().url();
+ }
+
+ @DisplayName("instance 생성에 성공한다.")
+ @Test
+ void testCreate() {
+ // given
+
+ // when
+ ThrowingCallable createUser = this::buildUser;
+
+ // then
+ assertThatNoException().isThrownBy(createUser);
+ }
+
+ @DisplayName("잘못된 형식의 이메일인 경우 생성에 실패한다.")
+ @NullAndEmptySource
+ @MethodSource("generateInvalidEmails")
+ @ParameterizedTest()
+ void testCreateInvalidEmail(String email) {
+ // given
+ this.email = email;
+
+ // when
+ ThrowingCallable createUser = this::buildUser;
+
+ // then
+ assertThatIllegalArgumentException()
+ .isThrownBy(createUser)
+ .withMessage(Message.INVALID_EMAIL);
+ }
+
+ @DisplayName("비밀번호가 공백인 경우 생성에 실패한다.")
+ @NullAndEmptySource
+ @ParameterizedTest
+ void testCreateBlankPassword(String password) {
+ // given
+ this.password = password;
+
+ // when
+ ThrowingCallable createUser = this::buildUser;
+
+ // then
+ assertThatIllegalArgumentException()
+ .isThrownBy(createUser)
+ .withMessage(Message.BLANK_PASSWORD);
+ }
+
+ @DisplayName("닉네임이 공백인 경우 생성에 실패한다.")
+ @NullAndEmptySource
+ @ParameterizedTest
+ void testCreateBlankNickName(String nickname) {
+ // given
+ this.nickname = nickname;
+
+ // when
+ ThrowingCallable createUser = this::buildUser;
+
+ // then
+ assertThatIllegalArgumentException()
+ .isThrownBy(createUser)
+ .withMessage(Message.BLANK_NICKNAME);
+ }
+
+ private static Stream generateInvalidEmails() {
+ return Stream.of(
+ faker.app().name(),
+ faker.name().fullName(),
+ faker.internet().url(),
+ faker.internet().domainName(),
+ faker.internet().webdomain(),
+ faker.internet().botUserAgentAny()
+ );
+ }
+
+ private User buildUser() {
+ return User.builder()
+ .id(id)
+ .email(email)
+ .password(password)
+ .nickname(nickname)
+ .image(image)
+ .build();
+ }
+
+}
diff --git a/src/test/java/com/backendoori/ootw/user/service/UserServiceTest.java b/src/test/java/com/backendoori/ootw/user/service/UserServiceTest.java
index 7f36f59f..401f3c7b 100644
--- a/src/test/java/com/backendoori/ootw/user/service/UserServiceTest.java
+++ b/src/test/java/com/backendoori/ootw/user/service/UserServiceTest.java
@@ -1,17 +1,21 @@
package com.backendoori.ootw.user.service;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
-import com.backendoori.ootw.exception.AlreadyExistEmailException;
-import com.backendoori.ootw.exception.IncorrectPasswordException;
+import java.util.stream.Stream;
import com.backendoori.ootw.exception.UserNotFoundException;
import com.backendoori.ootw.user.domain.User;
import com.backendoori.ootw.user.dto.LoginDto;
import com.backendoori.ootw.user.dto.SignupDto;
import com.backendoori.ootw.user.dto.TokenDto;
+import com.backendoori.ootw.user.exception.AlreadyExistEmailException;
+import com.backendoori.ootw.user.exception.IncorrectPasswordException;
import com.backendoori.ootw.user.repository.UserRepository;
+import com.backendoori.ootw.user.validation.Message;
import net.datafaker.Faker;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.AfterEach;
@@ -21,6 +25,10 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -73,10 +81,37 @@ void failAlreadyExistUser() {
ThrowingCallable signup = () -> userService.signup(signupDto);
// then
- assertThatExceptionOfType(AlreadyExistEmailException.class).isThrownBy(signup)
+ assertThatExceptionOfType(AlreadyExistEmailException.class)
+ .isThrownBy(signup)
.withMessage(AlreadyExistEmailException.DEFAULT_MESSAGE);
}
+ @DisplayName("비밀번호 형식이 올바르지 않을 경우 회원가입에 실패한다")
+ @NullAndEmptySource
+ @MethodSource("generateInvalidPasswords")
+ @ParameterizedTest
+ void failInvalidPassword(String password) {
+ // given
+ SignupDto signupDto = generateSignupDto(password);
+
+ // when
+ ThrowingCallable signup = () -> userService.signup(signupDto);
+
+ // then
+ assertThatIllegalArgumentException()
+ .isThrownBy(signup)
+ .withMessage(Message.INVALID_PASSWORD);
+ }
+
+ private static Stream generateInvalidPasswords() {
+ return Stream.of(
+ faker.internet().password(1, 7, true, true, true),
+ faker.internet().password(31, 50, true, true, true),
+ faker.internet().password(8, 30, true, false, true),
+ faker.internet().password(8, 30, true, true, false)
+ );
+ }
+
}
@DisplayName("로그인 테스트")
@@ -95,14 +130,15 @@ void success() {
TokenDto tokenDto = userService.login(loginDto);
// then
- assertThat(tokenDto.token()).isInstanceOf(String.class)
+ assertThat(tokenDto.token())
+ .isInstanceOf(String.class)
.isNotNull()
.isNotBlank();
}
@DisplayName("email이 일치하는 사용자가 없으면 로그인에 실패한다")
@Test
- void failNotExistUser() {
+ void failUserNotFound() {
// given
String password = faker.internet().password();
User user = generateUser(password);
@@ -112,7 +148,8 @@ void failNotExistUser() {
ThrowingCallable login = () -> userService.login(loginDto);
// then
- assertThatExceptionOfType(UserNotFoundException.class).isThrownBy(login)
+ assertThatExceptionOfType(UserNotFoundException.class)
+ .isThrownBy(login)
.withMessage(UserNotFoundException.DEFAULT_MESSAGE);
}
@@ -128,19 +165,22 @@ void failIncorrectPassword() {
ThrowingCallable login = () -> userService.login(loginDto);
// then
- assertThatExceptionOfType(IncorrectPasswordException.class).isThrownBy(login)
+ assertThatExceptionOfType(IncorrectPasswordException.class)
+ .isThrownBy(login)
.withMessage(IncorrectPasswordException.DEFAULT_MESSAGE);
}
}
private SignupDto generateSignupDto() {
+ return generateSignupDto(faker.internet().password(8, 30, true, true, true));
+ }
+
+ private SignupDto generateSignupDto(String password) {
String email = faker.internet().emailAddress();
- String password = faker.internet().password();
String nickname = faker.internet().username();
- String image = faker.internet().url();
- return new SignupDto(email, password, nickname, image);
+ return new SignupDto(email, password, nickname);
}
private User generateUser(String password) {