go.uber.org/cadence@v1.2.9/internal/internal_workflow_client_test.go (about) 1 // Copyright (c) 2018 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 package internal 21 22 import ( 23 "context" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "log" 28 "os" 29 "testing" 30 "time" 31 32 "github.com/golang/mock/gomock" 33 "github.com/pborman/uuid" 34 "github.com/stretchr/testify/suite" 35 "go.uber.org/yarpc" 36 37 "go.uber.org/cadence/.gen/go/cadence/workflowservicetest" 38 "go.uber.org/cadence/.gen/go/shared" 39 "go.uber.org/cadence/internal/common" 40 "go.uber.org/cadence/internal/common/metrics" 41 "go.uber.org/cadence/internal/common/serializer" 42 ) 43 44 const ( 45 domain = "some random domain" 46 workflowID = "some random workflow ID" 47 workflowType = "some random workflow type" 48 runID = "some random run ID" 49 tasklist = "some random tasklist" 50 identity = "some random identity" 51 timeoutInSeconds = 20 52 workflowIDReusePolicy = WorkflowIDReusePolicyAllowDuplicateFailedOnly 53 testHeader = "test-header" 54 ) 55 56 // historyEventIteratorSuite 57 58 type ( 59 historyEventIteratorSuite struct { 60 suite.Suite 61 mockCtrl *gomock.Controller 62 workflowServiceClient *workflowservicetest.MockClient 63 wfClient *workflowClient 64 } 65 ) 66 67 // stringMapPropagator propagates the list of keys across a workflow, 68 // interpreting the payloads as strings. 69 type stringMapPropagator struct { 70 keys map[string]struct{} 71 } 72 73 // NewStringMapPropagator returns a context propagator that propagates a set of 74 // string key-value pairs across a workflow 75 func NewStringMapPropagator(keys []string) ContextPropagator { 76 keyMap := make(map[string]struct{}, len(keys)) 77 for _, key := range keys { 78 keyMap[key] = struct{}{} 79 } 80 return &stringMapPropagator{keyMap} 81 } 82 83 // Inject injects values from context into headers for propagation 84 func (s *stringMapPropagator) Inject(ctx context.Context, writer HeaderWriter) error { 85 for key := range s.keys { 86 value, ok := ctx.Value(contextKey(key)).(string) 87 if !ok { 88 return fmt.Errorf("unable to extract key from context %v", key) 89 } 90 writer.Set(key, []byte(value)) 91 } 92 return nil 93 } 94 95 // InjectFromWorkflow injects values from context into headers for propagation 96 func (s *stringMapPropagator) InjectFromWorkflow(ctx Context, writer HeaderWriter) error { 97 for key := range s.keys { 98 value, ok := ctx.Value(contextKey(key)).(string) 99 if !ok { 100 return fmt.Errorf("unable to extract key from context %v", key) 101 } 102 writer.Set(key, []byte(value)) 103 } 104 return nil 105 } 106 107 // Extract extracts values from headers and puts them into context 108 func (s *stringMapPropagator) Extract(ctx context.Context, reader HeaderReader) (context.Context, error) { 109 if err := reader.ForEachKey(func(key string, value []byte) error { 110 if _, ok := s.keys[key]; ok { 111 ctx = context.WithValue(ctx, contextKey(key), string(value)) 112 } 113 return nil 114 }); err != nil { 115 return nil, err 116 } 117 return ctx, nil 118 } 119 120 // ExtractToWorkflow extracts values from headers and puts them into context 121 func (s *stringMapPropagator) ExtractToWorkflow(ctx Context, reader HeaderReader) (Context, error) { 122 if err := reader.ForEachKey(func(key string, value []byte) error { 123 if _, ok := s.keys[key]; ok { 124 ctx = WithValue(ctx, contextKey(key), string(value)) 125 } 126 return nil 127 }); err != nil { 128 return nil, err 129 } 130 return ctx, nil 131 } 132 133 func TestHistoryEventIteratorSuite(t *testing.T) { 134 s := new(historyEventIteratorSuite) 135 suite.Run(t, s) 136 } 137 138 func (s *historyEventIteratorSuite) SetupSuite() { 139 if testing.Verbose() { 140 log.SetOutput(os.Stdout) 141 } 142 } 143 144 func (s *historyEventIteratorSuite) SetupTest() { 145 // Create service endpoint 146 s.mockCtrl = gomock.NewController(s.T()) 147 s.workflowServiceClient = workflowservicetest.NewMockClient(s.mockCtrl) 148 149 s.wfClient = &workflowClient{ 150 workflowService: s.workflowServiceClient, 151 domain: domain, 152 } 153 } 154 155 func (s *historyEventIteratorSuite) TearDownTest() { 156 s.mockCtrl.Finish() // assert mock’s expectations 157 } 158 159 func (s *historyEventIteratorSuite) TestIterator_NoError() { 160 filterType := shared.HistoryEventFilterTypeAllEvent 161 request1 := getGetWorkflowExecutionHistoryRequest(filterType) 162 response1 := &shared.GetWorkflowExecutionHistoryResponse{ 163 History: &shared.History{ 164 Events: []*shared.HistoryEvent{ 165 // dummy history event 166 &shared.HistoryEvent{}, 167 }, 168 }, 169 NextPageToken: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 170 } 171 request2 := getGetWorkflowExecutionHistoryRequest(filterType) 172 request2.NextPageToken = response1.NextPageToken 173 response2 := &shared.GetWorkflowExecutionHistoryResponse{ 174 History: &shared.History{ 175 Events: []*shared.HistoryEvent{ 176 // dummy history event 177 &shared.HistoryEvent{}, 178 }, 179 }, 180 NextPageToken: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 181 } 182 183 dummyEvent := []*shared.HistoryEvent{ 184 // dummy history event 185 &shared.HistoryEvent{}, 186 } 187 188 blobData := serializeEvents(dummyEvent) 189 request3 := getGetWorkflowExecutionHistoryRequest(filterType) 190 request3.NextPageToken = response2.NextPageToken 191 response3 := &shared.GetWorkflowExecutionHistoryResponse{ 192 RawHistory: []*shared.DataBlob{ 193 // dummy history event 194 blobData, 195 }, 196 NextPageToken: nil, 197 } 198 199 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request1, gomock.Any()).Return(response1, nil).Times(1) 200 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request2, gomock.Any()).Return(response2, nil).Times(1) 201 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request3, gomock.Any()).Return(response3, nil).Times(1) 202 203 events := []*shared.HistoryEvent{} 204 iter := s.wfClient.GetWorkflowHistory(context.Background(), workflowID, runID, true, shared.HistoryEventFilterTypeAllEvent) 205 for iter.HasNext() { 206 event, err := iter.Next() 207 s.Nil(err) 208 events = append(events, event) 209 } 210 s.Equal(3, len(events)) 211 } 212 213 func (s *historyEventIteratorSuite) TestIterator_NoError_EmptyPage() { 214 filterType := shared.HistoryEventFilterTypeAllEvent 215 request1 := getGetWorkflowExecutionHistoryRequest(filterType) 216 response1 := &shared.GetWorkflowExecutionHistoryResponse{ 217 History: &shared.History{ 218 Events: []*shared.HistoryEvent{}, 219 }, 220 NextPageToken: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 221 } 222 request2 := getGetWorkflowExecutionHistoryRequest(filterType) 223 request2.NextPageToken = response1.NextPageToken 224 response2 := &shared.GetWorkflowExecutionHistoryResponse{ 225 History: &shared.History{ 226 Events: []*shared.HistoryEvent{ 227 // dummy history event 228 &shared.HistoryEvent{}, 229 }, 230 }, 231 NextPageToken: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 232 } 233 234 dummyEvent := []*shared.HistoryEvent{ 235 // dummy history event 236 &shared.HistoryEvent{}, 237 } 238 239 blobData := serializeEvents(dummyEvent) 240 request3 := getGetWorkflowExecutionHistoryRequest(filterType) 241 request3.NextPageToken = response2.NextPageToken 242 response3 := &shared.GetWorkflowExecutionHistoryResponse{ 243 RawHistory: []*shared.DataBlob{ 244 // dummy history event 245 blobData, 246 }, 247 NextPageToken: nil, 248 } 249 250 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request1, gomock.Any()).Return(response1, nil).Times(1) 251 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request2, gomock.Any()).Return(response2, nil).Times(1) 252 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request3, gomock.Any()).Return(response3, nil).Times(1) 253 254 events := []*shared.HistoryEvent{} 255 iter := s.wfClient.GetWorkflowHistory(context.Background(), workflowID, runID, true, shared.HistoryEventFilterTypeAllEvent) 256 for iter.HasNext() { 257 event, err := iter.Next() 258 s.Nil(err) 259 events = append(events, event) 260 } 261 s.Equal(2, len(events)) 262 } 263 264 func (s *historyEventIteratorSuite) TestIterator_Error() { 265 filterType := shared.HistoryEventFilterTypeAllEvent 266 request1 := getGetWorkflowExecutionHistoryRequest(filterType) 267 response1 := &shared.GetWorkflowExecutionHistoryResponse{ 268 History: &shared.History{ 269 Events: []*shared.HistoryEvent{ 270 // dummy history event 271 &shared.HistoryEvent{}, 272 }, 273 }, 274 NextPageToken: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 275 } 276 request2 := getGetWorkflowExecutionHistoryRequest(filterType) 277 request2.NextPageToken = response1.NextPageToken 278 279 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request1, gomock.Any()).Return(response1, nil).Times(1) 280 281 iter := s.wfClient.GetWorkflowHistory(context.Background(), workflowID, runID, true, shared.HistoryEventFilterTypeAllEvent) 282 283 s.True(iter.HasNext()) 284 event, err := iter.Next() 285 s.NotNil(event) 286 s.Nil(err) 287 288 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), request2, gomock.Any()).Return(nil, &shared.EntityNotExistsError{}).Times(1) 289 290 s.True(iter.HasNext()) 291 event, err = iter.Next() 292 s.Nil(event) 293 s.NotNil(err) 294 } 295 296 func (s *historyEventIteratorSuite) TestIterator_StopsTryingNearTimeout() { 297 // ensuring "when GetWorkflow().Get(...) times out while waiting", we return a timed-out error of some kind, 298 // and stop sending requests rather than trying again and getting some other kind of error. 299 // historically this led to a "bad request", insufficient time left for long poll, which was confusing and noisy. 300 301 filterType := shared.HistoryEventFilterTypeCloseEvent 302 reqNormal := getGetWorkflowExecutionHistoryRequest(filterType) 303 reqFinal := getGetWorkflowExecutionHistoryRequest(filterType) 304 305 // all items filtered out for both requests 306 resEmpty := &shared.GetWorkflowExecutionHistoryResponse{ 307 History: &shared.History{Events: nil}, // this or RawHistory must be non-nil, but they can be empty 308 NextPageToken: []byte{1, 2, 3, 4, 5}, 309 } 310 reqFinal.NextPageToken = resEmpty.NextPageToken 311 312 s.True(time.Second < (defaultGetHistoryTimeoutInSecs*time.Second), "sanity check: default timeout must be longer than how long we extend the timeout for tests") 313 314 // begin with a deadline that is long enough to allow 2 requests 315 d := time.Now().Add((defaultGetHistoryTimeoutInSecs * time.Second) + time.Second) 316 baseCtx, cancel := context.WithDeadline(context.Background(), d) 317 defer cancel() 318 ctx := &fakeDeadlineContext{baseCtx, d} 319 320 // prep the iterator 321 iter := s.wfClient.GetWorkflowHistory(ctx, workflowID, runID, true, filterType) 322 323 // first attempt should occur, and trigger a second request with less than the requested timeout 324 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), reqNormal, gomock.Any()).DoAndReturn(func(_ context.Context, _ *shared.GetWorkflowExecutionHistoryRequest, _ ...yarpc.CallOption) (*shared.GetWorkflowExecutionHistoryResponse, error) { 325 // first request is being sent, modify the context to simulate time passing, 326 // and give the second request insufficient time to trigger another poll. 327 // 328 // without this, you should see an attempt at a third call, as the context is not canceled, 329 // and this mock took no time to respond. 330 ctx.d = time.Now().Add(time.Second) 331 return resEmpty, nil 332 }).Times(1) 333 // second request should occur, but not another 334 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), reqFinal, gomock.Any()).Return(resEmpty, nil).Times(1) 335 336 // trigger both paginated requests as part of the single HasNext call 337 s.True(iter.HasNext()) 338 event, err := iter.Next() 339 s.Nil(event, "iterator should not have returned any events") 340 s.Error(err, "iterator should have errored") 341 // canceled may also be appropriate, but currently this is true 342 s.Truef(errors.Is(err, context.DeadlineExceeded), "iterator should have returned a deadline-exceeded error, but returned a: %#v", err) 343 s.Contains(err.Error(), "waiting for the workflow to finish", "should be descriptive of what happened") 344 } 345 346 // minor helper type to allow faking deadlines between calls, as we cannot normally modify a context that way. 347 type fakeDeadlineContext struct { 348 context.Context 349 350 d time.Time 351 } 352 353 func (f *fakeDeadlineContext) Deadline() (time.Time, bool) { 354 return f.d, true 355 } 356 357 // workflowRunSuite 358 359 type ( 360 workflowRunSuite struct { 361 suite.Suite 362 mockCtrl *gomock.Controller 363 workflowServiceClient *workflowservicetest.MockClient 364 workflowClient Client 365 } 366 ) 367 368 func TestWorkflowRunSuite(t *testing.T) { 369 s := new(workflowRunSuite) 370 suite.Run(t, s) 371 } 372 373 func (s *workflowRunSuite) SetupSuite() { 374 if testing.Verbose() { 375 log.SetOutput(os.Stdout) 376 } 377 } 378 379 func (s *workflowRunSuite) TearDownSuite() { 380 381 } 382 383 func (s *workflowRunSuite) SetupTest() { 384 // Create service endpoint 385 s.mockCtrl = gomock.NewController(s.T()) 386 s.workflowServiceClient = workflowservicetest.NewMockClient(s.mockCtrl) 387 388 metricsScope := metrics.NewTaggedScope(nil) 389 options := &ClientOptions{ 390 MetricsScope: metricsScope, 391 Identity: identity, 392 } 393 s.workflowClient = NewClient(s.workflowServiceClient, domain, options) 394 } 395 396 func (s *workflowRunSuite) TearDownTest() { 397 s.mockCtrl.Finish() 398 } 399 400 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_Success() { 401 createResponse := &shared.StartWorkflowExecutionResponse{ 402 RunId: common.StringPtr(runID), 403 } 404 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 405 406 filterType := shared.HistoryEventFilterTypeCloseEvent 407 eventType := shared.EventTypeWorkflowExecutionCompleted 408 workflowResult := time.Hour * 59 409 encodedResult, _ := encodeArg(getDefaultDataConverter(), workflowResult) 410 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 411 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 412 History: &shared.History{ 413 Events: []*shared.HistoryEvent{ 414 &shared.HistoryEvent{ 415 EventType: &eventType, 416 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 417 Result: encodedResult, 418 }, 419 }, 420 }, 421 }, 422 NextPageToken: nil, 423 } 424 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 425 426 workflowRun, err := s.workflowClient.ExecuteWorkflow( 427 context.Background(), 428 StartWorkflowOptions{ 429 ID: workflowID, 430 TaskList: tasklist, 431 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 432 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 433 WorkflowIDReusePolicy: workflowIDReusePolicy, 434 }, workflowType, 435 ) 436 s.Nil(err) 437 s.Equal(workflowRun.GetID(), workflowID) 438 s.Equal(workflowRun.GetRunID(), runID) 439 decodedResult := time.Minute 440 err = workflowRun.Get(context.Background(), &decodedResult) 441 s.Nil(err) 442 s.Equal(workflowResult, decodedResult) 443 } 444 445 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_RawHistory_Success() { 446 createResponse := &shared.StartWorkflowExecutionResponse{ 447 RunId: common.StringPtr(runID), 448 } 449 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 450 451 filterType := shared.HistoryEventFilterTypeCloseEvent 452 eventType := shared.EventTypeWorkflowExecutionCompleted 453 workflowResult := time.Hour * 59 454 encodedResult, _ := encodeArg(getDefaultDataConverter(), workflowResult) 455 events := []*shared.HistoryEvent{ 456 &shared.HistoryEvent{ 457 EventType: &eventType, 458 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 459 Result: encodedResult, 460 }, 461 }, 462 } 463 464 blobData := serializeEvents(events) 465 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 466 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 467 RawHistory: []*shared.DataBlob{ 468 blobData, 469 }, 470 NextPageToken: nil, 471 } 472 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 473 474 workflowRun, err := s.workflowClient.ExecuteWorkflow( 475 context.Background(), 476 StartWorkflowOptions{ 477 ID: workflowID, 478 TaskList: tasklist, 479 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 480 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 481 WorkflowIDReusePolicy: workflowIDReusePolicy, 482 }, workflowType, 483 ) 484 s.Nil(err) 485 s.Equal(workflowRun.GetID(), workflowID) 486 s.Equal(workflowRun.GetRunID(), runID) 487 decodedResult := time.Minute 488 err = workflowRun.Get(context.Background(), &decodedResult) 489 s.Nil(err) 490 s.Equal(workflowResult, decodedResult) 491 } 492 493 func (s *workflowRunSuite) TestExecuteWorkflowWorkflowExecutionAlreadyStartedError() { 494 alreadyStartedErr := &shared.WorkflowExecutionAlreadyStartedError{ 495 RunId: common.StringPtr(runID), 496 Message: common.StringPtr("Already Started"), 497 StartRequestId: common.StringPtr(uuid.NewRandom().String()), 498 } 499 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...). 500 Return(nil, alreadyStartedErr).Times(1) 501 502 eventType := shared.EventTypeWorkflowExecutionCompleted 503 workflowResult := time.Hour * 59 504 encodedResult, _ := encodeArg(nil, workflowResult) 505 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 506 History: &shared.History{ 507 Events: []*shared.HistoryEvent{ 508 { 509 EventType: &eventType, 510 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 511 Result: encodedResult, 512 }, 513 }, 514 }, 515 }, 516 NextPageToken: nil, 517 } 518 getHistory := s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...). 519 Return(getResponse, nil).Times(1) 520 getHistory.Do(func(ctx interface{}, getRequest *shared.GetWorkflowExecutionHistoryRequest, opt1 interface{}, opt2 interface{}, opt3 interface{}, opt4 interface{}) { 521 workflowID := getRequest.Execution.WorkflowId 522 s.NotNil(workflowID) 523 s.NotEmpty(*workflowID) 524 }) 525 526 workflowRun, err := s.workflowClient.ExecuteWorkflow( 527 context.Background(), 528 StartWorkflowOptions{ 529 ID: workflowID, 530 TaskList: tasklist, 531 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 532 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 533 WorkflowIDReusePolicy: workflowIDReusePolicy, 534 }, workflowType, 535 ) 536 s.Nil(err) 537 s.Equal(workflowRun.GetID(), workflowID) 538 s.Equal(workflowRun.GetRunID(), runID) 539 decodedResult := time.Minute 540 err = workflowRun.Get(context.Background(), &decodedResult) 541 s.Nil(err) 542 s.Equal(workflowResult, decodedResult) 543 } 544 545 func (s *workflowRunSuite) TestExecuteWorkflowWorkflowExecutionAlreadyStartedError_RawHistory() { 546 alreadyStartedErr := &shared.WorkflowExecutionAlreadyStartedError{ 547 RunId: common.StringPtr(runID), 548 Message: common.StringPtr("Already Started"), 549 StartRequestId: common.StringPtr(uuid.NewRandom().String()), 550 } 551 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...). 552 Return(nil, alreadyStartedErr).Times(1) 553 554 eventType := shared.EventTypeWorkflowExecutionCompleted 555 workflowResult := time.Hour * 59 556 encodedResult, _ := encodeArg(nil, workflowResult) 557 events := []*shared.HistoryEvent{ 558 { 559 EventType: &eventType, 560 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 561 Result: encodedResult, 562 }, 563 }, 564 } 565 566 blobData := serializeEvents(events) 567 568 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 569 RawHistory: []*shared.DataBlob{ 570 blobData, 571 }, 572 NextPageToken: nil, 573 } 574 getHistory := s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...). 575 Return(getResponse, nil).Times(1) 576 getHistory.Do(func(ctx interface{}, getRequest *shared.GetWorkflowExecutionHistoryRequest, opt1 interface{}, opt2 interface{}, opt3 interface{}, opt4 interface{}) { 577 workflowID := getRequest.Execution.WorkflowId 578 s.NotNil(workflowID) 579 s.NotEmpty(*workflowID) 580 }) 581 582 workflowRun, err := s.workflowClient.ExecuteWorkflow( 583 context.Background(), 584 StartWorkflowOptions{ 585 ID: workflowID, 586 TaskList: tasklist, 587 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 588 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 589 WorkflowIDReusePolicy: workflowIDReusePolicy, 590 }, workflowType, 591 ) 592 s.Nil(err) 593 s.Equal(workflowRun.GetID(), workflowID) 594 s.Equal(workflowRun.GetRunID(), runID) 595 decodedResult := time.Minute 596 err = workflowRun.Get(context.Background(), &decodedResult) 597 s.Nil(err) 598 s.Equal(workflowResult, decodedResult) 599 } 600 601 // Test for the bug in ExecuteWorkflow. 602 // When Options.ID was empty then GetWorkflowExecutionHistory was called with an empty WorkflowID. 603 func (s *workflowRunSuite) TestExecuteWorkflow_NoIdInOptions() { 604 createResponse := &shared.StartWorkflowExecutionResponse{ 605 RunId: common.StringPtr(runID), 606 } 607 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 608 609 eventType := shared.EventTypeWorkflowExecutionCompleted 610 workflowResult := time.Hour * 59 611 encodedResult, _ := encodeArg(nil, workflowResult) 612 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 613 History: &shared.History{ 614 Events: []*shared.HistoryEvent{ 615 { 616 EventType: &eventType, 617 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 618 Result: encodedResult, 619 }, 620 }, 621 }, 622 }, 623 NextPageToken: nil, 624 } 625 var wid *string 626 getHistory := s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(getResponse, nil).Times(1) 627 getHistory.Do(func(ctx interface{}, getRequest *shared.GetWorkflowExecutionHistoryRequest, opt1 interface{}, opt2 interface{}, opt3 interface{}, opt4 interface{}) { 628 wid = getRequest.Execution.WorkflowId 629 s.NotNil(wid) 630 s.NotEmpty(*wid) 631 }) 632 633 workflowRun, err := s.workflowClient.ExecuteWorkflow( 634 context.Background(), 635 StartWorkflowOptions{ 636 TaskList: tasklist, 637 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 638 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 639 WorkflowIDReusePolicy: workflowIDReusePolicy, 640 }, workflowType, 641 ) 642 s.Nil(err) 643 s.Equal(workflowRun.GetRunID(), runID) 644 decodedResult := time.Minute 645 err = workflowRun.Get(context.Background(), &decodedResult) 646 s.Nil(err) 647 s.Equal(workflowResult, decodedResult) 648 s.Equal(workflowRun.GetID(), *wid) 649 } 650 651 // Test for the bug in ExecuteWorkflow in the case of raw history returned from API. 652 // When Options.ID was empty then GetWorkflowExecutionHistory was called with an empty WorkflowID. 653 func (s *workflowRunSuite) TestExecuteWorkflow_NoIdInOptions_RawHistory() { 654 createResponse := &shared.StartWorkflowExecutionResponse{ 655 RunId: common.StringPtr(runID), 656 } 657 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 658 659 eventType := shared.EventTypeWorkflowExecutionCompleted 660 workflowResult := time.Hour * 59 661 encodedResult, _ := encodeArg(nil, workflowResult) 662 events := []*shared.HistoryEvent{ 663 { 664 EventType: &eventType, 665 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 666 Result: encodedResult, 667 }, 668 }, 669 } 670 671 blobData := serializeEvents(events) 672 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 673 RawHistory: []*shared.DataBlob{ 674 blobData, 675 }, 676 NextPageToken: nil, 677 } 678 var wid *string 679 getHistory := s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(getResponse, nil).Times(1) 680 getHistory.Do(func(ctx interface{}, getRequest *shared.GetWorkflowExecutionHistoryRequest, opt1 interface{}, opt2 interface{}, opt3 interface{}, opt4 interface{}) { 681 wid = getRequest.Execution.WorkflowId 682 s.NotNil(wid) 683 s.NotEmpty(*wid) 684 }) 685 686 workflowRun, err := s.workflowClient.ExecuteWorkflow( 687 context.Background(), 688 StartWorkflowOptions{ 689 TaskList: tasklist, 690 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 691 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 692 WorkflowIDReusePolicy: workflowIDReusePolicy, 693 }, workflowType, 694 ) 695 s.Nil(err) 696 s.Equal(workflowRun.GetRunID(), runID) 697 decodedResult := time.Minute 698 err = workflowRun.Get(context.Background(), &decodedResult) 699 s.Nil(err) 700 s.Equal(workflowResult, decodedResult) 701 s.Equal(workflowRun.GetID(), *wid) 702 } 703 704 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_Cancelled() { 705 createResponse := &shared.StartWorkflowExecutionResponse{ 706 RunId: common.StringPtr(runID), 707 } 708 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 709 710 filterType := shared.HistoryEventFilterTypeCloseEvent 711 eventType := shared.EventTypeWorkflowExecutionCanceled 712 details := "some details" 713 encodedDetails, _ := encodeArg(getDefaultDataConverter(), details) 714 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 715 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 716 History: &shared.History{ 717 Events: []*shared.HistoryEvent{ 718 &shared.HistoryEvent{ 719 EventType: &eventType, 720 WorkflowExecutionCanceledEventAttributes: &shared.WorkflowExecutionCanceledEventAttributes{ 721 Details: encodedDetails, 722 }, 723 }, 724 }, 725 }, 726 NextPageToken: nil, 727 } 728 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 729 730 workflowRun, err := s.workflowClient.ExecuteWorkflow( 731 context.Background(), 732 StartWorkflowOptions{ 733 ID: workflowID, 734 TaskList: tasklist, 735 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 736 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 737 WorkflowIDReusePolicy: workflowIDReusePolicy, 738 }, workflowType, 739 ) 740 s.Nil(err) 741 s.Equal(workflowRun.GetID(), workflowID) 742 s.Equal(workflowRun.GetRunID(), runID) 743 decodedResult := time.Minute 744 err = workflowRun.Get(context.Background(), &decodedResult) 745 s.NotNil(err) 746 _, ok := err.(*CanceledError) 747 s.True(ok) 748 s.Equal(time.Minute, decodedResult) 749 } 750 751 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_Failed() { 752 createResponse := &shared.StartWorkflowExecutionResponse{ 753 RunId: common.StringPtr(runID), 754 } 755 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 756 757 filterType := shared.HistoryEventFilterTypeCloseEvent 758 eventType := shared.EventTypeWorkflowExecutionFailed 759 reason := "some reason" 760 details := "some details" 761 dataConverter := getDefaultDataConverter() 762 encodedDetails, _ := encodeArg(dataConverter, details) 763 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 764 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 765 History: &shared.History{ 766 Events: []*shared.HistoryEvent{ 767 &shared.HistoryEvent{ 768 EventType: &eventType, 769 WorkflowExecutionFailedEventAttributes: &shared.WorkflowExecutionFailedEventAttributes{ 770 Reason: common.StringPtr(reason), 771 Details: encodedDetails, 772 }, 773 }, 774 }, 775 }, 776 NextPageToken: nil, 777 } 778 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 779 780 workflowRun, err := s.workflowClient.ExecuteWorkflow( 781 context.Background(), 782 StartWorkflowOptions{ 783 ID: workflowID, 784 TaskList: tasklist, 785 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 786 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 787 WorkflowIDReusePolicy: workflowIDReusePolicy, 788 }, workflowType, 789 ) 790 s.Nil(err) 791 s.Equal(workflowRun.GetID(), workflowID) 792 s.Equal(workflowRun.GetRunID(), runID) 793 decodedResult := time.Minute 794 err = workflowRun.Get(context.Background(), &decodedResult) 795 s.Equal(constructError(reason, encodedDetails, dataConverter), err) 796 s.Equal(time.Minute, decodedResult) 797 } 798 799 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_Terminated() { 800 createResponse := &shared.StartWorkflowExecutionResponse{ 801 RunId: common.StringPtr(runID), 802 } 803 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 804 805 filterType := shared.HistoryEventFilterTypeCloseEvent 806 eventType := shared.EventTypeWorkflowExecutionTerminated 807 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 808 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 809 History: &shared.History{ 810 Events: []*shared.HistoryEvent{ 811 &shared.HistoryEvent{ 812 EventType: &eventType, 813 WorkflowExecutionTerminatedEventAttributes: &shared.WorkflowExecutionTerminatedEventAttributes{}, 814 }, 815 }, 816 }, 817 NextPageToken: nil, 818 } 819 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 820 821 workflowRun, err := s.workflowClient.ExecuteWorkflow( 822 context.Background(), 823 StartWorkflowOptions{ 824 ID: workflowID, 825 TaskList: tasklist, 826 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 827 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 828 WorkflowIDReusePolicy: workflowIDReusePolicy, 829 }, workflowType, 830 ) 831 s.Nil(err) 832 s.Equal(workflowRun.GetID(), workflowID) 833 s.Equal(workflowRun.GetRunID(), runID) 834 decodedResult := time.Minute 835 err = workflowRun.Get(context.Background(), &decodedResult) 836 s.Equal(newTerminatedError(), err) 837 s.Equal(time.Minute, decodedResult) 838 } 839 840 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_TimedOut() { 841 createResponse := &shared.StartWorkflowExecutionResponse{ 842 RunId: common.StringPtr(runID), 843 } 844 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 845 846 filterType := shared.HistoryEventFilterTypeCloseEvent 847 eventType := shared.EventTypeWorkflowExecutionTimedOut 848 timeType := shared.TimeoutTypeScheduleToStart 849 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 850 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 851 History: &shared.History{ 852 Events: []*shared.HistoryEvent{ 853 &shared.HistoryEvent{ 854 EventType: &eventType, 855 WorkflowExecutionTimedOutEventAttributes: &shared.WorkflowExecutionTimedOutEventAttributes{ 856 TimeoutType: &timeType, 857 }, 858 }, 859 }, 860 }, 861 NextPageToken: nil, 862 } 863 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 864 865 workflowRun, err := s.workflowClient.ExecuteWorkflow( 866 context.Background(), 867 StartWorkflowOptions{ 868 ID: workflowID, 869 TaskList: tasklist, 870 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 871 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 872 WorkflowIDReusePolicy: workflowIDReusePolicy, 873 }, workflowType, 874 ) 875 s.Nil(err) 876 s.Equal(workflowRun.GetID(), workflowID) 877 s.Equal(workflowRun.GetRunID(), runID) 878 decodedResult := time.Minute 879 err = workflowRun.Get(context.Background(), &decodedResult) 880 s.NotNil(err) 881 _, ok := err.(*TimeoutError) 882 s.True(ok) 883 s.Equal(timeType, err.(*TimeoutError).TimeoutType()) 884 s.Equal(time.Minute, decodedResult) 885 } 886 887 func (s *workflowRunSuite) TestExecuteWorkflow_NoDup_ContinueAsNew() { 888 createResponse := &shared.StartWorkflowExecutionResponse{ 889 RunId: common.StringPtr(runID), 890 } 891 s.workflowServiceClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), callOptions()...).Return(createResponse, nil).Times(1) 892 893 newRunID := "some other random run ID" 894 filterType := shared.HistoryEventFilterTypeCloseEvent 895 eventType1 := shared.EventTypeWorkflowExecutionContinuedAsNew 896 getRequest1 := getGetWorkflowExecutionHistoryRequest(filterType) 897 getResponse1 := &shared.GetWorkflowExecutionHistoryResponse{ 898 History: &shared.History{ 899 Events: []*shared.HistoryEvent{ 900 &shared.HistoryEvent{ 901 EventType: &eventType1, 902 WorkflowExecutionContinuedAsNewEventAttributes: &shared.WorkflowExecutionContinuedAsNewEventAttributes{ 903 NewExecutionRunId: common.StringPtr(newRunID), 904 }, 905 }, 906 }, 907 }, 908 NextPageToken: nil, 909 } 910 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest1, callOptions()...).Return(getResponse1, nil).Times(1) 911 912 workflowResult := time.Hour * 59 913 encodedResult, _ := encodeArg(getDefaultDataConverter(), workflowResult) 914 eventType2 := shared.EventTypeWorkflowExecutionCompleted 915 getRequest2 := getGetWorkflowExecutionHistoryRequest(filterType) 916 getRequest2.Execution.RunId = common.StringPtr(newRunID) 917 getResponse2 := &shared.GetWorkflowExecutionHistoryResponse{ 918 History: &shared.History{ 919 Events: []*shared.HistoryEvent{ 920 &shared.HistoryEvent{ 921 EventType: &eventType2, 922 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 923 Result: encodedResult, 924 }, 925 }, 926 }, 927 }, 928 NextPageToken: nil, 929 } 930 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest2, callOptions()...).Return(getResponse2, nil).Times(1) 931 932 workflowRun, err := s.workflowClient.ExecuteWorkflow( 933 context.Background(), 934 StartWorkflowOptions{ 935 ID: workflowID, 936 TaskList: tasklist, 937 ExecutionStartToCloseTimeout: timeoutInSeconds * time.Second, 938 DecisionTaskStartToCloseTimeout: timeoutInSeconds * time.Second, 939 WorkflowIDReusePolicy: workflowIDReusePolicy, 940 }, workflowType, 941 ) 942 s.Nil(err) 943 s.Equal(workflowRun.GetID(), workflowID) 944 s.Equal(workflowRun.GetRunID(), runID) 945 decodedResult := time.Minute 946 err = workflowRun.Get(context.Background(), &decodedResult) 947 s.Nil(err) 948 s.Equal(workflowResult, decodedResult) 949 } 950 951 func (s *workflowRunSuite) TestGetWorkflow() { 952 filterType := shared.HistoryEventFilterTypeCloseEvent 953 eventType := shared.EventTypeWorkflowExecutionCompleted 954 workflowResult := time.Hour * 59 955 encodedResult, _ := encodeArg(getDefaultDataConverter(), workflowResult) 956 getRequest := getGetWorkflowExecutionHistoryRequest(filterType) 957 getResponse := &shared.GetWorkflowExecutionHistoryResponse{ 958 History: &shared.History{ 959 Events: []*shared.HistoryEvent{ 960 &shared.HistoryEvent{ 961 EventType: &eventType, 962 WorkflowExecutionCompletedEventAttributes: &shared.WorkflowExecutionCompletedEventAttributes{ 963 Result: encodedResult, 964 }, 965 }, 966 }, 967 }, 968 NextPageToken: nil, 969 } 970 s.workflowServiceClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), getRequest, callOptions()...).Return(getResponse, nil).Times(1) 971 972 workflowID := workflowID 973 runID := runID 974 975 workflowRun := s.workflowClient.GetWorkflow( 976 context.Background(), 977 workflowID, 978 runID, 979 ) 980 s.Equal(workflowRun.GetID(), workflowID) 981 s.Equal(workflowRun.GetRunID(), runID) 982 decodedResult := time.Minute 983 err := workflowRun.Get(context.Background(), &decodedResult) 984 s.Nil(err) 985 s.Equal(workflowResult, decodedResult) 986 } 987 988 func getGetWorkflowExecutionHistoryRequest(filterType shared.HistoryEventFilterType) *shared.GetWorkflowExecutionHistoryRequest { 989 isLongPoll := true 990 991 request := &shared.GetWorkflowExecutionHistoryRequest{ 992 Domain: common.StringPtr(domain), 993 Execution: &shared.WorkflowExecution{ 994 WorkflowId: common.StringPtr(workflowID), 995 RunId: getRunID(runID), 996 }, 997 WaitForNewEvent: common.BoolPtr(isLongPoll), 998 HistoryEventFilterType: &filterType, 999 SkipArchival: common.BoolPtr(true), 1000 } 1001 1002 return request 1003 } 1004 1005 // workflow client test suite 1006 type ( 1007 workflowClientTestSuite struct { 1008 suite.Suite 1009 mockCtrl *gomock.Controller 1010 service *workflowservicetest.MockClient 1011 client Client 1012 } 1013 ) 1014 1015 func TestWorkflowClientSuite(t *testing.T) { 1016 suite.Run(t, new(workflowClientTestSuite)) 1017 } 1018 1019 func (s *workflowClientTestSuite) SetupSuite() { 1020 if testing.Verbose() { 1021 log.SetOutput(os.Stdout) 1022 } 1023 } 1024 1025 func (s *workflowClientTestSuite) SetupTest() { 1026 s.mockCtrl = gomock.NewController(s.T()) 1027 s.service = workflowservicetest.NewMockClient(s.mockCtrl) 1028 s.client = NewClient(s.service, domain, nil) 1029 } 1030 1031 func (s *workflowClientTestSuite) TearDownTest() { 1032 s.mockCtrl.Finish() // assert mock’s expectations 1033 } 1034 1035 func (s *workflowClientTestSuite) TestSignalWithStartWorkflow() { 1036 signalName := "my signal" 1037 signalInput := []byte("my signal input") 1038 options := StartWorkflowOptions{ 1039 ID: workflowID, 1040 TaskList: tasklist, 1041 ExecutionStartToCloseTimeout: timeoutInSeconds, 1042 DecisionTaskStartToCloseTimeout: timeoutInSeconds, 1043 } 1044 1045 createResponse := &shared.StartWorkflowExecutionResponse{ 1046 RunId: common.StringPtr(runID), 1047 } 1048 s.service.EXPECT().SignalWithStartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(createResponse, nil).Times(2) 1049 1050 resp, err := s.client.SignalWithStartWorkflow(context.Background(), workflowID, signalName, signalInput, 1051 options, workflowType) 1052 s.Nil(err) 1053 s.Equal(createResponse.GetRunId(), resp.RunID) 1054 1055 resp, err = s.client.SignalWithStartWorkflow(context.Background(), "", signalName, signalInput, 1056 options, workflowType) 1057 s.Nil(err) 1058 s.Equal(createResponse.GetRunId(), resp.RunID) 1059 } 1060 1061 func (s *workflowClientTestSuite) TestSignalWithStartWorkflow_Error() { 1062 signalName := "my signal" 1063 signalInput := []byte("my signal input") 1064 options := StartWorkflowOptions{} 1065 1066 resp, err := s.client.SignalWithStartWorkflow(context.Background(), workflowID, signalName, signalInput, 1067 options, workflowType) 1068 s.Equal(errors.New("missing TaskList"), err) 1069 s.Nil(resp) 1070 1071 options.TaskList = tasklist 1072 resp, err = s.client.SignalWithStartWorkflow(context.Background(), workflowID, signalName, signalInput, 1073 options, workflowType) 1074 s.NotNil(err) 1075 s.Nil(resp) 1076 1077 options.ExecutionStartToCloseTimeout = timeoutInSeconds 1078 createResponse := &shared.StartWorkflowExecutionResponse{ 1079 RunId: common.StringPtr(runID), 1080 } 1081 s.service.EXPECT().SignalWithStartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(createResponse, nil) 1082 resp, err = s.client.SignalWithStartWorkflow(context.Background(), workflowID, signalName, signalInput, 1083 options, workflowType) 1084 s.Nil(err) 1085 s.Equal(createResponse.GetRunId(), resp.RunID) 1086 } 1087 1088 func (s *workflowClientTestSuite) TestStartWorkflow() { 1089 client, ok := s.client.(*workflowClient) 1090 s.True(ok) 1091 options := StartWorkflowOptions{ 1092 ID: workflowID, 1093 TaskList: tasklist, 1094 ExecutionStartToCloseTimeout: timeoutInSeconds, 1095 DecisionTaskStartToCloseTimeout: timeoutInSeconds, 1096 } 1097 f1 := func(ctx Context, r []byte) string { 1098 return "result" 1099 } 1100 1101 createResponse := &shared.StartWorkflowExecutionResponse{ 1102 RunId: common.StringPtr(runID), 1103 } 1104 s.service.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(createResponse, nil) 1105 1106 resp, err := client.StartWorkflow(context.Background(), options, f1, []byte("test")) 1107 s.Equal(getDefaultDataConverter(), client.dataConverter) 1108 s.Nil(err) 1109 s.Equal(createResponse.GetRunId(), resp.RunID) 1110 } 1111 1112 func (s *workflowClientTestSuite) TestStartWorkflow_WithContext() { 1113 s.client = NewClient(s.service, domain, &ClientOptions{ContextPropagators: []ContextPropagator{NewStringMapPropagator([]string{testHeader})}}) 1114 client, ok := s.client.(*workflowClient) 1115 s.True(ok) 1116 options := StartWorkflowOptions{ 1117 ID: workflowID, 1118 TaskList: tasklist, 1119 ExecutionStartToCloseTimeout: timeoutInSeconds, 1120 DecisionTaskStartToCloseTimeout: timeoutInSeconds, 1121 } 1122 f1 := func(ctx Context, r []byte) error { 1123 value := ctx.Value(contextKey(testHeader)) 1124 if val, ok := value.([]byte); ok { 1125 s.Equal("test-data", string(val)) 1126 return nil 1127 } 1128 return fmt.Errorf("context did not propagate to workflow") 1129 } 1130 1131 createResponse := &shared.StartWorkflowExecutionResponse{ 1132 RunId: common.StringPtr(runID), 1133 } 1134 s.service.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(createResponse, nil) 1135 1136 resp, err := client.StartWorkflow(context.Background(), options, f1, []byte("test")) 1137 s.Nil(err) 1138 s.Equal(createResponse.GetRunId(), resp.RunID) 1139 } 1140 1141 func (s *workflowClientTestSuite) TestStartWorkflow_WithDataConverter() { 1142 dc := newTestDataConverter() 1143 s.client = NewClient(s.service, domain, &ClientOptions{DataConverter: dc}) 1144 client, ok := s.client.(*workflowClient) 1145 s.True(ok) 1146 options := StartWorkflowOptions{ 1147 ID: workflowID, 1148 TaskList: tasklist, 1149 ExecutionStartToCloseTimeout: timeoutInSeconds, 1150 DecisionTaskStartToCloseTimeout: timeoutInSeconds, 1151 } 1152 f1 := func(ctx Context, r []byte) string { 1153 return "result" 1154 } 1155 input := []byte("test") 1156 1157 createResponse := &shared.StartWorkflowExecutionResponse{ 1158 RunId: common.StringPtr(runID), 1159 } 1160 s.service.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(createResponse, nil). 1161 Do(func(_ interface{}, req *shared.StartWorkflowExecutionRequest, _ ...interface{}) { 1162 dc := client.dataConverter 1163 encodedArg, _ := dc.ToData(input) 1164 s.Equal(req.Input, encodedArg) 1165 var decodedArg []byte 1166 dc.FromData(req.Input, &decodedArg) 1167 s.Equal(input, decodedArg) 1168 }) 1169 1170 resp, err := client.StartWorkflow(context.Background(), options, f1, input) 1171 s.Equal(newTestDataConverter(), client.dataConverter) 1172 s.Nil(err) 1173 s.Equal(createResponse.GetRunId(), resp.RunID) 1174 } 1175 1176 func (s *workflowClientTestSuite) TestStartWorkflow_WithMemoAndSearchAttr() { 1177 memo := map[string]interface{}{ 1178 "testMemo": "memo value", 1179 } 1180 searchAttributes := map[string]interface{}{ 1181 "testAttr": "attr value", 1182 } 1183 options := StartWorkflowOptions{ 1184 ID: workflowID, 1185 TaskList: tasklist, 1186 ExecutionStartToCloseTimeout: timeoutInSeconds, 1187 DecisionTaskStartToCloseTimeout: timeoutInSeconds, 1188 Memo: memo, 1189 SearchAttributes: searchAttributes, 1190 } 1191 wf := func(ctx Context) string { 1192 return "result" 1193 } 1194 startResp := &shared.StartWorkflowExecutionResponse{} 1195 1196 s.service.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(startResp, nil). 1197 Do(func(_ interface{}, req *shared.StartWorkflowExecutionRequest, _ ...interface{}) { 1198 var resultMemo, resultAttr string 1199 err := json.Unmarshal(req.Memo.Fields["testMemo"], &resultMemo) 1200 s.NoError(err) 1201 s.Equal("memo value", resultMemo) 1202 1203 err = json.Unmarshal(req.SearchAttributes.IndexedFields["testAttr"], &resultAttr) 1204 s.NoError(err) 1205 s.Equal("attr value", resultAttr) 1206 }) 1207 s.client.StartWorkflow(context.Background(), options, wf) 1208 } 1209 1210 func (s *workflowClientTestSuite) SignalWithStartWorkflowWithMemoAndSearchAttr() { 1211 memo := map[string]interface{}{ 1212 "testMemo": "memo value", 1213 } 1214 searchAttributes := map[string]interface{}{ 1215 "testAttr": "attr value", 1216 } 1217 options := StartWorkflowOptions{ 1218 ID: workflowID, 1219 TaskList: tasklist, 1220 ExecutionStartToCloseTimeout: timeoutInSeconds, 1221 DecisionTaskStartToCloseTimeout: timeoutInSeconds, 1222 Memo: memo, 1223 SearchAttributes: searchAttributes, 1224 } 1225 wf := func(ctx Context) string { 1226 return "result" 1227 } 1228 startResp := &shared.StartWorkflowExecutionResponse{} 1229 1230 s.service.EXPECT().SignalWithStartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any(), 1231 gomock.Any(), gomock.Any(), gomock.Any()).Return(startResp, nil). 1232 Do(func(_ interface{}, req *shared.SignalWithStartWorkflowExecutionRequest, _ ...interface{}) { 1233 var resultMemo, resultAttr string 1234 err := json.Unmarshal(req.Memo.Fields["testMemo"], &resultMemo) 1235 s.NoError(err) 1236 s.Equal("memo value", resultMemo) 1237 1238 err = json.Unmarshal(req.SearchAttributes.IndexedFields["testAttr"], &resultAttr) 1239 s.NoError(err) 1240 s.Equal("attr value", resultAttr) 1241 }) 1242 s.client.SignalWithStartWorkflow(context.Background(), "wid", "signal", "value", options, wf) 1243 } 1244 1245 func (s *workflowClientTestSuite) TestGetWorkflowMemo() { 1246 var input1 map[string]interface{} 1247 result1, err := getWorkflowMemo(input1, nil) 1248 s.NoError(err) 1249 s.Nil(result1) 1250 1251 input1 = make(map[string]interface{}) 1252 result2, err := getWorkflowMemo(input1, nil) 1253 s.NoError(err) 1254 s.NotNil(result2) 1255 s.Equal(0, len(result2.Fields)) 1256 1257 input1["t1"] = "v1" 1258 result3, err := getWorkflowMemo(input1, nil) 1259 s.NoError(err) 1260 s.NotNil(result3) 1261 s.Equal(1, len(result3.Fields)) 1262 var resultString string 1263 decodeArg(nil, result3.Fields["t1"], &resultString) 1264 s.Equal("v1", resultString) 1265 1266 input1["non-serializable"] = make(chan int) 1267 _, err = getWorkflowMemo(input1, nil) 1268 s.Error(err) 1269 } 1270 1271 func (s *workflowClientTestSuite) TestSerializeSearchAttributes() { 1272 var input1 map[string]interface{} 1273 result1, err := serializeSearchAttributes(input1) 1274 s.NoError(err) 1275 s.Nil(result1) 1276 1277 input1 = make(map[string]interface{}) 1278 result2, err := serializeSearchAttributes(input1) 1279 s.NoError(err) 1280 s.NotNil(result2) 1281 s.Equal(0, len(result2.IndexedFields)) 1282 1283 input1["t1"] = "v1" 1284 result3, err := serializeSearchAttributes(input1) 1285 s.NoError(err) 1286 s.NotNil(result3) 1287 s.Equal(1, len(result3.IndexedFields)) 1288 var resultString string 1289 decodeArg(nil, result3.IndexedFields["t1"], &resultString) 1290 s.Equal("v1", resultString) 1291 1292 input1["non-serializable"] = make(chan int) 1293 _, err = serializeSearchAttributes(input1) 1294 s.Error(err) 1295 } 1296 1297 func (s *workflowClientTestSuite) TestListWorkflow() { 1298 request := &shared.ListWorkflowExecutionsRequest{} 1299 response := &shared.ListWorkflowExecutionsResponse{} 1300 s.service.EXPECT().ListWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(response, nil). 1301 Do(func(_ interface{}, req *shared.ListWorkflowExecutionsRequest, _ ...interface{}) { 1302 s.Equal(domain, request.GetDomain()) 1303 }) 1304 resp, err := s.client.ListWorkflow(context.Background(), request) 1305 s.Nil(err) 1306 s.Equal(response, resp) 1307 1308 responseErr := &shared.BadRequestError{} 1309 request.Domain = common.StringPtr("another") 1310 s.service.EXPECT().ListWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, responseErr). 1311 Do(func(_ interface{}, req *shared.ListWorkflowExecutionsRequest, _ ...interface{}) { 1312 s.Equal("another", request.GetDomain()) 1313 }) 1314 resp, err = s.client.ListWorkflow(context.Background(), request) 1315 s.Equal(responseErr, err) 1316 } 1317 1318 func (s *workflowClientTestSuite) TestListArchivedWorkflow() { 1319 request := &shared.ListArchivedWorkflowExecutionsRequest{} 1320 response := &shared.ListArchivedWorkflowExecutionsResponse{} 1321 s.service.EXPECT().ListArchivedWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(response, nil). 1322 Do(func(_ interface{}, req *shared.ListArchivedWorkflowExecutionsRequest, _ ...interface{}) { 1323 s.Equal(domain, request.GetDomain()) 1324 }) 1325 ctxWithTimeout, cancel := context.WithTimeout(context.Background(), time.Minute) 1326 defer cancel() 1327 resp, err := s.client.ListArchivedWorkflow(ctxWithTimeout, request) 1328 s.Nil(err) 1329 s.Equal(response, resp) 1330 1331 responseErr := &shared.BadRequestError{} 1332 request.Domain = common.StringPtr("another") 1333 s.service.EXPECT().ListArchivedWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, responseErr). 1334 Do(func(_ interface{}, req *shared.ListArchivedWorkflowExecutionsRequest, _ ...interface{}) { 1335 s.Equal("another", request.GetDomain()) 1336 }) 1337 resp, err = s.client.ListArchivedWorkflow(ctxWithTimeout, request) 1338 s.Equal(responseErr, err) 1339 } 1340 1341 func (s *workflowClientTestSuite) TestScanWorkflow() { 1342 request := &shared.ListWorkflowExecutionsRequest{} 1343 response := &shared.ListWorkflowExecutionsResponse{} 1344 s.service.EXPECT().ScanWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(response, nil). 1345 Do(func(_ interface{}, req *shared.ListWorkflowExecutionsRequest, _ ...interface{}) { 1346 s.Equal(domain, request.GetDomain()) 1347 }) 1348 resp, err := s.client.ScanWorkflow(context.Background(), request) 1349 s.Nil(err) 1350 s.Equal(response, resp) 1351 1352 responseErr := &shared.BadRequestError{} 1353 request.Domain = common.StringPtr("another") 1354 s.service.EXPECT().ScanWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, responseErr). 1355 Do(func(_ interface{}, req *shared.ListWorkflowExecutionsRequest, _ ...interface{}) { 1356 s.Equal("another", request.GetDomain()) 1357 }) 1358 resp, err = s.client.ScanWorkflow(context.Background(), request) 1359 s.Equal(responseErr, err) 1360 } 1361 1362 func (s *workflowClientTestSuite) TestCountWorkflow() { 1363 request := &shared.CountWorkflowExecutionsRequest{} 1364 response := &shared.CountWorkflowExecutionsResponse{} 1365 s.service.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(response, nil). 1366 Do(func(_ interface{}, req *shared.CountWorkflowExecutionsRequest, _ ...interface{}) { 1367 s.Equal(domain, request.GetDomain()) 1368 }) 1369 resp, err := s.client.CountWorkflow(context.Background(), request) 1370 s.Nil(err) 1371 s.Equal(response, resp) 1372 1373 responseErr := &shared.BadRequestError{} 1374 request.Domain = common.StringPtr("another") 1375 s.service.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, responseErr). 1376 Do(func(_ interface{}, req *shared.CountWorkflowExecutionsRequest, _ ...interface{}) { 1377 s.Equal("another", request.GetDomain()) 1378 }) 1379 resp, err = s.client.CountWorkflow(context.Background(), request) 1380 s.Equal(responseErr, err) 1381 } 1382 1383 func (s *workflowClientTestSuite) TestGetSearchAttributes() { 1384 response := &shared.GetSearchAttributesResponse{} 1385 s.service.EXPECT().GetSearchAttributes(gomock.Any(), gomock.Any()).Return(response, nil) 1386 resp, err := s.client.GetSearchAttributes(context.Background()) 1387 s.Nil(err) 1388 s.Equal(response, resp) 1389 1390 responseErr := &shared.BadRequestError{} 1391 s.service.EXPECT().GetSearchAttributes(gomock.Any(), gomock.Any()).Return(nil, responseErr) 1392 resp, err = s.client.GetSearchAttributes(context.Background()) 1393 s.Equal(responseErr, err) 1394 } 1395 1396 func serializeEvents(events []*shared.HistoryEvent) *shared.DataBlob { 1397 1398 blob, _ := serializer.SerializeBatchEvents(events, shared.EncodingTypeThriftRW) 1399 1400 return &shared.DataBlob{ 1401 EncodingType: shared.EncodingTypeThriftRW.Ptr(), 1402 Data: blob.Data, 1403 } 1404 } 1405 1406 func (s *workflowClientTestSuite) TestCancelWorkflow() { 1407 s.service.EXPECT().RequestCancelWorkflowExecution(gomock.Any(), newPartialCancelRequestMatcher(common.StringPtr("testWf"), common.StringPtr("test reason")), gomock.All(gomock.Any())).Return(nil) 1408 1409 err := s.client.CancelWorkflow(context.Background(), "testWf", "testRun", WithCancelReason("test reason")) 1410 1411 s.Nil(err) 1412 } 1413 1414 func (s *workflowClientTestSuite) TestCancelWorkflowBackwardsCompatible() { 1415 s.service.EXPECT().RequestCancelWorkflowExecution(gomock.Any(), newPartialCancelRequestMatcher(common.StringPtr("testWf"), nil), gomock.All(gomock.Any())).Return(nil) 1416 1417 err := s.client.CancelWorkflow(context.Background(), "testWf", "testRun") 1418 1419 s.Nil(err) 1420 } 1421 1422 type PartialCancelRequestMatcher struct { 1423 wfId *string 1424 cause *string 1425 } 1426 1427 func newPartialCancelRequestMatcher(wfId *string, cause *string) gomock.Matcher { 1428 return &PartialCancelRequestMatcher{ 1429 wfId: wfId, 1430 cause: cause, 1431 } 1432 } 1433 1434 func (m *PartialCancelRequestMatcher) Matches(a interface{}) bool { 1435 aEx, ok := a.(*shared.RequestCancelWorkflowExecutionRequest) 1436 if !ok { 1437 return false 1438 } 1439 1440 return (aEx.Cause == m.cause || *aEx.Cause == *m.cause) && *aEx.WorkflowExecution.WorkflowId == *m.wfId 1441 } 1442 1443 func (m *PartialCancelRequestMatcher) String() string { 1444 return "partial cancellation request matcher matches cause and wfId fields" 1445 }