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  }