Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
import com.somemore.global.imageupload.usecase.ImageUploadUseCase;
import com.somemore.global.imageupload.util.ImageUploadUtils;
import com.somemore.global.imageupload.validator.ImageUploadValidator;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import java.io.IOException;
import java.time.Duration;

import static com.somemore.global.exception.ExceptionMessage.UPLOAD_FAILED;

Expand All @@ -23,6 +25,7 @@
public class ImageUploadService implements ImageUploadUseCase {

private final S3Client s3Client;
private final S3Presigner s3Presigner;
private final ImageUploadValidator imageUploadValidator;

@Value("${cloud.aws.s3.bucket}")
Expand All @@ -34,11 +37,28 @@ public class ImageUploadService implements ImageUploadUseCase {
@Value("${default.image.url}")
private String defaultImageUrl;

public static String DEFAULT_IMAGE_URL;
public static final String DEFAULT_IMAGE_URL = "";
private static final Duration GET_URL_EXPIRATION_DURATION = Duration.ofMinutes(3);

@PostConstruct
private void init() {
DEFAULT_IMAGE_URL = defaultImageUrl;

@Override
public String getPresignedUrl(String filename) {
if(imageUploadValidator.isEmptyFileName(filename)) {
return null;
}

String uniqueFilename = ImageUploadUtils.generateUniqueFileName(filename);

GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
.signatureDuration(GET_URL_EXPIRATION_DURATION)
.getObjectRequest(builder -> builder
.bucket(bucket)
.key(uniqueFilename))
.build();

return s3Presigner.presignGetObject(getObjectPresignRequest)
.url()
.toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

public interface ImageUploadUseCase {
String uploadImage(ImageUploadRequestDto requestDto);
String getPresignedUrl(String filename);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ private ImageUploadUtils() {
}

public static String generateUniqueFileName(String originalFileName) {

String uuid = UUID.randomUUID().toString();

String fileExtension = extractFileExtension(originalFileName);
return uuid + fileExtension;

String fileNameWithoutExtension = originalFileName.substring(0, originalFileName.lastIndexOf("."));

return uuid + "_" + fileNameWithoutExtension + fileExtension;
}

private static String extractFileExtension(String fileName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public void validateFileType(MultipartFile file) {
}
}

@Override
public boolean isEmptyFileName(String fileName) {
return fileName == null || fileName.isEmpty();
}

@Override
public boolean isEmptyFile(MultipartFile file) {
return file == null || file.isEmpty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface ImageUploadValidator {
void validateFileType(MultipartFile file);

boolean isEmptyFile(MultipartFile file);

boolean isEmptyFileName(String fileName);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.somemore.global.imageupload.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
Expand All @@ -17,6 +15,8 @@
import com.somemore.support.IntegrationTestSupport;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -28,6 +28,9 @@
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;

class ImageUploadServiceTest extends IntegrationTestSupport {

Expand Down Expand Up @@ -76,6 +79,7 @@ void testUploadImage_success() {
@DisplayName("이미지 형식이 올바르지 않다면 업로드 할 수 없다.")
@Test
void testUploadImage_failure() throws IOException {

// given
when(multipartFile.getInputStream()).thenThrow(new IOException());

Expand All @@ -88,6 +92,7 @@ void testUploadImage_failure() throws IOException {
@DisplayName("이미지 파일이 없다면 기본 이미지 링크를 반환한다.")
@Test
void uploadImageWithEmptyFile() {

// given
MultipartFile emptyFile = new MockMultipartFile("file", new byte[0]);
given(imageUploadValidator.isEmptyFile(emptyFile)).willReturn(true);
Expand All @@ -99,4 +104,53 @@ void uploadImageWithEmptyFile() {
// then
assertThat(imgUrl).isEqualTo(ImageUploadService.DEFAULT_IMAGE_URL);
}

@DisplayName("유효한 파일명으로 사전 서명된 URL을 생성할 수 있다.")
@Test
void getPresignedUrl_success() {

// given
String filename = "testImage.jpg";

when(imageUploadValidator.isEmptyFileName(filename)).thenReturn(false);

S3Presigner mockPresigner = mock(S3Presigner.class);
ReflectionTestUtils.setField(imageUploadService, "s3Presigner", mockPresigner);

PresignedGetObjectRequest mockPresignedRequest = mock(PresignedGetObjectRequest.class);
URL mockUrl = mock(URL.class);

when(mockUrl.toString()).thenReturn("https://test-bucket.s3.amazonaws.com/unique-test-image.jpg");
when(mockPresignedRequest.url()).thenReturn(mockUrl);
when(mockPresigner.presignGetObject(any(GetObjectPresignRequest.class))).thenReturn(mockPresignedRequest);

// when
String presignedUrl = imageUploadService.getPresignedUrl(filename);

// then
assertNotNull(presignedUrl);
assertTrue(presignedUrl.startsWith("https://test-bucket.s3.amazonaws.com/"));
assertTrue(presignedUrl.endsWith(".jpg"));

verify(imageUploadValidator, times(1)).isEmptyFileName(filename);
verify(mockPresigner, times(1)).presignGetObject(any(GetObjectPresignRequest.class));
}

@DisplayName("파일명 검증에 실패하면 null을 반환한다.")
@Test
void getPresignedUrl_invalidFileName() {

// given
String filename = "";

when(imageUploadValidator.isEmptyFileName(filename)).thenReturn(true);

// when
String presignedUrl = imageUploadService.getPresignedUrl(filename);

// then
assertNull(presignedUrl);

verify(imageUploadValidator, times(1)).isEmptyFileName(filename);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,43 @@ void shouldThrowExceptionWhenFileTypeIsNull() {
// then
assertEquals(ImageUploadException.class, exception.getClass());
}

@Test
@DisplayName("파일 이름이 비어있는지 확인할 수 있다.")
void shouldReturnTrueWhenFileNameIsEmpty() {
// given
String emptyFileName = "";

// when
boolean isEmptyFileName = imageUploadValidator.isEmptyFileName(emptyFileName);

// then
assertThat(isEmptyFileName).isTrue();
}

@Test
@DisplayName("파일 이름이 null이면 true를 반환한다.")
void shouldReturnTrueWhenFileNameIsNull() {
// given
String nullFileName = null;

// when
boolean isEmptyFileName = imageUploadValidator.isEmptyFileName(nullFileName);

// then
assertThat(isEmptyFileName).isTrue();
}

@Test
@DisplayName("파일 이름이 비어있지 않으면 false를 반환한다.")
void shouldReturnFalseWhenFileNameIsNotEmpty() {
// given
String validFileName = "testImage.jpg";

// when
boolean isEmptyFileName = imageUploadValidator.isEmptyFileName(validFileName);

// then
assertThat(isEmptyFileName).isFalse();
}
}
Loading