diff --git a/src/main/java/com/somemore/imageupload/converter/MultipartJackson2HttpMessageConverter.java b/src/main/java/com/somemore/imageupload/converter/MultipartJackson2HttpMessageConverter.java new file mode 100644 index 000000000..6f403dd73 --- /dev/null +++ b/src/main/java/com/somemore/imageupload/converter/MultipartJackson2HttpMessageConverter.java @@ -0,0 +1,33 @@ +package com.somemore.imageupload.converter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.lang.reflect.Type; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +@Component +public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { + + /** + * Converter for support http request with header Content-Type: multipart/form-data + */ + public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} diff --git a/src/main/java/com/somemore/imageupload/service/ImageUploadService.java b/src/main/java/com/somemore/imageupload/service/ImageUploadService.java index 1542ac30a..f4ba47882 100644 --- a/src/main/java/com/somemore/imageupload/service/ImageUploadService.java +++ b/src/main/java/com/somemore/imageupload/service/ImageUploadService.java @@ -5,6 +5,7 @@ import com.somemore.imageupload.usecase.ImageUploadUseCase; import com.somemore.imageupload.util.ImageUploadUtils; import com.somemore.imageupload.validator.ImageUploadValidator; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -30,8 +31,22 @@ public class ImageUploadService implements ImageUploadUseCase { @Value("${cloud.aws.s3.base-url}") private String baseUrl; + @Value("${default.image.url}") + private String defaultImageUrl; + + public static String DEFAULT_IMAGE_URL; + + @PostConstruct + private void init() { + DEFAULT_IMAGE_URL = defaultImageUrl; + } + @Override public String uploadImage(ImageUploadRequestDto requestDto) { + if (imageUploadValidator.isEmptyFile(requestDto.imageFile())) { + return DEFAULT_IMAGE_URL; + } + imageUploadValidator.validateFileSize(requestDto.imageFile()); imageUploadValidator.validateFileType(requestDto.imageFile()); diff --git a/src/main/java/com/somemore/imageupload/validator/DefaultImageUploadValidator.java b/src/main/java/com/somemore/imageupload/validator/DefaultImageUploadValidator.java index 865dd2844..75c610838 100644 --- a/src/main/java/com/somemore/imageupload/validator/DefaultImageUploadValidator.java +++ b/src/main/java/com/somemore/imageupload/validator/DefaultImageUploadValidator.java @@ -12,10 +12,6 @@ public class DefaultImageUploadValidator implements ImageUploadValidator { private static final long MAX_FILE_SIZE = 8L * 1024 * 1024; // 8MB public void validateFileSize(MultipartFile file) { - if (file == null || file.isEmpty()) { - throw new ImageUploadException(EMPTY_FILE.getMessage()); - } - if (file.getSize() > MAX_FILE_SIZE) { throw new ImageUploadException(FILE_SIZE_EXCEEDED.getMessage()); } @@ -28,6 +24,11 @@ public void validateFileType(MultipartFile file) { } } + @Override + public boolean isEmptyFile(MultipartFile file) { + return file == null || file.isEmpty(); + } + private boolean isAllowedImageType(String contentType) { return contentType != null && ( contentType.equals("image/jpeg") || diff --git a/src/main/java/com/somemore/imageupload/validator/ImageUploadValidator.java b/src/main/java/com/somemore/imageupload/validator/ImageUploadValidator.java index 2481c5d48..1d97dcbf1 100644 --- a/src/main/java/com/somemore/imageupload/validator/ImageUploadValidator.java +++ b/src/main/java/com/somemore/imageupload/validator/ImageUploadValidator.java @@ -6,4 +6,5 @@ public interface ImageUploadValidator { void validateFileSize(MultipartFile file); void validateFileType(MultipartFile file); + boolean isEmptyFile(MultipartFile file); } diff --git a/src/main/java/com/somemore/recruitboard/controller/RecruitBoardCommandApiController.java b/src/main/java/com/somemore/recruitboard/controller/RecruitBoardCommandApiController.java new file mode 100644 index 000000000..6d55d5ff2 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/controller/RecruitBoardCommandApiController.java @@ -0,0 +1,123 @@ +package com.somemore.recruitboard.controller; + + +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + +import com.somemore.global.common.response.ApiResponse; +import com.somemore.imageupload.dto.ImageUploadRequestDto; +import com.somemore.imageupload.usecase.ImageUploadUseCase; +import com.somemore.recruitboard.dto.request.RecruitBoardCreateRequestDto; +import com.somemore.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto; +import com.somemore.recruitboard.dto.request.RecruitBoardStatusUpdateRequestDto; +import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto; +import com.somemore.recruitboard.usecase.command.CreateRecruitBoardUseCase; +import com.somemore.recruitboard.usecase.command.DeleteRecruitBoardUseCase; +import com.somemore.recruitboard.usecase.command.UpdateRecruitBoardUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +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; + +@Tag(name = "Recruit Board Command API", description = "봉사 활동 모집글 생성 수정 삭제 API") +@RequiredArgsConstructor +@RequestMapping("/api") +@RestController +public class RecruitBoardCommandApiController { + + private final CreateRecruitBoardUseCase createRecruitBoardUseCase; + private final UpdateRecruitBoardUseCase updateRecruitBoardUseCase; + private final DeleteRecruitBoardUseCase deleteRecruitBoardUseCase; + private final ImageUploadUseCase imageUploadUseCase; + + @Secured("ROLE_CENTER") + @Operation(summary = "봉사 활동 모집글 등록", description = "봉사 활동 모집글을 등록합니다.") + @PostMapping(value = "/recruit-board", consumes = MULTIPART_FORM_DATA_VALUE) + public ApiResponse createRecruitBoard( + @AuthenticationPrincipal String userId, + @Valid @RequestPart("data") RecruitBoardCreateRequestDto requestDto, + @RequestPart(value = "img_file", required = false) MultipartFile image + ) { + + String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); + return ApiResponse.ok( + 201, + createRecruitBoardUseCase.createRecruitBoard(requestDto, getCenterId(userId), + imgUrl), + "봉사 활동 모집글 등록 성공" + ); + } + + @Secured("ROLE_CENTER") + @Operation(summary = "봉사 활동 모집글 수정", description = "봉사 활동 모집글을 수정합니다.") + @PutMapping(value = "/recruit-board/{id}", consumes = MULTIPART_FORM_DATA_VALUE) + public ApiResponse updateRecruitBoard( + @AuthenticationPrincipal String userId, + @PathVariable Long id, + @Valid @RequestPart("data") RecruitBoardUpdateRequestDto requestDto, + @RequestPart("img_file") MultipartFile image + ) { + String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); + updateRecruitBoardUseCase.updateRecruitBoard(requestDto, id, getCenterId(userId), imgUrl); + + return ApiResponse.ok("봉사 활동 모집글 수정 성공"); + } + + @Secured("ROLE_CENTER") + @Operation(summary = "봉사 활동 모집글 위치 수정", description = "봉사 활동 모집글의 위치를 수정합니다.") + @PutMapping(value = "/recruit-board/{id}/location") + public ApiResponse updateRecruitBoardLocation( + @AuthenticationPrincipal String userId, + @PathVariable Long id, + @Valid @RequestBody RecruitBoardLocationUpdateRequestDto requestDto + ) { + + updateRecruitBoardUseCase.updateRecruitBoardLocation(requestDto, id, getCenterId(userId)); + return ApiResponse.ok("봉사 활동 모집글 위치 수정 성공"); + } + + @Secured("ROLE_CENTER") + @Operation(summary = "봉사 활동 모집글 상태 수정", description = "봉사 활동 모집글의 상태를 수정합니다.") + @PatchMapping(value = "/recruit-board/{id}") + public ApiResponse updateRecruitBoardStatus( + @AuthenticationPrincipal String userId, + @PathVariable Long id, + @RequestBody RecruitBoardStatusUpdateRequestDto requestDto + ) { + LocalDateTime now = LocalDateTime.now(); + updateRecruitBoardUseCase.updateRecruitBoardStatus(requestDto.status(), id, + getCenterId(userId), + now); + + return ApiResponse.ok("봉사 활동 모집글 상태 수정 성공"); + } + + @Secured("ROLE_CENTER") + @Operation(summary = "봉사 활동 모집글 삭제", description = "봉사 활동 모집글을 삭제합니다.") + @DeleteMapping(value = "/recruit-board/{id}") + public ApiResponse deleteRecruitBoard( + @AuthenticationPrincipal String userId, + @PathVariable Long id + ) { + deleteRecruitBoardUseCase.deleteRecruitBoard(getCenterId(userId), id); + return ApiResponse.ok("봉사 활동 모집글 삭제 성공"); + } + + private static UUID getCenterId(String userId) { + return UUID.fromString(userId); + } + +} diff --git a/src/main/java/com/somemore/recruitboard/controller/RecruitBoardQueryController.java b/src/main/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiController.java similarity index 99% rename from src/main/java/com/somemore/recruitboard/controller/RecruitBoardQueryController.java rename to src/main/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiController.java index f632e6acd..4ddb6e58d 100644 --- a/src/main/java/com/somemore/recruitboard/controller/RecruitBoardQueryController.java +++ b/src/main/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiController.java @@ -29,7 +29,7 @@ @RequiredArgsConstructor @RequestMapping("/api") @RestController -public class RecruitBoardQueryController { +public class RecruitBoardQueryApiController { private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; diff --git a/src/main/java/com/somemore/recruitboard/dto/request/RecruitBoardStatusUpdateRequestDto.java b/src/main/java/com/somemore/recruitboard/dto/request/RecruitBoardStatusUpdateRequestDto.java new file mode 100644 index 000000000..16fea9fdd --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/dto/request/RecruitBoardStatusUpdateRequestDto.java @@ -0,0 +1,17 @@ +package com.somemore.recruitboard.dto.request; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.somemore.recruitboard.domain.RecruitStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +@JsonNaming(SnakeCaseStrategy.class) +@Schema(description = "봉사 활동 모집글 상태 수정 요청 DTO") +public record RecruitBoardStatusUpdateRequestDto( + @Schema(description = "변경할 봉사 활동 모집글의 상태", example = "CLOSED") + RecruitStatus status +) { + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cdbad3892..b350d50cd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -101,3 +101,7 @@ server: charset: UTF-8 enabled: true force: true + +default: + image: + url: ${DEFAULT_IMG_URL} diff --git a/src/test/java/com/somemore/CustomSecurityContextFactory.java b/src/test/java/com/somemore/CustomSecurityContextFactory.java index 5ec7d1bd6..15c9c3457 100644 --- a/src/test/java/com/somemore/CustomSecurityContextFactory.java +++ b/src/test/java/com/somemore/CustomSecurityContextFactory.java @@ -17,7 +17,7 @@ public SecurityContext createSecurityContext(WithMockCustomUser annotation) { Authentication auth = new UsernamePasswordAuthenticationToken( annotation.username(), "password", - Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + annotation.role())) ); context.setAuthentication(auth); diff --git a/src/test/java/com/somemore/WithMockCustomUser.java b/src/test/java/com/somemore/WithMockCustomUser.java index da5e38c23..1280ddbbd 100644 --- a/src/test/java/com/somemore/WithMockCustomUser.java +++ b/src/test/java/com/somemore/WithMockCustomUser.java @@ -9,4 +9,5 @@ @WithSecurityContext(factory = CustomSecurityContextFactory.class) public @interface WithMockCustomUser { String username() default "123e4567-e89b-12d3-a456-426614174000"; + String role() default "VOLUNTEER"; } diff --git a/src/test/java/com/somemore/imageupload/service/ImageUploadServiceTest.java b/src/test/java/com/somemore/imageupload/service/ImageUploadServiceTest.java index 31e2fbfd6..bdc49a87d 100644 --- a/src/test/java/com/somemore/imageupload/service/ImageUploadServiceTest.java +++ b/src/test/java/com/somemore/imageupload/service/ImageUploadServiceTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.core.sync.RequestBody; @@ -18,7 +19,9 @@ import java.io.IOException; import java.io.InputStream; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.*; class ImageUploadServiceTest extends IntegrationTestSupport { @@ -76,4 +79,19 @@ void testUploadImage_failure() throws IOException { // when, then assertThrows(ImageUploadException.class, () -> imageUploadService.uploadImage(requestDto)); } + + @DisplayName("이미지 파일이 없다면 기본 이미지 링크를 반환한다.") + @Test + void uploadImageWithEmptyFile() { + // given + MultipartFile emptyFile = new MockMultipartFile("file", new byte[0]); + given(imageUploadValidator.isEmptyFile(emptyFile)).willReturn(true); + ImageUploadRequestDto requestDto = new ImageUploadRequestDto(emptyFile); + + // when + String imgUrl = imageUploadService.uploadImage(requestDto); + + // then + assertThat(imgUrl).isEqualTo(ImageUploadService.DEFAULT_IMAGE_URL); + } } diff --git a/src/test/java/com/somemore/imageupload/validator/DefaultImageUploadValidatorTest.java b/src/test/java/com/somemore/imageupload/validator/DefaultImageUploadValidatorTest.java index 03c2d6b0e..3a33e0b4c 100644 --- a/src/test/java/com/somemore/imageupload/validator/DefaultImageUploadValidatorTest.java +++ b/src/test/java/com/somemore/imageupload/validator/DefaultImageUploadValidatorTest.java @@ -7,6 +7,7 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DefaultImageUploadValidatorTest { @@ -20,16 +21,16 @@ void setUp() { } @Test - @DisplayName("파일이 비어있으면 예외가 발생한다.") + @DisplayName("파일이 비어있는지 확인할 수 있다.") void shouldThrowExceptionWhenFileIsEmpty() { //given MultipartFile emptyFile = new MockMultipartFile("file", new byte[0]); //when - Throwable exception = assertThrows(ImageUploadException.class, () -> imageUploadValidator.validateFileSize(emptyFile)); + boolean isEmpty = imageUploadValidator.isEmptyFile(emptyFile); //then - assertEquals(ImageUploadException.class, exception.getClass()); + assertThat(isEmpty).isTrue(); } @Test diff --git a/src/test/java/com/somemore/recruitboard/controller/RecruitBoardCommandApiControllerTest.java b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardCommandApiControllerTest.java new file mode 100644 index 000000000..7d88f64aa --- /dev/null +++ b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardCommandApiControllerTest.java @@ -0,0 +1,261 @@ +package com.somemore.recruitboard.controller; + +import static com.somemore.common.fixture.LocalDateTimeFixture.createStartDateTime; +import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.somemore.ControllerTestSupport; +import com.somemore.WithMockCustomUser; +import com.somemore.imageupload.usecase.ImageUploadUseCase; +import com.somemore.location.dto.request.LocationCreateRequestDto; +import com.somemore.recruitboard.domain.RecruitStatus; +import com.somemore.recruitboard.domain.VolunteerType; +import com.somemore.recruitboard.dto.request.RecruitBoardCreateRequestDto; +import com.somemore.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto; +import com.somemore.recruitboard.dto.request.RecruitBoardStatusUpdateRequestDto; +import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto; +import com.somemore.recruitboard.usecase.command.CreateRecruitBoardUseCase; +import com.somemore.recruitboard.usecase.command.DeleteRecruitBoardUseCase; +import com.somemore.recruitboard.usecase.command.UpdateRecruitBoardUseCase; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +class RecruitBoardCommandApiControllerTest extends ControllerTestSupport { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CreateRecruitBoardUseCase createRecruitBoardUseCase; + + @MockBean + private UpdateRecruitBoardUseCase updateRecruitBoardUseCase; + + @MockBean + private DeleteRecruitBoardUseCase deleteRecruitBoardUseCase; + + @MockBean + private ImageUploadUseCase imageUploadUseCase; + + @Test + @DisplayName("봉사 활동 모집글 등록 성공 테스트") + @WithMockCustomUser(role = "CENTER") + void createRecruitBoard_success() throws Exception { + // given + LocalDateTime startDateTime = createStartDateTime(); + LocalDateTime endDateTime = startDateTime.plusHours(2); + + LocationCreateRequestDto location = LocationCreateRequestDto.builder() + .address("위치위치") + .latitude(BigDecimal.valueOf(37.4845373748015)) + .longitude(BigDecimal.valueOf(127.010842267696)) + .build(); + + RecruitBoardCreateRequestDto dto = RecruitBoardCreateRequestDto.builder() + .title("봉사 모집글 작성") + .content("봉사 하실분을 모집합니다.
") + .region("지역") + .recruitmentCount(10) + .volunteerStartDateTime(startDateTime) + .volunteerEndDateTime(endDateTime) + .volunteerType(VolunteerType.OTHER) + .admitted(true) + .location(location) + .build(); + + MockMultipartFile imageFile = new MockMultipartFile( + "img_file", + "test-image.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image content".getBytes() + ); + + MockMultipartFile requestData = new MockMultipartFile( + "data", + "", + MediaType.APPLICATION_JSON_VALUE, + objectMapper.writeValueAsBytes(dto) + ); + + String mockImageUrl = "http://example.com/image/test-image.jpg"; + long mockRecruitBoardId = 1L; + + given(imageUploadUseCase.uploadImage(any())).willReturn(mockImageUrl); + given(createRecruitBoardUseCase.createRecruitBoard(any(), any(UUID.class), + anyString())).willReturn(mockRecruitBoardId); + + // when + mockMvc.perform(multipart("/api/recruit-board") + .file(requestData) + .file(imageFile) + .contentType(MULTIPART_FORM_DATA) + .header("Authorization", "Bearer access-token")) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(201)) + .andExpect(jsonPath("$.data").value(mockRecruitBoardId)) + .andExpect(jsonPath("$.message").value("봉사 활동 모집글 등록 성공")); + } + + @DisplayName("봉사 활동 모집글 수정 성공 테스트") + @Test + @WithMockCustomUser(role = "CENTER") + void updateRecruitBoard() throws Exception { + // given + LocalDateTime startDateTime = createStartDateTime(); + LocalDateTime endDateTime = startDateTime.plusHours(2); + + RecruitBoardUpdateRequestDto requestDto = RecruitBoardUpdateRequestDto.builder() + .title("서울 청계천 환경 미화 봉사 모집") + .content("서울 청계천 주변 환경 미화 봉사 모집합니다.
") + .recruitmentCount(10) + .volunteerStartDateTime(startDateTime) + .volunteerEndDateTime(endDateTime) + .volunteerType(VolunteerType.OTHER) + .admitted(true) + .build(); + + MockMultipartFile imageFile = new MockMultipartFile( + "img_file", + "test-image.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image content".getBytes() + ); + + MockMultipartFile requestData = new MockMultipartFile( + "data", + "", + MediaType.APPLICATION_JSON_VALUE, + objectMapper.writeValueAsBytes(requestDto) + ); + + String mockImageUrl = "http://example.com/image/test-image.jpg"; + + given(imageUploadUseCase.uploadImage(any())).willReturn(mockImageUrl); + willDoNothing().given(updateRecruitBoardUseCase) + .updateRecruitBoard(any(), any(), any(UUID.class), anyString()); + + MockMultipartHttpServletRequestBuilder builder = multipart("/api/recruit-board/{id}", 1); + builder.with(new RequestPostProcessor() { + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + request.setMethod("PUT"); + return request; + } + }); + + // when + mockMvc.perform(builder + .file(requestData) + .file(imageFile) + .contentType(MULTIPART_FORM_DATA) + .header("Authorization", "Bearer access-token")) + //then + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").isEmpty()) + .andExpect(jsonPath("$.message").value("봉사 활동 모집글 수정 성공")); + } + + @DisplayName("봉사 활동 모집글 위치 수정 성공 테스트") + @Test + @WithMockCustomUser(role = "CENTER") + void updateRecruitBoardLocation() throws Exception { + // given + RecruitBoardLocationUpdateRequestDto requestDto = RecruitBoardLocationUpdateRequestDto.builder() + .region("새로새로지역지역") + .address("새로새로주소주소") + .latitude(BigDecimal.valueOf(37.2222222)) + .longitude(BigDecimal.valueOf(127.2222222)) + .build(); + + willDoNothing().given(updateRecruitBoardUseCase) + .updateRecruitBoardLocation(any(), any(), any(UUID.class)); + + String requestBody = objectMapper.writeValueAsString(requestDto); + + // when + mockMvc.perform(put("/api/recruit-board/{id}/location", 1L) + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer access-token")) + // then + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").isEmpty()) + .andExpect(jsonPath("$.message").value("봉사 활동 모집글 위치 수정 성공")); + } + + @DisplayName("봉사 활동 상태 변경 성공") + @Test + @WithMockCustomUser(role = "CENTER") + void updateRecruitBoardStatus() throws Exception { + // given + RecruitStatus status = CLOSED; + RecruitBoardStatusUpdateRequestDto dto = new RecruitBoardStatusUpdateRequestDto( + status); + String requestBody = objectMapper.writeValueAsString(dto); + willDoNothing().given(updateRecruitBoardUseCase) + .updateRecruitBoardStatus(any(), any(), any(UUID.class), any(LocalDateTime.class)); + + // when + mockMvc.perform(patch("/api/recruit-board/{id}", 1L) + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer access-token")) + //then + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("봉사 활동 모집글 상태 수정 성공")) + .andExpect(jsonPath("$.data").isEmpty()); + } + + @DisplayName("봉사 활동 모집글 삭제 성공") + @Test + @WithMockCustomUser(role = "CENTER") + void deleteRecruitBoard() throws Exception { + // given + Long recruitBoardId = 1L; + willDoNothing().given(deleteRecruitBoardUseCase).deleteRecruitBoard(any(UUID.class), any()); + + // when + mockMvc.perform(delete("/api/recruit-board/{id}", recruitBoardId) + .header("Authorization", "Bearer access-token")) + // then + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("봉사 활동 모집글 삭제 성공")) + .andExpect(jsonPath("$.data").isEmpty()); + } +} + diff --git a/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryControllerTest.java b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiControllerTest.java similarity index 98% rename from src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryControllerTest.java rename to src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiControllerTest.java index 3a2502308..c441dd9e7 100644 --- a/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryControllerTest.java +++ b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiControllerTest.java @@ -29,7 +29,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -class RecruitBoardQueryControllerTest extends ControllerTestSupport { +class RecruitBoardQueryApiControllerTest extends ControllerTestSupport { @Autowired private MockMvc mockMvc; diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index d0d564d75..537ef65eb 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -60,3 +60,6 @@ cloud: stack: auto: false +default: + image: + url: ""