diff --git a/src/main/java/com/back/domain/file/controller/FileController.java b/src/main/java/com/back/domain/file/controller/FileController.java index df0e4a60..6a45dd5f 100644 --- a/src/main/java/com/back/domain/file/controller/FileController.java +++ b/src/main/java/com/back/domain/file/controller/FileController.java @@ -29,8 +29,6 @@ public ResponseEntity> uploadFile( ) { FileUploadResponseDto res = fileService.uploadFile( req.getMultipartFile(), - req.getEntityType(), - req.getEntityId(), user.getUserId() ); @@ -39,44 +37,41 @@ public ResponseEntity> uploadFile( .body(RsData.success("파일 업로드 성공", res)); } - @GetMapping(value = "/read") + @GetMapping(value = "/read/{attachmentId}") public ResponseEntity> getFile( - @RequestParam("entityType") @NotBlank(message = "entityType은 필수입니다.") EntityType entityType, - @RequestParam("entityId") @NotBlank(message = "entityId는 필수입니다.") Long entityId + @PathVariable("attachmentId") Long attachmentId ) { - FileReadResponseDto res = fileService.getFile(entityType, entityId); + FileReadResponseDto res = fileService.getFile(attachmentId); return ResponseEntity .status(HttpStatus.OK) .body(RsData.success("파일 조회 성공", res)); } - @PutMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> updateFile( + @PutMapping(value = "/update/{attachmentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> updateFile( + @PathVariable("attachmentId") Long attachmentId, @ModelAttribute @Valid FileUpdateRequestDto req, @AuthenticationPrincipal CustomUserDetails user ) { - fileService.updateFile( + FileUpdateResponseDto res = fileService.updateFile( + attachmentId, req.getMultipartFile(), - req.getEntityType(), - req.getEntityId(), user.getUserId() ); return ResponseEntity .status(HttpStatus.OK) - .body(RsData.success("파일 업데이트 성공")); + .body(RsData.success("파일 업데이트 성공", res)); } - @DeleteMapping(value = "/delete") + @DeleteMapping(value = "/delete/{attachmentId}") public ResponseEntity> deleteFile( - @RequestParam("entityType") @NotBlank(message = "entityType은 필수입니다.") EntityType entityType, - @RequestParam("entityId") @NotBlank(message = "entityId는 필수입니다.") Long entityId, + @PathVariable("attachmentId") Long attachmentId, @AuthenticationPrincipal CustomUserDetails user ) { fileService.deleteFile( - entityType, - entityId, + attachmentId, user.getUserId() ); diff --git a/src/main/java/com/back/domain/file/dto/FileReadResponseDto.java b/src/main/java/com/back/domain/file/dto/FileReadResponseDto.java index 574c200b..1eb3d5c2 100644 --- a/src/main/java/com/back/domain/file/dto/FileReadResponseDto.java +++ b/src/main/java/com/back/domain/file/dto/FileReadResponseDto.java @@ -4,9 +4,9 @@ @Data public class FileReadResponseDto { - private String imageUrl; + private String publicURL; - public FileReadResponseDto(String imageUrl) { - this.imageUrl = imageUrl; + public FileReadResponseDto(String publicURL) { + this.publicURL = publicURL; } } diff --git a/src/main/java/com/back/domain/file/dto/FileUpdateRequestDto.java b/src/main/java/com/back/domain/file/dto/FileUpdateRequestDto.java index 6b84d6af..c7c6de3c 100644 --- a/src/main/java/com/back/domain/file/dto/FileUpdateRequestDto.java +++ b/src/main/java/com/back/domain/file/dto/FileUpdateRequestDto.java @@ -10,8 +10,4 @@ public class FileUpdateRequestDto { @NotNull(message = "파일 입력은 필수입니다.") private MultipartFile multipartFile; - - private EntityType entityType; - - private Long entityId; } \ No newline at end of file diff --git a/src/main/java/com/back/domain/file/dto/FileUpdateResponseDto.java b/src/main/java/com/back/domain/file/dto/FileUpdateResponseDto.java new file mode 100644 index 00000000..caa4fa35 --- /dev/null +++ b/src/main/java/com/back/domain/file/dto/FileUpdateResponseDto.java @@ -0,0 +1,12 @@ +package com.back.domain.file.dto; + +import lombok.Data; + +@Data +public class FileUpdateResponseDto { + private String publicURL; + + public FileUpdateResponseDto(String publicURL) { + this.publicURL = publicURL; + } +} diff --git a/src/main/java/com/back/domain/file/dto/FileUploadRequestDto.java b/src/main/java/com/back/domain/file/dto/FileUploadRequestDto.java index 4ef8d60f..516dfc6e 100644 --- a/src/main/java/com/back/domain/file/dto/FileUploadRequestDto.java +++ b/src/main/java/com/back/domain/file/dto/FileUploadRequestDto.java @@ -10,8 +10,4 @@ public class FileUploadRequestDto { @NotNull(message = "파일 입력은 필수입니다.") private MultipartFile multipartFile; - - private EntityType entityType; - - private Long entityId; } diff --git a/src/main/java/com/back/domain/file/dto/FileUploadResponseDto.java b/src/main/java/com/back/domain/file/dto/FileUploadResponseDto.java index bee746c6..d4c00f93 100644 --- a/src/main/java/com/back/domain/file/dto/FileUploadResponseDto.java +++ b/src/main/java/com/back/domain/file/dto/FileUploadResponseDto.java @@ -4,9 +4,11 @@ @Data public class FileUploadResponseDto { - private String imageUrl; + private Long attachmentId; + private String publicURL; - public FileUploadResponseDto(String imageUrl) { - this.imageUrl = imageUrl; + public FileUploadResponseDto(Long attachmentId, String publicURL) { + this.attachmentId = attachmentId; + this.publicURL = publicURL; } } \ No newline at end of file diff --git a/src/main/java/com/back/domain/file/entity/FileAttachment.java b/src/main/java/com/back/domain/file/entity/FileAttachment.java index 3bd63417..195da19d 100644 --- a/src/main/java/com/back/domain/file/entity/FileAttachment.java +++ b/src/main/java/com/back/domain/file/entity/FileAttachment.java @@ -18,7 +18,7 @@ public class FileAttachment extends BaseEntity { private String originalName; - private String filePath; + private String publicURL; private long fileSize; @@ -36,25 +36,21 @@ public FileAttachment( String storedName, MultipartFile multipartFile, User user, - EntityType entityType, - Long entityId, - String filePath + String publicURL ) { this.storedName = storedName; - originalName = multipartFile.getOriginalFilename(); - this.filePath = filePath; - fileSize = multipartFile.getSize(); + this.originalName = multipartFile.getOriginalFilename(); + this.publicURL = publicURL; + this.fileSize = multipartFile.getSize(); this.contentType = multipartFile.getContentType(); this.user = user; - - attachmentMappings.add(new AttachmentMapping(this ,entityType, entityId)); } - public void update(String storedName, MultipartFile multipartFile, String filePath) { + public void update(String storedName, MultipartFile multipartFile, String publicURL) { this.storedName = storedName; - originalName = multipartFile.getOriginalFilename(); - this.filePath = filePath; - fileSize = multipartFile.getSize(); + this.originalName = multipartFile.getOriginalFilename(); + this.publicURL = publicURL; + this.fileSize = multipartFile.getSize(); this.contentType = multipartFile.getContentType(); } } diff --git a/src/main/java/com/back/domain/file/service/FileService.java b/src/main/java/com/back/domain/file/service/FileService.java index 77f1b836..74b03023 100644 --- a/src/main/java/com/back/domain/file/service/FileService.java +++ b/src/main/java/com/back/domain/file/service/FileService.java @@ -5,6 +5,7 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.back.domain.file.dto.FileReadResponseDto; +import com.back.domain.file.dto.FileUpdateResponseDto; import com.back.domain.file.dto.FileUploadResponseDto; import com.back.domain.file.entity.AttachmentMapping; import com.back.domain.file.entity.EntityType; @@ -20,6 +21,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -34,14 +37,10 @@ public class FileService { private final AmazonS3 amazonS3; private final FileAttachmentRepository fileAttachmentRepository; private final UserRepository userRepository; - private final AttachmentMappingRepository attachmentMappingRepository; - private final EntityValidator entityValidator; @Transactional public FileUploadResponseDto uploadFile( MultipartFile multipartFile, - EntityType entityType, - Long entityId, Long userId ) { User user = userRepository.findById(userId) @@ -53,46 +52,44 @@ public FileUploadResponseDto uploadFile( String storedFileName = createFileName(multipartFile.getOriginalFilename()); // S3의 저장된 파일의 PublicURL - String filePath = s3Upload(storedFileName, multipartFile); + String publicURL = s3Upload(storedFileName, multipartFile); // FileAttachment 정보 저장 - fileAttachmentRepository.save( + FileAttachment fileAttachment = fileAttachmentRepository.save( new FileAttachment( storedFileName, multipartFile, user, - entityType, - entityId, - filePath + publicURL ) ); - return new FileUploadResponseDto(filePath); + return new FileUploadResponseDto(fileAttachment.getId(), publicURL); } @Transactional(readOnly = true) - public FileReadResponseDto getFile( - EntityType entityType, - Long entityId - ) { - FileAttachment fileAttachment = getFileAttachmentOrThrow(entityType, entityId); + public FileReadResponseDto getFile(Long attachmentId) { + FileAttachment fileAttachment = fileAttachmentRepository.findById(attachmentId) + .orElseThrow(() -> + new CustomException(ErrorCode.FILE_NOT_FOUND) + ); - String filePath = fileAttachment.getFilePath(); + String publicURL = fileAttachment.getPublicURL(); - return new FileReadResponseDto(filePath); + return new FileReadResponseDto(publicURL); } @Transactional - public void updateFile( + public FileUpdateResponseDto updateFile( + Long attachmentId, MultipartFile multipartFile, - EntityType entityType, - Long entityId, Long userId ) { - entityValidator.validate(entityType, entityId); - - FileAttachment fileAttachment = getFileAttachmentOrThrow(entityType, entityId); + FileAttachment fileAttachment = fileAttachmentRepository.findById(attachmentId) + .orElseThrow(() -> + new CustomException(ErrorCode.FILE_NOT_FOUND) + ); checkAccessPermission(fileAttachment, userId); @@ -102,26 +99,22 @@ public void updateFile( // S3에 새롭게 저장할 파일 이름 String newStoredName = createFileName(multipartFile.getOriginalFilename()); - String filePath = s3Upload(newStoredName, multipartFile); + String publicURL = s3Upload(newStoredName, multipartFile); s3Delete(oldStoredName); // fileAttachment 정보 업데이트 - fileAttachment.update(newStoredName, multipartFile, filePath); + fileAttachment.update(newStoredName, multipartFile, publicURL); + return new FileUpdateResponseDto(publicURL); } @Transactional - public void deleteFile(EntityType entityType, Long entityId, Long userId) { - entityValidator.validate(entityType, entityId); - - AttachmentMapping attachmentMapping = attachmentMappingRepository - .findByEntityTypeAndEntityId(entityType, entityId) + public void deleteFile(Long attachmentId, Long userId) { + FileAttachment fileAttachment = fileAttachmentRepository.findById(attachmentId) .orElseThrow(() -> - new CustomException(ErrorCode.ATTACHMENT_MAPPING_NOT_FOUND) + new CustomException(ErrorCode.FILE_NOT_FOUND) ); - FileAttachment fileAttachment = attachmentMapping.getFileAttachment(); - checkAccessPermission(fileAttachment, userId); s3Delete(fileAttachment.getStoredName()); @@ -173,19 +166,8 @@ private String createFileName(String fileName) { // 파일 접근 권한 체크 private void checkAccessPermission(FileAttachment fileAttachment, Long userId) { - if (fileAttachment.getUser().getId() != userId) { + if (!fileAttachment.getUser().getId().equals(userId)) { throw new CustomException(ErrorCode.FILE_ACCESS_DENIED); } } - - // AttachmentMapping -> fileAttachment 추출 - private FileAttachment getFileAttachmentOrThrow(EntityType entityType, Long entityId) { - AttachmentMapping attachmentMapping = attachmentMappingRepository - .findByEntityTypeAndEntityId(entityType, entityId) - .orElseThrow(() -> - new CustomException(ErrorCode.ATTACHMENT_MAPPING_NOT_FOUND) - ); - - return attachmentMapping.getFileAttachment(); - } } \ No newline at end of file diff --git a/src/main/java/com/back/global/exception/ErrorCode.java b/src/main/java/com/back/global/exception/ErrorCode.java index 9c6daf1e..051c2eef 100644 --- a/src/main/java/com/back/global/exception/ErrorCode.java +++ b/src/main/java/com/back/global/exception/ErrorCode.java @@ -143,6 +143,7 @@ public enum ErrorCode { FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE_001", "파일 업로드에 실패했습니다."), ATTACHMENT_MAPPING_NOT_FOUND(HttpStatus.NOT_FOUND, "FILE_002", "매핑된 파일 정보를 찾을 수 없습니다."), FILE_ACCESS_DENIED(HttpStatus.FORBIDDEN, "FILE_003", "파일을 접근할 권한이 없습니다."), + FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "FILE_004", "파일 정보를 찾을 수 없습니다."), // ======================== 토큰 관련 ======================== INVALID_EMAIL_TOKEN(HttpStatus.UNAUTHORIZED, "TOKEN_001", "유효하지 않은 이메일 인증 토큰입니다."), diff --git a/src/main/java/com/back/global/security/SecurityConfig.java b/src/main/java/com/back/global/security/SecurityConfig.java index fdd13498..edcde415 100644 --- a/src/main/java/com/back/global/security/SecurityConfig.java +++ b/src/main/java/com/back/global/security/SecurityConfig.java @@ -89,7 +89,7 @@ public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins( "http://localhost:3000", // Catfe 프론트 개발 서버 - "https://www.catfe.com" // Catfe 프론트 운영 서버 + "https://www.catfe.site" // Catfe 프론트 운영 서버 ) .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") .allowedHeaders("*") diff --git a/src/test/java/com/back/domain/file/controller/FileControllerTest.java b/src/test/java/com/back/domain/file/controller/FileControllerTest.java index 874644ae..93970213 100644 --- a/src/test/java/com/back/domain/file/controller/FileControllerTest.java +++ b/src/test/java/com/back/domain/file/controller/FileControllerTest.java @@ -1,8 +1,8 @@ package com.back.domain.file.controller; -import com.back.domain.board.post.entity.Post; -import com.back.domain.board.post.repository.PostRepository; import com.back.domain.file.config.S3MockConfig; +import com.back.domain.file.dto.FileUploadResponseDto; +import com.back.domain.file.service.FileService; import com.back.domain.user.entity.User; import com.back.domain.user.entity.UserProfile; import com.back.domain.user.entity.UserStatus; @@ -25,7 +25,8 @@ import java.time.LocalDate; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 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; @@ -49,10 +50,10 @@ class FileControllerTest { private PasswordEncoder passwordEncoder; @Autowired - private PostRepository postRepository; + private TestJwtTokenProvider testJwtTokenProvider; @Autowired - private TestJwtTokenProvider testJwtTokenProvider; + private FileService fileService; private String generateAccessToken(User user) { return testJwtTokenProvider.createAccessToken( @@ -78,9 +79,6 @@ void uploadFile_success() throws Exception { String accessToken = generateAccessToken(user); - Post post = new Post(user, "첫 글", "내용", null); - postRepository.save(post); - MockMultipartFile multipartFile = new MockMultipartFile( "multipartFile", "test.png", @@ -92,8 +90,6 @@ void uploadFile_success() throws Exception { ResultActions resultActions = mvc.perform( multipart("/api/file/upload") // 👈 post() 대신 multipart() 사용 .file(multipartFile) // 파일 필드 - .param("entityType", "POST") // DTO 필드 매핑 - .param("entityId", post.getId().toString()) .header("Authorization", "Bearer " + accessToken) .characterEncoding("UTF-8") ); @@ -105,7 +101,7 @@ void uploadFile_success() throws Exception { } @Test - @DisplayName("파일 업로드 실패 - 파일이 없는 경우") + @DisplayName("파일 업로드 실패 - 파일 입력이 없는 경우") void uploadFile_fail_noFile() throws Exception { // given User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); @@ -115,14 +111,9 @@ void uploadFile_fail_noFile() throws Exception { String accessToken = generateAccessToken(user); - Post post = new Post(user, "첫 글", "내용", null); - postRepository.save(post); - // when ResultActions resultActions = mvc.perform( multipart("/api/file/upload") // 👈 post() 대신 multipart() 사용 - .param("entityType", "POST") // DTO 필드 매핑 - .param("entityId", post.getId().toString()) .header("Authorization", "Bearer " + accessToken) .characterEncoding("UTF-8") ); @@ -133,4 +124,314 @@ void uploadFile_fail_noFile() throws Exception { .andExpect(jsonPath("$.message").value("잘못된 요청입니다.")) .andDo(print()); } + + @Test + @DisplayName("파일 조회 성공") + void readFile_success() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + MockMultipartFile multipartFile = new MockMultipartFile( + "multipartFile", + "test.png", + "image/png", + "test".getBytes() + ); + + FileUploadResponseDto fileUploadResponseDto = fileService.uploadFile(multipartFile, user.getId()); + + // when + ResultActions resultActions = mvc.perform( + get("/api/file/read/" + fileUploadResponseDto.getAttachmentId()) + .header("Authorization", "Bearer " + accessToken) + ); + + // then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.message").value("파일 조회 성공")) + .andExpect(jsonPath("$.data.publicURL").value(fileUploadResponseDto.getPublicURL())) + .andDo(print()); + + } + + @Test + @DisplayName("파일 조회 실패 - 없는 파일 정보 조회") + void readFile_failWhenFileNotFound() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + Long attachmentId = 10000000L; + + // when + ResultActions resultActions = mvc.perform( + get("/api/file/read/" + attachmentId) + .header("Authorization", "Bearer " + accessToken) + ); + + // then + resultActions.andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("FILE_004")) + .andExpect(jsonPath("$.message").value("파일 정보를 찾을 수 없습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("파일 수정 성공") + void updateFile_success() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + // 기존(삭제할) 파일 정보 + String path = "test.png"; + String contentType = "image/png"; + MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); + FileUploadResponseDto fileUploadResponseDto = fileService.uploadFile(oldFile, user.getId()); + + // 새 파일 정보 + String newPath = "newTest.png"; + MockMultipartFile newFile = new MockMultipartFile("multipartFile", newPath, contentType, "newTest".getBytes()); + + // when + ResultActions resultActions = mvc.perform( + multipart("/api/file/update/" + fileUploadResponseDto.getAttachmentId()) + .file(newFile) // 파일 필드 + .header("Authorization", "Bearer " + accessToken) + .characterEncoding("UTF-8") + .with(request -> { + request.setMethod("PUT"); + return request; + }) // PUT 매핑인 것을 나타낸다. + ); + + // then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.message").value("파일 업데이트 성공")) + .andExpect(jsonPath("$.data.publicURL", not(fileUploadResponseDto.getPublicURL()))) + .andDo(print()); + } + + @Test + @DisplayName("파일 수정 실패 - 없는 아이디 조회") + void updateFile_failWhenFileNotFound() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + // 새 파일 정보 + String newPath = "newTest.png"; + String contentType = "image/png"; + MockMultipartFile newFile = new MockMultipartFile("multipartFile", newPath, contentType, "newTest".getBytes()); + + Long attachmentId = 1000000L; + + // when + ResultActions resultActions = mvc.perform( + multipart("/api/file/update/" + attachmentId) + .file(newFile) // 파일 필드 + .header("Authorization", "Bearer " + accessToken) + .characterEncoding("UTF-8") + .with(request -> { + request.setMethod("PUT"); + return request; + }) // PUT 매핑인 것을 나타낸다. + ); + + // then + resultActions.andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("FILE_004")) + .andExpect(jsonPath("$.message").value("파일 정보를 찾을 수 없습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("파일 수정 실패 - 파일 입력이 없는 경우") + void updateFile_fail_noFile() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + // 기존(삭제할) 파일 정보 + String path = "test.png"; + String contentType = "image/png"; + MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); + FileUploadResponseDto fileUploadResponseDto = fileService.uploadFile(oldFile, user.getId()); + + // when + ResultActions resultActions = mvc.perform( + multipart("/api/file/update/" + fileUploadResponseDto.getAttachmentId()) + .header("Authorization", "Bearer " + accessToken) + .characterEncoding("UTF-8") + .with(request -> { + request.setMethod("PUT"); + return request; + }) // PUT 매핑인 것을 나타낸다. + ); + + // then + resultActions.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("COMMON_400")) + .andExpect(jsonPath("$.message").value("잘못된 요청입니다.")) + .andDo(print()); + } + + @Test + @DisplayName("파일 수정 성공 - 파일 접근 권한 없음") + void updateFile_failWhenAccessDenied() throws Exception { + // given + User writer = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + writer.setUserProfile(new UserProfile(writer, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + writer.setUserStatus(UserStatus.ACTIVE); + userRepository.save(writer); + + User reader = User.createUser("reader", "reader@example.com", passwordEncoder.encode("P@ssw0rd!")); + reader.setUserProfile(new UserProfile(reader, "홍길순", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + reader.setUserStatus(UserStatus.ACTIVE); + userRepository.save(reader); + + String accessToken = generateAccessToken(reader); + + // 기존(삭제할) 파일 정보 + String path = "test.png"; + String contentType = "image/png"; + MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); + FileUploadResponseDto fileUploadResponseDto = fileService.uploadFile(oldFile, writer.getId()); + + // 새 파일 정보 + String newPath = "newTest.png"; + MockMultipartFile newFile = new MockMultipartFile("multipartFile", newPath, contentType, "newTest".getBytes()); + + // when + ResultActions resultActions = mvc.perform( + multipart("/api/file/update/" + fileUploadResponseDto.getAttachmentId()) + .file(newFile) // 파일 필드 + .header("Authorization", "Bearer " + accessToken) + .characterEncoding("UTF-8") + .with(request -> { + request.setMethod("PUT"); + return request; + }) // PUT 매핑인 것을 나타낸다. + ); + + // then + resultActions.andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FILE_003")) + .andExpect(jsonPath("$.message").value("파일을 접근할 권한이 없습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("파일 삭제 성공") + void deleteFile_success() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + // 기존(삭제할) 파일 정보 + String path = "test.png"; + String contentType = "image/png"; + MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); + FileUploadResponseDto fileUploadResponseDto = fileService.uploadFile(oldFile, user.getId()); + + // when + ResultActions resultActions = mvc.perform( + delete("/api/file/delete/" + fileUploadResponseDto.getAttachmentId()) + .header("Authorization", "Bearer " + accessToken) + ); + + // then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.message").value("파일 삭제 성공")) + .andDo(print()); + } + + @Test + @DisplayName("파일 삭제 실패 - 파일 접근 권한 없음") + void deleteFile_failWhenAccessDenied() throws Exception { + // given + User writer = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + writer.setUserProfile(new UserProfile(writer, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + writer.setUserStatus(UserStatus.ACTIVE); + userRepository.save(writer); + + User reader = User.createUser("reader", "reader@example.com", passwordEncoder.encode("P@ssw0rd!")); + reader.setUserProfile(new UserProfile(reader, "홍길순", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + reader.setUserStatus(UserStatus.ACTIVE); + userRepository.save(reader); + + String accessToken = generateAccessToken(reader); + + // 기존(삭제할) 파일 정보 + String path = "test.png"; + String contentType = "image/png"; + MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); + FileUploadResponseDto fileUploadResponseDto = fileService.uploadFile(oldFile, writer.getId()); + + // when + ResultActions resultActions = mvc.perform( + delete("/api/file/delete/" + fileUploadResponseDto.getAttachmentId()) + .header("Authorization", "Bearer " + accessToken) + ); + + // then + resultActions.andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FILE_003")) + .andExpect(jsonPath("$.message").value("파일을 접근할 권한이 없습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("파일 삭제 실패 - 없는 파일 정보 조회") + void deleteFile_failWhenFileNotFound() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + Long attachmentId = 1000000L; + + // when + ResultActions resultActions = mvc.perform( + delete("/api/file/delete/" + attachmentId) + .header("Authorization", "Bearer " + accessToken) + ); + + // then + resultActions.andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("FILE_004")) + .andExpect(jsonPath("$.message").value("파일 정보를 찾을 수 없습니다.")) + .andDo(print()); + } } \ No newline at end of file diff --git a/src/test/java/com/back/domain/file/service/FileServiceTest.java b/src/test/java/com/back/domain/file/service/FileServiceTest.java index 00fb020b..2d6124d8 100644 --- a/src/test/java/com/back/domain/file/service/FileServiceTest.java +++ b/src/test/java/com/back/domain/file/service/FileServiceTest.java @@ -49,9 +49,6 @@ class FileServiceTest { @Autowired private PasswordEncoder passwordEncoder; - @Autowired - private PostRepository postRepository; - @AfterEach public void tearDown() { s3Mock.stop(); @@ -65,9 +62,6 @@ void uploadFile() { user.setUserStatus(UserStatus.ACTIVE); userRepository.save(user); - Post post = new Post(user, "제목", "내용", null); - postRepository.save(post); - String path = "test.png"; String contentType = "image/png"; String dirName = "test"; @@ -75,11 +69,12 @@ void uploadFile() { MockMultipartFile file = new MockMultipartFile("test", path, contentType, "test".getBytes()); // when - FileUploadResponseDto res = fileService.uploadFile(file, EntityType.POST, post.getId(), user.getId()); + FileUploadResponseDto res = fileService.uploadFile(file, user.getId()); // then - assertThat(res.getImageUrl()).contains(path); - assertThat(res.getImageUrl()).contains(dirName); + assertThat(res.getAttachmentId()).isPositive(); + assertThat(res.getPublicURL()).contains(path); + assertThat(res.getPublicURL()).contains(dirName); } @Test @@ -90,21 +85,18 @@ void readFile() { user.setUserStatus(UserStatus.ACTIVE); userRepository.save(user); - Post post = new Post(user, "제목", "내용", null); - postRepository.save(post); - String path = "test.png"; String contentType = "image/png"; String dirName = "test"; MockMultipartFile file = new MockMultipartFile("test", path, contentType, "test".getBytes()); - fileService.uploadFile(file, EntityType.POST, post.getId(), user.getId()); + Long attachmentId = fileService.uploadFile(file, user.getId()).getAttachmentId(); //when - FileReadResponseDto res = fileService.getFile(EntityType.POST, post.getId()); + FileReadResponseDto res = fileService.getFile(attachmentId); // then - assertThat(res.getImageUrl()).contains(path); - assertThat(res.getImageUrl()).contains(dirName); + assertThat(res.getPublicURL()).contains(path); + assertThat(res.getPublicURL()).contains(dirName); } @Test @@ -115,15 +107,11 @@ void updateFile() { user.setUserStatus(UserStatus.ACTIVE); userRepository.save(user); - Post post = new Post(user, "제목", "내용", null); - postRepository.save(post); - // 기존(삭제할) 파일 정보 String path = "test.png"; String contentType = "image/png"; - String dirName = "test"; MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); - fileService.uploadFile(oldFile, EntityType.POST, post.getId(), user.getId()); + Long attachmentId = fileService.uploadFile(oldFile, user.getId()).getAttachmentId(); // 새 파일 정보 String newPath = "newTest.png"; @@ -131,12 +119,12 @@ void updateFile() { MockMultipartFile newFile = new MockMultipartFile("newTest", newPath, contentType, "newTest".getBytes()); // when - fileService.updateFile(newFile, EntityType.POST, post.getId(), user.getId()); - FileReadResponseDto res = fileService.getFile(EntityType.POST, post.getId()); + fileService.updateFile(attachmentId, newFile, user.getId()); + FileReadResponseDto res = fileService.getFile(attachmentId); // then - assertThat(res.getImageUrl()).contains(newPath); - assertThat(res.getImageUrl()).contains(newDirName); + assertThat(res.getPublicURL()).contains(newPath); + assertThat(res.getPublicURL()).contains(newDirName); } @Test @@ -147,23 +135,20 @@ void deleteFile() { user.setUserStatus(UserStatus.ACTIVE); userRepository.save(user); - Post post = new Post(user, "제목", "내용", null); - postRepository.save(post); - // 기존(삭제할) 파일 정보 String path = "test.png"; String contentType = "image/png"; - String dirName = "test"; MockMultipartFile oldFile = new MockMultipartFile("test", path, contentType, "test".getBytes()); - fileService.uploadFile(oldFile, EntityType.POST, post.getId(), user.getId()); + Long attachmentId = fileService.uploadFile(oldFile, user.getId()).getAttachmentId(); + // when - fileService.deleteFile(EntityType.POST, post.getId(), user.getId()); + fileService.deleteFile(attachmentId, user.getId()); CustomException exception = assertThrows(CustomException.class, () -> { - fileService.getFile(EntityType.POST, post.getId()); + fileService.getFile(attachmentId); }); // then - assertEquals(ErrorCode.ATTACHMENT_MAPPING_NOT_FOUND, exception.getErrorCode()); + assertEquals(ErrorCode.FILE_NOT_FOUND, exception.getErrorCode()); } } \ No newline at end of file