From 12281f1bbd7c69582b2739a226aa2cb9f2c4362f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=B1=EC=A4=80?= Date: Thu, 6 Apr 2023 20:38:34 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20Test=20:=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(#430)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - 이미지 리사이징로직 테스트 추가 * - 이미지 리사이징로직 테스트 추가 * github 환경변수 추가 * Update testAfterPR.yml testAfterPR 수정 * Update testAfterPR.yml testAfterPR 다시 수정 * - kakaoBookInfoFetcher 주석처리 * Update testAfterPR.yml testAfterPR 초기화 --- .github/workflows/testAfterPR.yml | 5 +- .../domain/chat/ui/RedisSubscriber.java | 6 +- .../image/infrastructure/AwsS3Service.java | 7 +- .../infrastructure/DefaultImageIOService.java | 23 +++++++ .../image/infrastructure/ImageIOService.java | 13 ++++ .../infrastructure/TestImageIOService.java | 22 +++++++ .../model/image/ui/ImageUploadController.java | 3 +- .../global/config/SecurityConfig.java | 8 +-- .../global/infra/KaKaoBookInfoFetcher.java | 3 + .../security/auth/oauth/OAuthAttributes.java | 28 -------- .../security/auth/oauth/OAuthService.java | 53 --------------- .../security/auth/oauth/OAuthUserProfile.java | 31 --------- .../chat/controller/RedisPublisherTest.java | 45 +++++++++++++ .../chat/controller/RedisSubscriberTest.java | 66 +++++++++++++++++++ .../infrastructure/AwsS3ServiceTest.java | 55 ++++++++++++++++ .../infrastructure/JavaImageResizerTest.java | 49 ++++++++++++++ .../infrastructure/TestImageResizer.java | 12 ++++ .../image/ui/ImageUploadControllerTest.java | 43 ++++++++++++ .../global/GBookInfoParserTest.java | 51 ++++++++++++++ .../global/KakaoBookInfoFetcherTest.java | 57 ++++++++++++++++ .../test/resources/application-API-KEY.yml | 1 + backend/src/test/resources/application.yml | 2 + 22 files changed, 455 insertions(+), 128 deletions(-) create mode 100644 backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/DefaultImageIOService.java create mode 100644 backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/ImageIOService.java create mode 100644 backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/TestImageIOService.java delete mode 100644 backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthAttributes.java delete mode 100644 backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthService.java delete mode 100644 backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthUserProfile.java create mode 100644 backend/src/test/java/com/dongnebook/domain/chat/controller/RedisPublisherTest.java create mode 100644 backend/src/test/java/com/dongnebook/domain/chat/controller/RedisSubscriberTest.java create mode 100644 backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/AwsS3ServiceTest.java create mode 100644 backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/JavaImageResizerTest.java create mode 100644 backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/TestImageResizer.java create mode 100644 backend/src/test/java/com/dongnebook/domain/model/image/ui/ImageUploadControllerTest.java create mode 100644 backend/src/test/java/com/dongnebook/global/GBookInfoParserTest.java create mode 100644 backend/src/test/java/com/dongnebook/global/KakaoBookInfoFetcherTest.java create mode 100644 backend/src/test/resources/application-API-KEY.yml diff --git a/.github/workflows/testAfterPR.yml b/.github/workflows/testAfterPR.yml index dc028e34..08a852e0 100644 --- a/.github/workflows/testAfterPR.yml +++ b/.github/workflows/testAfterPR.yml @@ -14,7 +14,6 @@ on: permissions: write-all - jobs: build: runs-on: ubuntu-latest @@ -39,7 +38,7 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew working-directory: ./backend/ - + - name: Test with Gradle run: ./gradlew test --stacktrace working-directory: ./backend/ @@ -66,4 +65,4 @@ jobs: run: | rm -f ~/.gradle/caches/modules-2/modules-2.lock rm -f ~/.gradle/caches/modules-2/gc.properties - \ No newline at end of file + diff --git a/backend/src/main/java/com/dongnebook/domain/chat/ui/RedisSubscriber.java b/backend/src/main/java/com/dongnebook/domain/chat/ui/RedisSubscriber.java index 0f67e9f0..aaec16a2 100644 --- a/backend/src/main/java/com/dongnebook/domain/chat/ui/RedisSubscriber.java +++ b/backend/src/main/java/com/dongnebook/domain/chat/ui/RedisSubscriber.java @@ -3,6 +3,7 @@ import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Service; @@ -25,7 +26,10 @@ public class RedisSubscriber implements MessageListener { public void onMessage(Message message, byte[] pattern) { try { //레디스 에서 받은 데이터를 역직렬화 - String publishMessage = String.valueOf(redisTemplate.getStringSerializer().deserialize(message.getBody())); + RedisSerializer stringSerializer = redisTemplate + .getStringSerializer(); + String publishMessage = String + .valueOf(stringSerializer.deserialize(message.getBody())); RedisChat redisChat = objectMapper.readValue(publishMessage, RedisChat.class); messagingTemplate.convertAndSend("/sub/room/" + redisChat.getRoomId(), redisChat); log.info("레디스에서 받아옴"); diff --git a/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/AwsS3Service.java b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/AwsS3Service.java index 43918951..cb19b027 100644 --- a/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/AwsS3Service.java +++ b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/AwsS3Service.java @@ -5,8 +5,6 @@ import java.io.IOException; import java.util.UUID; -import javax.imageio.ImageIO; - import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -31,6 +29,7 @@ public class AwsS3Service implements ImageUploadService { private final ImageResizer imageResizer; @Value("${cloud.aws.s3.bucket}") private String bucketName; + private final ImageIOService imageIOService; @@ -39,11 +38,11 @@ public String storeImage(MultipartFile beforeImage) throws IOException { validateFileExists(beforeImage); String originalFilename = beforeImage.getOriginalFilename(); String storeFileName = createStoreFileName(originalFilename); - BufferedImage bufferedImage = ImageIO.read(beforeImage.getInputStream()); + BufferedImage bufferedImage = imageIOService.read(beforeImage.getInputStream()); BufferedImage resizedImage = imageResizer.resizeWithOriginalAspectRatio(bufferedImage); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - ImageIO.write(resizedImage, "jpg", baos); + imageIOService.write(resizedImage, "jpg", baos); baos.flush(); byte[] bytes = baos.toByteArray(); ObjectMetadata objectMetadata = new ObjectMetadata(); diff --git a/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/DefaultImageIOService.java b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/DefaultImageIOService.java new file mode 100644 index 00000000..98bd5738 --- /dev/null +++ b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/DefaultImageIOService.java @@ -0,0 +1,23 @@ +package com.dongnebook.domain.model.image.infrastructure; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; +import org.springframework.stereotype.Component; + +@Component +public class DefaultImageIOService implements ImageIOService { + @Override + public BufferedImage read(InputStream inputStream) throws IOException { + return ImageIO.read(inputStream); + } + + @Override + public void write(BufferedImage resizedImage, String jpg, ByteArrayOutputStream baos) throws IOException { + ImageIO.write(resizedImage, jpg, baos); + } +} diff --git a/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/ImageIOService.java b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/ImageIOService.java new file mode 100644 index 00000000..a6e54b40 --- /dev/null +++ b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/ImageIOService.java @@ -0,0 +1,13 @@ +package com.dongnebook.domain.model.image.infrastructure; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; + +public interface ImageIOService { + BufferedImage read(InputStream inputStream) throws IOException; + + void write(BufferedImage resizedImage, String jpg, ByteArrayOutputStream baos) throws IOException; +} diff --git a/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/TestImageIOService.java b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/TestImageIOService.java new file mode 100644 index 00000000..2cf1e6a3 --- /dev/null +++ b/backend/src/main/java/com/dongnebook/domain/model/image/infrastructure/TestImageIOService.java @@ -0,0 +1,22 @@ +package com.dongnebook.domain.model.image.infrastructure; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TestImageIOService implements ImageIOService{ + @Override + public BufferedImage read(InputStream inputStream) throws IOException { + return new BufferedImage(200,200 ,BufferedImage.TYPE_INT_RGB ); + } + + @Override + public void write(BufferedImage resizedImage, String jpg, ByteArrayOutputStream baos) throws IOException { + log.info("do Nothing"); + } +} diff --git a/backend/src/main/java/com/dongnebook/domain/model/image/ui/ImageUploadController.java b/backend/src/main/java/com/dongnebook/domain/model/image/ui/ImageUploadController.java index 77535569..0846f414 100644 --- a/backend/src/main/java/com/dongnebook/domain/model/image/ui/ImageUploadController.java +++ b/backend/src/main/java/com/dongnebook/domain/model/image/ui/ImageUploadController.java @@ -5,7 +5,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; -import com.dongnebook.domain.model.image.infrastructure.AwsS3Service; import com.dongnebook.domain.model.image.application.ImageUploadService; import com.dongnebook.domain.model.image.dto.ImageUploadRequest; @@ -16,7 +15,7 @@ public class ImageUploadController { private final ImageUploadService awsS3Service; - public ImageUploadController(AwsS3Service awsS3Service) { + public ImageUploadController(ImageUploadService awsS3Service) { this.awsS3Service = awsS3Service; } diff --git a/backend/src/main/java/com/dongnebook/global/config/SecurityConfig.java b/backend/src/main/java/com/dongnebook/global/config/SecurityConfig.java index aefb322d..514f895b 100644 --- a/backend/src/main/java/com/dongnebook/global/config/SecurityConfig.java +++ b/backend/src/main/java/com/dongnebook/global/config/SecurityConfig.java @@ -25,7 +25,6 @@ import com.dongnebook.global.security.auth.handler.MemberAuthenticationEntryPoint; import com.dongnebook.global.security.auth.handler.MemberAuthenticationFailureHandler; import com.dongnebook.global.security.auth.handler.MemberAuthenticationSuccessHandler; -import com.dongnebook.global.security.auth.oauth.OAuthService; import lombok.RequiredArgsConstructor; @@ -35,7 +34,7 @@ public class SecurityConfig { private final RefreshTokenRepository refreshTokenRepository; private final TokenProvider tokenProvider; - private final OAuthService oAuthService; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -56,10 +55,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .apply(new CustomFilterConfigurer()) .and() .authorizeHttpRequests(authorize -> authorize - .anyRequest().permitAll()) - .oauth2Login() // OAuth2 로그인 설정 시작점 - .userInfoEndpoint() // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 - .userService(oAuthService); + .anyRequest().permitAll()); return http.build(); } diff --git a/backend/src/main/java/com/dongnebook/global/infra/KaKaoBookInfoFetcher.java b/backend/src/main/java/com/dongnebook/global/infra/KaKaoBookInfoFetcher.java index de09a0b3..4624eb5e 100644 --- a/backend/src/main/java/com/dongnebook/global/infra/KaKaoBookInfoFetcher.java +++ b/backend/src/main/java/com/dongnebook/global/infra/KaKaoBookInfoFetcher.java @@ -24,6 +24,9 @@ public class KaKaoBookInfoFetcher implements BookInfoFetcher { @Value("${KAKAO_KEY}") private String kakaoKey; + public KaKaoBookInfoFetcher(String kakaoKey) { + this.kakaoKey = kakaoKey; + } @Override public ResponseEntity getBookInfo(String bookTitle) { diff --git a/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthAttributes.java b/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthAttributes.java deleted file mode 100644 index e108c7f6..00000000 --- a/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthAttributes.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.dongnebook.global.security.auth.oauth; - -import java.util.Arrays; -import java.util.Map; -import java.util.function.Function; - -public enum OAuthAttributes { - GOOGLE("google", attributes -> new OAuthUserProfile( - String.valueOf(attributes.get("sub")), - (String) attributes.get("name"), - (String) attributes.get("email") - )); - private final String registrationId; - private final Function, OAuthUserProfile> of; - - OAuthAttributes(String registrationId, Function, OAuthUserProfile> of) { - this.registrationId = registrationId; - this.of = of; - } - - public static OAuthUserProfile extract(String registrationId, Map attributes) { - return Arrays.stream(values()) - .filter(provider -> registrationId.equals(provider.registrationId)) - .findFirst() - .orElseThrow(IllegalArgumentException::new) - .of.apply(attributes); - } -} diff --git a/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthService.java b/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthService.java deleted file mode 100644 index d671ddd3..00000000 --- a/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthService.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.dongnebook.global.security.auth.oauth; - -import java.util.Collections; -import java.util.Map; - -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; - -import com.dongnebook.domain.member.domain.Member; -import com.dongnebook.domain.member.repository.MemberRepository; - -@Service -public class OAuthService implements OAuth2UserService { - private final MemberRepository memberRepository; - - public OAuthService(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2UserService delegate = new DefaultOAuth2UserService(); - OAuth2User oAuth2User = delegate.loadUser(userRequest); // OAuth 서비스에서 가져온 유저 정보를 담고있음 - - String registrationId = userRequest.getClientRegistration() - .getRegistrationId(); // OAuth 서비스 이름(ex. github, naver, google) - String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() - .getUserInfoEndpoint().getUserNameAttributeName(); // OAuth 로그인 시 키(pk)가 되는 값 - Map attributes = oAuth2User.getAttributes(); // OAuth 서비스의 유저 정보들 - - OAuthUserProfile oAuthUserProfile = OAuthAttributes.extract(registrationId, attributes); // registrationId에 따라 유저 정보를 통해 공통된 UserProfile 객체로 만들어 줌 - - Member member = saveOrUpdate(oAuthUserProfile); // DB에 저장 - - return new DefaultOAuth2User( - Collections.singleton(new SimpleGrantedAuthority(member.getAuthority().toString())), - attributes, userNameAttributeName); - } - - /*oauth 이메일(아이디)로 회원가입 전 중복체크하고 oauth 계정에서 닉네임 등 변동 있을시 업데이트*/ - private Member saveOrUpdate(OAuthUserProfile userProfile) { - Member member = memberRepository.findByUserId(userProfile.getEmail()) - .map(m -> m.oauthUpdate(userProfile.getName(), userProfile.getEmail())) // OAuth 서비스 사이트에서 유저 정보 변경이 있을 수 있기 때문에 우리 DB에도 update - .orElse(userProfile.createOauth2Member()); - return memberRepository.save(member); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthUserProfile.java b/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthUserProfile.java deleted file mode 100644 index d3fd9108..00000000 --- a/backend/src/main/java/com/dongnebook/global/security/auth/oauth/OAuthUserProfile.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.dongnebook.global.security.auth.oauth; - -import com.dongnebook.domain.member.domain.Member; - -public class OAuthUserProfile { - private final String name; - private final String email; - private final String oauthId; - - public OAuthUserProfile(String oauthId, String name, String email) { - this.name = name; - this.email = email; - this.oauthId = oauthId; - } - - public Member createOauth2Member() { - return Member.builder() - .userId(email) //이메일 - .nickname(name) // 이름 - .password(oauthId) // 고유값 - .build(); - } - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } -} diff --git a/backend/src/test/java/com/dongnebook/domain/chat/controller/RedisPublisherTest.java b/backend/src/test/java/com/dongnebook/domain/chat/controller/RedisPublisherTest.java new file mode 100644 index 00000000..9e80bc47 --- /dev/null +++ b/backend/src/test/java/com/dongnebook/domain/chat/controller/RedisPublisherTest.java @@ -0,0 +1,45 @@ +package com.dongnebook.domain.chat.controller; + +import static org.mockito.BDDMockito.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; + +import com.dongnebook.domain.chat.domain.RedisChat; +import com.dongnebook.domain.chat.ui.RedisPublisher; + +@ExtendWith(MockitoExtension.class) +class RedisPublisherTest { + @InjectMocks + private RedisPublisher redisPublisher; + + @Mock + private RedisTemplate redisTemplate; + + @Test + @DisplayName("레디스로 채팅을 보낸다.") + void publish(){ + // Given + Long roomId = 1L; + Long senderId = 2L; + String message = "hello"; + LocalDateTime createdAt = LocalDateTime.now(); + RedisChat redisChat = new RedisChat(roomId, senderId, message, createdAt); + doNothing().when(redisTemplate).convertAndSend(anyString(), eq(redisChat)); + ChannelTopic topic = ChannelTopic.of("room" + roomId); + + // When + redisPublisher.publish(topic,redisChat); + + // Then + verify(redisTemplate,times(1)).convertAndSend(String.valueOf(topic),redisChat); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/dongnebook/domain/chat/controller/RedisSubscriberTest.java b/backend/src/test/java/com/dongnebook/domain/chat/controller/RedisSubscriberTest.java new file mode 100644 index 00000000..d62002bb --- /dev/null +++ b/backend/src/test/java/com/dongnebook/domain/chat/controller/RedisSubscriberTest.java @@ -0,0 +1,66 @@ +package com.dongnebook.domain.chat.controller; + +import static org.mockito.Mockito.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.messaging.simp.SimpMessageSendingOperations; + +import com.dongnebook.domain.chat.domain.RedisChat; +import com.dongnebook.domain.chat.ui.RedisSubscriber; +import com.fasterxml.jackson.databind.ObjectMapper; + +@ExtendWith(MockitoExtension.class) +class RedisSubscriberTest { + + @InjectMocks + private RedisSubscriber redisSubscriber; + + @Mock + private ObjectMapper objectMapper; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private SimpMessageSendingOperations messagingTemplate; + + + @Test + @DisplayName("레디스에서 받은 데이터를 역직렬화한다.") + void testOnMessage() throws Exception { + // Given + Long roomId = 1L; + Long senderId = 2L; + String message = "hello"; + LocalDateTime createdAt = LocalDateTime.now(); + RedisChat redisChat = new RedisChat(roomId, senderId, message, createdAt); + String publishMessage = "serialized redisChat"; + Message messageMock = mock(Message.class); + + when(messageMock.getBody()).thenReturn(publishMessage.getBytes()); + when(redisTemplate.getStringSerializer()).thenReturn(mock(StringRedisSerializer.class)); + when(redisTemplate.getStringSerializer().deserialize(any())).thenReturn(publishMessage); + + + when(objectMapper.readValue(publishMessage, RedisChat.class)).thenReturn(redisChat); + + // When + redisSubscriber.onMessage(messageMock, null); + + // Then + verify(messagingTemplate, times(1)).convertAndSend("/sub/room/" + redisChat.getRoomId(), redisChat); + verify(redisTemplate, times(2)).getStringSerializer(); + verify(objectMapper, times(1)).readValue(publishMessage, RedisChat.class); + verifyNoMoreInteractions(messagingTemplate, redisTemplate, objectMapper); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/AwsS3ServiceTest.java b/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/AwsS3ServiceTest.java new file mode 100644 index 00000000..886910f7 --- /dev/null +++ b/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/AwsS3ServiceTest.java @@ -0,0 +1,55 @@ +package com.dongnebook.domain.model.image.infrastructure; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.services.s3.AmazonS3; +import com.dongnebook.domain.model.image.application.ImageResizer; + +@ExtendWith(MockitoExtension.class) +class AwsS3ServiceTest { + + private AwsS3Service awsS3Service; + + @Mock + private AmazonS3 amazonS3; + + private final ImageResizer imageResizer= new TestImageResizer(); + private final ImageIOService imageIOService = new TestImageIOService(); + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + awsS3Service = new AwsS3Service(amazonS3, imageResizer,imageIOService); + } + + + @Test + void testStoreImage() throws IOException { + // given + byte[] fileBytes = {0x12, 0x34, 0x56}; + MultipartFile multipartFile = new MockMultipartFile("test.jpg", fileBytes); + BufferedImage mockBufferedImage = mock(BufferedImage.class); + when(amazonS3.getUrl(any(), any())).thenReturn( new URL("https://www.example.com")); + // when + awsS3Service.storeImage(multipartFile); + + // then + assertThat(imageResizer.resizeWithOriginalAspectRatio(mockBufferedImage)).usingRecursiveComparison().isEqualTo(new BufferedImage(100,100,BufferedImage.TYPE_INT_RGB)); + verify(amazonS3, times(1)).getUrl(any(), any()); + + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/JavaImageResizerTest.java b/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/JavaImageResizerTest.java new file mode 100644 index 00000000..93f5225e --- /dev/null +++ b/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/JavaImageResizerTest.java @@ -0,0 +1,49 @@ +package com.dongnebook.domain.model.image.infrastructure; + +import static org.junit.jupiter.api.Assertions.*; + +import java.awt.*; +import java.awt.image.BufferedImage; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.dongnebook.domain.model.image.application.ImageResizer; + +class JavaImageResizerTest { + + private static final int MAX_SIZE = 400; + private ImageResizer imageResizer; + + @BeforeEach + public void setUp() { + imageResizer = new JavaImageResizer(); + } + + @Test + void testResizeWithOriginalAspectRatio() { + // given + BufferedImage originalImage = TestImageUtil.createTestImage(600, 400); + + // when + BufferedImage resizedImage = imageResizer.resizeWithOriginalAspectRatio(originalImage); + + // then + assertEquals(MAX_SIZE, resizedImage.getWidth()); + assertEquals(266, resizedImage.getHeight()); + } + + public static class TestImageUtil { + + public static BufferedImage createTestImage(int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics2D = image.createGraphics(); + graphics2D.setPaint(Color.WHITE); + graphics2D.fillRect(0, 0, width, height); + graphics2D.setPaint(Color.BLACK); + graphics2D.drawRect(0, 0, width - 1, height - 1); + graphics2D.dispose(); + return image; + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/TestImageResizer.java b/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/TestImageResizer.java new file mode 100644 index 00000000..b492a028 --- /dev/null +++ b/backend/src/test/java/com/dongnebook/domain/model/image/infrastructure/TestImageResizer.java @@ -0,0 +1,12 @@ +package com.dongnebook.domain.model.image.infrastructure; + +import java.awt.image.BufferedImage; + +import com.dongnebook.domain.model.image.application.ImageResizer; + +public class TestImageResizer implements ImageResizer { + @Override + public BufferedImage resizeWithOriginalAspectRatio(BufferedImage originalImage) { + return new BufferedImage(100,100,BufferedImage.TYPE_INT_RGB); + } +} diff --git a/backend/src/test/java/com/dongnebook/domain/model/image/ui/ImageUploadControllerTest.java b/backend/src/test/java/com/dongnebook/domain/model/image/ui/ImageUploadControllerTest.java new file mode 100644 index 00000000..c111c43f --- /dev/null +++ b/backend/src/test/java/com/dongnebook/domain/model/image/ui/ImageUploadControllerTest.java @@ -0,0 +1,43 @@ +package com.dongnebook.domain.model.image.ui; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; + +import com.dongnebook.domain.model.image.application.ImageUploadService; +import com.dongnebook.domain.model.image.dto.ImageUploadRequest; + +class ImageUploadControllerTest { + private ImageUploadController imageUploadController; + private ImageUploadService mockAwsS3Service; + + @BeforeEach + void setUp() { + mockAwsS3Service = mock(ImageUploadService.class); + imageUploadController = new ImageUploadController(mockAwsS3Service); + } + + @Test + void saveImage_returnsS3Url() throws IOException, IOException { + // given + MockMultipartFile mockMultipartFile = new MockMultipartFile("img", "test.jpg", MediaType.IMAGE_JPEG_VALUE, "test".getBytes()); + ImageUploadRequest imageUploadRequest = new ImageUploadRequest(mockMultipartFile); + + + String s3Url = "https://example.com/test.jpg"; + when(mockAwsS3Service.storeImage(mockMultipartFile)).thenReturn(s3Url); + + // when + String actual = imageUploadController.saveImage(imageUploadRequest); + + // then + assertEquals(s3Url, actual); + verify(mockAwsS3Service).storeImage(mockMultipartFile); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/dongnebook/global/GBookInfoParserTest.java b/backend/src/test/java/com/dongnebook/global/GBookInfoParserTest.java new file mode 100644 index 00000000..d23ea0c2 --- /dev/null +++ b/backend/src/test/java/com/dongnebook/global/GBookInfoParserTest.java @@ -0,0 +1,51 @@ +package com.dongnebook.global; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpEntity; + +import com.dongnebook.domain.book.application.port.in.response.ApiBookInfoResponse; +import com.dongnebook.global.infra.GsonBookInfoParser; + +class GsonBookInfoParserTest { + + private GsonBookInfoParser parser; + + @Mock + private HttpEntity httpEntityMock; + + private final String testJson = "{\"documents\":[{\"title\":\"Book 1\",\"publisher\":\"Publisher 1\",\"authors\":[\"Author 1\",\"Author 2\"]}," + + "{\"title\":\"Book 2\",\"publisher\":\"Publisher 2\",\"authors\":[\"Author 3\",\"Author 4\"]}]}"; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + parser = new GsonBookInfoParser(); + } + + @Test + void toResponse_validJson_shouldReturnCorrectList() { + when(httpEntityMock.getBody()).thenReturn(testJson); + + List response = parser.toResponse(httpEntityMock); + + assertEquals(2, response.size()); + assertEquals("Book 1", response.get(0).getTitle()); + assertEquals("Publisher 1", response.get(0).getPublisher()); + assertEquals(2, response.get(0).getAuthors().size()); + assertEquals("Author 1", response.get(0).getAuthors().get(0)); + assertEquals("Author 2", response.get(0).getAuthors().get(1)); + assertEquals("Book 2", response.get(1).getTitle()); + assertEquals("Publisher 2", response.get(1).getPublisher()); + assertEquals(2, response.get(1).getAuthors().size()); + assertEquals("Author 3", response.get(1).getAuthors().get(0)); + assertEquals("Author 4", response.get(1).getAuthors().get(1)); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/dongnebook/global/KakaoBookInfoFetcherTest.java b/backend/src/test/java/com/dongnebook/global/KakaoBookInfoFetcherTest.java new file mode 100644 index 00000000..7521d1d3 --- /dev/null +++ b/backend/src/test/java/com/dongnebook/global/KakaoBookInfoFetcherTest.java @@ -0,0 +1,57 @@ +package com.dongnebook.global; + +import static org.assertj.core.api.Assertions.*; + +import java.net.URI; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.RequestMatcher; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; +import org.springframework.web.client.RestTemplate; + +import com.dongnebook.global.infra.KaKaoBookInfoFetcher; + + +class KakaoBookInfoFetcherTest { + + private static final String KAKAO_KEY = System.getenv("KAKAO_KEY"); + private static final String KAKAO_BOOKSEARCH_API = "https://dapi.kakao.com/v3/search/book"; + private static final String KAKAOAK = "KakaoAK "; + + + void getBookInfo_shouldReturnValidResponse() { + RestTemplate restTemplate = new RestTemplate(); + KaKaoBookInfoFetcher kaKaoBookInfoFetcher = new KaKaoBookInfoFetcher(KAKAO_KEY); + MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); + + HttpHeaders expectedHeaders = new HttpHeaders(); + expectedHeaders.set(HttpHeaders.AUTHORIZATION, KAKAOAK + KAKAO_KEY); + + String bookTitle = "자바의정석"; + URI expectedUri = URI.create(KAKAO_BOOKSEARCH_API + "?query=" + bookTitle + "&target=title&size=50"); + + String expectedResponseBody = "testResponseBody"; + + mockServer.expect(requestTo(expectedUri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, KAKAOAK + KAKAO_KEY)) + .andRespond(MockRestResponseCreators.withSuccess(expectedResponseBody, MediaType.APPLICATION_JSON)); + + // when + ResponseEntity actualResponse = kaKaoBookInfoFetcher.getBookInfo(bookTitle); + + // then + assertThat(actualResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + private RequestMatcher requestTo(URI uri) { + return MockRestRequestMatchers.requestTo(uri); + } + } + diff --git a/backend/src/test/resources/application-API-KEY.yml b/backend/src/test/resources/application-API-KEY.yml new file mode 100644 index 00000000..5927eb40 --- /dev/null +++ b/backend/src/test/resources/application-API-KEY.yml @@ -0,0 +1 @@ +KAKAO_KEY: ${KAKAO_KEY} \ No newline at end of file diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index ac174542..ecdc810c 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -1,3 +1,5 @@ spring: + profiles: + include: API-KEY jpa: database-platform: org.hibernate.dialect.H2Dialect