1010import com .backend .domain .payment .repository .PaymentMethodRepository ;
1111import com .backend .domain .payment .repository .PaymentRepository ;
1212import com .backend .domain .payment .service .TossBillingClientService ;
13+ import com .backend .global .security .JwtUtil ;
1314import com .fasterxml .jackson .databind .ObjectMapper ;
1415import org .junit .jupiter .api .BeforeEach ;
1516import org .junit .jupiter .api .Test ;
2122import org .springframework .test .context .bean .override .mockito .MockitoBean ;
2223import org .springframework .test .web .servlet .MockMvc ;
2324import org .springframework .transaction .annotation .Transactional ;
24- import static org .hamcrest .Matchers .is ;
2525import static org .hamcrest .Matchers .notNullValue ;
2626import static org .hamcrest .Matchers .greaterThanOrEqualTo ;
27+ import static org .junit .jupiter .api .Assertions .assertTrue ;
2728import static org .mockito .ArgumentMatchers .*;
2829import static org .mockito .BDDMockito .given ;
29-
30+ import static org . hamcrest . Matchers .*;
3031import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
3132import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .post ;
3233import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .*;
@@ -42,7 +43,11 @@ class ApiV1PaymentControllerTest {
4243 @ Autowired MemberRepository memberRepository ;
4344 @ Autowired PaymentMethodRepository paymentMethodRepository ;
4445 @ Autowired PaymentRepository paymentRepository ;
45-
46+ @ Autowired
47+ JwtUtil jwtUtil ;
48+ private String bearer (String email ) {
49+ return "Bearer " + jwtUtil .generateAccessToken (email );
50+ }
4651 // 외부 호출은 Mock 처리 (실제 네트워크 X)
4752 @ MockitoBean
4853 TossBillingClientService tossBillingClientService ;
@@ -104,20 +109,20 @@ void charge_success() throws Exception {
104109 mockMvc .perform (post ("/api/v1/payments" )
105110 .contentType (MediaType .APPLICATION_JSON )
106111 .content (om .writeValueAsString (req )))
107- .andExpect (status ().isOk ())
112+ .andExpect (status ().isCreated ())
108113 // PaymentResponse(JSON) 필드 검증
109- .andExpect (jsonPath ("$.paymentId" , notNullValue ()))
110- .andExpect (jsonPath ("$.paymentMethodId" , is (pmCard .getId ().intValue ())))
111- .andExpect (jsonPath ("$.status" , is ("SUCCESS" )))
112- .andExpect (jsonPath ("$.amount" , is (5_000 )))
113- .andExpect (jsonPath ("$.currency" , is ("KRW" )))
114- .andExpect (jsonPath ("$.provider" , is ("toss" )))
115- .andExpect (jsonPath ("$.methodType" , is ("CARD" )))
116- .andExpect (jsonPath ("$.transactionId" , is ("payKey_abc123" )))
117- .andExpect (jsonPath ("$.createdAt" , notNullValue ()))
118- .andExpect (jsonPath ("$.paidAt" , notNullValue ()))
119- .andExpect (jsonPath ("$.idempotencyKey" , is ("idem-001" )))
120- .andExpect (jsonPath ("$.balanceAfter" , notNullValue ()));
114+ .andExpect (jsonPath ("$.data. paymentId" , notNullValue ()))
115+ .andExpect (jsonPath ("$.data. paymentMethodId" , is (pmCard .getId ().intValue ())))
116+ .andExpect (jsonPath ("$.data. status" , is ("SUCCESS" )))
117+ .andExpect (jsonPath ("$.data. amount" , is (5_000 )))
118+ .andExpect (jsonPath ("$.data. currency" , is ("KRW" )))
119+ .andExpect (jsonPath ("$.data. provider" , is ("toss" )))
120+ .andExpect (jsonPath ("$.data. methodType" , is ("CARD" )))
121+ .andExpect (jsonPath ("$.data. transactionId" , is ("payKey_abc123" )))
122+ .andExpect (jsonPath ("$.data. createdAt" , notNullValue ()))
123+ .andExpect (jsonPath ("$.data. paidAt" , notNullValue ()))
124+ .andExpect (jsonPath ("$.data. idempotencyKey" , is ("idem-001" )))
125+ .andExpect (jsonPath ("$.data. balanceAfter" , notNullValue ()));
121126 }
122127
123128 // 2) 멱등: 같은 키로 두 번 쏘면 같은 결과
@@ -140,17 +145,17 @@ void charge_idempotent_returns_same_result() throws Exception {
140145 mockMvc .perform (post ("/api/v1/payments" )
141146 .contentType (MediaType .APPLICATION_JSON )
142147 .content (om .writeValueAsString (req )))
143- .andExpect (status ().isOk ())
144- .andExpect (jsonPath ("$.status" , is ("SUCCESS" )))
145- .andExpect (jsonPath ("$.transactionId" , is ("payKey_idem" )));
148+ .andExpect (status ().isCreated ())
149+ .andExpect (jsonPath ("$.data. status" , is ("SUCCESS" )))
150+ .andExpect (jsonPath ("$.data. transactionId" , is ("payKey_idem" )));
146151
147152 // 2차 호출(같은 키) → PG 안 타고 기존 결과 그대로
148153 mockMvc .perform (post ("/api/v1/payments" )
149154 .contentType (MediaType .APPLICATION_JSON )
150155 .content (om .writeValueAsString (req )))
151- .andExpect (status ().isOk ())
152- .andExpect (jsonPath ("$.status" , is ("SUCCESS" )))
153- .andExpect (jsonPath ("$.transactionId" , is ("payKey_idem" )));
156+ .andExpect (status ().isCreated ())
157+ .andExpect (jsonPath ("$.data. status" , is ("SUCCESS" )))
158+ .andExpect (jsonPath ("$.data. transactionId" , is ("payKey_idem" )));
154159 }
155160
156161 // 3) 내 결제 목록 조회
@@ -169,18 +174,18 @@ void get_my_payments_list() throws Exception {
169174 mockMvc .perform (post ("/api/v1/payments" )
170175 .contentType (MediaType .APPLICATION_JSON )
171176 .content (om .writeValueAsString (req )))
172- .andExpect (status ().isOk ());
177+ .andExpect (status ().isCreated ());
173178
174179 mockMvc .perform (get ("/api/v1/payments/me" )
175180 .param ("page" , "1" )
176181 .param ("size" , "20" ))
177182 .andExpect (status ().isOk ())
178- .andExpect (jsonPath ("$.page" , is (1 )))
179- .andExpect (jsonPath ("$.size" , is (20 )))
180- .andExpect (jsonPath ("$.total" , greaterThanOrEqualTo (1 )))
181- .andExpect (jsonPath ("$.items[0].paymentId" , notNullValue ()))
182- .andExpect (jsonPath ("$.items[0].amount" , notNullValue ()))
183- .andExpect (jsonPath ("$.items[0].status" , notNullValue ()));
183+ .andExpect (jsonPath ("$.data. page" , is (1 )))
184+ .andExpect (jsonPath ("$.data. size" , is (20 )))
185+ .andExpect (jsonPath ("$.data. total" , greaterThanOrEqualTo (1 )))
186+ .andExpect (jsonPath ("$.data. items[0].paymentId" , notNullValue ()))
187+ .andExpect (jsonPath ("$.data. items[0].amount" , notNullValue ()))
188+ .andExpect (jsonPath ("$.data. items[0].status" , notNullValue ()));
184189 }
185190
186191 // 4) 내 결제 단건 상세
@@ -200,31 +205,33 @@ void get_my_payment_detail() throws Exception {
200205 String body = mockMvc .perform (post ("/api/v1/payments" )
201206 .contentType (MediaType .APPLICATION_JSON )
202207 .content (om .writeValueAsString (req )))
203- .andExpect (status ().isOk ())
208+ .andExpect (status ().isCreated ())
204209 .andReturn ().getResponse ().getContentAsString ();
205210
211+ var root = om .readTree (body );
206212 // JsonPath로 꺼내도 되고, 간단히 Jackson으로 map 해도 됨
207- long paymentId = om .readTree (body ).get ("paymentId" ).asLong ();
213+ long paymentId = root .path ("data" ).path ("paymentId" ).asLong (root .path ("paymentId" ).asLong (0 ));
214+ assertTrue (paymentId > 0 , "paymentId must be positive" );
208215
209216 mockMvc .perform (get ("/api/v1/payments/me/{paymentId}" , paymentId ))
210217 .andExpect (status ().isOk ())
211- .andExpect (jsonPath ("$.paymentId" , is ((int ) paymentId )))
212- .andExpect (jsonPath ("$.amount" , is (4_000 )))
213- .andExpect (jsonPath ("$.status" , is ("SUCCESS" )))
214- .andExpect (jsonPath ("$.transactionId" , is ("k2" )))
215- .andExpect (jsonPath ("$.paidAt" , notNullValue ()));
218+ .andExpect (jsonPath ("$.data. paymentId" , is ((int ) paymentId )))
219+ .andExpect (jsonPath ("$.data. amount" , is (4_000 )))
220+ .andExpect (jsonPath ("$.data. status" , is ("SUCCESS" )))
221+ .andExpect (jsonPath ("$.data. transactionId" , is ("k2" )))
222+ .andExpect (jsonPath ("$.data. paidAt" , notNullValue ()));
216223 }
217224
218225 // 5) 토스 빌링키 발급 API
219226 @ Test
220227 @ WithMockUser (
username =
"[email protected] " )
221228 void issue_billing_key_success () throws Exception {
222- given (tossBillingClientService .issueBillingKey (startsWith ("user-" ), eq ("AUTH-123" )))
229+ given (tossBillingClientService .issueBillingKey (org . mockito . ArgumentMatchers . startsWith ("user-" ), eq ("AUTH-123" )))
223230 .willReturn (TossIssueBillingKeyResponse .builder ()
224231 .billingKey ("BILL-XYZ" )
225232 .provider ("toss" )
226233 .cardBrand ("SHINHAN" )
227- .cardNumber ("****-****-****-1234" )
234+ .last4 ("****-****-****-1234" )
228235 .expMonth (12 )
229236 .expYear (2030 )
230237 .build ());
@@ -237,9 +244,9 @@ void issue_billing_key_success() throws Exception {
237244 .contentType (MediaType .APPLICATION_JSON )
238245 .content (payload ))
239246 .andExpect (status ().isOk ())
240- .andExpect (jsonPath ("$.billingKey" , is ("BILL-XYZ" )))
241- .andExpect (jsonPath ("$.provider" , is ("toss" )))
242- .andExpect (jsonPath ("$.cardBrand" , is ("SHINHAN" )));
247+ .andExpect (jsonPath ("$.data. billingKey" , is ("BILL-XYZ" )))
248+ .andExpect (jsonPath ("$.data. provider" , is ("toss" )))
249+ .andExpect (jsonPath ("$.data. cardBrand" , is ("SHINHAN" )));
243250 }
244251
245252 // 6) 인증 없으면 401
@@ -255,4 +262,30 @@ void charge_unauthorized_401() throws Exception {
255262 .content (om .writeValueAsString (req )))
256263 .andExpect (status ().isUnauthorized ());
257264 }
265+
266+ // 토스 카드등록(빌링) 팝업 파라미터 조회
267+ @ Test
268+ @ WithMockUser (
username =
"[email protected] " )
269+ void get_billing_auth_params_success () throws Exception {
270+ // setUp()에서 me 저장되어 있음: customerKey = "user-" + me.getId()
271+ String expectedCustomerKey = "user-" + me .getId ();
272+
273+ mockMvc .perform (get ("/api/v1/payments/toss/billing-auth-params" ))
274+ .andExpect (status ().isOk ())
275+ .andExpect (jsonPath ("$.data.clientKey" , notNullValue ())) // 값만 존재 검증 (테스트 환경 clientKey 미설정 대비)
276+ .andExpect (jsonPath ("$.data.customerKey" , is (expectedCustomerKey ))) // user-{id}
277+ .andExpect (jsonPath ("$.data.successUrl" , containsString ("/payments/toss/billing-success.html" )))
278+ .andExpect (jsonPath ("$.data.failUrl" , containsString ("/payments/toss/billing-fail.html" )));
279+ }
280+
281+ // 멱등키 발급
282+ @ Test
283+ void new_idempotency_key_success () throws Exception {
284+ mockMvc .perform (get ("/api/v1/payments/idempotency-key" )
285+ .
header (
"Authorization" ,
bearer (
"[email protected] " )))
286+ .andExpect (status ().isOk ())
287+ .andExpect (jsonPath ("$.data.idempotencyKey" , notNullValue ()))
288+ // 간단한 UUID 패턴(36자 16진/하이픈) 체크
289+ .andExpect (jsonPath ("$.data.idempotencyKey" , matchesPattern ("^[0-9a-fA-F\\ -]{36}$" )));
290+ }
258291}
0 commit comments