go.uber.org/cadence@v1.2.9/internal/internal_decision_state_machine_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package internal
    22  
    23  import (
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/require"
    27  
    28  	s "go.uber.org/cadence/.gen/go/shared"
    29  	"go.uber.org/cadence/internal/common"
    30  )
    31  
    32  func Test_TimerStateMachine_CancelBeforeSent(t *testing.T) {
    33  	t.Parallel()
    34  	timerID := "test-timer-1"
    35  	attributes := &s.StartTimerDecisionAttributes{
    36  		TimerId: common.StringPtr(timerID),
    37  	}
    38  	h := newDecisionsHelper()
    39  	d := h.startTimer(attributes)
    40  	require.Equal(t, decisionStateCreated, d.getState())
    41  	h.cancelTimer(timerID)
    42  	require.Equal(t, decisionStateCompleted, d.getState())
    43  	decisions := h.getDecisions(true)
    44  	require.Equal(t, 0, len(decisions))
    45  }
    46  
    47  func Test_TimerStateMachine_CancelAfterInitiated(t *testing.T) {
    48  	t.Parallel()
    49  	timerID := "test-timer-1"
    50  	attributes := &s.StartTimerDecisionAttributes{
    51  		TimerId: common.StringPtr(timerID),
    52  	}
    53  	h := newDecisionsHelper()
    54  	d := h.startTimer(attributes)
    55  	require.Equal(t, decisionStateCreated, d.getState())
    56  	decisions := h.getDecisions(true)
    57  	require.Equal(t, decisionStateDecisionSent, d.getState())
    58  	require.Equal(t, 1, len(decisions))
    59  	require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType())
    60  	require.Equal(t, attributes, decisions[0].StartTimerDecisionAttributes)
    61  	h.handleTimerStarted(timerID)
    62  	require.Equal(t, decisionStateInitiated, d.getState())
    63  	h.cancelTimer(timerID)
    64  	require.Equal(t, decisionStateCanceledAfterInitiated, d.getState())
    65  	decisions = h.getDecisions(true)
    66  	require.Equal(t, 1, len(decisions))
    67  	require.Equal(t, s.DecisionTypeCancelTimer, decisions[0].GetDecisionType())
    68  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
    69  	h.handleTimerCanceled(timerID)
    70  	require.Equal(t, decisionStateCompleted, d.getState())
    71  }
    72  
    73  func Test_TimerStateMachine_CompletedAfterCancel(t *testing.T) {
    74  	t.Parallel()
    75  	timerID := "test-timer-1"
    76  	attributes := &s.StartTimerDecisionAttributes{
    77  		TimerId: common.StringPtr(timerID),
    78  	}
    79  	h := newDecisionsHelper()
    80  	d := h.startTimer(attributes)
    81  	require.Equal(t, decisionStateCreated, d.getState())
    82  	decisions := h.getDecisions(true)
    83  	require.Equal(t, decisionStateDecisionSent, d.getState())
    84  	require.Equal(t, 1, len(decisions))
    85  	require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType())
    86  	h.cancelTimer(timerID)
    87  	require.Equal(t, decisionStateCanceledBeforeInitiated, d.getState())
    88  	require.Equal(t, 0, len(h.getDecisions(true)))
    89  	h.handleTimerStarted(timerID)
    90  	require.Equal(t, decisionStateCanceledAfterInitiated, d.getState())
    91  	decisions = h.getDecisions(true)
    92  	require.Equal(t, 1, len(decisions))
    93  	require.Equal(t, s.DecisionTypeCancelTimer, decisions[0].GetDecisionType())
    94  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
    95  	h.handleTimerClosed(timerID)
    96  	require.Equal(t, decisionStateCompletedAfterCancellationDecisionSent, d.getState())
    97  }
    98  
    99  func Test_TimerStateMachine_CompleteWithoutCancel(t *testing.T) {
   100  	t.Parallel()
   101  	timerID := "test-timer-1"
   102  	attributes := &s.StartTimerDecisionAttributes{
   103  		TimerId: common.StringPtr(timerID),
   104  	}
   105  	h := newDecisionsHelper()
   106  	d := h.startTimer(attributes)
   107  	require.Equal(t, decisionStateCreated, d.getState())
   108  	decisions := h.getDecisions(true)
   109  	require.Equal(t, decisionStateDecisionSent, d.getState())
   110  	require.Equal(t, 1, len(decisions))
   111  	require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType())
   112  	h.handleTimerStarted(timerID)
   113  	require.Equal(t, decisionStateInitiated, d.getState())
   114  	require.Equal(t, 0, len(h.getDecisions(false)))
   115  	h.handleTimerClosed(timerID)
   116  	require.Equal(t, decisionStateCompleted, d.getState())
   117  }
   118  
   119  func Test_TimerStateMachine_PanicInvalidStateTransition(t *testing.T) {
   120  	t.Parallel()
   121  	timerID := "test-timer-1"
   122  	attributes := &s.StartTimerDecisionAttributes{
   123  		TimerId: common.StringPtr(timerID),
   124  	}
   125  	h := newDecisionsHelper()
   126  	h.startTimer(attributes)
   127  	h.getDecisions(true)
   128  	h.handleTimerStarted(timerID)
   129  	h.handleTimerClosed(timerID)
   130  
   131  	panicErr := runAndCatchPanic(func() {
   132  		h.handleCancelTimerFailed(timerID)
   133  	})
   134  
   135  	require.NotNil(t, panicErr)
   136  }
   137  
   138  func Test_TimerCancelEventOrdering(t *testing.T) {
   139  	t.Parallel()
   140  	timerID := "test-timer-1"
   141  	localActivityID := "test-activity-1"
   142  	attributes := &s.StartTimerDecisionAttributes{
   143  		TimerId: common.StringPtr(timerID),
   144  	}
   145  	h := newDecisionsHelper()
   146  	d := h.startTimer(attributes)
   147  	require.Equal(t, decisionStateCreated, d.getState())
   148  	decisions := h.getDecisions(true)
   149  	require.Equal(t, decisionStateDecisionSent, d.getState())
   150  	require.Equal(t, 1, len(decisions))
   151  	require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType())
   152  	require.Equal(t, attributes, decisions[0].StartTimerDecisionAttributes)
   153  	h.handleTimerStarted(timerID)
   154  	require.Equal(t, decisionStateInitiated, d.getState())
   155  	m := h.recordLocalActivityMarker(localActivityID, []byte{})
   156  	require.Equal(t, decisionStateCreated, m.getState())
   157  	h.cancelTimer(timerID)
   158  	require.Equal(t, decisionStateCanceledAfterInitiated, d.getState())
   159  	decisions = h.getDecisions(true)
   160  	require.Equal(t, 2, len(decisions))
   161  	require.Equal(t, s.DecisionTypeRecordMarker, decisions[0].GetDecisionType())
   162  	require.Equal(t, s.DecisionTypeCancelTimer, decisions[1].GetDecisionType())
   163  }
   164  
   165  func Test_ActivityStateMachine_CompleteWithoutCancel(t *testing.T) {
   166  	t.Parallel()
   167  	activityID := "test-activity-1"
   168  	attributes := &s.ScheduleActivityTaskDecisionAttributes{
   169  		ActivityId: common.StringPtr(activityID),
   170  	}
   171  	h := newDecisionsHelper()
   172  
   173  	// schedule activity
   174  	d := h.scheduleActivityTask(attributes)
   175  	require.Equal(t, decisionStateCreated, d.getState())
   176  	decisions := h.getDecisions(true)
   177  	require.Equal(t, decisionStateDecisionSent, d.getState())
   178  	require.Equal(t, 1, len(decisions))
   179  	require.Equal(t, s.DecisionTypeScheduleActivityTask, decisions[0].GetDecisionType())
   180  
   181  	// activity scheduled
   182  	h.handleActivityTaskScheduled(1, activityID)
   183  	require.Equal(t, decisionStateInitiated, d.getState())
   184  
   185  	// activity completed
   186  	h.handleActivityTaskClosed(activityID)
   187  	require.Equal(t, decisionStateCompleted, d.getState())
   188  }
   189  
   190  func Test_ActivityStateMachine_CancelBeforeSent(t *testing.T) {
   191  	t.Parallel()
   192  	activityID := "test-activity-1"
   193  	attributes := &s.ScheduleActivityTaskDecisionAttributes{
   194  		ActivityId: common.StringPtr(activityID),
   195  	}
   196  	h := newDecisionsHelper()
   197  
   198  	// schedule activity
   199  	d := h.scheduleActivityTask(attributes)
   200  	require.Equal(t, decisionStateCreated, d.getState())
   201  
   202  	// cancel before decision sent, this will put decision state machine directly into completed state
   203  	h.requestCancelActivityTask(activityID)
   204  	require.Equal(t, decisionStateCompleted, d.getState())
   205  
   206  	// there should be no decisions needed to be send
   207  	decisions := h.getDecisions(true)
   208  	require.Equal(t, 0, len(decisions))
   209  }
   210  
   211  func Test_ActivityStateMachine_CancelAfterSent(t *testing.T) {
   212  	t.Parallel()
   213  	activityID := "test-activity-1"
   214  	attributes := &s.ScheduleActivityTaskDecisionAttributes{
   215  		ActivityId: common.StringPtr(activityID),
   216  	}
   217  	h := newDecisionsHelper()
   218  
   219  	// schedule activity
   220  	d := h.scheduleActivityTask(attributes)
   221  	require.Equal(t, decisionStateCreated, d.getState())
   222  	decisions := h.getDecisions(true)
   223  	require.Equal(t, 1, len(decisions))
   224  	require.Equal(t, s.DecisionTypeScheduleActivityTask, decisions[0].GetDecisionType())
   225  
   226  	// cancel activity
   227  	h.requestCancelActivityTask(activityID)
   228  	require.Equal(t, decisionStateCanceledBeforeInitiated, d.getState())
   229  	require.Equal(t, 0, len(h.getDecisions(true)))
   230  
   231  	// activity scheduled
   232  	h.handleActivityTaskScheduled(1, activityID)
   233  	require.Equal(t, decisionStateCanceledAfterInitiated, d.getState())
   234  	decisions = h.getDecisions(true)
   235  	require.Equal(t, 1, len(decisions))
   236  	require.Equal(t, s.DecisionTypeRequestCancelActivityTask, decisions[0].GetDecisionType())
   237  
   238  	// activity canceled
   239  	h.handleActivityTaskCanceled(activityID)
   240  	require.Equal(t, decisionStateCompleted, d.getState())
   241  	require.Equal(t, 0, len(h.getDecisions(false)))
   242  }
   243  
   244  func Test_ActivityStateMachine_CompletedAfterCancel(t *testing.T) {
   245  	t.Parallel()
   246  	activityID := "test-activity-1"
   247  	attributes := &s.ScheduleActivityTaskDecisionAttributes{
   248  		ActivityId: common.StringPtr(activityID),
   249  	}
   250  	h := newDecisionsHelper()
   251  
   252  	// schedule activity
   253  	d := h.scheduleActivityTask(attributes)
   254  	require.Equal(t, decisionStateCreated, d.getState())
   255  	decisions := h.getDecisions(true)
   256  	require.Equal(t, 1, len(decisions))
   257  	require.Equal(t, s.DecisionTypeScheduleActivityTask, decisions[0].GetDecisionType())
   258  
   259  	// cancel activity
   260  	h.requestCancelActivityTask(activityID)
   261  	require.Equal(t, decisionStateCanceledBeforeInitiated, d.getState())
   262  	require.Equal(t, 0, len(h.getDecisions(true)))
   263  
   264  	// activity scheduled
   265  	h.handleActivityTaskScheduled(1, activityID)
   266  	require.Equal(t, decisionStateCanceledAfterInitiated, d.getState())
   267  	decisions = h.getDecisions(true)
   268  	require.Equal(t, 1, len(decisions))
   269  	require.Equal(t, s.DecisionTypeRequestCancelActivityTask, decisions[0].GetDecisionType())
   270  
   271  	// activity completed after cancel
   272  	h.handleActivityTaskClosed(activityID)
   273  	require.Equal(t, decisionStateCompletedAfterCancellationDecisionSent, d.getState())
   274  	require.Equal(t, 0, len(h.getDecisions(false)))
   275  }
   276  
   277  func Test_ActivityStateMachine_PanicInvalidStateTransition(t *testing.T) {
   278  	t.Parallel()
   279  	activityID := "test-activity-1"
   280  	attributes := &s.ScheduleActivityTaskDecisionAttributes{
   281  		ActivityId: common.StringPtr(activityID),
   282  	}
   283  	h := newDecisionsHelper()
   284  
   285  	// schedule activity
   286  	h.scheduleActivityTask(attributes)
   287  
   288  	// verify that using invalid activity id will panic
   289  	err := runAndCatchPanic(func() {
   290  		h.handleActivityTaskClosed("invalid-activity-id")
   291  	})
   292  	require.NotNil(t, err)
   293  
   294  	// send schedule decision
   295  	h.getDecisions(true)
   296  	// activity scheduled
   297  	h.handleActivityTaskScheduled(1, activityID)
   298  
   299  	// now simulate activity canceled, which is invalid transition
   300  	err = runAndCatchPanic(func() {
   301  		h.handleActivityTaskCanceled(activityID)
   302  	})
   303  	require.NotNil(t, err)
   304  }
   305  
   306  func Test_ChildWorkflowStateMachine_Basic(t *testing.T) {
   307  	t.Parallel()
   308  	workflowID := "test-child-workflow-1"
   309  	attributes := &s.StartChildWorkflowExecutionDecisionAttributes{
   310  		WorkflowId: common.StringPtr(workflowID),
   311  	}
   312  	h := newDecisionsHelper()
   313  
   314  	// start child workflow
   315  	d := h.startChildWorkflowExecution(attributes)
   316  	require.Equal(t, decisionStateCreated, d.getState())
   317  
   318  	// send decision
   319  	decisions := h.getDecisions(true)
   320  	require.Equal(t, decisionStateDecisionSent, d.getState())
   321  	require.Equal(t, 1, len(decisions))
   322  	require.Equal(t, s.DecisionTypeStartChildWorkflowExecution, decisions[0].GetDecisionType())
   323  
   324  	// child workflow initiated
   325  	h.handleStartChildWorkflowExecutionInitiated(workflowID)
   326  	require.Equal(t, decisionStateInitiated, d.getState())
   327  	require.Equal(t, 0, len(h.getDecisions(true)))
   328  
   329  	// child workflow started
   330  	h.handleChildWorkflowExecutionStarted(workflowID)
   331  	require.Equal(t, decisionStateStarted, d.getState())
   332  	require.Equal(t, 0, len(h.getDecisions(true)))
   333  
   334  	// child workflow completed
   335  	h.handleChildWorkflowExecutionClosed(workflowID)
   336  	require.Equal(t, decisionStateCompleted, d.getState())
   337  	require.Equal(t, 0, len(h.getDecisions(true)))
   338  }
   339  
   340  func Test_ChildWorkflowStateMachine_CancelSucceed(t *testing.T) {
   341  	t.Parallel()
   342  	domain := "test-domain"
   343  	workflowID := "test-child-workflow"
   344  	runID := ""
   345  	cancellationID := ""
   346  	initiatedEventID := int64(28)
   347  	isChildWorkflowOnly := true
   348  	attributes := &s.StartChildWorkflowExecutionDecisionAttributes{
   349  		WorkflowId: common.StringPtr(workflowID),
   350  	}
   351  	h := newDecisionsHelper()
   352  
   353  	// start child workflow
   354  	d := h.startChildWorkflowExecution(attributes)
   355  	// send decision
   356  	decisions := h.getDecisions(true)
   357  	// child workflow initiated
   358  	h.handleStartChildWorkflowExecutionInitiated(workflowID)
   359  	// child workflow started
   360  	h.handleChildWorkflowExecutionStarted(workflowID)
   361  
   362  	// cancel child workflow
   363  	h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, isChildWorkflowOnly)
   364  	require.Equal(t, decisionStateCanceledAfterStarted, d.getState())
   365  
   366  	// send cancel request
   367  	decisions = h.getDecisions(true)
   368  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
   369  	require.Equal(t, 1, len(decisions))
   370  	require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType())
   371  
   372  	// cancel request initiated
   373  	h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID)
   374  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
   375  
   376  	// cancel request accepted
   377  	h.handleExternalWorkflowExecutionCancelRequested(initiatedEventID, workflowID)
   378  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
   379  
   380  	// child workflow canceled
   381  	h.handleChildWorkflowExecutionCanceled(workflowID)
   382  	require.Equal(t, decisionStateCompleted, d.getState())
   383  }
   384  
   385  func Test_ChildWorkflowStateMachine_InvalidStates(t *testing.T) {
   386  	t.Parallel()
   387  	domain := "test-domain"
   388  	workflowID := "test-workflow-id"
   389  	runID := ""
   390  	attributes := &s.StartChildWorkflowExecutionDecisionAttributes{
   391  		WorkflowId: common.StringPtr(workflowID),
   392  	}
   393  	cancellationID := ""
   394  	initiatedEventID := int64(28)
   395  	isChildWorkflowOnly := true
   396  	h := newDecisionsHelper()
   397  
   398  	// start child workflow
   399  	d := h.startChildWorkflowExecution(attributes)
   400  	require.Equal(t, decisionStateCreated, d.getState())
   401  
   402  	// invalid: start child workflow failed before decision was sent
   403  	err := runAndCatchPanic(func() {
   404  		h.handleStartChildWorkflowExecutionFailed(workflowID)
   405  	})
   406  	require.NotNil(t, err)
   407  
   408  	// send decision
   409  	decisions := h.getDecisions(true)
   410  	require.Equal(t, decisionStateDecisionSent, d.getState())
   411  	require.Equal(t, 1, len(decisions))
   412  
   413  	// invalid: child workflow completed before it was initiated
   414  	err = runAndCatchPanic(func() {
   415  		h.handleChildWorkflowExecutionClosed(workflowID)
   416  	})
   417  	require.NotNil(t, err)
   418  
   419  	// child workflow initiated
   420  	h.handleStartChildWorkflowExecutionInitiated(workflowID)
   421  	require.Equal(t, decisionStateInitiated, d.getState())
   422  
   423  	h.handleChildWorkflowExecutionStarted(workflowID)
   424  	require.Equal(t, decisionStateStarted, d.getState())
   425  	// invalid: cancel child workflow failed before cancel request
   426  	err = runAndCatchPanic(func() {
   427  		h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID)
   428  	})
   429  	require.NotNil(t, err)
   430  
   431  	// cancel child workflow after child workflow is started
   432  	h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, isChildWorkflowOnly)
   433  	require.Equal(t, decisionStateCanceledAfterStarted, d.getState())
   434  
   435  	// send cancel request
   436  	decisions = h.getDecisions(true)
   437  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
   438  	require.Equal(t, 1, len(decisions))
   439  	require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType())
   440  
   441  	// invalid: start child workflow failed after it was already started
   442  	err = runAndCatchPanic(func() {
   443  		h.handleStartChildWorkflowExecutionFailed(workflowID)
   444  	})
   445  	require.NotNil(t, err)
   446  
   447  	// invalid: child workflow initiated again
   448  	err = runAndCatchPanic(func() {
   449  		h.handleStartChildWorkflowExecutionInitiated(workflowID)
   450  	})
   451  	require.NotNil(t, err)
   452  
   453  	// cancel request initiated
   454  	h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID)
   455  	require.Equal(t, decisionStateCancellationDecisionSent, d.getState())
   456  
   457  	// child workflow completed
   458  	h.handleChildWorkflowExecutionClosed(workflowID)
   459  	require.Equal(t, decisionStateCompletedAfterCancellationDecisionSent, d.getState())
   460  
   461  	// invalid: child workflow canceled after it was completed
   462  	err = runAndCatchPanic(func() {
   463  		h.handleChildWorkflowExecutionCanceled(workflowID)
   464  	})
   465  	require.NotNil(t, err)
   466  }
   467  
   468  func Test_ChildWorkflowStateMachine_CancelFailed(t *testing.T) {
   469  	t.Parallel()
   470  	domain := "test-domain"
   471  	workflowID := "test-workflow-id"
   472  	runID := ""
   473  	attributes := &s.StartChildWorkflowExecutionDecisionAttributes{
   474  		WorkflowId: common.StringPtr(workflowID),
   475  	}
   476  	cancellationID := ""
   477  	initiatedEventID := int64(28)
   478  	isChildWorkflowOnly := true
   479  	h := newDecisionsHelper()
   480  
   481  	// start child workflow
   482  	d := h.startChildWorkflowExecution(attributes)
   483  	// send decision
   484  	h.getDecisions(true)
   485  	// child workflow initiated
   486  	h.handleStartChildWorkflowExecutionInitiated(workflowID)
   487  	// child workflow started
   488  	h.handleChildWorkflowExecutionStarted(workflowID)
   489  	// cancel child workflow
   490  	h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, isChildWorkflowOnly)
   491  	// send cancel request
   492  	h.getDecisions(true)
   493  	// cancel request initiated
   494  	h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID)
   495  
   496  	// cancel request failed
   497  	h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID)
   498  	require.Equal(t, decisionStateStarted, d.getState())
   499  
   500  	// child workflow completed
   501  	h.handleChildWorkflowExecutionClosed(workflowID)
   502  	require.Equal(t, decisionStateCompleted, d.getState())
   503  }
   504  
   505  func Test_MarkerStateMachine(t *testing.T) {
   506  	t.Parallel()
   507  	h := newDecisionsHelper()
   508  
   509  	// record marker for side effect
   510  	d := h.recordSideEffectMarker(1, []byte{})
   511  	require.Equal(t, decisionStateCreated, d.getState())
   512  
   513  	// send decisions
   514  	decisions := h.getDecisions(true)
   515  	require.Equal(t, decisionStateCompleted, d.getState())
   516  	require.Equal(t, 1, len(decisions))
   517  	require.Equal(t, s.DecisionTypeRecordMarker, decisions[0].GetDecisionType())
   518  }
   519  
   520  func Test_UpsertSearchAttributesDecisionStateMachine(t *testing.T) {
   521  	t.Parallel()
   522  	h := newDecisionsHelper()
   523  
   524  	attr := &s.SearchAttributes{}
   525  	d := h.upsertSearchAttributes("1", attr)
   526  	require.Equal(t, decisionStateCreated, d.getState())
   527  
   528  	decisions := h.getDecisions(true)
   529  	require.Equal(t, decisionStateCompleted, d.getState())
   530  	require.Equal(t, 1, len(decisions))
   531  	require.Equal(t, s.DecisionTypeUpsertWorkflowSearchAttributes, decisions[0].GetDecisionType())
   532  }
   533  
   534  func Test_CancelExternalWorkflowStateMachine_Succeed(t *testing.T) {
   535  	t.Parallel()
   536  	domain := "test-domain"
   537  	workflowID := "test-workflow-id"
   538  	runID := "test-run-id"
   539  	cancellationID := "1"
   540  	initiatedEventID := int64(28)
   541  	childWorkflowOnly := false
   542  	h := newDecisionsHelper()
   543  
   544  	// request cancel external workflow
   545  	decision := h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, childWorkflowOnly)
   546  	require.False(t, decision.isDone())
   547  	d := h.getDecision(makeDecisionID(decisionTypeCancellation, cancellationID))
   548  	require.Equal(t, decisionStateCreated, d.getState())
   549  
   550  	// send decisions
   551  	decisions := h.getDecisions(true)
   552  	require.Equal(t, 1, len(decisions))
   553  	require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType())
   554  	require.Equal(
   555  		t,
   556  		&s.RequestCancelExternalWorkflowExecutionDecisionAttributes{
   557  			Domain:            common.StringPtr(domain),
   558  			WorkflowId:        common.StringPtr(workflowID),
   559  			RunId:             common.StringPtr(runID),
   560  			Control:           []byte(cancellationID),
   561  			ChildWorkflowOnly: common.BoolPtr(childWorkflowOnly),
   562  		},
   563  		decisions[0].RequestCancelExternalWorkflowExecutionDecisionAttributes,
   564  	)
   565  
   566  	// cancel request initiated
   567  	h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID)
   568  	require.Equal(t, decisionStateInitiated, d.getState())
   569  
   570  	// cancel requested
   571  	h.handleExternalWorkflowExecutionCancelRequested(initiatedEventID, workflowID)
   572  	require.Equal(t, decisionStateCompleted, d.getState())
   573  
   574  	// mark the cancel request failed now will make it invalid state transition
   575  	err := runAndCatchPanic(func() {
   576  		h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID)
   577  	})
   578  	require.NotNil(t, err)
   579  }
   580  
   581  func Test_CancelExternalWorkflowStateMachine_Failed(t *testing.T) {
   582  	t.Parallel()
   583  	domain := "test-domain"
   584  	workflowID := "test-workflow-id"
   585  	runID := "test-run-id"
   586  	cancellationID := "2"
   587  	initiatedEventID := int64(28)
   588  	childWorkflowOnly := false
   589  	h := newDecisionsHelper()
   590  
   591  	// request cancel external workflow
   592  	decision := h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, childWorkflowOnly)
   593  	require.False(t, decision.isDone())
   594  	d := h.getDecision(makeDecisionID(decisionTypeCancellation, cancellationID))
   595  	require.Equal(t, decisionStateCreated, d.getState())
   596  
   597  	// send decisions
   598  	decisions := h.getDecisions(true)
   599  	require.Equal(t, 1, len(decisions))
   600  	require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType())
   601  	require.Equal(
   602  		t,
   603  		&s.RequestCancelExternalWorkflowExecutionDecisionAttributes{
   604  			Domain:            common.StringPtr(domain),
   605  			WorkflowId:        common.StringPtr(workflowID),
   606  			RunId:             common.StringPtr(runID),
   607  			Control:           []byte(cancellationID),
   608  			ChildWorkflowOnly: common.BoolPtr(childWorkflowOnly),
   609  		},
   610  		decisions[0].RequestCancelExternalWorkflowExecutionDecisionAttributes,
   611  	)
   612  
   613  	// cancel request initiated
   614  	h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID)
   615  	require.Equal(t, decisionStateInitiated, d.getState())
   616  
   617  	// cancel request failed
   618  	h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID)
   619  	require.Equal(t, decisionStateCompleted, d.getState())
   620  
   621  	// mark the cancel request succeed now will make it invalid state transition
   622  	err := runAndCatchPanic(func() {
   623  		h.handleExternalWorkflowExecutionCancelRequested(initiatedEventID, workflowID)
   624  	})
   625  	require.NotNil(t, err)
   626  }
   627  
   628  func runAndCatchPanic(f func()) (err *workflowPanicError) {
   629  	// panic handler
   630  	defer func() {
   631  		if p := recover(); p != nil {
   632  			topLine := "runAndCatchPanic [panic]:"
   633  			st := getStackTraceRaw(topLine, 7, 0)
   634  			err = newWorkflowPanicError(p, st) // Fail decision on panic
   635  		}
   636  	}()
   637  
   638  	f()
   639  	return nil
   640  }