From 2a5064c7ded2d7ad0d4686b7ed15cbd5d7296c9e Mon Sep 17 00:00:00 2001 From: peanut990 Date: Mon, 30 Dec 2024 10:56:45 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20S3=20=EC=84=A4=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C,?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 모든 응답 헤더에 cors 헤더 추가 Refs: #4 --- BACK/spring-app/build.gradle | 3 +- .../com/starchive/springapp/S3Config.java | 33 ++++++++ .../com/starchive/springapp/S3Service.java | 81 +++++++++++++++++++ .../springapp/global/ErrorMessage.java | 7 ++ .../exception/GlobalExceptionHandler.java | 10 +++ 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java diff --git a/BACK/spring-app/build.gradle b/BACK/spring-app/build.gradle index c9afb3a..9c55be3 100644 --- a/BACK/spring-app/build.gradle +++ b/BACK/spring-app/build.gradle @@ -28,7 +28,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' - + + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' compileOnly 'org.projectlombok:lombok' diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java b/BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java new file mode 100644 index 0000000..e194856 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java @@ -0,0 +1,33 @@ +package com.starchive.springapp; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3Client() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java b/BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java new file mode 100644 index 0000000..d45fe4d --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java @@ -0,0 +1,81 @@ +package com.starchive.springapp; + +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.starchive.springapp.global.ErrorMessage; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@Service +@RequiredArgsConstructor +public class S3Service { + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Value("${spring.servlet.multipart.max-file-size}") + private String maxSizeString; + + // 단일 파일 저장 + public String saveFile(MultipartFile file) { + String randomFilename = generateRandomFilename(file); + + log.info("File upload started: " + randomFilename); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + try { + amazonS3.putObject(bucket, randomFilename, file.getInputStream(), metadata); + } catch (AmazonS3Exception e) { + log.error("Amazon S3 error while uploading file: " + e.getMessage()); + throw new RuntimeException(ErrorMessage.FAIL_UPLOAD); + } catch (SdkClientException e) { + log.error("AWS SDK client error while uploading file: " + e.getMessage()); + throw new RuntimeException(ErrorMessage.FAIL_UPLOAD); + } catch (IOException e) { + log.error("IO error while uploading file: " + e.getMessage()); + throw new RuntimeException(ErrorMessage.FAIL_UPLOAD); + } + + log.info("File upload completed: " + randomFilename); + + return amazonS3.getUrl(bucket, randomFilename).toString(); + } + + public void deleteObject(String key) { + amazonS3.deleteObject(bucket, key); + log.info("Deleted S3 Object: {}", key); + } + + // 랜덤파일명 생성 (파일명 중복 방지) + private String generateRandomFilename(MultipartFile multipartFile) { + String originalFilename = multipartFile.getOriginalFilename(); + String fileExtension = validateFileExtension(originalFilename); + String randomFilename = UUID.randomUUID() + "." + fileExtension; + return randomFilename; + } + + // 파일 확장자 체크 + private String validateFileExtension(String originalFilename) { + String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase(); + List allowedExtensions = Arrays.asList("jpg", "png", "gif", "jpeg"); + + if (!allowedExtensions.contains(fileExtension)) { + throw new RuntimeException(ErrorMessage.NOT_IMAGE_EXTENSION); + } + return fileExtension; + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java new file mode 100644 index 0000000..89a7212 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java @@ -0,0 +1,7 @@ +package com.starchive.springapp.global; + +public class ErrorMessage { + final public static String FAIL_UPLOAD = "이미지 업로드 실패"; + final public static String NOT_IMAGE_EXTENSION = "올바르지 않은 이미지 확장자(허용 확장자: \"jpg\", \"png\", \"gif\", \"jpeg\""; + final public static String INVALID_FILE_SIZE = "파일 크기가 너무 큽니다. 최대 이미지 크기: 5MB"; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java index 28f3b81..0b6bce9 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java @@ -1,11 +1,15 @@ package com.starchive.springapp.global.exception; +import static com.starchive.springapp.global.ErrorMessage.INVALID_FILE_SIZE; + import java.util.HashMap; import java.util.Map; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MaxUploadSizeExceededException; @RestControllerAdvice public class GlobalExceptionHandler { @@ -21,4 +25,10 @@ public ResponseEntity> handleValidationExceptions(MethodArgu return ResponseEntity.badRequest().body(errors); } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException ex) { + return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE) + .body(INVALID_FILE_SIZE); + } } From 384471bab31ad6e5ac38c128e20fe80a518cb128 Mon Sep 17 00:00:00 2001 From: peanut990 Date: Mon, 30 Dec 2024 11:00:07 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "/postImage" 를 통해 요청된 이미지를 s3에 저장 - s3 이미지 경로와 업로드 날짜를 db에 저장 - 매일 새벽 2시 기준 전날 이후로 업로드만 된 이미지 db와 s3에서 삭제 Refs: #4 --- .../global/config/SchedulerConfig.java | 9 ++++ .../image/controller/PostImageController.java | 23 ++++++++ .../springapp/image/domain/PostImage.java | 36 +++++++++++++ .../image/repository/PostImageRepository.java | 18 +++++++ .../image/service/PostImageService.java | 53 +++++++++++++++++++ 5 files changed, 139 insertions(+) create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/global/config/SchedulerConfig.java create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/image/domain/PostImage.java create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/image/repository/PostImageRepository.java create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/config/SchedulerConfig.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/config/SchedulerConfig.java new file mode 100644 index 0000000..d90bf10 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/config/SchedulerConfig.java @@ -0,0 +1,9 @@ +package com.starchive.springapp.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulerConfig { +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java new file mode 100644 index 0000000..3743260 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java @@ -0,0 +1,23 @@ +package com.starchive.springapp.image.controller; + +import com.starchive.springapp.image.service.PostImageService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +public class PostImageController { + private final PostImageService postImageService; + + @PostMapping("/postImage") + public ResponseEntity imageUpload(@RequestParam("image") MultipartFile image) { + String imagePath = postImageService.uploadImage(image); + + return ResponseEntity.ok(imagePath); + + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/domain/PostImage.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/domain/PostImage.java new file mode 100644 index 0000000..6e7bfd2 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/domain/PostImage.java @@ -0,0 +1,36 @@ +package com.starchive.springapp.image.domain; + +import static jakarta.persistence.FetchType.LAZY; + +import com.starchive.springapp.post.domain.Post; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PostImage { + @Id + @GeneratedValue + private Long id; + + @Lob + String imagePath; + + LocalDateTime uploadDate; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "postId") + Post post; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/repository/PostImageRepository.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/repository/PostImageRepository.java new file mode 100644 index 0000000..8afd157 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/repository/PostImageRepository.java @@ -0,0 +1,18 @@ +package com.starchive.springapp.image.repository; + +import com.starchive.springapp.image.domain.PostImage; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PostImageRepository extends JpaRepository { + @Query("SELECT p FROM PostImage p WHERE p.post IS NULL AND p.uploadDate < :cutoffDate") + List findOldOrphanedPostImages(@Param("cutoffDate") LocalDateTime cutoffDate); + + @Modifying + @Query("DELETE FROM PostImage p WHERE p.id IN :ids") + void deleteByIds(@Param("ids") List ids); +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java new file mode 100644 index 0000000..2e03fce --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java @@ -0,0 +1,53 @@ +package com.starchive.springapp.image.service; + +import com.starchive.springapp.S3Service; +import com.starchive.springapp.image.domain.PostImage; +import com.starchive.springapp.image.repository.PostImageRepository; +import java.net.URI; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class PostImageService { + private final S3Service s3Service; + private final PostImageRepository postImageRepository; + + public String uploadImage(MultipartFile image) { + String imagePath = s3Service.saveFile(image); + PostImage postImage = PostImage.builder() + .imagePath(imagePath) + .uploadDate(LocalDateTime.now()) + .build(); + + postImageRepository.save(postImage); + + return imagePath; + } + + @Scheduled(cron = "0 0 2 * * ?") + public void deleteOldOrphanedPostImages() { + LocalDateTime cutoffDate = LocalDateTime.now().minusDays(1); // 하루 전 + + List oldOrphanedPostImages = postImageRepository.findOldOrphanedPostImages(cutoffDate); + oldOrphanedPostImages.forEach(postImage -> { + s3Service.deleteObject(extractKeyFromUrl(postImage.getImagePath())); + postImageRepository.delete(postImage); + log.info("Deleted old orphaned PostImages: {}", postImage.getImagePath()); + }); + + } + + private String extractKeyFromUrl(String url) { + URI uri = URI.create(url); + return uri.getPath().substring(1); // 첫 번째 '/' 제거 + } +} From 40103e388ff82aa9807a2552e452b326c761aae0 Mon Sep 17 00:00:00 2001 From: peanut990 Date: Tue, 31 Dec 2024 12:13:26 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=85=8C=EC=9D=B4=EB=B8=94=EB=AA=85,=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - categorys -> categories --- .../category/controller/CategoryController.java | 12 ++++++------ .../springapp/category/domain/Category.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java index a9e6cde..85e4938 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java @@ -21,19 +21,19 @@ public class CategoryController { private final CategoryService categoryService; private final HashTagService hashTagService; - @GetMapping("/categorys") + @GetMapping("/categories") @Operation(summary = "카테고리 목록 전체 조회") public ResponseEntity>> showCategories() { - List categorys = categoryService.findAll(); - ResponseDto> listResponseDto = new ResponseDto<>(categorys); + List categories = categoryService.findAll(); + ResponseDto> listResponseDto = new ResponseDto<>(categories); return ResponseEntity.ok(listResponseDto); } - @GetMapping("/categorys/{categoryId}/hashtags") + @GetMapping("/categories/{categoryId}/hashtags") @Operation(summary = "특정 카테고리에 포함되는 해쉬태그 목록 조회") public ResponseEntity>> showHashTags(@PathVariable("categoryId") Long categoryId) { - List categorys = hashTagService.findManyByCategory(categoryId); - ResponseDto> listResponseDto = new ResponseDto<>(categorys); + List categories = hashTagService.findManyByCategory(categoryId); + ResponseDto> listResponseDto = new ResponseDto<>(categories); return ResponseEntity.ok(listResponseDto); } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java index 77b9266..b4c63e8 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java @@ -19,7 +19,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@Table(name = "Categorys") +@Table(name = "Categories") public class Category { @Id @GeneratedValue From 0a449534f8f6b479195b7412766590adffb662e9 Mon Sep 17 00:00:00 2001 From: peanut990 Date: Tue, 31 Dec 2024 12:42:07 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=9B=84=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EC=8B=9C=20DTO=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경된 반환 형식 - 이미지id - 이미지경로 Refs: #4 --- .../image/controller/PostImageController.java | 16 +++++++++++----- .../springapp/image/dto/PostImageDto.java | 11 +++++++++++ .../image/service/PostImageService.java | 5 +++-- 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 BACK/spring-app/src/main/java/com/starchive/springapp/image/dto/PostImageDto.java diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java index 3743260..407eef8 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/controller/PostImageController.java @@ -1,23 +1,29 @@ package com.starchive.springapp.image.controller; +import com.starchive.springapp.global.dto.ResponseDto; +import com.starchive.springapp.image.dto.PostImageDto; import com.starchive.springapp.image.service.PostImageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +@Tag(name = "게시글 이미지") @RestController @RequiredArgsConstructor public class PostImageController { private final PostImageService postImageService; - @PostMapping("/postImage") - public ResponseEntity imageUpload(@RequestParam("image") MultipartFile image) { - String imagePath = postImageService.uploadImage(image); - - return ResponseEntity.ok(imagePath); + @Operation(summary = "게시글에 포함될 이미지 업로드") + @PostMapping(value = "/postImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> imageUpload(@RequestParam("image") MultipartFile image) { + PostImageDto postImageDto = postImageService.uploadImage(image); + return ResponseEntity.ok(new ResponseDto<>(postImageDto)); } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/dto/PostImageDto.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/dto/PostImageDto.java new file mode 100644 index 0000000..91a47e2 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/dto/PostImageDto.java @@ -0,0 +1,11 @@ +package com.starchive.springapp.image.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class PostImageDto { + Long id; + String imagePath; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java index 2e03fce..1fdaa6d 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java @@ -2,6 +2,7 @@ import com.starchive.springapp.S3Service; import com.starchive.springapp.image.domain.PostImage; +import com.starchive.springapp.image.dto.PostImageDto; import com.starchive.springapp.image.repository.PostImageRepository; import java.net.URI; import java.time.LocalDateTime; @@ -21,7 +22,7 @@ public class PostImageService { private final S3Service s3Service; private final PostImageRepository postImageRepository; - public String uploadImage(MultipartFile image) { + public PostImageDto uploadImage(MultipartFile image) { String imagePath = s3Service.saveFile(image); PostImage postImage = PostImage.builder() .imagePath(imagePath) @@ -30,7 +31,7 @@ public String uploadImage(MultipartFile image) { postImageRepository.save(postImage); - return imagePath; + return new PostImageDto(postImage.getId(), postImage.getImagePath()); } @Scheduled(cron = "0 0 2 * * ?") From 8e273a0802f15f8243da62ec98e63108fcedf7c0 Mon Sep 17 00:00:00 2001 From: peanut990 Date: Tue, 31 Dec 2024 19:46:27 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20s3=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs: #4 --- BACK/spring-app/build.gradle | 4 +- .../starchive/springapp/s3/S3MockConfig.java | 44 +++++++++++++++ .../starchive/springapp/s3/S3ServiceTest.java | 53 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java create mode 100644 BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java diff --git a/BACK/spring-app/build.gradle b/BACK/spring-app/build.gradle index 9c55be3..34a6269 100644 --- a/BACK/spring-app/build.gradle +++ b/BACK/spring-app/build.gradle @@ -28,7 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' - + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' compileOnly 'org.projectlombok:lombok' @@ -39,6 +39,8 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.findify:s3mock_2.13:0.2.6' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java new file mode 100644 index 0000000..afd9eab --- /dev/null +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java @@ -0,0 +1,44 @@ +package com.starchive.springapp.s3; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.AnonymousAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import io.findify.s3mock.S3Mock; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +class S3MockConfig { + + public String bucket = "testbucket-in-config"; + + private String region = "na-east-1"; + + @Bean + public S3Mock s3Mock() { + return new S3Mock.Builder() + .withPort(8001) + .withInMemoryBackend() + .build(); + } + + @Bean + public AmazonS3 amazonS3(S3Mock s3Mock) { + s3Mock.start(); + AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration( + "http://localhost:8001", region); + + AmazonS3 client = AmazonS3ClientBuilder + .standard() + .withPathStyleAccessEnabled(true) + .withEndpointConfiguration(endpoint) + .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) + .build(); + + client.createBucket(bucket); + + return client; + } +} \ No newline at end of file diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java new file mode 100644 index 0000000..0c575c5 --- /dev/null +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java @@ -0,0 +1,53 @@ +package com.starchive.springapp.s3; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.amazonaws.services.s3.AmazonS3; +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.mock.web.MockMultipartFile; + +@SpringBootTest(properties = "spring.profiles.active=test") +@Import(S3MockConfig.class) +class S3ServiceTest { + + @Autowired + private AmazonS3 amazonS3; + + @Autowired + private S3Service s3Service; + + @Test + public void 이미지_업로드_테스트() throws Exception { + //given + String path = "test.png"; + String contentType = "image/png"; + String bucket = "testbucket-in-config"; + + MockMultipartFile file = new MockMultipartFile("test", path, contentType, "test".getBytes()); + //when + Field field = s3Service.getClass().getDeclaredField("bucket"); + field.setAccessible(true); + field.set(s3Service, bucket); + + String urlPath = s3Service.saveFile(file); + + //then + assertThat(urlPath).contains(bucket); + + amazonS3.listBuckets().forEach(System.out::println); + amazonS3.listObjects(bucket).getObjectSummaries().forEach(System.out::println); + + assertThat(amazonS3.listObjects(bucket).getObjectSummaries().size()).isEqualTo(1); + + String key = urlPath.substring(urlPath.lastIndexOf("/") + 1); + + assertThat(amazonS3.listObjects(bucket).getObjectSummaries().get(0).getKey()) + .isEqualTo(key); + + } + +} \ No newline at end of file From 92053ed6c28e43c06b0ccfd93683426a8ab34391 Mon Sep 17 00:00:00 2001 From: peanut990 Date: Tue, 31 Dec 2024 19:49:10 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=9D=B4=EB=8F=99,=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - s3 관련 파일 s3 디렉토리 밑으로 이동 - 테스트 코드 엔드포인트 "/categorys" -> "categories" 로 수정 Refs: #4 --- .../starchive/springapp/image/service/PostImageService.java | 2 +- .../main/java/com/starchive/springapp/{ => s3}/S3Config.java | 4 +++- .../main/java/com/starchive/springapp/{ => s3}/S3Service.java | 4 ++-- .../springapp/category/controller/CategoryControllerTest.java | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) rename BACK/spring-app/src/main/java/com/starchive/springapp/{ => s3}/S3Config.java (90%) rename BACK/spring-app/src/main/java/com/starchive/springapp/{ => s3}/S3Service.java (96%) diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java b/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java index 1fdaa6d..d96e3e3 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/image/service/PostImageService.java @@ -1,9 +1,9 @@ package com.starchive.springapp.image.service; -import com.starchive.springapp.S3Service; import com.starchive.springapp.image.domain.PostImage; import com.starchive.springapp.image.dto.PostImageDto; import com.starchive.springapp.image.repository.PostImageRepository; +import com.starchive.springapp.s3.S3Service; import java.net.URI; import java.time.LocalDateTime; import java.util.List; diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java similarity index 90% rename from BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java rename to BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java index e194856..fbec8a4 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/S3Config.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java @@ -1,4 +1,4 @@ -package com.starchive.springapp; +package com.starchive.springapp.s3; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSStaticCredentialsProvider; @@ -8,8 +8,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; @Configuration +@Profile("!test") public class S3Config { @Value("${cloud.aws.credentials.access-key}") private String accessKey; diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java similarity index 96% rename from BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java rename to BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java index d45fe4d..4572fe3 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/S3Service.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java @@ -1,4 +1,4 @@ -package com.starchive.springapp; +package com.starchive.springapp.s3; import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3; @@ -31,7 +31,7 @@ public class S3Service { public String saveFile(MultipartFile file) { String randomFilename = generateRandomFilename(file); - log.info("File upload started: " + randomFilename); + log.info("File upload started: {}, bucketName: {}", randomFilename, bucket); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(file.getSize()); diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/category/controller/CategoryControllerTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/category/controller/CategoryControllerTest.java index 6b50b7e..7646e34 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/category/controller/CategoryControllerTest.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/category/controller/CategoryControllerTest.java @@ -51,7 +51,7 @@ class CategoryControllerTest { categoryRepository.save(child3); // when - mockMvc.perform(get("/categorys") + mockMvc.perform(get("/categories") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.data").isArray()) @@ -98,7 +98,7 @@ class CategoryControllerTest { entityManager.persist(postHashTag2); // When & Then - mockMvc.perform(get("/categorys/{categoryId}/hashtags", category.getId()) + mockMvc.perform(get("/categories/{categoryId}/hashtags", category.getId()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.data").isArray()) From 147bae99cd2e52bcbcb31d1212b85eabd362979d Mon Sep 17 00:00:00 2001 From: peanut990 Date: Tue, 31 Dec 2024 23:44:05 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs: #4 --- .../controller/PostImageControllerTest.java | 54 +++++++++++++++++ .../image/service/PostImageServiceTest.java | 59 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 BACK/spring-app/src/test/java/com/starchive/springapp/image/controller/PostImageControllerTest.java create mode 100644 BACK/spring-app/src/test/java/com/starchive/springapp/image/service/PostImageServiceTest.java diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/image/controller/PostImageControllerTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/image/controller/PostImageControllerTest.java new file mode 100644 index 0000000..4dbfb6a --- /dev/null +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/image/controller/PostImageControllerTest.java @@ -0,0 +1,54 @@ +package com.starchive.springapp.image.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.starchive.springapp.image.dto.PostImageDto; +import com.starchive.springapp.image.service.PostImageService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(PostImageController.class) +@ExtendWith(MockitoExtension.class) +class PostImageControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private PostImageService postImageService; + + @InjectMocks + private PostImageController postImageController; + + @Test + public void 이미지_업로드_컨트롤러_단위_테스트() throws Exception { + //given + String path = "test.png"; + String contentType = "image/png"; + String content = "테스트 내용"; + + MockMultipartFile file = new MockMultipartFile("image", path, contentType, content.getBytes()); + + Mockito.when(postImageService.uploadImage(Mockito.any(MockMultipartFile.class))) + .thenReturn(new PostImageDto(1L, "Https://" + path)); + //when + //then + mockMvc.perform(multipart("/postImage") + .file(file) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + ).andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1L)) + .andExpect(jsonPath("$.data.imagePath").value("Https://" + path)); + } + +} \ No newline at end of file diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/image/service/PostImageServiceTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/image/service/PostImageServiceTest.java new file mode 100644 index 0000000..d7fb81a --- /dev/null +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/image/service/PostImageServiceTest.java @@ -0,0 +1,59 @@ +package com.starchive.springapp.image.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.starchive.springapp.image.domain.PostImage; +import com.starchive.springapp.image.dto.PostImageDto; +import com.starchive.springapp.image.repository.PostImageRepository; +import com.starchive.springapp.s3.S3Service; +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; + +@ExtendWith(MockitoExtension.class) +class PostImageServiceTest { + @Mock + private S3Service s3Service; + + @Mock + private PostImageRepository postImageRepository; + + @InjectMocks + private PostImageService postImageService; + + @Test + public void 이미지_업로드_서비스로직_단위_테스트() throws Exception { + //given + String path = "test.png"; + String contentType = "image/png"; + String content = "테스트 내용"; + + MockMultipartFile file = new MockMultipartFile("test", path, contentType, content.getBytes()); + + when(s3Service.saveFile(Mockito.any(MockMultipartFile.class))).thenReturn("https://test.png"); + when(postImageRepository.save(Mockito.any())).then(invocation -> { + PostImage postImage = invocation.getArgument(0); + Field field = postImage.getClass().getDeclaredField("id"); + field.setAccessible(true); + field.set(postImage, 1L); + return postImage; + }); + + //when + PostImageDto postImageDto = postImageService.uploadImage(file); + + //then + verify(s3Service).saveFile(Mockito.any(MockMultipartFile.class)); + verify(postImageRepository).save(Mockito.any()); + assertThat(postImageDto.getId()).isEqualTo(1L); + assertThat(postImageDto.getImagePath()).contains("test.png"); + } + +} \ No newline at end of file From a1e51c1b95c0560f521c2620a04f738b26a36632 Mon Sep 17 00:00:00 2001 From: peanut990 Date: Fri, 3 Jan 2025 01:41:47 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20s3Client=20=EC=9D=98=20=EB=B9=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=EC=9D=B4=20=EC=9E=98=EB=AA=BB=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EB=90=98=EC=96=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=EC=9D=B4=20=EC=8B=A4=ED=8C=A8=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - amazonS3Client의 @Primary을 통해 기본 빈으로 주입되도록 변경 (프로덕션 코드에 사용) - S3Mock을 사용하는 테스트용 s3Client 빈 이름을 "MockS3Client"로 명시해서 테스트 코드 실행시 주입되도록 수정 (테스트 코드에 사용) Refs: #4 --- BACK/spring-app/build.gradle | 6 +++++ .../com/starchive/springapp/s3/S3Config.java | 6 +++-- .../com/starchive/springapp/s3/S3Service.java | 4 ---- .../starchive/springapp/s3/S3MockConfig.java | 14 +++++++---- .../starchive/springapp/s3/S3ServiceTest.java | 23 ++++++++++++++----- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/BACK/spring-app/build.gradle b/BACK/spring-app/build.gradle index 34a6269..0d8ec50 100644 --- a/BACK/spring-app/build.gradle +++ b/BACK/spring-app/build.gradle @@ -48,3 +48,9 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +tasks.withType(Test) { + systemProperty "spring.profiles.active", "test" +} + + diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java index fbec8a4..51ecfec 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Config.java @@ -8,10 +8,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Primary; @Configuration -@Profile("!test") public class S3Config { @Value("${cloud.aws.credentials.access-key}") private String accessKey; @@ -23,6 +22,7 @@ public class S3Config { private String region; @Bean + @Primary public AmazonS3 amazonS3Client() { AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); @@ -32,4 +32,6 @@ public AmazonS3 amazonS3Client() { .withRegion(region) .build(); } + + } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java index 4572fe3..15b560c 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/s3/S3Service.java @@ -24,13 +24,9 @@ public class S3Service { @Value("${cloud.aws.s3.bucket}") private String bucket; - @Value("${spring.servlet.multipart.max-file-size}") - private String maxSizeString; - // 단일 파일 저장 public String saveFile(MultipartFile file) { String randomFilename = generateRandomFilename(file); - log.info("File upload started: {}, bucketName: {}", randomFilename, bucket); ObjectMetadata metadata = new ObjectMetadata(); diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java index afd9eab..f01c3fa 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3MockConfig.java @@ -12,9 +12,9 @@ @TestConfiguration class S3MockConfig { - public String bucket = "testbucket-in-config"; + private String testBucketName = "test-bucket-in-memory"; - private String region = "na-east-1"; + private String testRegion = "na-east-1"; @Bean public S3Mock s3Mock() { @@ -24,11 +24,11 @@ public S3Mock s3Mock() { .build(); } - @Bean + @Bean(name = "MockS3Client") public AmazonS3 amazonS3(S3Mock s3Mock) { s3Mock.start(); AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration( - "http://localhost:8001", region); + "http://localhost:8001", testRegion); AmazonS3 client = AmazonS3ClientBuilder .standard() @@ -37,8 +37,12 @@ public AmazonS3 amazonS3(S3Mock s3Mock) { .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) .build(); - client.createBucket(bucket); + client.createBucket(testBucketName); return client; } + + public String getTestBucketName() { + return testBucketName; + } } \ No newline at end of file diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java index 0c575c5..3d0e3cb 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/s3/S3ServiceTest.java @@ -6,17 +6,23 @@ import java.lang.reflect.Field; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; import org.springframework.mock.web.MockMultipartFile; -@SpringBootTest(properties = "spring.profiles.active=test") + +@SpringBootTest @Import(S3MockConfig.class) class S3ServiceTest { @Autowired + @Qualifier("MockS3Client") private AmazonS3 amazonS3; + @Autowired + S3MockConfig s3MockConfig; + @Autowired private S3Service s3Service; @@ -25,14 +31,19 @@ class S3ServiceTest { //given String path = "test.png"; String contentType = "image/png"; - String bucket = "testbucket-in-config"; + String bucket = s3MockConfig.getTestBucketName(); MockMultipartFile file = new MockMultipartFile("test", path, contentType, "test".getBytes()); - //when - Field field = s3Service.getClass().getDeclaredField("bucket"); - field.setAccessible(true); - field.set(s3Service, bucket); + //Reflection s3Service + Field reflectionFieldFor_amazonS3 = s3Service.getClass().getDeclaredField("amazonS3"); + reflectionFieldFor_amazonS3.setAccessible(true); + reflectionFieldFor_amazonS3.set(s3Service, amazonS3); + + Field reflectionFieldFor_bucket = s3Service.getClass().getDeclaredField("bucket"); + reflectionFieldFor_bucket.setAccessible(true); + reflectionFieldFor_bucket.set(s3Service, bucket); + //when String urlPath = s3Service.saveFile(file); //then