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
12 changes: 9 additions & 3 deletions src/main/java/org/runnect/server/external/aws/S3Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,27 @@ private String createFileName(String fileName) {

// 파일 유효성 검사
private String getFileExtension(String fileName) {
if (fileName.length() == 0) {
if (fileName == null || fileName.isEmpty()) {
throw new NotFoundException(ErrorStatus.NOT_FOUND_IMAGE_EXCEPTION, ErrorStatus.NOT_FOUND_IMAGE_EXCEPTION.getMessage());
}
int dotIndex = fileName.lastIndexOf(".");
// 파일명에 "."가 아예 없으면(lastIndexOf가 -1) substring(-1)에서 그대로
// StringIndexOutOfBoundsException(500)이 나던 부분 — 확장자 없는 파일로 명확히 처리한다.
if (dotIndex == -1) {
throw new BadRequestException(ErrorStatus.NOT_FOUND_IMAGE_EXCEPTION, ErrorStatus.NOT_FOUND_IMAGE_EXCEPTION.getMessage());
}
ArrayList<String> fileValidate = new ArrayList<>();
fileValidate.add(".jpg");
fileValidate.add(".jpeg");
fileValidate.add(".png");
fileValidate.add(".JPG");
fileValidate.add(".JPEG");
fileValidate.add(".PNG");
String idxFileName = fileName.substring(fileName.lastIndexOf("."));
String idxFileName = fileName.substring(dotIndex);
if (!fileValidate.contains(idxFileName)) {
throw new BadRequestException(ErrorStatus.NOT_FOUND_IMAGE_EXCEPTION, ErrorStatus.NOT_FOUND_IMAGE_EXCEPTION.getMessage());
}
return fileName.substring(fileName.lastIndexOf("."));
return idxFileName;
}

// 이미지 삭제
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.runnect.server.banner.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.runnect.server.banner.dto.response.GetBannerResponseDto;
import org.runnect.server.banner.entity.Banner;
import org.runnect.server.banner.repository.BannerRepository;
import org.springframework.test.util.ReflectionTestUtils;

@ExtendWith(MockitoExtension.class)
class BannerServiceTest {

@Mock
private BannerRepository bannerRepository;

private BannerService bannerService;

private Banner buildBanner(Long id, String imageUrl, String linkUrl, int sortOrder) {
Banner banner = Banner.builder().imageUrl(imageUrl).linkUrl(linkUrl).sortOrder(sortOrder).build();
ReflectionTestUtils.setField(banner, "id", id);
return banner;
}

@Test
void 활성_배너를_정렬된_순서_그대로_0부터_인덱싱해서_반환한다() {
bannerService = new BannerService(bannerRepository);
Banner first = buildBanner(1L, "image1.png", "https://a.com", 0);
Banner second = buildBanner(2L, "image2.png", "https://b.com", 1);
when(bannerRepository.findByIsActiveTrueOrderBySortOrderAscIdAsc())
.thenReturn(Arrays.asList(first, second));

GetBannerResponseDto response = bannerService.getBanners();

assertThat(response.getBanners()).hasSize(2);
assertThat(response.getBanners().get(0).getIndex()).isEqualTo(0);
assertThat(response.getBanners().get(0).getImageUrl()).isEqualTo("image1.png");
assertThat(response.getBanners().get(1).getIndex()).isEqualTo(1);
assertThat(response.getBanners().get(1).getLinkUrl()).isEqualTo("https://b.com");
}

@Test
void 활성_배너가_없으면_빈_목록을_반환한다() {
bannerService = new BannerService(bannerRepository);
when(bannerRepository.findByIsActiveTrueOrderBySortOrderAscIdAsc())
.thenReturn(Collections.emptyList());

GetBannerResponseDto response = bannerService.getBanners();

assertThat(response.getBanners()).isEmpty();
}
}
120 changes: 120 additions & 0 deletions src/test/java/org/runnect/server/external/aws/S3ServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.runnect.server.external.aws;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.io.ByteArrayInputStream;
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.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.runnect.server.common.exception.BadRequestException;
import org.runnect.server.common.exception.NotFoundException;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.multipart.MultipartFile;

@ExtendWith(MockitoExtension.class)
class S3ServiceTest {

@Mock
private AmazonS3 amazonS3;

private S3Service s3Service;

@BeforeEach
void setUp() {
s3Service = new S3Service(amazonS3);
ReflectionTestUtils.setField(s3Service, "bucket", "runnect-test-bucket");
}

private void stubUploadedUrl() throws Exception {
when(amazonS3.getUrl(any(), any())).thenReturn(
new URL("https://runnect-test-bucket.s3.ap-northeast-2.amazonaws.com/course/image/test.jpg"));
}

@Nested
@DisplayName("uploadImage")
class UploadImage {

@Test
@DisplayName("정상적인 이미지 파일이면 업로드하고 URL을 반환한다")
void 정상_업로드() throws Exception {
stubUploadedUrl();
MultipartFile file = new MockMultipartFile("image", "photo.jpg", "image/jpeg", "content".getBytes());

String url = s3Service.uploadImage(file, "course");

assertThat(url).isEqualTo(
"https://runnect-test-bucket.s3.ap-northeast-2.amazonaws.com/course/image/test.jpg");
}

@Test
@DisplayName("대문자 확장자(.PNG)도 정상 업로드된다")
void 대문자_확장자() throws Exception {
stubUploadedUrl();
MultipartFile file = new MockMultipartFile("image", "photo.PNG", "image/png", "content".getBytes());

assertThat(s3Service.uploadImage(file, "course")).isNotNull();
}

@Test
@DisplayName("지원하지 않는 확장자면 BadRequestException")
void 지원하지_않는_확장자() {
MultipartFile file = new MockMultipartFile("image", "photo.gif", "image/gif", "content".getBytes());

assertThatThrownBy(() -> s3Service.uploadImage(file, "course"))
.isInstanceOf(BadRequestException.class);
}

@Test
@DisplayName("[버그 수정 검증] 파일명이 null이면 500(NPE) 대신 NotFoundException")
void 파일명이_null() {
MultipartFile file = new MockMultipartFile("image", null, "image/jpeg", "content".getBytes());

assertThatThrownBy(() -> s3Service.uploadImage(file, "course"))
.isInstanceOf(NotFoundException.class);
}

@Test
@DisplayName("파일명이 빈 문자열이면 NotFoundException")
void 파일명이_빈문자열() {
MultipartFile file = new MockMultipartFile("image", "", "image/jpeg", "content".getBytes());

assertThatThrownBy(() -> s3Service.uploadImage(file, "course"))
.isInstanceOf(NotFoundException.class);
}

@Test
@DisplayName("[버그 수정 검증] 확장자(.)가 없는 파일명이면 500(StringIndexOutOfBoundsException) 대신 BadRequestException")
void 확장자가_없는_파일명() {
MultipartFile file = new MockMultipartFile("image", "photo_without_extension", "image/jpeg",
"content".getBytes());

assertThatThrownBy(() -> s3Service.uploadImage(file, "course"))
.isInstanceOf(BadRequestException.class);
}

@Test
@DisplayName("파일 스트림을 읽는 중 오류가 나면 NotFoundException")
void 스트림_읽기_실패() throws IOException {
MultipartFile file = mock(MultipartFile.class);
when(file.getOriginalFilename()).thenReturn("photo.jpg");
when(file.getInputStream()).thenThrow(new IOException("disk error"));

assertThatThrownBy(() -> s3Service.uploadImage(file, "course"))
.isInstanceOf(NotFoundException.class);
}
}
}
Loading