flamingo.me/flamingo-commerce/v3@v3.11.0/test/integrationtest/projecttest/tests/graphql/placeorder_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package graphql_test 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "testing" 11 "time" 12 13 "github.com/gavv/httpexpect/v2" 14 "github.com/stretchr/testify/assert" 15 16 "flamingo.me/flamingo-commerce/v3/checkout/domain/placeorder/states" 17 "flamingo.me/flamingo-commerce/v3/payment/domain" 18 "flamingo.me/flamingo-commerce/v3/test/integrationtest" 19 "flamingo.me/flamingo-commerce/v3/test/integrationtest/projecttest/helper" 20 "flamingo.me/flamingo-commerce/v3/test/integrationtest/projecttest/modules/cart" 21 "flamingo.me/flamingo-commerce/v3/test/integrationtest/projecttest/modules/placeorder" 22 ) 23 24 func Test_PlaceOrderWithPaymentService(t *testing.T) { 25 t.Parallel() 26 baseURL := "http://" + FlamingoURL 27 tests := []struct { 28 name string 29 gatewayMethod string 30 expectedState map[string]interface{} 31 expectedGraphQLState string 32 }{ 33 { 34 name: "Payment Completed", 35 gatewayMethod: domain.PaymentFlowStatusCompleted, 36 expectedState: map[string]interface{}{ 37 "name": states.Success{}.Name(), 38 "__typename": "Commerce_Checkout_PlaceOrderState_State_Success", 39 }, 40 }, 41 { 42 name: "Payment Cancelled", 43 gatewayMethod: domain.PaymentFlowStatusCancelled, 44 expectedState: map[string]interface{}{ 45 "name": states.Failed{}.Name(), 46 "__typename": "Commerce_Checkout_PlaceOrderState_State_Failed", 47 "reason": map[string]interface{}{ 48 "__typename": "Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError", 49 "reason": "", 50 }, 51 }, 52 }, 53 { 54 name: "Payment Approved", 55 gatewayMethod: domain.PaymentFlowStatusApproved, 56 expectedState: map[string]interface{}{ 57 "name": states.Success{}.Name(), 58 "__typename": "Commerce_Checkout_PlaceOrderState_State_Success", 59 }, 60 }, 61 { 62 name: "Payment Failed", 63 gatewayMethod: domain.PaymentFlowStatusFailed, 64 expectedState: map[string]interface{}{ 65 "name": states.Failed{}.Name(), 66 "__typename": "Commerce_Checkout_PlaceOrderState_State_Failed", 67 "reason": map[string]interface{}{ 68 "__typename": "Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError", 69 "reason": "", 70 }, 71 }, 72 }, 73 { 74 name: "Payment Aborted", 75 gatewayMethod: domain.PaymentFlowStatusAborted, 76 expectedState: map[string]interface{}{ 77 "name": states.Failed{}.Name(), 78 "__typename": "Commerce_Checkout_PlaceOrderState_State_Failed", 79 "reason": map[string]interface{}{ 80 "__typename": "Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentCanceledByCustomer", 81 "reason": "Payment canceled by customer", 82 }, 83 }, 84 }, 85 { 86 name: "Payment Waiting For Customer", 87 gatewayMethod: domain.PaymentFlowWaitingForCustomer, 88 expectedState: map[string]interface{}{ 89 "name": states.WaitForCustomer{}.Name(), 90 "__typename": "Commerce_Checkout_PlaceOrderState_State_WaitForCustomer", 91 }, 92 }, 93 { 94 name: "Payment Unapproved, Iframe", 95 gatewayMethod: domain.PaymentFlowActionShowIframe, 96 expectedState: map[string]interface{}{ 97 "name": states.ShowIframe{}.Name(), 98 "__typename": "Commerce_Checkout_PlaceOrderState_State_ShowIframe", 99 "URL": "https://url.com", 100 }, 101 }, 102 { 103 name: "Payment Unapproved, HTML", 104 gatewayMethod: domain.PaymentFlowActionShowHTML, 105 expectedState: map[string]interface{}{ 106 "name": states.ShowHTML{}.Name(), 107 "__typename": "Commerce_Checkout_PlaceOrderState_State_ShowHTML", 108 "HTML": "<h2>test</h2>", 109 }, 110 }, 111 { 112 name: "Payment Unapproved, Redirect", 113 gatewayMethod: domain.PaymentFlowActionRedirect, 114 expectedState: map[string]interface{}{ 115 "name": states.Redirect{}.Name(), 116 "__typename": "Commerce_Checkout_PlaceOrderState_State_Redirect", 117 "URL": "https://url.com", 118 }, 119 }, 120 { 121 name: "Payment Unapproved, Post Redirect", 122 gatewayMethod: domain.PaymentFlowActionPostRedirect, 123 expectedState: map[string]interface{}{ 124 "name": states.PostRedirect{}.Name(), 125 "__typename": "Commerce_Checkout_PlaceOrderState_State_PostRedirect", 126 "URL": "https://url.com", 127 "Parameters": []interface{}{}, // todo: better data in fake gateway 128 }, 129 }, 130 { 131 name: "Payment Unapproved, Show Wallet Payment", 132 gatewayMethod: domain.PaymentFlowActionShowWalletPayment, 133 expectedState: map[string]interface{}{ 134 "name": states.ShowWalletPayment{}.Name(), 135 "__typename": "Commerce_Checkout_PlaceOrderState_State_ShowWalletPayment", 136 "paymentMethod": "ApplePay", 137 }, 138 }, 139 { 140 name: "Payment Unapproved, Unknown", 141 gatewayMethod: "unknown", 142 expectedState: map[string]interface{}{ 143 "name": states.Failed{}.Name(), 144 "__typename": "Commerce_Checkout_PlaceOrderState_State_Failed", 145 "reason": map[string]interface{}{ 146 "__typename": "Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError", 147 "reason": `Payment action not supported: "unknown"`, 148 }, 149 }, 150 }, 151 } 152 153 for _, tt := range tests { 154 t.Run(tt.name, func(t *testing.T) { 155 e := integrationtest.NewHTTPExpect(t, baseURL) 156 prepareCartWithPaymentSelection(t, e, tt.gatewayMethod, nil) 157 _, uuid := assertStartPlaceOrderWithValidUUID(t, e) 158 159 helper.AsyncCheckWithTimeout(t, time.Second, func() error { 160 return checkRefreshForExpectedState(t, e, uuid, tt.expectedState) 161 }) 162 }) 163 } 164 } 165 166 func Test_PlaceOrderWithOrderService(t *testing.T) { 167 t.Run("PlaceOrder fails due to payment error, rollback of place order fails, restart afterwards can succeed ", func(t *testing.T) { 168 baseURL := "http://" + FlamingoURL 169 170 e := integrationtest.NewHTTPExpect(t, baseURL) 171 prepareCartWithPaymentSelection(t, e, domain.PaymentFlowStatusFailed, nil) 172 placeorder.NextCancelFails = true 173 174 _, firstUUID := assertStartPlaceOrderWithValidUUID(t, e) 175 176 expectedState := map[string]interface{}{ 177 "name": states.Failed{}.Name(), 178 "__typename": "Commerce_Checkout_PlaceOrderState_State_Failed", 179 "reason": map[string]interface{}{ 180 "__typename": "Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError", 181 "reason": "", 182 }, 183 } 184 helper.AsyncCheckWithTimeout(t, time.Second, func() error { 185 return checkRefreshForExpectedState(t, e, firstUUID, expectedState) 186 }) 187 188 updatePaymentSelection(t, e, domain.PaymentFlowStatusApproved) 189 190 var secondUUID string 191 helper.AsyncCheckWithTimeout(t, time.Second, func() error { 192 _, secondUUID = assertStartPlaceOrderWithValidUUID(t, e) 193 if secondUUID == firstUUID { 194 return errors.New("UUID didn't change during new start place order") 195 } 196 return nil 197 }) 198 assert.NotEmpty(t, secondUUID, "start order should return uuid") 199 200 expectedState = map[string]interface{}{ 201 "name": states.Success{}.Name(), 202 "__typename": "Commerce_Checkout_PlaceOrderState_State_Success", 203 } 204 helper.AsyncCheckWithTimeout(t, time.Second, func() error { 205 return checkRefreshForExpectedState(t, e, secondUUID, expectedState) 206 }) 207 }) 208 209 } 210 211 func Test_StartPlaceOrder(t *testing.T) { 212 t.Parallel() 213 baseURL := "http://" + FlamingoURL 214 t.Run("no payment selection", func(t *testing.T) { 215 e := integrationtest.NewHTTPExpect(t, baseURL) 216 prepareCart(t, e) 217 assertStartPlaceOrderWithValidUUID(t, e) 218 219 response, _ := assertRefreshPlaceOrder(t, e, true) 220 221 actualState := getValue(response, "Commerce_Checkout_RefreshPlaceOrderBlocking", "state") 222 reason := actualState.Object().Value("reason").Object() 223 reason.Value("__typename").IsEqual("Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError") 224 reason.Value("reason").IsEqual("paymentSelection not set") 225 }) 226 227 t.Run("payment selection invalid", func(t *testing.T) { 228 e := integrationtest.NewHTTPExpectWithCookies(t, baseURL, map[string]string{cart.FakePaymentSelectionValidatorCookie: ""}) 229 230 prepareCartWithPaymentSelection(t, e, domain.PaymentFlowActionShowIframe, nil) 231 assertStartPlaceOrderWithValidUUID(t, e) 232 233 response, _ := assertRefreshPlaceOrder(t, e, true) 234 235 actualState := getValue(response, "Commerce_Checkout_RefreshPlaceOrderBlocking", "state") 236 fmt.Println(actualState.Raw()) 237 reason := actualState.Object().Value("reason").Object() 238 reason.Value("__typename").IsEqual("Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError") 239 reason.Value("reason").IsEqual("fake payment selection validator error") 240 }) 241 242 t.Run("replace already running process", func(t *testing.T) { 243 e := integrationtest.NewHTTPExpect(t, baseURL) 244 prepareCartWithPaymentSelection(t, e, domain.PaymentFlowActionShowIframe, nil) 245 246 _, firstUUID := assertStartPlaceOrderWithValidUUID(t, e) 247 248 _, refreshUUID := assertRefreshPlaceOrder(t, e, true) 249 assert.Equal(t, firstUUID, refreshUUID) 250 251 _, secondUUID := assertStartPlaceOrderWithValidUUID(t, e) 252 253 assert.NotEqual(t, firstUUID, secondUUID, "already running process should be replaced by a new one") 254 }) 255 256 } 257 258 func Test_CancelPlaceOrder(t *testing.T) { 259 t.Parallel() 260 baseURL := "http://" + FlamingoURL 261 tests := []struct { 262 name string 263 gatewayMethod string 264 prepareAndRun bool 265 validator func(*testing.T, *httpexpect.Object) 266 }{ 267 { 268 name: "already final", 269 gatewayMethod: domain.PaymentFlowStatusCompleted, 270 prepareAndRun: true, 271 validator: func(t *testing.T, response *httpexpect.Object) { 272 err := response.Value("errors").Array().Value(0).Object() 273 err.Value("message").IsEqual("process already in final state, cancel not possible") 274 err.Value("path").Array().Value(0).IsEqual("Commerce_Checkout_CancelPlaceOrder") 275 }, 276 }, 277 { 278 name: "not final", 279 gatewayMethod: domain.PaymentFlowActionShowIframe, 280 prepareAndRun: true, 281 validator: func(t *testing.T, response *httpexpect.Object) { 282 response.Value("data").Object().Value("Commerce_Checkout_CancelPlaceOrder").Boolean().IsTrue() 283 }, 284 }, 285 { 286 name: "no running process", 287 gatewayMethod: domain.PaymentFlowStatusCompleted, 288 prepareAndRun: false, 289 validator: func(t *testing.T, response *httpexpect.Object) { 290 err := response.Value("errors").Array().Value(0).Object() 291 err.Value("message").IsEqual("ErrNoPlaceOrderProcess") 292 err.Value("path").Array().Value(0).IsEqual("Commerce_Checkout_CancelPlaceOrder") 293 }, 294 }, 295 } 296 297 for _, tt := range tests { 298 t.Run(tt.name, func(t *testing.T) { 299 e := integrationtest.NewHTTPExpect(t, baseURL) 300 if tt.prepareAndRun { 301 prepareCartWithPaymentSelection(t, e, tt.gatewayMethod, nil) 302 assertStartPlaceOrderWithValidUUID(t, e) 303 assertRefreshPlaceOrder(t, e, true) 304 } 305 306 request := helper.GraphQlRequest(t, e, loadGraphQL(t, "cancel", nil)) 307 response := request.Expect().JSON().Object() 308 tt.validator(t, response) 309 310 }) 311 } 312 } 313 314 func Test_ClearPlaceOrder(t *testing.T) { 315 t.Parallel() 316 baseURL := "http://" + FlamingoURL 317 tests := []struct { 318 name string 319 gatewayMethod string 320 prepareAndRun bool 321 }{ 322 { 323 name: "final", 324 gatewayMethod: domain.PaymentFlowStatusCompleted, 325 prepareAndRun: true, 326 }, 327 { 328 name: "not final", 329 gatewayMethod: domain.PaymentFlowActionShowIframe, 330 prepareAndRun: true, 331 }, 332 { 333 name: "no process", 334 gatewayMethod: domain.PaymentFlowStatusCompleted, 335 prepareAndRun: false, 336 }, 337 } 338 339 for _, tt := range tests { 340 t.Run(tt.name, func(t *testing.T) { 341 e := integrationtest.NewHTTPExpect(t, baseURL) 342 if tt.prepareAndRun { 343 prepareCartWithPaymentSelection(t, e, tt.gatewayMethod, nil) 344 assertStartPlaceOrderWithValidUUID(t, e) 345 assertRefreshPlaceOrder(t, e, true) 346 } 347 348 request := helper.GraphQlRequest(t, e, loadGraphQL(t, "clear_place_order", nil)) 349 response := request.Expect().JSON().Object() 350 response.Value("data").Object().Value("Commerce_Checkout_ClearPlaceOrder").Boolean().IsTrue() 351 352 request = helper.GraphQlRequest(t, e, loadGraphQL(t, "refresh_blocking", nil)) 353 response = request.Expect().JSON().Object() 354 err := response.Value("errors").Array().Value(0).Object() 355 err.Value("message").IsEqual("ErrNoPlaceOrderProcess") 356 err.Value("path").Array().Value(0).IsEqual("Commerce_Checkout_RefreshPlaceOrderBlocking") 357 }) 358 } 359 } 360 361 func Test_RestartStartPlaceOrder(t *testing.T) { 362 t.Parallel() 363 baseURL := "http://" + FlamingoURL 364 e := integrationtest.NewHTTPExpect(t, baseURL) 365 prepareCartWithPaymentSelection(t, e, domain.PaymentFlowStatusFailed, nil) 366 _, uuid1 := assertStartPlaceOrderWithValidUUID(t, e) 367 // wait for fail 368 res, _ := assertRefreshPlaceOrder(t, e, true) 369 getValue(res, "Commerce_Checkout_RefreshPlaceOrderBlocking", "state").Object().Value("name").IsEqual("Failed") 370 orderInfo1 := getValue(res, "Commerce_Checkout_RefreshPlaceOrderBlocking", "orderInfos") 371 orderNumber1 := orderInfo1.Object().Value("placedOrderInfos").Array().Value(0).Object().Value("orderNumber").String() 372 373 // restart 374 _, uuid2 := assertStartPlaceOrderWithValidUUID(t, e) 375 assert.NotEqual(t, uuid1, uuid2, "new process should have been started") 376 res, _ = assertRefreshPlaceOrder(t, e, true) 377 state := getValue(res, "Commerce_Checkout_RefreshPlaceOrderBlocking", "state") 378 state.Object().Value("name").IsEqual("Failed") 379 // rollback of place order should lead to a new order number 380 orderInfo2 := getValue(res, "Commerce_Checkout_RefreshPlaceOrderBlocking", "orderInfos") 381 orderNumber2 := orderInfo2.Object().Value("placedOrderInfos").Array().Value(0).Object().Value("orderNumber").String() 382 orderNumber2.NotEqual(orderNumber1.Raw()) 383 384 // payment selection should still be set, so we get the payment error (not PaymentSelection not set) 385 reason := state.Object().Value("reason").Object() 386 reason.Value("__typename").IsEqual("Commerce_Checkout_PlaceOrderState_State_FailedReason_PaymentError") 387 reason.Value("reason").IsEqual("") 388 389 // update payment selection 390 helper.GraphQlRequest(t, e, loadGraphQL(t, "update_payment_selection", map[string]string{"PAYMENT_METHOD": domain.PaymentFlowStatusCompleted})).Expect().Status(http.StatusOK) 391 _, uuid3 := assertStartPlaceOrderWithValidUUID(t, e) 392 assert.NotEqual(t, uuid2, uuid3, "new process should have been started") 393 res, _ = assertRefreshPlaceOrder(t, e, true) 394 getValue(res, "Commerce_Checkout_RefreshPlaceOrderBlocking", "state").Object().Value("name").IsEqual("Success") 395 396 // rollback of place order should lead to a new order number 397 orderInfo3 := getValue(res, "Commerce_Checkout_RefreshPlaceOrderBlocking", "orderInfos") 398 orderNumber3 := orderInfo3.Object().Value("placedOrderInfos").Array().Value(0).Object().Value("orderNumber").String() 399 orderNumber3.NotEqual(orderNumber1.Raw()) 400 orderNumber3.NotEqual(orderNumber2.Raw()) 401 } 402 403 func Test_ActivePlaceOrder(t *testing.T) { 404 baseURL := "http://" + FlamingoURL 405 e := integrationtest.NewHTTPExpect(t, baseURL) 406 query := loadGraphQL(t, "active_place_order", nil) 407 408 // no process should be running at the start 409 request := helper.GraphQlRequest(t, e, query) 410 response := request.Expect() 411 response.JSON().Object().Value("data").Object().Value("Commerce_Checkout_ActivePlaceOrder").Boolean().IsFalse() 412 413 // let the process wait in iframe status 414 prepareCartWithPaymentSelection(t, e, domain.PaymentFlowActionShowIframe, nil) 415 assertStartPlaceOrderWithValidUUID(t, e) 416 // wait for goroutine to be finished 417 assertRefreshPlaceOrder(t, e, true) 418 419 // now we have a running process 420 request = helper.GraphQlRequest(t, e, query) 421 response = request.Expect() 422 response.JSON().Object().Value("data").Object().Value("Commerce_Checkout_ActivePlaceOrder").Boolean().IsTrue() 423 } 424 425 func Test_GetCurrentState(t *testing.T) { 426 t.Parallel() 427 baseURL := "http://" + FlamingoURL 428 e := integrationtest.NewHTTPExpect(t, baseURL) 429 // no current context before start 430 request := helper.GraphQlRequest(t, e, loadGraphQL(t, "current_context", nil)) 431 request.Expect().JSON().Object().Value("errors").Array().Value(0).Object().Value("message").String().IsEqual("ErrNoPlaceOrderProcess") 432 433 // prepare start and wait 434 prepareCartWithPaymentSelection(t, e, domain.PaymentFlowActionShowIframe, nil) 435 _, uuid := assertStartPlaceOrderWithValidUUID(t, e) 436 result, uuid2 := assertRefreshPlaceOrder(t, e, true) 437 assert.Equal(t, uuid, uuid2) 438 state := getValue(result, "Commerce_Checkout_RefreshPlaceOrderBlocking", "state") 439 440 // now we can get the current state 441 request = helper.GraphQlRequest(t, e, loadGraphQL(t, "current_context", nil)) 442 response := request.Expect() 443 uuid3 := getValue(response, "Commerce_Checkout_CurrentContext", "uuid").Raw() 444 assert.Equal(t, uuid, uuid3) 445 state2 := getValue(response, "Commerce_Checkout_CurrentContext", "state") 446 447 assert.Equal(t, state.Raw(), state2.Raw(), "current state must be the same as from RefreshPlaceOrderBlocking") 448 }