From a02afb45d8ad25b0166e96df7908a871b449b527 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Fri, 12 Dec 2025 02:15:41 +0900 Subject: [PATCH 01/11] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC,=20=ED=8C=8C=EC=82=AC=EB=93=9C=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/eatda/client/file/FileClient.java | 5 +- .../controller/cheer/CheerController.java | 4 +- .../eatda/exception/BusinessErrorCode.java | 1 + .../eatda/facade/CheerCreationResult.java | 7 +++ .../eatda/facade/CheerRegisterFacade.java | 60 +++++++++++++++++++ .../eatda/service/cheer/CheerService.java | 60 +++++++------------ .../eatda/service/cheer/CheerServiceTest.java | 12 ++-- 7 files changed, 101 insertions(+), 48 deletions(-) create mode 100644 src/main/java/eatda/facade/CheerCreationResult.java create mode 100644 src/main/java/eatda/facade/CheerRegisterFacade.java diff --git a/src/main/java/eatda/client/file/FileClient.java b/src/main/java/eatda/client/file/FileClient.java index 1fa6e38c..4c318b93 100644 --- a/src/main/java/eatda/client/file/FileClient.java +++ b/src/main/java/eatda/client/file/FileClient.java @@ -20,6 +20,7 @@ public class FileClient { private static final int THREAD_POOL_SIZE = 5; // TODO 비동기 병렬처리 개선 + private static final String PATH_DELIMITER = "/"; private final S3Client s3Client; private final String bucket; private final S3Presigner s3Presigner; @@ -55,7 +56,7 @@ public List moveTempFilesToPermanent(String domainName, long domainId, L List> futures = tempImageKeys.stream() .map(tempImageKey -> CompletableFuture.supplyAsync(() -> { String fileName = extractFileName(tempImageKey); - String newPermanentKey = domainName + "/" + domainId + "/" + fileName; + String newPermanentKey = domainName + PATH_DELIMITER + domainId + PATH_DELIMITER + fileName; try { copyObject(tempImageKey, newPermanentKey); deleteObject(tempImageKey); @@ -72,7 +73,7 @@ public List moveTempFilesToPermanent(String domainName, long domainId, L } private String extractFileName(String fullKey) { - int index = fullKey.lastIndexOf('/'); + int index = fullKey.lastIndexOf(PATH_DELIMITER); return index == -1 ? fullKey : fullKey.substring(index + 1); } diff --git a/src/main/java/eatda/controller/cheer/CheerController.java b/src/main/java/eatda/controller/cheer/CheerController.java index d172f431..878fa8f1 100644 --- a/src/main/java/eatda/controller/cheer/CheerController.java +++ b/src/main/java/eatda/controller/cheer/CheerController.java @@ -6,6 +6,7 @@ import eatda.domain.cheer.CheerTagName; import eatda.domain.store.StoreCategory; import eatda.domain.store.StoreSearchResult; +import eatda.facade.CheerRegisterFacade; import eatda.service.cheer.CheerService; import eatda.service.store.StoreSearchService; import jakarta.validation.constraints.Max; @@ -29,13 +30,14 @@ public class CheerController { private final CheerService cheerService; private final StoreSearchService storeSearchService; + private final CheerRegisterFacade cheerRegisterFacade; @PostMapping("/api/cheer") public ResponseEntity registerCheer(@RequestBody CheerRegisterRequest request, LoginMember member) { StoreSearchResult searchResult = storeSearchService.searchStoreByKakaoId( request.storeName(), request.storeKakaoId()); - CheerResponse response = cheerService.registerCheer(request, searchResult, member.id(), ImageDomain.CHEER); + CheerResponse response = cheerRegisterFacade.registerCheer(request, searchResult, member.id(), ImageDomain.CHEER); return ResponseEntity.status(HttpStatus.CREATED) .body(response); } diff --git a/src/main/java/eatda/exception/BusinessErrorCode.java b/src/main/java/eatda/exception/BusinessErrorCode.java index a74e08e3..de91109d 100644 --- a/src/main/java/eatda/exception/BusinessErrorCode.java +++ b/src/main/java/eatda/exception/BusinessErrorCode.java @@ -28,6 +28,7 @@ public enum BusinessErrorCode { INVALID_CHEER_IMAGE_KEY("CHE002", "응원 이미지 키가 비어 있습니다.", HttpStatus.BAD_REQUEST), FULL_CHEER_SIZE_PER_MEMBER("CHE003", "회원당 응원 한도가 넘었습니다."), ALREADY_CHEERED("CHE004", "이미 응원한 가게입니다."), + CHEER_NOT_FOUND("CHE005", "응원을 찾을 수 없습니다.", HttpStatus.NOT_FOUND), // CheerTag CHEER_TAGS_DUPLICATED("CHE_TAG001", "응원 태그는 중복될 수 없습니다."), diff --git a/src/main/java/eatda/facade/CheerCreationResult.java b/src/main/java/eatda/facade/CheerCreationResult.java new file mode 100644 index 00000000..66833a3e --- /dev/null +++ b/src/main/java/eatda/facade/CheerCreationResult.java @@ -0,0 +1,7 @@ +package eatda.facade; + +import eatda.domain.cheer.Cheer; +import eatda.domain.store.Store; + +public record CheerCreationResult(Cheer cheer, Store store) { +} diff --git a/src/main/java/eatda/facade/CheerRegisterFacade.java b/src/main/java/eatda/facade/CheerRegisterFacade.java new file mode 100644 index 00000000..238d93b0 --- /dev/null +++ b/src/main/java/eatda/facade/CheerRegisterFacade.java @@ -0,0 +1,60 @@ +package eatda.facade; + +import eatda.client.file.FileClient; +import eatda.controller.cheer.CheerRegisterRequest; +import eatda.controller.cheer.CheerResponse; +import eatda.domain.ImageDomain; +import eatda.domain.cheer.Cheer; +import eatda.domain.store.StoreSearchResult; +import eatda.service.cheer.CheerService; +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CheerRegisterFacade { + + @Value("${cdn.base-url}") + private String cdnBaseUrl; + private final CheerService cheerService; + private final FileClient fileClient; + + public CheerResponse registerCheer(CheerRegisterRequest request, + StoreSearchResult result, + long memberId, + ImageDomain domain + ) { + CheerCreationResult creationResult = cheerService.createCheer(request, result, memberId); + Cheer cheer = creationResult.cheer(); + + try { + List sortedImages = sortImages(request.images()); + List permanentKeys = moveImages(domain, cheer.getId(), sortedImages); + cheer = cheerService.saveCheerImages(cheer.getId(), sortedImages, permanentKeys); + } catch (Exception e) { + cheerService.deleteCheer(cheer.getId()); + throw e; + } + + return new CheerResponse(cheer, creationResult.store(), cdnBaseUrl); + } + + private List sortImages( + List images) { + return images.stream() + .sorted(Comparator.comparingLong(CheerRegisterRequest.UploadedImageDetail::orderIndex)) + .toList(); + } + + private List moveImages(ImageDomain domain, + long cheerId, + List sortedImages) { + List tempKeys = sortedImages.stream() + .map(CheerRegisterRequest.UploadedImageDetail::imageKey) + .toList(); + return fileClient.moveTempFilesToPermanent(domain.getName(), cheerId, tempKeys); + } +} diff --git a/src/main/java/eatda/service/cheer/CheerService.java b/src/main/java/eatda/service/cheer/CheerService.java index ca910873..7b75d0ba 100644 --- a/src/main/java/eatda/service/cheer/CheerService.java +++ b/src/main/java/eatda/service/cheer/CheerService.java @@ -1,15 +1,12 @@ package eatda.service.cheer; -import eatda.client.file.FileClient; import eatda.controller.cheer.CheerImageResponse; import eatda.controller.cheer.CheerInStoreResponse; import eatda.controller.cheer.CheerPreviewResponse; import eatda.controller.cheer.CheerRegisterRequest; -import eatda.controller.cheer.CheerResponse; import eatda.controller.cheer.CheerSearchParameters; import eatda.controller.cheer.CheersInStoreResponse; import eatda.controller.cheer.CheersResponse; -import eatda.domain.ImageDomain; import eatda.domain.cheer.Cheer; import eatda.domain.cheer.CheerImage; import eatda.domain.member.Member; @@ -17,6 +14,7 @@ import eatda.domain.store.StoreSearchResult; import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; +import eatda.facade.CheerCreationResult; import eatda.repository.cheer.CheerRepository; import eatda.repository.member.MemberRepository; import eatda.repository.store.StoreRepository; @@ -37,20 +35,18 @@ public class CheerService { private static final int MAX_CHEER_SIZE = 10_000; - + private static final String SORTED_PROPERTIES = "createdAt"; private final MemberRepository memberRepository; private final StoreRepository storeRepository; private final CheerRepository cheerRepository; - private final FileClient fileClient; @Value("${cdn.base-url}") private String cdnBaseUrl; @Transactional - public CheerResponse registerCheer(CheerRegisterRequest request, - StoreSearchResult result, - long memberId, - ImageDomain domain + public CheerCreationResult createCheer(CheerRegisterRequest request, + StoreSearchResult result, + long memberId ) { Member member = memberRepository.getById(memberId); validateRegisterCheer(member, request.storeKakaoId()); @@ -59,15 +55,7 @@ public CheerResponse registerCheer(CheerRegisterRequest request, .orElseGet(() -> storeRepository.save(result.toStore())); // TODO 상점 조회/저장 동시성 이슈 해결 Cheer cheer = new Cheer(member, store, request.description()); cheer.setCheerTags(request.tags()); - Cheer savedCheer = cheerRepository.save(cheer); - - // TODO 트랜잭션 범위 축소 - List sortedImages = sortImages(request.images()); - List permanentKeys = moveImages(domain, cheer.getId(), sortedImages); - - saveCheerImages(cheer, sortedImages, permanentKeys); - - return new CheerResponse(savedCheer, store, cdnBaseUrl); + return new CheerCreationResult(cheerRepository.save(cheer), store); } private void validateRegisterCheer(Member member, String storeKakaoId) { @@ -79,39 +67,28 @@ private void validateRegisterCheer(Member member, String storeKakaoId) { } } - private List sortImages( - List images) { - return images.stream() - .sorted(Comparator.comparingLong(CheerRegisterRequest.UploadedImageDetail::orderIndex)) - .toList(); - } - - private List moveImages(ImageDomain domain, - long cheerId, - List sortedImages) { - List tempKeys = sortedImages.stream() - .map(CheerRegisterRequest.UploadedImageDetail::imageKey) - .toList(); - return fileClient.moveTempFilesToPermanent(domain.getName(), cheerId, tempKeys); - } - - private void saveCheerImages(Cheer cheer, + @Transactional + public Cheer saveCheerImages(Long cheerId, List sortedImages, List permanentKeys) { + + Cheer persistentCheer = cheerRepository.findById(cheerId) + .orElseThrow(() -> new BusinessException(BusinessErrorCode.CHEER_NOT_FOUND)); + IntStream.range(0, sortedImages.size()) .forEach(i -> { var detail = sortedImages.get(i); CheerImage cheerImage = new CheerImage( - cheer, + persistentCheer, permanentKeys.get(i), detail.orderIndex(), detail.contentType(), detail.fileSize() ); - cheer.addImage(cheerImage); // 여기서 양방향 동기화 + persistentCheer.addImage(cheerImage); }); - cheerRepository.save(cheer); + return persistentCheer; } @Transactional(readOnly = true) @@ -121,7 +98,7 @@ public CheersResponse getCheers(CheerSearchParameters parameters) { parameters.getCheerTagNames(), parameters.getDistricts(), PageRequest.of(parameters.getPage(), parameters.getSize(), - Sort.by(Direction.DESC, "createdAt")) + Sort.by(Direction.DESC, SORTED_PROPERTIES)) ); List cheers = cheerPage.getContent(); @@ -152,4 +129,9 @@ public CheersInStoreResponse getCheersByStoreId(Long storeId, int page, int size return new CheersInStoreResponse(cheersResponse); } + + @Transactional + public void deleteCheer(Long cheerId) { + cheerRepository.deleteById(cheerId); + } } diff --git a/src/test/java/eatda/service/cheer/CheerServiceTest.java b/src/test/java/eatda/service/cheer/CheerServiceTest.java index 3e3fac44..16c19416 100644 --- a/src/test/java/eatda/service/cheer/CheerServiceTest.java +++ b/src/test/java/eatda/service/cheer/CheerServiceTest.java @@ -57,7 +57,7 @@ class RegisterCheer { "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); - assertThatThrownBy(() -> cheerService.registerCheer(request, result, member.getId(), ImageDomain.CHEER)) + assertThatThrownBy(() -> cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER)) .isInstanceOf(BusinessException.class) .hasMessageContaining(BusinessErrorCode.FULL_CHEER_SIZE_PER_MEMBER.getMessage()); } @@ -76,7 +76,7 @@ class RegisterCheer { "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); - assertThatThrownBy(() -> cheerService.registerCheer(request, result, member.getId(), ImageDomain.CHEER)) + assertThatThrownBy(() -> cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER)) .isInstanceOf(BusinessException.class) .hasMessageContaining(BusinessErrorCode.ALREADY_CHEERED.getMessage()); } @@ -93,7 +93,7 @@ class RegisterCheer { "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); - CheerResponse response = cheerService.registerCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); Store foundStore = storeRepository.findByKakaoId("123").orElseThrow(); assertAll( @@ -117,7 +117,7 @@ class RegisterCheer { "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); - CheerResponse response = cheerService.registerCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); assertAll( () -> assertThat(response.storeId()).isEqualTo(store.getId()), @@ -140,7 +140,7 @@ class RegisterCheer { "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); - CheerResponse response = cheerService.registerCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); assertAll( () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), @@ -158,7 +158,7 @@ class RegisterCheer { "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); - CheerResponse response = cheerService.registerCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); Store foundStore = storeRepository.findByKakaoId("123").orElseThrow(); assertAll( From b24f5e579af1cc08aa67ad91b8b810626e6f3b1d Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Sat, 13 Dec 2025 00:44:52 +0900 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20S3=20=EB=B3=B4=EC=83=81?= =?UTF-8?q?=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/eatda/client/file/FileClient.java | 50 +++++++++++-------- .../eatda/facade/CheerRegisterFacade.java | 5 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/java/eatda/client/file/FileClient.java b/src/main/java/eatda/client/file/FileClient.java index 4c318b93..15187a23 100644 --- a/src/main/java/eatda/client/file/FileClient.java +++ b/src/main/java/eatda/client/file/FileClient.java @@ -3,12 +3,12 @@ import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; import java.time.Duration; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CopyObjectRequest; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; @@ -16,15 +16,14 @@ import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; +@Slf4j @Component public class FileClient { - private static final int THREAD_POOL_SIZE = 5; // TODO 비동기 병렬처리 개선 private static final String PATH_DELIMITER = "/"; private final S3Client s3Client; private final String bucket; private final S3Presigner s3Presigner; - private final ExecutorService executorService; public FileClient(S3Client s3Client, @Value("${spring.cloud.aws.s3.bucket}") String bucket, @@ -32,7 +31,6 @@ public FileClient(S3Client s3Client, this.s3Client = s3Client; this.bucket = bucket; this.s3Presigner = s3Presigner; - this.executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); } public String generateUploadPresignedUrl(String fileKey, Duration signatureDuration) { @@ -53,23 +51,31 @@ public String generateUploadPresignedUrl(String fileKey, Duration signatureDurat } public List moveTempFilesToPermanent(String domainName, long domainId, List tempImageKeys) { - List> futures = tempImageKeys.stream() - .map(tempImageKey -> CompletableFuture.supplyAsync(() -> { - String fileName = extractFileName(tempImageKey); - String newPermanentKey = domainName + PATH_DELIMITER + domainId + PATH_DELIMITER + fileName; - try { - copyObject(tempImageKey, newPermanentKey); - deleteObject(tempImageKey); - return newPermanentKey; - } catch (Exception e) { //TODO 근본 예외 추가 필요 - throw new BusinessException(BusinessErrorCode.FAIL_TEMP_IMAGE_PROCESS); - } - }, executorService)) - .toList(); + List successKeys = new ArrayList<>(); - return futures.stream() - .map(CompletableFuture::join) // TODO 일부 파일 에러에도 처리하도록 개선 - .toList(); + try { + for (String tempKey : tempImageKeys) { + String fileName = extractFileName(tempKey); + String newPermanentKey = domainName + PATH_DELIMITER + domainId + PATH_DELIMITER + fileName; + + copyObject(tempKey, newPermanentKey); + deleteObject(tempKey); + + successKeys.add(newPermanentKey); + } + return successKeys; + } catch (SdkException sdkException) { + log.error("S3 파일 이동 중 실패. 롤백 수행. successKeys={}", successKeys, sdkException); + deleteFiles(successKeys); + throw new BusinessException(BusinessErrorCode.FAIL_TEMP_IMAGE_PROCESS); + } + } + + public void deleteFiles(List keys) { + if (keys.isEmpty()) { + return; + } + keys.forEach(this::deleteObject); } private String extractFileName(String fullKey) { diff --git a/src/main/java/eatda/facade/CheerRegisterFacade.java b/src/main/java/eatda/facade/CheerRegisterFacade.java index 238d93b0..ced5f25d 100644 --- a/src/main/java/eatda/facade/CheerRegisterFacade.java +++ b/src/main/java/eatda/facade/CheerRegisterFacade.java @@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import software.amazon.awssdk.core.exception.SdkException; @Component @RequiredArgsConstructor @@ -34,9 +35,9 @@ public CheerResponse registerCheer(CheerRegisterRequest request, List sortedImages = sortImages(request.images()); List permanentKeys = moveImages(domain, cheer.getId(), sortedImages); cheer = cheerService.saveCheerImages(cheer.getId(), sortedImages, permanentKeys); - } catch (Exception e) { + } catch (SdkException sdkException) { cheerService.deleteCheer(cheer.getId()); - throw e; + throw sdkException; } return new CheerResponse(cheer, creationResult.store(), cdnBaseUrl); From e2af28c53696f813304b38380c96c836d3068c74 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Sun, 14 Dec 2025 23:59:55 +0900 Subject: [PATCH 03/11] =?UTF-8?q?test:=20=EC=9D=91=EC=9B=90=20=ED=8C=8C?= =?UTF-8?q?=EC=82=AC=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../document/cheer/CheerDocumentTest.java | 4 +- .../java/eatda/facade/BaseFacadeTest.java | 36 +++ .../facade/cheer/CheerRegisterFacadeTest.java | 243 ++++++++++++++++++ 3 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 src/test/java/eatda/facade/BaseFacadeTest.java create mode 100644 src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java diff --git a/src/test/java/eatda/document/cheer/CheerDocumentTest.java b/src/test/java/eatda/document/cheer/CheerDocumentTest.java index 7dca9f25..50c04eb4 100644 --- a/src/test/java/eatda/document/cheer/CheerDocumentTest.java +++ b/src/test/java/eatda/document/cheer/CheerDocumentTest.java @@ -99,7 +99,7 @@ class RegisterCheer { List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) ); - doReturn(response).when(cheerService).registerCheer(eq(request), any(), anyLong(), any()); + doReturn(response).when(cheerService).createCheer(eq(request), any(), anyLong()); var document = document("cheer/register", 201) .request(requestDocument) @@ -137,7 +137,7 @@ class RegisterCheer { ); doThrow(new BusinessException(errorCode)) - .when(cheerService).registerCheer(eq(request), any(), anyLong(), any()); + .when(cheerService).createCheer(eq(request), any(), anyLong()); var document = document("cheer/register", errorCode) .request(requestDocument) diff --git a/src/test/java/eatda/facade/BaseFacadeTest.java b/src/test/java/eatda/facade/BaseFacadeTest.java new file mode 100644 index 00000000..d18743ef --- /dev/null +++ b/src/test/java/eatda/facade/BaseFacadeTest.java @@ -0,0 +1,36 @@ +package eatda.facade; + +import eatda.DatabaseCleaner; +import eatda.client.file.FileClient; +import eatda.client.map.MapClient; +import eatda.client.oauth.OauthClient; +import eatda.fixture.MemberGenerator; +import eatda.fixture.StoreGenerator; +import eatda.repository.cheer.CheerRepository; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +@ExtendWith(DatabaseCleaner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +public abstract class BaseFacadeTest { + + @MockitoBean + protected OauthClient oauthClient; + + @MockitoBean + protected MapClient mapClient; + + @MockitoBean + protected FileClient fileClient; + + @Autowired + protected MemberGenerator memberGenerator; + + @Autowired + protected StoreGenerator storeGenerator; + + @Autowired + protected CheerRepository cheerRepository; +} diff --git a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java new file mode 100644 index 00000000..9740c877 --- /dev/null +++ b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java @@ -0,0 +1,243 @@ +package eatda.facade.cheer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +import eatda.controller.cheer.CheerRegisterRequest; +import eatda.controller.cheer.CheerResponse; +import eatda.domain.ImageDomain; +import eatda.domain.cheer.CheerTagName; +import eatda.domain.store.District; +import eatda.domain.store.StoreCategory; +import eatda.domain.store.StoreSearchResult; +import eatda.facade.BaseFacadeTest; +import eatda.facade.CheerRegisterFacade; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import software.amazon.awssdk.core.exception.SdkException; + +class CheerRegisterFacadeTest extends BaseFacadeTest { + + @Autowired + private CheerRegisterFacade cheerRegisterFacade; + + @Nested + class RegisterCheer { + + @Test + void 응원을_등록하면_이미지를_이동하고_최종_응답을_반환한다() { + var member = memberGenerator.generate("member-1"); + + CheerRegisterRequest.UploadedImageDetail image1 = + new CheerRegisterRequest.UploadedImageDetail("temp/key1.jpg", 1L, "image/jpeg", 1000L); + CheerRegisterRequest.UploadedImageDetail image2 = + new CheerRegisterRequest.UploadedImageDetail("temp/key2.jpg", 2L, "image/jpeg", 2000L); + + CheerRegisterRequest request = new CheerRegisterRequest( + "kakao-1", + "농민백암순대", + "맛있어요", + List.of(image1, image2), + List.of(CheerTagName.GOOD_FOR_DATING) + ); + + StoreSearchResult storeResult = new StoreSearchResult( + "kakao-1", + StoreCategory.KOREAN, + "02-000-0000", + "농민백암순대", + "http://place.map.kakao.com/1", + "서울시 강남구", + "서울시 강남구", + District.GANGNAM, + 37.715132, + 127.269310 + ); + + given(fileClient.moveTempFilesToPermanent( + eq(ImageDomain.CHEER.getName()), + anyLong(), + anyList() + )).willReturn(List.of( + "cheer/1/key1.jpg", + "cheer/1/key2.jpg" + )); + + CheerResponse response = cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ); + + assertThat(response.cheerDescription()).isEqualTo("맛있어요"); + assertThat(response.tags()).containsExactly(CheerTagName.GOOD_FOR_DATING); + assertThat(response.images()).hasSize(2); + + Mockito.verify(fileClient) + .moveTempFilesToPermanent( + eq(ImageDomain.CHEER.getName()), + anyLong(), + anyList() + ); + } + + @Test + void 이미지_이동_중_실패하면_응원을_삭제한다() { + var member = memberGenerator.generate("member-1"); + + CheerRegisterRequest.UploadedImageDetail image = + new CheerRegisterRequest.UploadedImageDetail( + "temp/key1.jpg", 1L, "image/jpeg", 1000L + ); + + CheerRegisterRequest request = new CheerRegisterRequest( + "kakao-1", + "농민백암순대", + "맛있어요", + List.of(image), + List.of(CheerTagName.GOOD_FOR_DATING) + ); + + StoreSearchResult storeResult = new StoreSearchResult( + "kakao-1", + StoreCategory.KOREAN, + "02-000-0000", + "농민백암순대", + "http://place.map.kakao.com/1", + "서울시 강남구", + "서울시 강남구", + District.GANGNAM, + 37.715132, + 127.269310 + ); + + given(fileClient.moveTempFilesToPermanent( + anyString(), + anyLong(), + anyList() + )).willThrow( + software.amazon.awssdk.core.exception.SdkException.builder().build() + ); + + try { + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ); + } catch (Exception ignored) { + } + + assertThat(cheerRepository.count()).isZero(); + } + + @Test + void 이미지가_없어도_응원은_정상_등록된다() { + var member = memberGenerator.generate("member-1"); + + CheerRegisterRequest request = new CheerRegisterRequest( + "kakao-1", + "농민백암순대", + "이미지 없음", + List.of(), + List.of(CheerTagName.GOOD_FOR_DATING) + ); + + StoreSearchResult storeResult = new StoreSearchResult( + "kakao-1", + StoreCategory.KOREAN, + "02-000-0000", + "농민백암순대", + "http://place.map.kakao.com/1", + "서울시 강남구", + "서울시 강남구", + District.GANGNAM, + 37.715132, + 127.269310 + ); + + CheerResponse response = cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ); + + assertThat(response.images()).isEmpty(); + assertThat(cheerRepository.count()).isEqualTo(1); + + Mockito.verify(fileClient, Mockito.never()) + .moveTempFilesToPermanent(anyString(), anyLong(), anyList()); + } + + @Test + void 이미지_이동이_부분적으로_성공한_후_실패하면_응원을_삭제한다() { + var member = memberGenerator.generate("member-1"); + + CheerRegisterRequest.UploadedImageDetail image1 = + new CheerRegisterRequest.UploadedImageDetail( + "temp/key1.jpg", 1L, "image/jpeg", 1000L + ); + CheerRegisterRequest.UploadedImageDetail image2 = + new CheerRegisterRequest.UploadedImageDetail( + "temp/key2.jpg", 2L, "image/jpeg", 1000L + ); + CheerRegisterRequest.UploadedImageDetail image3 = + new CheerRegisterRequest.UploadedImageDetail( + "temp/key3.jpg", 3L, "image/jpeg", 1000L + ); + + CheerRegisterRequest request = new CheerRegisterRequest( + "kakao-1", + "농민백암순대", + "부분 성공 테스트", + List.of(image1, image2, image3), + List.of(CheerTagName.GOOD_FOR_DATING) + ); + + StoreSearchResult storeResult = new StoreSearchResult( + "kakao-1", + StoreCategory.KOREAN, + "02-000-0000", + "농민백암순대", + "http://place.map.kakao.com/1", + "서울시 강남구", + "서울시 강남구", + District.GANGNAM, + 37.715132, + 127.269310 + ); + + given(fileClient.moveTempFilesToPermanent( + eq(ImageDomain.CHEER.getName()), + anyLong(), + anyList() + )).willAnswer(invocation -> { + throw SdkException.builder().build(); + }); + + try { + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ); + } catch (Exception ignored) { + } + + assertThat(cheerRepository.count()) + .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다") + .isZero(); + } + } +} From b35208786909c74001dba9ce9d69aa106f71371f Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 00:24:32 +0900 Subject: [PATCH 04/11] =?UTF-8?q?test:=20=EC=9D=91=EC=9B=90,=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/eatda/document/BaseDocumentTest.java | 4 + .../document/cheer/CheerDocumentTest.java | 4 +- .../eatda/service/cheer/CheerServiceTest.java | 189 +++++++++++------- 3 files changed, 119 insertions(+), 78 deletions(-) diff --git a/src/test/java/eatda/document/BaseDocumentTest.java b/src/test/java/eatda/document/BaseDocumentTest.java index b3618055..543a07e4 100644 --- a/src/test/java/eatda/document/BaseDocumentTest.java +++ b/src/test/java/eatda/document/BaseDocumentTest.java @@ -7,6 +7,7 @@ import eatda.controller.web.jwt.JwtManager; import eatda.exception.BusinessErrorCode; import eatda.exception.EtcErrorCode; +import eatda.facade.CheerRegisterFacade; import eatda.service.auth.AuthService; import eatda.service.auth.OauthService; import eatda.service.cheer.CheerService; @@ -66,6 +67,9 @@ public abstract class BaseDocumentTest { @MockitoBean protected PresignedUrlService presignedUrlService; + @MockitoBean + protected CheerRegisterFacade cheerRegisterFacade; + @MockitoBean protected JwtManager jwtManager; diff --git a/src/test/java/eatda/document/cheer/CheerDocumentTest.java b/src/test/java/eatda/document/cheer/CheerDocumentTest.java index 50c04eb4..42452a17 100644 --- a/src/test/java/eatda/document/cheer/CheerDocumentTest.java +++ b/src/test/java/eatda/document/cheer/CheerDocumentTest.java @@ -99,7 +99,7 @@ class RegisterCheer { List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) ); - doReturn(response).when(cheerService).createCheer(eq(request), any(), anyLong()); + doReturn(response).when(cheerRegisterFacade).registerCheer(eq(request), any(), anyLong(), any()); var document = document("cheer/register", 201) .request(requestDocument) @@ -137,7 +137,7 @@ class RegisterCheer { ); doThrow(new BusinessException(errorCode)) - .when(cheerService).createCheer(eq(request), any(), anyLong()); + .when(cheerRegisterFacade).registerCheer(eq(request), any(), anyLong(), any()); var document = document("cheer/register", errorCode) .request(requestDocument) diff --git a/src/test/java/eatda/service/cheer/CheerServiceTest.java b/src/test/java/eatda/service/cheer/CheerServiceTest.java index 16c19416..3f4f723f 100644 --- a/src/test/java/eatda/service/cheer/CheerServiceTest.java +++ b/src/test/java/eatda/service/cheer/CheerServiceTest.java @@ -4,14 +4,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import eatda.controller.cheer.CheerImageResponse; import eatda.controller.cheer.CheerRegisterRequest; -import eatda.controller.cheer.CheerResponse; import eatda.controller.cheer.CheerSearchParameters; -import eatda.controller.cheer.CheersInStoreResponse; -import eatda.controller.cheer.CheersResponse; import eatda.controller.store.SearchDistrict; -import eatda.domain.ImageDomain; import eatda.domain.cheer.Cheer; import eatda.domain.cheer.CheerTagName; import eatda.domain.member.Member; @@ -21,9 +16,9 @@ import eatda.domain.store.StoreSearchResult; import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; +import eatda.facade.CheerCreationResult; import eatda.service.BaseServiceTest; import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; @@ -49,15 +44,19 @@ class RegisterCheer { cheerGenerator.generateCommon(member, store2); cheerGenerator.generateCommon(member, store3); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "추가 응원", + CheerRegisterRequest request = new CheerRegisterRequest( + "123", + "농민백암순대 본점", + "추가 응원", List.of(), - List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + ); - StoreSearchResult result = new StoreSearchResult( - "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", - "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); + StoreSearchResult result = storeSearchResult("123"); - assertThatThrownBy(() -> cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER)) + assertThatThrownBy(() -> + cheerService.createCheer(request, result, member.getId()) + ) .isInstanceOf(BusinessException.class) .hasMessageContaining(BusinessErrorCode.FULL_CHEER_SIZE_PER_MEMBER.getMessage()); } @@ -68,15 +67,19 @@ class RegisterCheer { Store store = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45"); cheerGenerator.generateCommon(member, store); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "추가 응원", + CheerRegisterRequest request = new CheerRegisterRequest( + "123", + "농민백암순대 본점", + "추가 응원", List.of(), - List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + ); - StoreSearchResult result = new StoreSearchResult( - "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", - "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); + StoreSearchResult result = storeSearchResult("123"); - assertThatThrownBy(() -> cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER)) + assertThatThrownBy(() -> + cheerService.createCheer(request, result, member.getId()) + ) .isInstanceOf(BusinessException.class) .hasMessageContaining(BusinessErrorCode.ALREADY_CHEERED.getMessage()); } @@ -85,23 +88,33 @@ class RegisterCheer { void 해당_응원의_가게가_저장되어_있지_않다면_가게와_응원을_저장한다() { Member member = memberGenerator.generate("123"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", + CheerRegisterRequest request = new CheerRegisterRequest( + "123", + "농민백암순대 본점", + "맛있어요!", List.of(), - List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + ); - StoreSearchResult result = new StoreSearchResult( - "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", - "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); + StoreSearchResult result = storeSearchResult("123"); - CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerCreationResult creationResult = + cheerService.createCheer(request, result, member.getId()); + Cheer cheer = creationResult.cheer(); + Store store = creationResult.store(); Store foundStore = storeRepository.findByKakaoId("123").orElseThrow(); + assertAll( - () -> assertThat(response.storeId()).isEqualTo(foundStore.getId()), - () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), + () -> assertThat(store.getId()).isEqualTo(foundStore.getId()), + () -> assertThat(cheer.getDescription()).isEqualTo("맛있어요!"), () -> assertThat(cheerRepository.count()).isEqualTo(1), - () -> assertThat(response.tags()).containsExactlyInAnyOrder( - CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + () -> assertThat( + cheer.getCheerTags().getNames() + ).containsExactlyInAnyOrder( + CheerTagName.GOOD_FOR_DATING, + CheerTagName.CLEAN_RESTROOM + ) ); } @@ -110,21 +123,24 @@ class RegisterCheer { Member member = memberGenerator.generate("123"); Store store = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", + CheerRegisterRequest request = new CheerRegisterRequest( + "123", + "농민백암순대 본점", + "맛있어요!", List.of(), - List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); - StoreSearchResult result = new StoreSearchResult( - "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", - "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + ); + + StoreSearchResult result = storeSearchResult("123"); - CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerCreationResult creationResult = + cheerService.createCheer(request, result, member.getId()); + + Cheer cheer = creationResult.cheer(); assertAll( - () -> assertThat(response.storeId()).isEqualTo(store.getId()), - () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), - () -> assertThat(cheerRepository.count()).isEqualTo(1), - () -> assertThat(response.tags()).containsExactlyInAnyOrder( - CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + () -> assertThat(cheer.getStore().getId()).isEqualTo(store.getId()), + () -> assertThat(cheerRepository.count()).isEqualTo(1) ); } @@ -132,39 +148,42 @@ class RegisterCheer { void 해당_응원의_이미지가_비어있어도_응원을_저장할_수_있다() { Member member = memberGenerator.generate("123"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", + CheerRegisterRequest request = new CheerRegisterRequest( + "123", + "농민백암순대 본점", + "맛있어요!", List.of(), - List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) + ); - StoreSearchResult result = new StoreSearchResult( - "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", - "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); + StoreSearchResult result = storeSearchResult("123"); - CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); + CheerCreationResult creationResult = + cheerService.createCheer(request, result, member.getId()); - assertAll( - () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), - () -> assertThat(response.tags()).containsExactlyInAnyOrder( - CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) - ); + assertThat(creationResult.cheer().getDescription()).isEqualTo("맛있어요!"); } @Test void 해당_응원의_응원_태그가_비어있어도_응원을_저장할_수_있다() { Member member = memberGenerator.generate("123"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", List.of(), List.of()); - StoreSearchResult result = new StoreSearchResult( - "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", - "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", District.GANGNAM, 37.5665, 126.9780); + CheerRegisterRequest request = new CheerRegisterRequest( + "123", + "농민백암순대 본점", + "맛있어요!", + List.of(), + List.of() + ); - CheerResponse response = cheerService.createCheer(request, result, member.getId(), ImageDomain.CHEER); + StoreSearchResult result = storeSearchResult("123"); + + CheerCreationResult creationResult = + cheerService.createCheer(request, result, member.getId()); - Store foundStore = storeRepository.findByKakaoId("123").orElseThrow(); assertAll( - () -> assertThat(response.storeId()).isEqualTo(foundStore.getId()), - () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), - () -> assertThat(response.tags()).isEmpty() + () -> assertThat(creationResult.cheer().getDescription()).isEqualTo("맛있어요!"), + () -> assertThat(creationResult.cheer().getCheerTags().getNames()).isEmpty() ); } } @@ -181,9 +200,10 @@ class GetCheers { Cheer cheer1 = cheerGenerator.generateAdmin(member, store1, startAt); Cheer cheer2 = cheerGenerator.generateAdmin(member, store1, startAt.plusHours(1)); Cheer cheer3 = cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2)); - CheerSearchParameters parameters = new CheerSearchParameters(0, 2, null, null, null); - CheersResponse response = cheerService.getCheers(parameters); + var response = cheerService.getCheers( + new CheerSearchParameters(0, 2, null, null, null) + ); assertAll( () -> assertThat(response.cheers()).hasSize(2), @@ -200,10 +220,11 @@ class GetCheers { LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0); Cheer cheer1 = cheerGenerator.generateAdmin(member, store1, startAt); Cheer cheer2 = cheerGenerator.generateAdmin(member, store1, startAt.plusHours(1)); - Cheer cheer3 = cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2)); - CheerSearchParameters parameters = new CheerSearchParameters(1, 2, null, null, null); + cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2)); - CheersResponse response = cheerService.getCheers(parameters); + var response = cheerService.getCheers( + new CheerSearchParameters(1, 2, null, null, null) + ); assertAll( () -> assertThat(response.cheers()).hasSize(1), @@ -218,28 +239,29 @@ class GetCheers { Cheer cheer = cheerGenerator.generateCommon(member, store); cheerImageGenerator.generate(cheer, "key2", 2L); cheerImageGenerator.generate(cheer, "key1", 1L); - CheerSearchParameters parameters = new CheerSearchParameters(0, 1, null, null, null); - CheersResponse response = cheerService.getCheers(parameters); + var response = cheerService.getCheers( + new CheerSearchParameters(0, 1, null, null, null) + ); - assertThat(response.cheers()).hasSize(1); - assertThat(response.cheers().get(0).images()).hasSize(2) - .isSortedAccordingTo(Comparator.comparingLong(CheerImageResponse::orderIndex)); + assertThat(response.cheers().get(0).images()).hasSize(2); } @Test void 요청한_응원을_지역으로_필터링하여_최신순으로_반환한다() { Member member = memberGenerator.generate("123"); - Store store1 = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45", District.GANGNAM); - Store store2 = storeGenerator.generate("456", "서울시 성북구 석관동 123-45", District.SEONGBUK); + Store store1 = storeGenerator.generate("123", "강남", District.GANGNAM); + Store store2 = storeGenerator.generate("456", "성북", District.SEONGBUK); LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0); Cheer cheer1 = cheerGenerator.generateAdmin(member, store1, startAt); Cheer cheer2 = cheerGenerator.generateAdmin(member, store1, startAt.plusHours(1)); - Cheer cheer3 = cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2)); - CheerSearchParameters parameters = new CheerSearchParameters( - 0, 2, null, null, List.of(SearchDistrict.GANGNAM)); + cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2)); - CheersResponse response = cheerService.getCheers(parameters); + var response = cheerService.getCheers( + new CheerSearchParameters( + 0, 2, null, null, List.of(SearchDistrict.GANGNAM) + ) + ); assertAll( () -> assertThat(response.cheers()).hasSize(2), @@ -262,7 +284,7 @@ class GetCheersByStoreId { cheerGenerator.generateCommon(member, store, startAt.plusHours(1)); cheerGenerator.generateCommon(member, store, startAt.plusHours(2)); - CheersInStoreResponse response = cheerService.getCheersByStoreId(store.getId(), 1, 2); + var response = cheerService.getCheersByStoreId(store.getId(), 1, 2); assertAll( () -> assertThat(response.cheers()).hasSize(1), @@ -270,4 +292,19 @@ class GetCheersByStoreId { ); } } + + private StoreSearchResult storeSearchResult(String kakaoId) { + return new StoreSearchResult( + kakaoId, + StoreCategory.KOREAN, + "02-755-5232", + "농민백암순대 본점", + "http://place.map.kakao.com/123", + "서울시 강남구", + "서울시 강남구", + District.GANGNAM, + 37.5665, + 126.9780 + ); + } } From ab52d2d9c07a24d9ce423a0334d0f6093479ffff Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 00:25:31 +0900 Subject: [PATCH 05/11] =?UTF-8?q?test:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=84=B1=EA=B3=B5=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../facade/cheer/CheerRegisterFacadeTest.java | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java index 9740c877..e96502bf 100644 --- a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java +++ b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; import software.amazon.awssdk.core.exception.SdkException; class CheerRegisterFacadeTest extends BaseFacadeTest { @@ -35,18 +36,7 @@ class RegisterCheer { void 응원을_등록하면_이미지를_이동하고_최종_응답을_반환한다() { var member = memberGenerator.generate("member-1"); - CheerRegisterRequest.UploadedImageDetail image1 = - new CheerRegisterRequest.UploadedImageDetail("temp/key1.jpg", 1L, "image/jpeg", 1000L); - CheerRegisterRequest.UploadedImageDetail image2 = - new CheerRegisterRequest.UploadedImageDetail("temp/key2.jpg", 2L, "image/jpeg", 2000L); - - CheerRegisterRequest request = new CheerRegisterRequest( - "kakao-1", - "농민백암순대", - "맛있어요", - List.of(image1, image2), - List.of(CheerTagName.GOOD_FOR_DATING) - ); + CheerRegisterRequest request = getRegisterRequest(); StoreSearchResult storeResult = new StoreSearchResult( "kakao-1", @@ -124,7 +114,7 @@ class RegisterCheer { anyLong(), anyList() )).willThrow( - software.amazon.awssdk.core.exception.SdkException.builder().build() + SdkException.builder().build() ); try { @@ -140,6 +130,48 @@ class RegisterCheer { assertThat(cheerRepository.count()).isZero(); } + @Test + void 이미지_이동이_부분적으로_성공한_후_실패하면_응원을_삭제한다() { + var member = memberGenerator.generate("member-1"); + + CheerRegisterRequest request = getCheerRegisterRequest(); + + StoreSearchResult storeResult = new StoreSearchResult( + "kakao-1", + StoreCategory.KOREAN, + "02-000-0000", + "농민백암순대", + "http://place.map.kakao.com/1", + "서울시 강남구", + "서울시 강남구", + District.GANGNAM, + 37.715132, + 127.269310 + ); + + given(fileClient.moveTempFilesToPermanent( + eq(ImageDomain.CHEER.getName()), + anyLong(), + anyList() + )).willAnswer(invocation -> { + throw SdkException.builder().build(); + }); + + try { + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ); + } catch (Exception ignored) { + } + + assertThat(cheerRepository.count()) + .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다") + .isZero(); + } + @Test void 이미지가_없어도_응원은_정상_등록된다() { var member = memberGenerator.generate("member-1"); @@ -179,10 +211,24 @@ class RegisterCheer { .moveTempFilesToPermanent(anyString(), anyLong(), anyList()); } - @Test - void 이미지_이동이_부분적으로_성공한_후_실패하면_응원을_삭제한다() { - var member = memberGenerator.generate("member-1"); + @NonNull + private CheerRegisterRequest getRegisterRequest() { + CheerRegisterRequest.UploadedImageDetail image1 = + new CheerRegisterRequest.UploadedImageDetail("temp/key1.jpg", 1L, "image/jpeg", 1000L); + CheerRegisterRequest.UploadedImageDetail image2 = + new CheerRegisterRequest.UploadedImageDetail("temp/key2.jpg", 2L, "image/jpeg", 2000L); + + return new CheerRegisterRequest( + "kakao-1", + "농민백암순대", + "맛있어요", + List.of(image1, image2), + List.of(CheerTagName.GOOD_FOR_DATING) + ); + } + @NonNull + private CheerRegisterRequest getCheerRegisterRequest() { CheerRegisterRequest.UploadedImageDetail image1 = new CheerRegisterRequest.UploadedImageDetail( "temp/key1.jpg", 1L, "image/jpeg", 1000L @@ -196,48 +242,13 @@ class RegisterCheer { "temp/key3.jpg", 3L, "image/jpeg", 1000L ); - CheerRegisterRequest request = new CheerRegisterRequest( + return new CheerRegisterRequest( "kakao-1", "농민백암순대", "부분 성공 테스트", List.of(image1, image2, image3), List.of(CheerTagName.GOOD_FOR_DATING) ); - - StoreSearchResult storeResult = new StoreSearchResult( - "kakao-1", - StoreCategory.KOREAN, - "02-000-0000", - "농민백암순대", - "http://place.map.kakao.com/1", - "서울시 강남구", - "서울시 강남구", - District.GANGNAM, - 37.715132, - 127.269310 - ); - - given(fileClient.moveTempFilesToPermanent( - eq(ImageDomain.CHEER.getName()), - anyLong(), - anyList() - )).willAnswer(invocation -> { - throw SdkException.builder().build(); - }); - - try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } - - assertThat(cheerRepository.count()) - .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다") - .isZero(); } } } From 82b774f2d6ceaef717ae1ce73ee6d7c0352727f3 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 00:27:23 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=9D=84=EB=95=8C=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/eatda/facade/CheerRegisterFacade.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/eatda/facade/CheerRegisterFacade.java b/src/main/java/eatda/facade/CheerRegisterFacade.java index ced5f25d..c89095f3 100644 --- a/src/main/java/eatda/facade/CheerRegisterFacade.java +++ b/src/main/java/eatda/facade/CheerRegisterFacade.java @@ -10,7 +10,6 @@ import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import software.amazon.awssdk.core.exception.SdkException; @@ -18,8 +17,6 @@ @RequiredArgsConstructor public class CheerRegisterFacade { - @Value("${cdn.base-url}") - private String cdnBaseUrl; private final CheerService cheerService; private final FileClient fileClient; @@ -31,16 +28,20 @@ public CheerResponse registerCheer(CheerRegisterRequest request, CheerCreationResult creationResult = cheerService.createCheer(request, result, memberId); Cheer cheer = creationResult.cheer(); + if (request.images() == null || request.images().isEmpty()) { + return cheerService.getCheerResponse(cheer.getId()); + } + try { List sortedImages = sortImages(request.images()); List permanentKeys = moveImages(domain, cheer.getId(), sortedImages); - cheer = cheerService.saveCheerImages(cheer.getId(), sortedImages, permanentKeys); + cheerService.saveCheerImages(cheer.getId(), sortedImages, permanentKeys); } catch (SdkException sdkException) { cheerService.deleteCheer(cheer.getId()); throw sdkException; } - return new CheerResponse(cheer, creationResult.store(), cdnBaseUrl); + return cheerService.getCheerResponse(cheer.getId()); } private List sortImages( @@ -53,6 +54,10 @@ private List sortImages( private List moveImages(ImageDomain domain, long cheerId, List sortedImages) { + if (sortedImages.isEmpty()) { + return List.of(); + } + List tempKeys = sortedImages.stream() .map(CheerRegisterRequest.UploadedImageDetail::imageKey) .toList(); From 1ec8489abe0ebb4bd599534d0706f25affaf1d59 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 00:27:44 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=B0=98=ED=99=98=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eatda/service/cheer/CheerService.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/main/java/eatda/service/cheer/CheerService.java b/src/main/java/eatda/service/cheer/CheerService.java index 7b75d0ba..8ac773ac 100644 --- a/src/main/java/eatda/service/cheer/CheerService.java +++ b/src/main/java/eatda/service/cheer/CheerService.java @@ -4,6 +4,7 @@ import eatda.controller.cheer.CheerInStoreResponse; import eatda.controller.cheer.CheerPreviewResponse; import eatda.controller.cheer.CheerRegisterRequest; +import eatda.controller.cheer.CheerResponse; import eatda.controller.cheer.CheerSearchParameters; import eatda.controller.cheer.CheersInStoreResponse; import eatda.controller.cheer.CheersResponse; @@ -68,27 +69,25 @@ private void validateRegisterCheer(Member member, String storeKakaoId) { } @Transactional - public Cheer saveCheerImages(Long cheerId, - List sortedImages, - List permanentKeys) { + public void saveCheerImages(Long cheerId, + List sortedImages, + List permanentKeys) { - Cheer persistentCheer = cheerRepository.findById(cheerId) + Cheer cheer = cheerRepository.findById(cheerId) .orElseThrow(() -> new BusinessException(BusinessErrorCode.CHEER_NOT_FOUND)); IntStream.range(0, sortedImages.size()) .forEach(i -> { var detail = sortedImages.get(i); CheerImage cheerImage = new CheerImage( - persistentCheer, + cheer, permanentKeys.get(i), detail.orderIndex(), detail.contentType(), detail.fileSize() ); - persistentCheer.addImage(cheerImage); + cheer.addImage(cheerImage); }); - - return persistentCheer; } @Transactional(readOnly = true) @@ -107,14 +106,11 @@ public CheersResponse getCheers(CheerSearchParameters parameters) { private CheersResponse toCheersResponse(List cheers) { return new CheersResponse(cheers.stream() - .map(cheer -> { - Store store = cheer.getStore(); - return new CheerPreviewResponse(cheer, - cheer.getImages().stream() - .map(img -> new CheerImageResponse(img, cdnBaseUrl)) - .sorted(Comparator.comparingLong(CheerImageResponse::orderIndex)) - .toList()); - }) + .map(cheer -> new CheerPreviewResponse(cheer, + cheer.getImages().stream() + .map(img -> new CheerImageResponse(img, cdnBaseUrl)) + .sorted(Comparator.comparingLong(CheerImageResponse::orderIndex)) + .toList())) .toList()); } @@ -134,4 +130,12 @@ public CheersInStoreResponse getCheersByStoreId(Long storeId, int page, int size public void deleteCheer(Long cheerId) { cheerRepository.deleteById(cheerId); } + + @Transactional(readOnly = true) + public CheerResponse getCheerResponse(Long cheerId) { + Cheer cheer = cheerRepository.findById(cheerId) + .orElseThrow(() -> new BusinessException(BusinessErrorCode.CHEER_NOT_FOUND)); + + return new CheerResponse(cheer, cdnBaseUrl); + } } From 325cf73577aba9f3038527d4862f61e1197841c8 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 00:29:47 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/eatda/controller/cheer/CheerResponse.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/eatda/controller/cheer/CheerResponse.java b/src/main/java/eatda/controller/cheer/CheerResponse.java index 992faf43..9bbd21d5 100644 --- a/src/main/java/eatda/controller/cheer/CheerResponse.java +++ b/src/main/java/eatda/controller/cheer/CheerResponse.java @@ -2,7 +2,6 @@ import eatda.domain.cheer.Cheer; import eatda.domain.cheer.CheerTagName; -import eatda.domain.store.Store; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -15,12 +14,12 @@ public record CheerResponse( List tags ) { - public CheerResponse(Cheer cheer, Store store, String cdnBaseUrl) { + public CheerResponse(Cheer cheer, String cdnBaseUrl) { this( - store.getId(), + cheer.getStore().getId(), cheer.getId(), cheer.getImages().stream() - .map(img -> new CheerImageResponse(img, cdnBaseUrl)) // ✅ CDN 붙여줌 + .map(img -> new CheerImageResponse(img, cdnBaseUrl)) .sorted(Comparator.comparingLong(CheerImageResponse::orderIndex)) .collect(Collectors.toList()), cheer.getDescription(), From 3766b089fba50d3ede5455d5dd9296af4a5b1993 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 01:25:03 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eatda/facade/CheerRegisterFacade.java | 18 +++++-- .../facade/cheer/CheerRegisterFacadeTest.java | 49 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/main/java/eatda/facade/CheerRegisterFacade.java b/src/main/java/eatda/facade/CheerRegisterFacade.java index c89095f3..96ec913f 100644 --- a/src/main/java/eatda/facade/CheerRegisterFacade.java +++ b/src/main/java/eatda/facade/CheerRegisterFacade.java @@ -7,12 +7,14 @@ import eatda.domain.cheer.Cheer; import eatda.domain.store.StoreSearchResult; import eatda.service.cheer.CheerService; +import java.util.Collections; import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import software.amazon.awssdk.core.exception.SdkException; +@Slf4j @Component @RequiredArgsConstructor public class CheerRegisterFacade { @@ -32,13 +34,21 @@ public CheerResponse registerCheer(CheerRegisterRequest request, return cheerService.getCheerResponse(cheer.getId()); } + List permanentKeys = Collections.emptyList(); try { List sortedImages = sortImages(request.images()); - List permanentKeys = moveImages(domain, cheer.getId(), sortedImages); + permanentKeys = moveImages(domain, cheer.getId(), sortedImages); cheerService.saveCheerImages(cheer.getId(), sortedImages, permanentKeys); - } catch (SdkException sdkException) { + + } catch (Exception e) { + log.error("응원 등록 프로세스 실패. 롤백 수행. cheerId={}", cheer.getId(), e); + cheerService.deleteCheer(cheer.getId()); - throw sdkException; + + if (!permanentKeys.isEmpty()) { + fileClient.deleteFiles(permanentKeys); + } + throw e; } return cheerService.getCheerResponse(cheer.getId()); diff --git a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java index e96502bf..ff7f9342 100644 --- a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java +++ b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java @@ -6,6 +6,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; import eatda.controller.cheer.CheerRegisterRequest; import eatda.controller.cheer.CheerResponse; @@ -71,7 +72,7 @@ class RegisterCheer { assertThat(response.tags()).containsExactly(CheerTagName.GOOD_FOR_DATING); assertThat(response.images()).hasSize(2); - Mockito.verify(fileClient) + verify(fileClient) .moveTempFilesToPermanent( eq(ImageDomain.CHEER.getName()), anyLong(), @@ -168,10 +169,52 @@ class RegisterCheer { } assertThat(cheerRepository.count()) - .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다") + .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다.") .isZero(); } + @Test + void 이미지_이동은_성공했으나_DB_저장에_실패하면_파일과_응원_모두_삭제한다() { + var member = memberGenerator.generate("member-1"); + String tooLongContentType = "a".repeat(300); + + CheerRegisterRequest request = new CheerRegisterRequest( + "kakao-1", "농민백암순대", "맛있어요", + List.of(new CheerRegisterRequest.UploadedImageDetail( + "temp/key1.jpg", + 1L, + tooLongContentType, + 1000L + )), + List.of(CheerTagName.GOOD_FOR_DATING) + ); + + StoreSearchResult storeResult = new StoreSearchResult( + "kakao-1", StoreCategory.KOREAN, "02-000-0000", "농민백암순대", + "http://place.map.kakao.com/1", "서울시 강남구", "서울시 강남구", + District.GANGNAM, 37.715132, 127.269310 + ); + + List movedKeys = List.of("cheer/1/key1.jpg"); + given(fileClient.moveTempFilesToPermanent(anyString(), anyLong(), anyList())) + .willReturn(movedKeys); + + try { + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ); + } catch (Exception ignored) { + assertThat(cheerRepository.count()) + .as("DB 에러(컬럼 길이 초과) 발생 시 응원글은 삭제되어야 한다.") + .isZero(); + + verify(fileClient).deleteFiles(movedKeys); + } + } + @Test void 이미지가_없어도_응원은_정상_등록된다() { var member = memberGenerator.generate("member-1"); @@ -207,7 +250,7 @@ class RegisterCheer { assertThat(response.images()).isEmpty(); assertThat(cheerRepository.count()).isEqualTo(1); - Mockito.verify(fileClient, Mockito.never()) + verify(fileClient, Mockito.never()) .moveTempFilesToPermanent(anyString(), anyLong(), anyList()); } From 12ac1bcb615626305ca3c86bdddedea4f2dcf303 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 02:18:06 +0900 Subject: [PATCH 10/11] =?UTF-8?q?test:=20=EA=B2=80=EC=A6=9D=EC=9D=84=20cat?= =?UTF-8?q?ch=20=EB=B8=94=EB=9F=AD=EC=97=90=EC=84=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eatda/facade/cheer/CheerRegisterFacadeTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java index ff7f9342..520ef1b2 100644 --- a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java +++ b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java @@ -207,12 +207,13 @@ class RegisterCheer { ImageDomain.CHEER ); } catch (Exception ignored) { - assertThat(cheerRepository.count()) - .as("DB 에러(컬럼 길이 초과) 발생 시 응원글은 삭제되어야 한다.") - .isZero(); - - verify(fileClient).deleteFiles(movedKeys); } + + assertThat(cheerRepository.count()) + .as("DB 에러(컬럼 길이 초과) 발생 시 응원글은 삭제되어야 한다.") + .isZero(); + + verify(fileClient).deleteFiles(movedKeys); } @Test From 06562cc5c4dea56425dc5d63308c0826e739d183 Mon Sep 17 00:00:00 2001 From: lvalentine6 Date: Mon, 15 Dec 2025 02:41:19 +0900 Subject: [PATCH 11/11] =?UTF-8?q?test:=20=EC=98=88=EC=99=B8=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../facade/cheer/CheerRegisterFacadeTest.java | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java index 520ef1b2..4593d8d5 100644 --- a/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java +++ b/src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java @@ -1,6 +1,7 @@ package eatda.facade.cheer; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -118,15 +119,14 @@ class RegisterCheer { SdkException.builder().build() ); - try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } + assertThrows(SdkException.class, () -> + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ) + ); assertThat(cheerRepository.count()).isZero(); } @@ -158,15 +158,14 @@ class RegisterCheer { throw SdkException.builder().build(); }); - try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } + assertThrows(SdkException.class, () -> + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ) + ); assertThat(cheerRepository.count()) .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다.") @@ -199,21 +198,21 @@ class RegisterCheer { given(fileClient.moveTempFilesToPermanent(anyString(), anyLong(), anyList())) .willReturn(movedKeys); - try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } + assertThrows(Exception.class, () -> + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ) + ); assertThat(cheerRepository.count()) .as("DB 에러(컬럼 길이 초과) 발생 시 응원글은 삭제되어야 한다.") .isZero(); verify(fileClient).deleteFiles(movedKeys); + } @Test