1+ package com .deliveranything .domain .product .product .service ;
2+
3+ import static org .assertj .core .api .Assertions .assertThat ;
4+ import static org .junit .jupiter .api .Assertions .assertThrows ;
5+ import static org .mockito .ArgumentMatchers .any ;
6+ import static org .mockito .ArgumentMatchers .anyLong ;
7+ import static org .mockito .Mockito .doNothing ;
8+ import static org .mockito .Mockito .doThrow ;
9+ import static org .mockito .Mockito .mock ;
10+ import static org .mockito .Mockito .never ;
11+ import static org .mockito .Mockito .verify ;
12+ import static org .mockito .Mockito .when ;
13+
14+ import com .deliveranything .domain .product .product .dto .ProductCreateRequest ;
15+ import com .deliveranything .domain .product .product .dto .ProductDetailResponse ;
16+ import com .deliveranything .domain .product .product .dto .ProductResponse ;
17+ import com .deliveranything .domain .product .product .dto .ProductSearchRequest ;
18+ import com .deliveranything .domain .product .product .dto .ProductUpdateRequest ;
19+ import com .deliveranything .domain .product .product .entity .Product ;
20+ import com .deliveranything .domain .product .product .repository .ProductRepository ;
21+ import com .deliveranything .domain .store .store .entity .Store ;
22+ import com .deliveranything .domain .store .store .service .StoreService ;
23+ import com .deliveranything .global .exception .CustomException ;
24+ import com .deliveranything .global .exception .ErrorCode ;
25+ import java .lang .reflect .Field ;
26+ import java .util .List ;
27+ import org .junit .jupiter .api .DisplayName ;
28+ import org .junit .jupiter .api .Test ;
29+ import org .junit .jupiter .api .extension .ExtendWith ;
30+ import org .mockito .InjectMocks ;
31+ import org .mockito .Mock ;
32+ import org .mockito .junit .jupiter .MockitoExtension ;
33+ import org .springframework .data .domain .PageRequest ;
34+ import org .springframework .data .domain .SliceImpl ;
35+
36+ @ ExtendWith (MockitoExtension .class )
37+ @ DisplayName ("ProductService 테스트" )
38+ class ProductServiceTest {
39+
40+ @ Mock
41+ private ProductRepository productRepository ;
42+
43+ @ Mock
44+ private StoreService storeService ;
45+
46+ @ Mock
47+ private KeywordGenerationService keywordGenerationService ;
48+
49+ @ InjectMocks
50+ private ProductService productService ;
51+
52+ // Helper method to create a Store object with ID for testing
53+ private Store createTestStore (Long id , Long sellerProfileId , String name ) {
54+ Store store = Store .builder ()
55+ .sellerProfileId (sellerProfileId )
56+ .name (name )
57+ .storeCategory (null ) // Not relevant for product tests
58+ .roadAddr ("Test Road" )
59+ .imageUrl ("test.jpg" )
60+ .location (null )
61+ .build ();
62+ try {
63+ Field idField = store .getClass ().getSuperclass ().getDeclaredField ("id" );
64+ idField .setAccessible (true );
65+ idField .set (store , id );
66+ } catch (NoSuchFieldException | IllegalAccessException e ) {
67+ throw new RuntimeException ("Failed to set ID for Store" , e );
68+ }
69+ return store ;
70+ }
71+
72+ // Helper method to create a Product object with ID for testing
73+ private Product createTestProduct (Long id , Store store , String name , String description , Integer price , String imageUrl , Integer initialStock ) {
74+ Product product = Product .builder ()
75+ .store (store )
76+ .name (name )
77+ .description (description )
78+ .price (price )
79+ .imageUrl (imageUrl )
80+ .initialStock (initialStock )
81+ .build ();
82+ try {
83+ Field idField = product .getClass ().getSuperclass ().getDeclaredField ("id" );
84+ idField .setAccessible (true );
85+ idField .set (product , id );
86+ } catch (NoSuchFieldException | IllegalAccessException e ) {
87+ throw new RuntimeException ("Failed to set ID for Product" , e );
88+ }
89+ return product ;
90+ }
91+
92+ @ Test
93+ @ DisplayName ("상품 생성 성공" )
94+ void createProduct_success () {
95+ // given
96+ Long storeId = 1L ;
97+ ProductCreateRequest request = new ProductCreateRequest ("Test Product" , "Description" , 1000 , "product.jpg" , 10 );
98+ Store store = createTestStore (storeId , 1L , "Test Store" );
99+
100+ Product savedProductMock = mock (Product .class );
101+ when (savedProductMock .getId ()).thenReturn (1L );
102+ when (savedProductMock .getName ()).thenReturn (request .name ());
103+ when (savedProductMock .getStore ()).thenReturn (store ); // Mock the store for ProductResponse.from
104+
105+ when (storeService .getStoreById (storeId )).thenReturn (store );
106+ when (productRepository .save (any (Product .class ))).thenReturn (savedProductMock );
107+ doNothing ().when (keywordGenerationService ).generateAndSaveKeywords (anyLong ());
108+
109+ // when
110+ ProductResponse response = productService .createProduct (storeId , request );
111+
112+ // then
113+ assertThat (response .productId ()).isEqualTo (1L );
114+ assertThat (response .name ()).isEqualTo ("Test Product" );
115+ verify (storeService ).getStoreById (storeId );
116+ verify (productRepository ).save (any (Product .class ));
117+ verify (keywordGenerationService ).generateAndSaveKeywords (1L );
118+ }
119+
120+ @ Test
121+ @ DisplayName ("상품 삭제 성공" )
122+ void deleteProduct_success () {
123+ // given
124+ Long storeId = 1L ;
125+ Long productId = 1L ;
126+ Store store = createTestStore (storeId , 1L , "Test Store" );
127+ Product product = mock (Product .class ); // Make product a mock
128+ // when(product.getId()).thenReturn(productId); // Not directly used by deleteProduct or validateStore
129+
130+ when (productRepository .getById (productId )).thenReturn (product );
131+ doNothing ().when (productRepository ).delete (any (Product .class ));
132+
133+ // when
134+ productService .deleteProduct (storeId , productId );
135+
136+ // then
137+ verify (productRepository ).getById (productId );
138+ verify (product ).validateStore (storeId ); // Verify interaction with the mock product
139+ verify (productRepository ).delete (product );
140+ }
141+
142+ @ Test
143+ @ DisplayName ("상품 삭제 실패 - 상점 ID 불일치" )
144+ void deleteProduct_fail_storeIdMismatch () {
145+ // given
146+ Long storeId = 1L ;
147+ Long productId = 1L ;
148+ Long wrongStoreId = 2L ;
149+ Store store = createTestStore (storeId , 1L , "Test Store" );
150+ Product product = mock (Product .class ); // Make product a mock
151+ // when(product.getId()).thenReturn(productId); // Not directly used by deleteProduct or validateStore
152+
153+ when (productRepository .getById (productId )).thenReturn (product );
154+ doThrow (new CustomException (ErrorCode .PRODUCT_STORE_MISMATCH ))
155+ .when (product ).validateStore (wrongStoreId );
156+
157+ // when & then
158+ CustomException exception = assertThrows (CustomException .class , () ->
159+ productService .deleteProduct (wrongStoreId , productId ));
160+ assertThat (exception .getCode ()).isEqualTo (ErrorCode .PRODUCT_STORE_MISMATCH .getCode ());
161+ verify (productRepository ).getById (productId );
162+ verify (productRepository , never ()).delete (any (Product .class ));
163+ verify (product ).validateStore (wrongStoreId ); // Verify interaction with the mock product
164+ }
165+
166+ @ Test
167+ @ DisplayName ("상품 업데이트 성공" )
168+ void updateProduct_success () {
169+ // given
170+ Long storeId = 1L ;
171+ Long productId = 1L ;
172+ ProductUpdateRequest request = new ProductUpdateRequest ("Updated Product" , "Updated Desc" , 2000 , 20 , "updated.jpg" );
173+ Store store = createTestStore (storeId , 1L , "Test Store" );
174+ Product existingProduct = createTestProduct (productId , store , "Original Product" , "Original Desc" , 1000 , "original.jpg" , 10 );
175+
176+ when (productRepository .getById (productId )).thenReturn (existingProduct );
177+ doNothing ().when (keywordGenerationService ).generateAndSaveKeywords (anyLong ());
178+
179+ // when
180+ ProductResponse response = productService .updateProduct (storeId , productId , request );
181+
182+ // then
183+ assertThat (response .name ()).isEqualTo ("Updated Product" );
184+ assertThat (response .price ()).isEqualTo (2000 );
185+ assertThat (response .imageUrl ()).isEqualTo ("updated.jpg" );
186+ assertThat (existingProduct .getStock ().getTotalQuantity ()).isEqualTo (20 );
187+ verify (productRepository ).getById (productId );
188+ verify (keywordGenerationService ).generateAndSaveKeywords (productId );
189+ }
190+
191+ @ Test
192+ @ DisplayName ("상품 조회 성공" )
193+ void getProduct_success () {
194+ // given
195+ Long storeId = 1L ;
196+ Long productId = 1L ;
197+ Store store = createTestStore (storeId , 1L , "Test Store" );
198+ Product product = createTestProduct (productId , store , "Test Product" , "Description" , 1000 , "product.jpg" , 10 );
199+
200+ when (productRepository .getById (productId )).thenReturn (product );
201+
202+ // when
203+ ProductDetailResponse response = productService .getProduct (storeId , productId );
204+
205+ // then
206+ assertThat (response .productId ()).isEqualTo (productId );
207+ assertThat (response .name ()).isEqualTo ("Test Product" );
208+ verify (productRepository ).getById (productId );
209+ }
210+
211+ @ Test
212+ @ DisplayName ("상품 검색 성공" )
213+ void searchProducts_success () {
214+ // given
215+ Long storeId = 1L ;
216+ ProductSearchRequest request = new ProductSearchRequest ("keyword" , null , 10 );
217+ Store store = createTestStore (storeId , 1L , "Test Store" );
218+ Product product = createTestProduct (1L , store , "Found Product" , "Description" , 1000 , "product.jpg" , 10 );
219+ SliceImpl <Product > productSlice = new SliceImpl <>(List .of (product ), PageRequest .of (0 , 10 ), false );
220+
221+ when (storeService .getStoreById (storeId )).thenReturn (store );
222+ when (productRepository .search (storeId , request )).thenReturn (productSlice );
223+
224+ // when
225+ var responseSlice = productService .searchProducts (storeId , request );
226+
227+ // then
228+ assertThat (responseSlice .getContent ()).hasSize (1 );
229+ assertThat (responseSlice .getContent ().get (0 ).name ()).isEqualTo ("Found Product" );
230+ verify (storeService ).getStoreById (storeId );
231+ verify (productRepository ).search (storeId , request );
232+ }
233+
234+ @ Test
235+ @ DisplayName ("ID로 상품 조회 성공" )
236+ void getProductById_success () {
237+ // given
238+ Long productId = 1L ;
239+ Store store = createTestStore (1L , 1L , "Test Store" );
240+ Product product = createTestProduct (productId , store , "Test Product" , "Description" , 1000 , "product.jpg" , 10 );
241+
242+ when (productRepository .getById (productId )).thenReturn (product );
243+
244+ // when
245+ Product foundProduct = productService .getProductById (productId );
246+
247+ // then
248+ assertThat (foundProduct .getId ()).isEqualTo (productId );
249+ assertThat (foundProduct .getName ()).isEqualTo ("Test Product" );
250+ verify (productRepository ).getById (productId );
251+ }
252+ }
0 commit comments