From 915875747f36503b707d8ed14c773951be6f020e Mon Sep 17 00:00:00 2001 From: MangKyu Date: Sat, 8 Jan 2022 23:50:14 +0900 Subject: [PATCH 1/6] Add Config for FileUpload --- build.gradle | 3 +++ src/main/resources/application-alpha.properties | 3 +++ src/main/resources/application-local.properties | 3 +++ 3 files changed, 9 insertions(+) diff --git a/build.gradle b/build.gradle index 54bfefb..311d2e0 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,9 @@ dependencies { // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' + // https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload + implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.4' + implementation group: 'com.querydsl', name: 'querydsl-apt', version: '5.0.0' implementation group: 'com.querydsl', name: 'querydsl-jpa', version: '5.0.0' diff --git a/src/main/resources/application-alpha.properties b/src/main/resources/application-alpha.properties index 921c96a..ea7c768 100644 --- a/src/main/resources/application-alpha.properties +++ b/src/main/resources/application-alpha.properties @@ -1,3 +1,6 @@ +# Custom +file.directory=/c/Users/Mang/IdeaProjects/InterviewSubscription/images/ + # Datasource spring.datasource.driver-class-name=org.mariadb.jdbc.Driver spring.datasource.url=jdbc:mariadb://localhost/quiz?characterEncoding=utf-8 diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 77db6a3..88c9282 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,3 +1,6 @@ +# Custom +file.directory=/Users/user/IdeaProjects/interview/images/ + # Datasource spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 From a9c0e3702ce8c1f60bd97ba0f4153eae5754c260 Mon Sep 17 00:00:00 2001 From: MangKyu Date: Sat, 8 Jan 2022 23:51:04 +0900 Subject: [PATCH 2/6] Add Upload and Get File APIs --- .../app/file/constants/FileConstants.java | 10 ++ .../app/file/controller/FileController.java | 30 ++++ .../app/file/dto/FileUploadResponse.java | 28 +++ .../interview/app/file/entity/MyFile.java | 27 +++ .../app/file/repository/FileRepository.java | 12 ++ .../app/file/service/FileService.java | 90 ++++++++++ .../file/controller/FileControllerTest.java | 86 +++++++++ .../file/repository/FileRepositoryTest.java | 60 +++++++ .../app/file/service/FileServiceTest.java | 163 ++++++++++++++++++ 9 files changed, 506 insertions(+) create mode 100644 src/main/java/com/mangkyu/employment/interview/app/file/constants/FileConstants.java create mode 100644 src/main/java/com/mangkyu/employment/interview/app/file/controller/FileController.java create mode 100644 src/main/java/com/mangkyu/employment/interview/app/file/dto/FileUploadResponse.java create mode 100644 src/main/java/com/mangkyu/employment/interview/app/file/entity/MyFile.java create mode 100644 src/main/java/com/mangkyu/employment/interview/app/file/repository/FileRepository.java create mode 100644 src/main/java/com/mangkyu/employment/interview/app/file/service/FileService.java create mode 100644 src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java create mode 100644 src/test/java/com/mangkyu/employment/interview/app/file/repository/FileRepositoryTest.java create mode 100644 src/test/java/com/mangkyu/employment/interview/app/file/service/FileServiceTest.java diff --git a/src/main/java/com/mangkyu/employment/interview/app/file/constants/FileConstants.java b/src/main/java/com/mangkyu/employment/interview/app/file/constants/FileConstants.java new file mode 100644 index 0000000..1a73247 --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/app/file/constants/FileConstants.java @@ -0,0 +1,10 @@ +package com.mangkyu.employment.interview.app.file.constants; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public final class FileConstants { + + public static final String FILE_API_PREFIX = "/file"; + +} diff --git a/src/main/java/com/mangkyu/employment/interview/app/file/controller/FileController.java b/src/main/java/com/mangkyu/employment/interview/app/file/controller/FileController.java new file mode 100644 index 0000000..2f7c1fb --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/app/file/controller/FileController.java @@ -0,0 +1,30 @@ +package com.mangkyu.employment.interview.app.file.controller; + +import com.mangkyu.employment.interview.app.file.constants.FileConstants; +import com.mangkyu.employment.interview.app.file.dto.FileUploadResponse; +import com.mangkyu.employment.interview.app.file.service.FileService; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import static com.mangkyu.employment.interview.app.file.constants.FileConstants.*; + +@RestController +@RequiredArgsConstructor +public class FileController { + + private final FileService fileService; + + @PostMapping(FILE_API_PREFIX) + public ResponseEntity uploadFile(@RequestParam("upload") MultipartFile file) { + return ResponseEntity.ok(fileService.upload(file)); + } + + @GetMapping(FILE_API_PREFIX + "/{resourceId}") + public ResponseEntity getFile(@PathVariable String resourceId) { + return ResponseEntity.ok(fileService.getFileAsResource(resourceId)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/mangkyu/employment/interview/app/file/dto/FileUploadResponse.java b/src/main/java/com/mangkyu/employment/interview/app/file/dto/FileUploadResponse.java new file mode 100644 index 0000000..07dcf62 --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/app/file/dto/FileUploadResponse.java @@ -0,0 +1,28 @@ +package com.mangkyu.employment.interview.app.file.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class FileUploadResponse { + + private final boolean uploaded; + private final String url; + + public static FileUploadResponse uploadSuccessResponse(final String url) { + return FileUploadResponse.builder() + .uploaded(true) + .url(url) + .build(); + } + + public static FileUploadResponse uploadFailResponse() { + return FileUploadResponse.builder() + .uploaded(false) + .build(); + } + +} diff --git a/src/main/java/com/mangkyu/employment/interview/app/file/entity/MyFile.java b/src/main/java/com/mangkyu/employment/interview/app/file/entity/MyFile.java new file mode 100644 index 0000000..6da01d6 --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/app/file/entity/MyFile.java @@ -0,0 +1,27 @@ +package com.mangkyu.employment.interview.app.file.entity; + +import com.mangkyu.employment.interview.app.common.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "my_file") +@Getter +@Builder +@NoArgsConstructor(force = true) +@AllArgsConstructor +public class MyFile extends BaseEntity { + + @Column(nullable = false) + private String resourceId; + + @Column(nullable = false) + private String fileName; + +} diff --git a/src/main/java/com/mangkyu/employment/interview/app/file/repository/FileRepository.java b/src/main/java/com/mangkyu/employment/interview/app/file/repository/FileRepository.java new file mode 100644 index 0000000..aa763ae --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/app/file/repository/FileRepository.java @@ -0,0 +1,12 @@ +package com.mangkyu.employment.interview.app.file.repository; + +import com.mangkyu.employment.interview.app.file.entity.MyFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FileRepository extends JpaRepository { + + Optional findByResourceId(final String resourceId); + +} \ No newline at end of file diff --git a/src/main/java/com/mangkyu/employment/interview/app/file/service/FileService.java b/src/main/java/com/mangkyu/employment/interview/app/file/service/FileService.java new file mode 100644 index 0000000..7d92231 --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/app/file/service/FileService.java @@ -0,0 +1,90 @@ +package com.mangkyu.employment.interview.app.file.service; + +import com.mangkyu.employment.interview.app.common.erros.errorcode.CommonErrorCode; +import com.mangkyu.employment.interview.app.common.erros.exception.QuizException; +import com.mangkyu.employment.interview.app.file.dto.FileUploadResponse; +import com.mangkyu.employment.interview.app.file.entity.MyFile; +import com.mangkyu.employment.interview.app.file.repository.FileRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +import static com.mangkyu.employment.interview.app.file.constants.FileConstants.FILE_API_PREFIX; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FileService { + + private final FileRepository fileRepository; + + @Value("${file.directory}") + private String fileDirectory; + + @PostConstruct + public void init() throws IOException { + if (StringUtils.isBlank(fileDirectory)) { + throw new QuizException(CommonErrorCode.INTERNAL_SERVER_ERROR); + } + + final Path fileDirectoryPath = Paths.get(fileDirectory); + if (Files.notExists(fileDirectoryPath)) { + Files.createDirectory(fileDirectoryPath); + } + } + + public FileUploadResponse upload(final MultipartFile multipartFile) { + try { + final String resourceId = UUID.randomUUID().toString(); + final String fileName = generateFileName(multipartFile.getOriginalFilename(), resourceId); + final File file = new File(fileDirectory + fileName); + FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file); + + final MyFile myFile = MyFile.builder() + .resourceId(resourceId) + .fileName(fileName) + .build(); + + fileRepository.save(myFile); + + return FileUploadResponse.uploadSuccessResponse(FILE_API_PREFIX + "/" + resourceId); + } catch (final IOException e) { + log.error("File Upload Fail", e); + return FileUploadResponse.uploadFailResponse(); + } + } + + private String generateFileName(final String originalFileName, final String uuid) { + final String fileExtension = FilenameUtils.getExtension(originalFileName); + return uuid + FilenameUtils.EXTENSION_SEPARATOR + fileExtension; + } + + public Resource getFileAsResource(final String resourceId) { + final MyFile myFile = fileRepository.findByResourceId(resourceId) + .orElseThrow(() -> new QuizException(CommonErrorCode.RESOURCE_NOT_FOUND)); + + try { + final Path path = Paths.get(fileDirectory + myFile.getFileName()); + return new ByteArrayResource(Files.readAllBytes(path)); + } catch (final IOException e) { + log.error("getFileAsResource Fail", e); + throw new QuizException(CommonErrorCode.INTERNAL_SERVER_ERROR); + } + } + +} diff --git a/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java b/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java new file mode 100644 index 0000000..dce09f4 --- /dev/null +++ b/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java @@ -0,0 +1,86 @@ +package com.mangkyu.employment.interview.app.file.controller; + +import com.google.gson.Gson; +import com.mangkyu.employment.interview.app.file.dto.FileUploadResponse; +import com.mangkyu.employment.interview.app.file.service.FileService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import static com.mangkyu.employment.interview.app.file.constants.FileConstants.FILE_API_PREFIX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +class FileControllerTest { + + @InjectMocks + private FileController target; + + @Mock + private FileService fileService; + + private MockMvc mockMvc; + + @BeforeEach + public void init() { + mockMvc = MockMvcBuilders.standaloneSetup(target).build(); + } + + @Test + public void uploadFileSuccess() throws Exception { + // given + final MockMultipartFile multipartFile = new MockMultipartFile("a", new byte[0]); + final FileUploadResponse fileUploadResponse = FileUploadResponse.builder() + .uploaded(false) + .build(); + + doReturn(fileUploadResponse).when(fileService).upload(multipartFile); + + // when + final ResultActions result = mockMvc.perform( + MockMvcRequestBuilders.multipart(FILE_API_PREFIX) + .file(multipartFile) + ); + + // then + final ResultActions resultActions = result.andExpect(status().isOk()); + final String stringResponse = resultActions.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8); + final FileUploadResponse responseResult = new Gson().fromJson(stringResponse, FileUploadResponse.class); + + assertThat(responseResult.isUploaded()).isFalse(); + } + + @Test + public void addAnswerFail_InvalidParameter(final String quizResourceId, final String desc) throws Exception { + // given + final String resourceId = UUID.randomUUID().toString(); + final String url = FILE_API_PREFIX + "/" + resourceId; + final Resource resource = mock(Resource.class); + + doReturn(resource).when(fileService).getFileAsResource(resourceId); + + // when + final ResultActions result = mockMvc.perform( + MockMvcRequestBuilders.get(url) + ); + + // then + result.andExpect(status().isOk()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/mangkyu/employment/interview/app/file/repository/FileRepositoryTest.java b/src/test/java/com/mangkyu/employment/interview/app/file/repository/FileRepositoryTest.java new file mode 100644 index 0000000..d63b95e --- /dev/null +++ b/src/test/java/com/mangkyu/employment/interview/app/file/repository/FileRepositoryTest.java @@ -0,0 +1,60 @@ +package com.mangkyu.employment.interview.app.file.repository; + +import com.mangkyu.employment.interview.JpaTestConfig; +import com.mangkyu.employment.interview.app.file.entity.MyFile; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@JpaTestConfig +class FileRepositoryTest { + + @Autowired + private FileRepository fileRepository; + + @Test + public void insertMyFile() { + // given + final String resourceId = UUID.randomUUID().toString(); + final String fileName = "fileName"; + final MyFile myFile = MyFile.builder() + .resourceId(resourceId) + .fileName(fileName) + .build(); + + // when + final MyFile result = fileRepository.save(myFile); + + // then + assertThat(result.getResourceId()).isEqualTo(resourceId); + assertThat(result.getFileName()).isEqualTo(fileName); + } + + @Test + public void findFileByResourceId() { + // given + final String resourceId = UUID.randomUUID().toString(); + final String fileName = "fileName"; + final MyFile myFile = MyFile.builder() + .resourceId(resourceId) + .fileName(fileName) + .build(); + + fileRepository.save(myFile); + + // when + final Optional optionalResult = fileRepository.findByResourceId(resourceId); + + // then + assertThat(optionalResult.isPresent()).isTrue(); + final MyFile result = optionalResult.get(); + + assertThat(result.getResourceId()).isEqualTo(resourceId); + assertThat(result.getFileName()).isEqualTo(fileName); + } + +} \ No newline at end of file diff --git a/src/test/java/com/mangkyu/employment/interview/app/file/service/FileServiceTest.java b/src/test/java/com/mangkyu/employment/interview/app/file/service/FileServiceTest.java new file mode 100644 index 0000000..a15e079 --- /dev/null +++ b/src/test/java/com/mangkyu/employment/interview/app/file/service/FileServiceTest.java @@ -0,0 +1,163 @@ +package com.mangkyu.employment.interview.app.file.service; + +import com.mangkyu.employment.interview.app.common.erros.errorcode.CommonErrorCode; +import com.mangkyu.employment.interview.app.common.erros.exception.QuizException; +import com.mangkyu.employment.interview.app.file.dto.FileUploadResponse; +import com.mangkyu.employment.interview.app.file.entity.MyFile; +import com.mangkyu.employment.interview.app.file.repository.FileRepository; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class FileServiceTest { + + @InjectMocks + private FileService target; + + @Mock + private FileRepository fileRepository; + + @Test + public void initFail_fileDirectoryEmpty() { + // given + + // when + final QuizException result = assertThrows(QuizException.class, () -> target.init()); + + // then + assertThat(result.getErrorCode()).isEqualTo(CommonErrorCode.INTERNAL_SERVER_ERROR); + } + + @Test + public void initSuccess_fileDirectoryExists() throws IOException { + // given + ReflectionTestUtils.setField(target, "fileDirectory", "/"); + + // when + target.init(); + + // then + } + + @Test + public void initSuccess_createFileDirectory() throws IOException { + // given + final Path currentPath = Paths.get(""); + final String testDirectory = currentPath.toAbsolutePath() + "/temp" + UUID.randomUUID() + "/"; + FileUtils.deleteDirectory(new File(testDirectory)); + ReflectionTestUtils.setField(target, "fileDirectory", testDirectory); + + // when + target.init(); + + // then + FileUtils.deleteDirectory(new File(testDirectory)); + } + + @Test + public void fileUploadFail_IOException() throws IOException { + // given + final Path currentPath = Paths.get(""); + final String testDirectory = currentPath.toAbsolutePath() + "/temp" + UUID.randomUUID() + "/"; + FileUtils.deleteDirectory(new File(testDirectory)); + ReflectionTestUtils.setField(target, "fileDirectory", testDirectory); + + final MultipartFile multipartFile = mock(MultipartFile.class); + doThrow(new IOException()).when(multipartFile).getInputStream(); + + // when + final FileUploadResponse result = target.upload(multipartFile); + + // then + assertThat(result.isUploaded()).isFalse(); + + // then + FileUtils.deleteDirectory(new File(testDirectory)); + } + + @Test + public void fileUploadSuccess() throws IOException { + // given + final Path currentPath = Paths.get(""); + final String fileName = "fileName"; + final MultipartFile multipartFile = new MockMultipartFile(fileName, new byte[]{}); + + final String testDirectory = currentPath.toAbsolutePath() + "/temp" + UUID.randomUUID() + "/"; + ReflectionTestUtils.setField(target, "fileDirectory", testDirectory); + FileUtils.deleteDirectory(new File(testDirectory)); + + // when + final FileUploadResponse result = target.upload(multipartFile); + + // then + assertThat(result.isUploaded()).isTrue(); + assertThat(result.getUrl()).isNotNull(); + + // then + FileUtils.deleteDirectory(new File(testDirectory)); + } + + @Test + public void getFileAsResourceFail_MyFileNotExists() { + // given + final String resourceId = UUID.randomUUID().toString(); + + doReturn(Optional.empty()).when(fileRepository).findByResourceId(resourceId); + + // when + final QuizException result = assertThrows(QuizException.class, () -> target.getFileAsResource(resourceId)); + + // then + assertThat(result.getErrorCode()).isEqualTo(CommonErrorCode.RESOURCE_NOT_FOUND); + } + + @Test + public void getFileAsResourceFail_IOException() throws IOException { + // given + final String resourceId = UUID.randomUUID().toString(); + final Path currentPath = Paths.get(""); + final MultipartFile multipartFile = new MockMultipartFile(resourceId, new byte[]{}); + + final String testDirectory = currentPath.toAbsolutePath() + "/temp" + resourceId + "/"; + FileUtils.deleteDirectory(new File(testDirectory)); + ReflectionTestUtils.setField(target, "fileDirectory", testDirectory); + + final File file = new File(testDirectory + resourceId); + FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file); + + final MyFile myFile = MyFile.builder() + .resourceId(resourceId) + .fileName(resourceId) + .build(); + + + doReturn(Optional.of(myFile)).when(fileRepository).findByResourceId(resourceId); + + // when + final Resource result = target.getFileAsResource(resourceId); + + // then + assertThat(result).isNotNull(); + FileUtils.deleteDirectory(new File(testDirectory)); + } + +} \ No newline at end of file From 359e0ff81c160d28d2bbe7f6c6c21c54dfd22210 Mon Sep 17 00:00:00 2001 From: MangKyu Date: Sun, 9 Jan 2022 00:01:56 +0900 Subject: [PATCH 3/6] Fix FileControllerTest --- .../interview/app/file/controller/FileControllerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java b/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java index dce09f4..fe2b3de 100644 --- a/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java +++ b/src/test/java/com/mangkyu/employment/interview/app/file/controller/FileControllerTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; @@ -22,7 +23,6 @@ import java.util.UUID; import static com.mangkyu.employment.interview.app.file.constants.FileConstants.FILE_API_PREFIX; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(MockitoExtension.class) @@ -44,7 +44,7 @@ class FileControllerTest { @Test public void uploadFileSuccess() throws Exception { // given - final MockMultipartFile multipartFile = new MockMultipartFile("a", new byte[0]); + final MockMultipartFile multipartFile = new MockMultipartFile("upload", new byte[0]); final FileUploadResponse fileUploadResponse = FileUploadResponse.builder() .uploaded(false) .build(); @@ -66,11 +66,11 @@ class FileControllerTest { } @Test - public void addAnswerFail_InvalidParameter(final String quizResourceId, final String desc) throws Exception { + public void getResource() throws Exception { // given final String resourceId = UUID.randomUUID().toString(); final String url = FILE_API_PREFIX + "/" + resourceId; - final Resource resource = mock(Resource.class); + final Resource resource = new ByteArrayResource(new byte[0]); doReturn(resource).when(fileService).getFileAsResource(resourceId); From 2556424c424e67f7ff1ec9c73d53bef621d5918d Mon Sep 17 00:00:00 2001 From: MangKyu Date: Sun, 9 Jan 2022 00:02:10 +0900 Subject: [PATCH 4/6] Modify answerResourceId as null --- .../interview/app/quiz/converter/QuizDtoConverter.java | 2 +- .../interview/app/quiz/converter/QuizDtoConverterTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverter.java b/src/main/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverter.java index ede6d74..0103489 100644 --- a/src/main/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverter.java +++ b/src/main/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverter.java @@ -62,7 +62,7 @@ public final class QuizDtoConverter { private static String getAnswerResourceId(final Quiz quiz) { return (quiz.getAnswer() == null) - ? StringUtils.EMPTY + ? null : quiz.getAnswer().getResourceId(); } diff --git a/src/test/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverterTest.java b/src/test/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverterTest.java index 6ca6926..66d17b2 100644 --- a/src/test/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverterTest.java +++ b/src/test/java/com/mangkyu/employment/interview/app/quiz/converter/QuizDtoConverterTest.java @@ -84,7 +84,7 @@ class QuizDtoConverterTest { assertThat(result.getQuizLevelList().size()).isEqualTo(quiz.getQuizLevel().size()); assertThat(result.getCreatedAt()).isEqualTo(Timestamp.valueOf(quiz.getCreatedAt()).getTime()); assertThat(result.getCategory().getCode()).isEqualTo(enumMapperValue(quiz.getQuizCategory()).getCode()); - assertThat(result.getAnswerResourceId()).isEqualTo(StringUtils.EMPTY); + assertThat(result.getAnswerResourceId()).isNull(); } @Test @@ -120,7 +120,7 @@ class QuizDtoConverterTest { assertThat(result.getQuizLevelList().size()).isEqualTo(quiz.getQuizLevel().size()); assertThat(result.getCreatedAt()).isEqualTo(Timestamp.valueOf(quiz.getCreatedAt()).getTime()); assertThat(result.getCategory()).isNull(); - assertThat(result.getAnswerResourceId()).isEqualTo(StringUtils.EMPTY); + assertThat(result.getAnswerResourceId()).isNull(); } private EnumMapperValue enumMapperValue(final EnumMapperType enumMapperType) { From 12e60ed3eb4f5caca933ddcdb36780af7647470e Mon Sep 17 00:00:00 2001 From: MangKyu Date: Sun, 9 Jan 2022 00:02:23 +0900 Subject: [PATCH 5/6] Add Answer POST APIs --- .../answer/controller/AnswerController.java | 7 +++ .../controller/AnswerControllerTest.java | 46 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/mangkyu/employment/interview/app/answer/controller/AnswerController.java b/src/main/java/com/mangkyu/employment/interview/app/answer/controller/AnswerController.java index b49a069..ded8732 100644 --- a/src/main/java/com/mangkyu/employment/interview/app/answer/controller/AnswerController.java +++ b/src/main/java/com/mangkyu/employment/interview/app/answer/controller/AnswerController.java @@ -24,6 +24,13 @@ public class AnswerController { .build(); } + @PostMapping("/answer") + public ResponseEntity postAnswer(@RequestBody @Valid final AddAnswerRequest addAnswerRequest) throws QuizException { + answerService.addAnswer(addAnswerRequest); + return ResponseEntity.noContent() + .build(); + } + @GetMapping("/answer/{resourceId}") public ResponseEntity getAnswer(@PathVariable final String resourceId) throws QuizException { return ResponseEntity.ok(answerService.getAnswer(resourceId)); diff --git a/src/test/java/com/mangkyu/employment/interview/app/answer/controller/AnswerControllerTest.java b/src/test/java/com/mangkyu/employment/interview/app/answer/controller/AnswerControllerTest.java index 1e15a71..2e33071 100644 --- a/src/test/java/com/mangkyu/employment/interview/app/answer/controller/AnswerControllerTest.java +++ b/src/test/java/com/mangkyu/employment/interview/app/answer/controller/AnswerControllerTest.java @@ -85,7 +85,7 @@ class AnswerControllerTest { // when final ResultActions result = mockMvc.perform( - MockMvcRequestBuilders.put(url) + MockMvcRequestBuilders.post(url) .content(new Gson().toJson(addAnswerRequest)) .contentType(MediaType.APPLICATION_JSON) ); @@ -105,6 +105,50 @@ class AnswerControllerTest { .build(); + // when + final ResultActions result = mockMvc.perform( + MockMvcRequestBuilders.post(url) + .content(new Gson().toJson(addAnswerRequest)) + .contentType(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isNoContent()); + } + + @ParameterizedTest + @MethodSource("provideParameters") + public void putAnswerFail_InvalidParameter(final String quizResourceId, final String desc) throws Exception { + // given + final String url = "/answer"; + + final AddAnswerRequest addAnswerRequest = AddAnswerRequest.builder() + .quizResourceId(quizResourceId) + .description(desc) + .build(); + + // when + final ResultActions result = mockMvc.perform( + MockMvcRequestBuilders.put(url) + .content(new Gson().toJson(addAnswerRequest)) + .contentType(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isBadRequest()); + } + + @Test + public void putAnswerSuccess() throws Exception { + // given + final String url = "/answer"; + + final AddAnswerRequest addAnswerRequest = AddAnswerRequest.builder() + .quizResourceId(UUID.randomUUID().toString()) + .description("desc") + .build(); + + // when final ResultActions result = mockMvc.perform( MockMvcRequestBuilders.put(url) From f8092c9701a2a221057c0ee8110156ebd0dbfd5d Mon Sep 17 00:00:00 2001 From: MangKyu Date: Sun, 9 Jan 2022 00:06:58 +0900 Subject: [PATCH 6/6] Add CorsConfig --- .../interview/config/web/WebMvcConfig.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/com/mangkyu/employment/interview/config/web/WebMvcConfig.java diff --git a/src/main/java/com/mangkyu/employment/interview/config/web/WebMvcConfig.java b/src/main/java/com/mangkyu/employment/interview/config/web/WebMvcConfig.java new file mode 100644 index 0000000..b9d4d76 --- /dev/null +++ b/src/main/java/com/mangkyu/employment/interview/config/web/WebMvcConfig.java @@ -0,0 +1,17 @@ +package com.mangkyu.employment.interview.config.web; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(final CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE"); + } + +} \ No newline at end of file