diff --git a/src/main/java/com/livable/server/invitation/domain/InvitationValidationMessage.java b/src/main/java/com/livable/server/invitation/domain/InvitationValidationMessage.java index e463a50a..05d62115 100644 --- a/src/main/java/com/livable/server/invitation/domain/InvitationValidationMessage.java +++ b/src/main/java/com/livable/server/invitation/domain/InvitationValidationMessage.java @@ -4,4 +4,8 @@ public interface InvitationValidationMessage { String REQUIRED_FUTURE_DATE = "선택된 시간이 현재보다 과거일 수 없습니다."; String REQUIRED_VISITOR_COUNT = "방문자는 최소 1명 이상 30명 이하여야 합니다. (면접은 1명)"; String NOT_NULL = "값이 Null 일수 없습니다."; + String VISITOR_NAME_MIN_SIZE = "방문자 이름은 2글자 이상이어야 합니다."; + String VISITOR_NAME_FORMAT = "방문자 이름은 한글만 입력해야 합니다."; + String VISITOR_CONTACT_MIN_SIZE = "방문자 전화번호는 10글자 이상이어야 합니다."; + String VISITOR_CONTACT_FORMAT = "방문자 전화번호는 숫자만 입력해야 합니다."; } diff --git a/src/main/java/com/livable/server/invitation/dto/InvitationRequest.java b/src/main/java/com/livable/server/invitation/dto/InvitationRequest.java index 064812af..c32dec98 100644 --- a/src/main/java/com/livable/server/invitation/dto/InvitationRequest.java +++ b/src/main/java/com/livable/server/invitation/dto/InvitationRequest.java @@ -12,6 +12,7 @@ import javax.validation.Valid; import javax.validation.constraints.FutureOrPresent; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.time.LocalDateTime; import java.util.List; @@ -45,6 +46,7 @@ public static class CreateDTO { @FutureOrPresent(message = REQUIRED_FUTURE_DATE) private LocalDateTime endDate; + @Valid @Size(min = 1, max = 30, message = REQUIRED_VISITOR_COUNT) private List visitors; @@ -67,10 +69,15 @@ public Invitation toEntity(Member member) { @NoArgsConstructor @AllArgsConstructor public static class VisitorCreateDTO { - @NotNull + + @Pattern(regexp = "^[가-힣]*$", message = VISITOR_NAME_FORMAT) + @Size(min = 2, message = VISITOR_NAME_MIN_SIZE) + @NotNull(message = NOT_NULL) private String name; - @NotNull + @Pattern(regexp = "^[0-9]*$", message = VISITOR_CONTACT_FORMAT) + @Size(min = 10, message = VISITOR_CONTACT_MIN_SIZE) + @NotNull(message = NOT_NULL) private String contact; public Visitor toEntity(Invitation invitation) { @@ -109,9 +116,14 @@ public static class UpdateDTO { @NoArgsConstructor @AllArgsConstructor public static class VisitorForUpdateDTO { + + @Pattern(regexp = "^[가-힣]*$", message = VISITOR_NAME_FORMAT) + @Size(min = 2, message = VISITOR_NAME_MIN_SIZE) @NotNull(message = NOT_NULL) private String name; + @Pattern(regexp = "^[0-9]*$", message = VISITOR_CONTACT_FORMAT) + @Size(min = 10, message = VISITOR_CONTACT_MIN_SIZE) @NotNull(message = NOT_NULL) private String contact; diff --git a/src/test/java/com/livable/server/invitation/controller/InvitationControllerTest.java b/src/test/java/com/livable/server/invitation/controller/InvitationControllerTest.java index f0384b3e..a5a7f213 100644 --- a/src/test/java/com/livable/server/invitation/controller/InvitationControllerTest.java +++ b/src/test/java/com/livable/server/invitation/controller/InvitationControllerTest.java @@ -23,7 +23,9 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -122,8 +124,8 @@ void createInvitationFail_01() throws Exception { InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() .purpose("interview") .officeName("공용 라운지") - .startDate(LocalDateTime.of(2023, 9, 18, 10, 0, 0)) - .endDate(LocalDateTime.of(2030, 10, 30, 10, 30, 0)) + .startDate(LocalDateTime.of(LocalDate.now().minusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) .description("엘리베이터 앞에 있어요.") .commonPlaceId(1L) .visitors(List.of( @@ -154,8 +156,8 @@ void createInvitationFail_02() throws Exception { InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() .purpose("interview") .officeName("공용 라운지") - .startDate(LocalDateTime.of(2030, 10, 30, 10, 0, 0)) - .endDate(LocalDateTime.of(2030, 10, 30, 10, 30, 0)) + .startDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) .description("엘리베이터 앞에 있어요.") .commonPlaceId(1L) .visitors(List.of()) @@ -180,14 +182,14 @@ void createInvitationFail_03() throws Exception { // Given List visitors = new ArrayList<>(); for (int i = 0; i < 31; i++) { - visitors.add(InvitationRequest.VisitorCreateDTO.builder().build()); + visitors.add(InvitationRequest.VisitorCreateDTO.builder().name("홍길동").contact("01012341234").build()); } InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() .purpose("interview") .officeName("공용 라운지") - .startDate(LocalDateTime.of(2030, 10, 30, 10, 0, 0)) - .endDate(LocalDateTime.of(2030, 10, 30, 10, 30, 0)) + .startDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) .description("엘리베이터 앞에 있어요.") .commonPlaceId(1L) .visitors(visitors) @@ -206,6 +208,126 @@ void createInvitationFail_03() throws Exception { .andExpect(jsonPath("$.message").value(InvitationValidationMessage.REQUIRED_VISITOR_COUNT)); } + @DisplayName("[실패] 초대장 저장 - 유효성 검사 실패 (방문자 이름이 영어인 경우)") + @Test + void createInvitationFail_04() throws Exception { + // Given + List visitors = new ArrayList<>(); + visitors.add(InvitationRequest.VisitorCreateDTO.builder().name("testName").contact("01012341234").build()); + + InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() + .purpose("interview") + .officeName("공용 라운지") + .startDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) + .description("엘리베이터 앞에 있어요.") + .commonPlaceId(1L) + .visitors(visitors) + .build(); + + String token = tokenProvider.createActorToken(ActorType.MEMBER, 1L, new Date(new Date().getTime() + 1000000)); + + // When & Then + mockMvc.perform( + post("/api/invitation") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto)) + ) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value(InvitationValidationMessage.VISITOR_NAME_FORMAT)); + } + + @DisplayName("[실패] 초대장 저장 - 유효성 검사 실패 (방문자 이름이 한 글자인 경우)") + @Test + void createInvitationFail_05() throws Exception { + // Given + List visitors = new ArrayList<>(); + visitors.add(InvitationRequest.VisitorCreateDTO.builder().name("김").contact("01012341234").build()); + + InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() + .purpose("interview") + .officeName("공용 라운지") + .startDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) + .description("엘리베이터 앞에 있어요.") + .commonPlaceId(1L) + .visitors(visitors) + .build(); + + String token = tokenProvider.createActorToken(ActorType.MEMBER, 1L, new Date(new Date().getTime() + 1000000)); + + // When & Then + mockMvc.perform( + post("/api/invitation") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto)) + ) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value(InvitationValidationMessage.VISITOR_NAME_MIN_SIZE)); + } + + @DisplayName("[실패] 초대장 저장 - 유효성 검사 실패 (방문자 전화번호에 숫자 이외의 문자가 섞인 경우)") + @Test + void createInvitationFail_06() throws Exception { + // Given + List visitors = new ArrayList<>(); + visitors.add(InvitationRequest.VisitorCreateDTO.builder().name("홍길동").contact("01012341234as").build()); + + InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() + .purpose("interview") + .officeName("공용 라운지") + .startDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) + .description("엘리베이터 앞에 있어요.") + .commonPlaceId(1L) + .visitors(visitors) + .build(); + + String token = tokenProvider.createActorToken(ActorType.MEMBER, 1L, new Date(new Date().getTime() + 1000000)); + + // When & Then + mockMvc.perform( + post("/api/invitation") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto)) + ) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value(InvitationValidationMessage.VISITOR_CONTACT_FORMAT)); + } + + @DisplayName("[실패] 초대장 저장 - 유효성 검사 실패 (방문자 전화번호 길이가 9자인 경우)") + @Test + void createInvitationFail_07() throws Exception { + // Given + List visitors = new ArrayList<>(); + visitors.add(InvitationRequest.VisitorCreateDTO.builder().name("홍길동").contact("010123412").build()); + + InvitationRequest.CreateDTO dto = InvitationRequest.CreateDTO.builder() + .purpose("interview") + .officeName("공용 라운지") + .startDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 0, 0))) + .endDate(LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.of(10, 30, 0))) + .description("엘리베이터 앞에 있어요.") + .commonPlaceId(1L) + .visitors(visitors) + .build(); + + String token = tokenProvider.createActorToken(ActorType.MEMBER, 1L, new Date(new Date().getTime() + 1000000)); + + // When & Then + mockMvc.perform( + post("/api/invitation") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto)) + ) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value(InvitationValidationMessage.VISITOR_CONTACT_MIN_SIZE)); + } + @DisplayName("[실패] 초대장 상세 조회 - 초대장 주인과 요청한 사람이 다른 경우") @Test void getInvitationFail_01() throws Exception {