diff --git a/backend/JiShop/src/main/java/com/jishop/config/RedisConfig.java b/backend/JiShop/src/main/java/com/jishop/config/RedisConfig.java index a936b68c..1b7c5b52 100644 --- a/backend/JiShop/src/main/java/com/jishop/config/RedisConfig.java +++ b/backend/JiShop/src/main/java/com/jishop/config/RedisConfig.java @@ -8,6 +8,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; @@ -19,6 +20,7 @@ import java.util.Arrays; @Configuration +@Profile("!test") @EnableRedisHttpSession public class RedisConfig { diff --git a/backend/JiShop/src/main/java/com/jishop/config/RedissonConfig.java b/backend/JiShop/src/main/java/com/jishop/config/RedissonConfig.java index 2c5c56c6..543af01d 100644 --- a/backend/JiShop/src/main/java/com/jishop/config/RedissonConfig.java +++ b/backend/JiShop/src/main/java/com/jishop/config/RedissonConfig.java @@ -6,8 +6,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; @Configuration +@Profile("!test") public class RedissonConfig { diff --git a/backend/JiShop/src/main/java/com/jishop/config/ShopDBConfig.java b/backend/JiShop/src/main/java/com/jishop/config/ShopDBConfig.java index 476bc862..0d522b65 100644 --- a/backend/JiShop/src/main/java/com/jishop/config/ShopDBConfig.java +++ b/backend/JiShop/src/main/java/com/jishop/config/ShopDBConfig.java @@ -30,7 +30,7 @@ public LocalContainerEntityManagerFactoryBean shopEntityManager() { em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); Properties properties = new Properties(); - properties.setProperty("hibernate.hbm2ddl.auto", "update"); + properties.put("hibernate.hbm2ddl.auto", "create-drop"); properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); properties.setProperty("hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy"); diff --git a/backend/JiShop/src/main/java/com/jishop/config/TossPaymentConfig.java b/backend/JiShop/src/main/java/com/jishop/config/TossPaymentConfig.java index 44beafac..4359f0af 100644 --- a/backend/JiShop/src/main/java/com/jishop/config/TossPaymentConfig.java +++ b/backend/JiShop/src/main/java/com/jishop/config/TossPaymentConfig.java @@ -1,13 +1,10 @@ package com.jishop.config; import lombok.Getter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; import java.nio.charset.StandardCharsets; import java.util.Base64; diff --git a/backend/JiShop/src/main/java/com/jishop/review/service/ReviewServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/review/service/ReviewServiceImpl.java index fc497438..423160e6 100644 --- a/backend/JiShop/src/main/java/com/jishop/review/service/ReviewServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/review/service/ReviewServiceImpl.java @@ -18,6 +18,7 @@ import com.jishop.saleproduct.domain.SaleProduct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -47,7 +48,6 @@ public Long createReview(ReviewRequest reviewRequest, User user) { SaleProduct saleProduct = orderDetail.getSaleProduct(); - String productSummary = makeProductSummar(saleProduct, orderDetail); Product product = saleProduct.getProduct(); @@ -64,10 +64,14 @@ public Long createReview(ReviewRequest reviewRequest, User user) { reviewProduct.increaseRating(reviewRequest.rating()); - // 리뷰 저장 - Review review = reviewRepository.save(reviewRequest.toEntity(reviewRequest.images(), product, orderDetail, user, productSummary)); + String productSummary = makeProductSummar(saleProduct, orderDetail); - return review.getId(); + try { + Review review = reviewRepository.save(reviewRequest.toEntity(reviewRequest.images(), product, orderDetail, user, productSummary)); + return review.getId(); + } catch (DataIntegrityViolationException e) { + throw new DomainException(ErrorType.REVIEW_DUPLICATE); + } } private String makeProductSummar(SaleProduct saleProduct, OrderDetail orderDetail) { diff --git a/backend/JiShop/src/test/java/com/jishop/config/TestRedisConfig.java b/backend/JiShop/src/test/java/com/jishop/config/TestRedisConfig.java new file mode 100644 index 00000000..0242b32c --- /dev/null +++ b/backend/JiShop/src/test/java/com/jishop/config/TestRedisConfig.java @@ -0,0 +1,29 @@ +package com.jishop.config; + +import org.redisson.api.RedissonClient; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +import static org.mockito.Mockito.mock; + +@TestConfiguration +public class TestRedisConfig { + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return mock(RedisConnectionFactory.class); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); // 이렇게 factory 연결 + return redisTemplate; + } + + @Bean + public RedissonClient redissonClient() { + return mock(RedissonClient.class); + } +} diff --git a/backend/JiShop/src/test/java/com/jishop/review/ReviewIntegrationTest.java b/backend/JiShop/src/test/java/com/jishop/review/ReviewIntegrationTest.java new file mode 100644 index 00000000..f7147a23 --- /dev/null +++ b/backend/JiShop/src/test/java/com/jishop/review/ReviewIntegrationTest.java @@ -0,0 +1,211 @@ +package com.jishop.review; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jishop.address.dto.AddressRequest; +import com.jishop.category.domain.Category; +import com.jishop.category.repository.CategoryRepository; +import com.jishop.config.TestRedisConfig; +import com.jishop.member.annotation.CurrentUserResolver; +import com.jishop.member.domain.LoginType; +import com.jishop.member.domain.User; +import com.jishop.member.repository.UserRepository; +import com.jishop.option.domain.Option; +import com.jishop.option.domain.OptionCategory; +import com.jishop.option.repository.OptionRepository; +import com.jishop.order.domain.Order; +import com.jishop.order.domain.OrderDetail; +import com.jishop.order.dto.OrderDetailRequest; +import com.jishop.order.dto.OrderRequest; +import com.jishop.order.repository.OrderRepository; +import com.jishop.product.domain.DiscountStatus; +import com.jishop.product.domain.Labels; +import com.jishop.product.domain.Product; +import com.jishop.product.domain.SaleStatus; +import com.jishop.product.repository.ProductRepository; +import com.jishop.review.domain.tag.Tag; +import com.jishop.review.dto.ReviewRequest; +import com.jishop.review.service.ReviewService; +import com.jishop.saleproduct.domain.SaleProduct; +import com.jishop.saleproduct.repository.SaleProductRepository; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@Import(TestRedisConfig.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ReviewIntegrationTest { + + @Autowired + private SaleProductRepository saleProductRepository; + + @Autowired + private OptionRepository optionRepository; + + @Autowired + private ReviewService reviewService; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private ProductRepository productRepository; + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private CurrentUserResolver currentUserResolver; + + @BeforeAll + void init() { + Category category = new Category(null, 5000L, "패션", "5000", "패션", 1); + Category category1 = new Category(category, 5010L, "상의", "5000>5010", "패션>상의", 2); + Category category2 = new Category(category1, 5100L, "점퍼", "5000>5010>5100", "패션>상의>점퍼", 3); + categoryRepository.save(category); + categoryRepository.save(category1); + categoryRepository.save(category2); + + Product product = new Product(category, 5000L, 5010L, 5100L, "MALL-001", "테스트 상품", "테스트 상품 설명", 10000, 8000, 20, LocalDateTime.now() + , false, SaleStatus.SELLING, DiscountStatus.NONE, true, "테스트 브랜드", 0, Labels.SPECIAL_PRICE, + "main.jpg", "image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg", "detail.jpg", 0); + + productRepository.save(product); + + Option option = new Option(OptionCategory.FASHION_CLOTHES, "화이트/FREE", 1000); + optionRepository.save(option); + + SaleProduct saleProduct = new SaleProduct(product, option, "화이트 점퍼"); + saleProductRepository.save(saleProduct); + + + OrderRequest sampleOrderRequest = createSampleOrderRequest(); + + User user = User.builder() + .loginId("testuser@example.com") // 로그인 아이디 (이메일 또는 소셜 ID) + .password("testPassword123!") // 비밀번호 (소셜 로그인은 null 가능) + .name("홍길동") // 이름 + .birthDate("1995-05-01") // 생년월일 + .gender("M") // 성별 (M/F) + .phone("010-1234-5678") // 휴대폰 번호 + .provider(LoginType.LOCAL) // 로그인 타입 (enum 값) + .ageAgreement(true) // 만 14세 이상 동의 + .useAgreement(true) // 서비스 이용 약관 동의 + .picAgreement(true) // 개인정보 처리 방침 동의 + .adAgreement(false) // 광고 수신 동의 + .build(); + + userRepository.save(user); + + Order order = Order.from(sampleOrderRequest, user, "1"); + OrderDetail orderDetail = OrderDetail.from(order, saleProduct, 3); + List list = new ArrayList<>(); + list.add(orderDetail); + order.updateOrderInfo(1000, 0, 1000, list, "1"); + orderRepository.save(order); + + + } + + @Test + @DisplayName("리뷰 작성") + void createReview() throws Exception { + // given + ReviewRequest request = new ReviewRequest(1L, "굳굳", null, Tag.RECOMMENDED, 3); + User user = userRepository.findById(1L).orElseThrow(IllegalStateException::new); + loginResolver(user); + + // when + Long result = 리뷰작성(request); + + // then + Assertions.assertNotNull(result); + } + + + @Test + @DisplayName("한번한 리뷰 작성 또 작성할때 에러 발생.") + void duplicate_review() throws Exception { + // given + ReviewRequest request = new ReviewRequest(1L, "굳굳", null, Tag.RECOMMENDED, 3); + User user = userRepository.findById(1L).orElseThrow(IllegalStateException::new); + loginResolver(user); + 리뷰작성(request); + + //when + mvc.perform(MockMvcRequestBuilders.post("/reviews") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()); + } + + + private OrderRequest createSampleOrderRequest() { + // 주소 정보 생성 + AddressRequest addressRequest = new AddressRequest( + "홍길동", + "010-1234-5678", + "12345", + "서울특별시 강남구 테헤란로 123", + "456동 789호", + false + ); + + // 주문 상품 목록 생성 + List orderDetailRequestList = List.of( + new OrderDetailRequest(1L, 3) // saleProductId: 1, quantity: 3 + ); + + // OrderRequest 객체 생성 및 반환 + return new OrderRequest(addressRequest, orderDetailRequestList); + } + + private void loginResolver(User user) throws Exception { + given(currentUserResolver.supportsParameter(any())) + .willReturn(true); + given(currentUserResolver.resolveArgument(any(), any(), any(), any())) + .willReturn(user);// 강제 리턴 + } + + + private Long 리뷰작성(ReviewRequest request) throws Exception { + MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post("/reviews") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andReturn(); + + String response = mvcResult.getResponse().getContentAsString(); + + return objectMapper.readValue(response, Long.class); + } +} diff --git a/backend/JiShop/src/test/java/com/jishop/review/dtoTest.java b/backend/JiShop/src/test/java/com/jishop/review/dtoTest.java deleted file mode 100644 index 112370b4..00000000 --- a/backend/JiShop/src/test/java/com/jishop/review/dtoTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.jishop.review; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class dtoTest { - @Test - @DisplayName("request 테스트") - void request() throws Exception { - // given - //when - System.out.println("df"); - //then - - } -} diff --git a/backend/JiShop/src/test/java/com/jishop/review/reviewRepositoryTest.java b/backend/JiShop/src/test/java/com/jishop/review/reviewRepositoryTest.java deleted file mode 100644 index de433453..00000000 --- a/backend/JiShop/src/test/java/com/jishop/review/reviewRepositoryTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jishop.review; - -import com.jishop.review.dto.ReviewImageResponse; -import com.jishop.review.repository.ReviewRepository; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; - -@DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -public class reviewRepositoryTest { - - @Autowired - private ReviewRepository reviewRepository; - - @Test - @DisplayName("리뷰 이미지 슬라이스") - void imagesSlice() throws Exception { - // given - PageRequest pageable = PageRequest.of(0, 1, Sort.by(Sort.Direction.DESC, "createdAt")); - Slice reviewSlice= reviewRepository.findByAllWithImage(pageable).map(ReviewImageResponse::from); - //then - System.out.println(reviewSlice.getContent()); - System.out.println(reviewSlice.getNumber()); - System.out.println(reviewSlice.hasNext()); - - } -} diff --git a/backend/JiShop/src/test/java/com/jishop/review/reviewServiceTest.java b/backend/JiShop/src/test/java/com/jishop/review/reviewServiceTest.java index b0862393..ff40a375 100644 --- a/backend/JiShop/src/test/java/com/jishop/review/reviewServiceTest.java +++ b/backend/JiShop/src/test/java/com/jishop/review/reviewServiceTest.java @@ -4,14 +4,14 @@ import com.jishop.option.domain.OptionCategory; import com.jishop.order.domain.Order; import com.jishop.product.domain.Product; -import com.jishop.review.domain.tag.Tag; -import com.jishop.review.dto.ReviewRequest; import com.jishop.saleproduct.domain.SaleProduct; -import org.springframework.boot.test.context.SpringBootTest; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@SpringBootTest +@ExtendWith(MockitoExtension.class) public class reviewServiceTest { + private Option createOption(OptionCategory optionCategory, String optionValue, int optionExtra) { return new Option(optionCategory, optionValue, optionExtra); } @@ -25,8 +25,8 @@ private SaleProduct createSaleProduct(Product product, Option option) { } private Order createOrder(Long paymentId, Long userId, String mainProductName, int totalPrice, - String receiver, String receiverNumber, String zipCode, - String baseAddress, String detailAddress) { + String receiver, String receiverNumber, String zipCode, + String baseAddress, String detailAddress) { return Order.builder() .recipient(receiver) .phone(receiverNumber) diff --git a/backend/JiShop/src/test/resources/application-test.yml b/backend/JiShop/src/test/resources/application-test.yml new file mode 100644 index 00000000..0bdea12c --- /dev/null +++ b/backend/JiShop/src/test/resources/application-test.yml @@ -0,0 +1,38 @@ +spring: + # 데이터베이스 설정 + datasource: + shopdb: + jdbc-url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1 + username: sa + password: + driver-class-name: org.h2.Driver + logdb: + jdbc-url: jdbc:h2:mem:testdb2;MODE=MYSQL;DB_CLOSE_DELAY=-1 + username: sa + password: + driver-class-name: org.h2.Driver + + # JPA 설정 + jpa: + show-sql: true + properties: + hibernate.format_sql: true # SQL 이쁘게 출력 + hibernate.highlight_sql: true # SQL 컬러 출력 + + data: + redis: + host: localhost + port: 6379 + # H2 콘솔 설정 + h2: + console: + enabled: true + path: /h2-console + + # 캐시 설정 + cache: + type: none + +# 서버 설정 +server: + port: 8081 \ No newline at end of file