github.com/alwitt/goutils@v0.6.4/req_resp_test.go (about) 1 package goutils_test 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/alwitt/goutils" 11 "github.com/apex/log" 12 "github.com/google/uuid" 13 "github.com/stretchr/testify/assert" 14 ) 15 16 func TestReqRespBasicOperation(t *testing.T) { 17 assert := assert.New(t) 18 log.SetLevel(log.DebugLevel) 19 20 utCtxt := context.Background() 21 22 // Create the PubSub clients 23 pubsubClients := []goutils.PubSubClient{} 24 for itr := 0; itr < 2; itr++ { 25 coreClient, err := createTestPubSubClient(utCtxt) 26 assert.Nil(err) 27 28 psClient, err := goutils.GetNewPubSubClientInstance( 29 coreClient, log.Fields{"instance": fmt.Sprintf("ut-ps-client-%d", itr)}, nil, 30 ) 31 assert.Nil(err) 32 33 assert.Nil(psClient.UpdateLocalTopicCache(utCtxt)) 34 assert.Nil(psClient.UpdateLocalSubscriptionCache(utCtxt)) 35 36 pubsubClients = append(pubsubClients, psClient) 37 } 38 39 // Create request-response clients 40 rrTopics := []string{ 41 fmt.Sprintf("goutil-ut-rr-bo-topic-0-%s", uuid.NewString()), 42 fmt.Sprintf("goutil-ut-rr-bo-topic-1-%s", uuid.NewString()), 43 } 44 uuts := []goutils.RequestResponseClient{} 45 for itr := 0; itr < 2; itr++ { 46 uut, err := goutils.GetNewPubSubRequestResponseClientInstance( 47 utCtxt, 48 goutils.PubSubRequestResponseClientParam{ 49 TargetID: rrTopics[itr], 50 Name: "ut-client", 51 PSClient: pubsubClients[itr], 52 MsgRetentionTTL: time.Minute * 10, 53 LogTags: log.Fields{"instance": fmt.Sprintf("ut-rr-client-%d", itr)}, 54 CustomLogModifiers: nil, 55 SupportWorkerCount: 2, 56 TimeoutEnforceInt: time.Minute, 57 }, 58 ) 59 assert.Nil(err) 60 uuts = append(uuts, uut) 61 } 62 63 // Sync both PubSub clients with the current set of topics and subscriptions 64 for idx, psClient := range pubsubClients { 65 log.Debugf("Re-syncing %d PubSub client", idx) 66 assert.Nil(psClient.UpdateLocalTopicCache(utCtxt)) 67 assert.Nil(psClient.UpdateLocalSubscriptionCache(utCtxt)) 68 } 69 70 // Install inbound request handlers 71 inboundRequestChans := []chan goutils.ReqRespMessage{} 72 genInboundRequestHandler := func(idx int, rxChan chan goutils.ReqRespMessage) goutils.ReqRespMessageHandler { 73 return func(ctxt context.Context, msg goutils.ReqRespMessage) error { 74 log.Debugf("Client %d received inbound REQUEST", idx) 75 rxChan <- msg 76 return nil 77 } 78 } 79 inboundResponseChans := []chan goutils.ReqRespMessage{} 80 genInboundResponseHandler := func(idx int, rxChan chan goutils.ReqRespMessage) goutils.ReqRespMessageHandler { 81 return func(ctxt context.Context, msg goutils.ReqRespMessage) error { 82 log.Debugf("Client %d received inbound RESPONSE", idx) 83 rxChan <- msg 84 return nil 85 } 86 } 87 responsesHandlers := []goutils.ReqRespMessageHandler{} 88 for itr := 0; itr < 2; itr++ { 89 inboundRequestChans = append(inboundRequestChans, make(chan goutils.ReqRespMessage)) 90 inboundResponseChans = append(inboundResponseChans, make(chan goutils.ReqRespMessage)) 91 assert.Nil(uuts[itr].SetInboundRequestHandler( 92 utCtxt, genInboundRequestHandler(itr, inboundRequestChans[itr]), 93 )) 94 responsesHandlers = append( 95 responsesHandlers, genInboundResponseHandler(itr, inboundResponseChans[itr]), 96 ) 97 } 98 99 // ========================================================================================= 100 101 // Case 0: send request 0 -> 1 102 { 103 lclCtxt0, cancel0 := context.WithTimeout(utCtxt, time.Second*10) 104 defer cancel0() 105 reqPayload := []byte(uuid.NewString()) 106 107 // Send request 108 _, err := uuts[0].Request(lclCtxt0, rrTopics[1], reqPayload, nil, goutils.RequestCallParam{ 109 RespHandler: responsesHandlers[0], 110 ExpectedResponsesCount: 1, 111 Blocking: false, 112 Timeout: time.Second * 5, 113 TimeoutHandler: func(ctxt context.Context) error { 114 assert.False(true, "request is expected to timeout") 115 return nil 116 }, 117 }) 118 assert.Nil(err) 119 120 // Check for request in other client 121 var rxMsg goutils.ReqRespMessage 122 select { 123 case <-lclCtxt0.Done(): 124 assert.False(true, "Timed out waiting for uut[1] to receive request") 125 case msg, ok := <-inboundRequestChans[1]: 126 assert.True(ok) 127 assert.True(msg.IsRequest) 128 assert.Equal(rrTopics[1], msg.TargetID) 129 assert.Equal(rrTopics[0], msg.SenderID) 130 assert.Equal(reqPayload, msg.Payload) 131 rxMsg = msg 132 } 133 134 lclCtxt1, cancel1 := context.WithTimeout(utCtxt, time.Second*10) 135 defer cancel1() 136 // Return a response 137 respPayload := []byte(uuid.NewString()) 138 assert.Nil(uuts[1].Respond(lclCtxt1, rxMsg, respPayload, nil, false)) 139 140 // Check for response in other client 141 select { 142 case <-lclCtxt0.Done(): 143 assert.False(true, "Timed out waiting for uut[0] to receive response") 144 case msg, ok := <-inboundResponseChans[0]: 145 assert.True(ok) 146 assert.False(msg.IsRequest) 147 assert.Equal(rrTopics[0], msg.TargetID) 148 assert.Equal(rrTopics[1], msg.SenderID) 149 assert.Equal(respPayload, msg.Payload) 150 } 151 } 152 153 // Case 1: blocking request with multi-response 154 { 155 lclCtxt0, cancel0 := context.WithTimeout(utCtxt, time.Second*20) 156 defer cancel0() 157 reqPayload := []byte(uuid.NewString()) 158 159 // Make request in separate thread 160 reqWG := sync.WaitGroup{} 161 reqWG.Add(1) 162 go func() { 163 defer reqWG.Done() 164 _, err := uuts[0].Request(lclCtxt0, rrTopics[1], reqPayload, nil, goutils.RequestCallParam{ 165 RespHandler: responsesHandlers[0], 166 ExpectedResponsesCount: 2, 167 Blocking: true, 168 Timeout: time.Second * 5, 169 TimeoutHandler: func(ctxt context.Context) error { 170 assert.False(true, "request is expected to timeout") 171 return nil 172 }, 173 }) 174 assert.Nil(err) 175 }() 176 177 // Check for request in other client 178 var rxMsg goutils.ReqRespMessage 179 select { 180 case <-lclCtxt0.Done(): 181 assert.False(true, "Timed out waiting for uut[1] to receive request") 182 case msg, ok := <-inboundRequestChans[1]: 183 assert.True(ok) 184 assert.True(msg.IsRequest) 185 assert.Equal(rrTopics[1], msg.TargetID) 186 assert.Equal(rrTopics[0], msg.SenderID) 187 assert.Equal(reqPayload, msg.Payload) 188 rxMsg = msg 189 } 190 191 // Return two responses 192 respPayloads := map[string]bool{} 193 for itr := 0; itr < 2; itr++ { 194 lclCtxt1, cancel1 := context.WithTimeout(utCtxt, time.Second*10) 195 defer cancel1() 196 payload := uuid.NewString() 197 respPayloads[payload] = true 198 assert.Nil(uuts[1].Respond(lclCtxt1, rxMsg, []byte(payload), nil, false)) 199 } 200 201 // Check for responses in other client 202 requesterReceived := map[string]bool{} 203 for itr := 0; itr < 2; itr++ { 204 select { 205 case <-lclCtxt0.Done(): 206 assert.False(true, "Timed out waiting for uut[0] to receive response") 207 case msg, ok := <-inboundResponseChans[0]: 208 assert.True(ok) 209 assert.False(msg.IsRequest) 210 assert.Equal(rrTopics[0], msg.TargetID) 211 assert.Equal(rrTopics[1], msg.SenderID) 212 requesterReceived[string(msg.Payload)] = true 213 } 214 } 215 assert.EqualValues(respPayloads, requesterReceived) 216 217 assert.Nil(goutils.TimeBoundedWaitGroupWait(lclCtxt0, &reqWG, time.Second*5)) 218 } 219 220 // ========================================================================================= 221 222 // Clean up 223 { 224 // Stop the request-response clients 225 for itr := 0; itr < 2; itr++ { 226 assert.Nil(uuts[itr].Stop(utCtxt)) 227 } 228 229 for itr := 0; itr < 2; itr++ { 230 assert.Nil(pubsubClients[itr].Close(utCtxt)) 231 232 // Delete the created subscriptions 233 subscription := fmt.Sprintf("ut-client.%s", rrTopics[itr]) 234 assert.Nil(pubsubClients[itr].DeleteSubscription(utCtxt, subscription)) 235 236 // Delete the created topics 237 assert.Nil(pubsubClients[itr].DeleteTopic(utCtxt, rrTopics[itr])) 238 } 239 } 240 } 241 242 func TestReqRespRequestTimeoutHandling(t *testing.T) { 243 assert := assert.New(t) 244 log.SetLevel(log.DebugLevel) 245 246 utCtxt := context.Background() 247 248 // Create the PubSub clients 249 pubsubClients := []goutils.PubSubClient{} 250 for itr := 0; itr < 2; itr++ { 251 coreClient, err := createTestPubSubClient(utCtxt) 252 assert.Nil(err) 253 254 psClient, err := goutils.GetNewPubSubClientInstance( 255 coreClient, log.Fields{"instance": fmt.Sprintf("ut-ps-client-%d", itr)}, nil, 256 ) 257 assert.Nil(err) 258 259 assert.Nil(psClient.UpdateLocalTopicCache(utCtxt)) 260 assert.Nil(psClient.UpdateLocalSubscriptionCache(utCtxt)) 261 262 pubsubClients = append(pubsubClients, psClient) 263 } 264 265 // Create request-response clients 266 rrTopics := []string{ 267 fmt.Sprintf("goutil-ut-rr-rt-topic-0-%s", uuid.NewString()), 268 fmt.Sprintf("goutil-ut-rr-rt-topic-1-%s", uuid.NewString()), 269 } 270 uuts := []goutils.RequestResponseClient{} 271 for itr := 0; itr < 2; itr++ { 272 uut, err := goutils.GetNewPubSubRequestResponseClientInstance( 273 utCtxt, 274 goutils.PubSubRequestResponseClientParam{ 275 TargetID: rrTopics[itr], 276 Name: "ut-client", 277 PSClient: pubsubClients[itr], 278 MsgRetentionTTL: time.Minute * 10, 279 LogTags: log.Fields{"instance": fmt.Sprintf("ut-rr-client-%d", itr)}, 280 CustomLogModifiers: nil, 281 SupportWorkerCount: 2, 282 TimeoutEnforceInt: time.Second * 5, 283 }, 284 ) 285 assert.Nil(err) 286 uuts = append(uuts, uut) 287 } 288 289 // Sync both PubSub clients with the current set of topics and subscriptions 290 for idx, psClient := range pubsubClients { 291 log.Debugf("Re-syncing %d PubSub client", idx) 292 assert.Nil(psClient.UpdateLocalTopicCache(utCtxt)) 293 assert.Nil(psClient.UpdateLocalSubscriptionCache(utCtxt)) 294 } 295 296 // Install inbound request handlers 297 inboundRequestChans := []chan goutils.ReqRespMessage{} 298 genInboundRequestHandler := func(idx int, rxChan chan goutils.ReqRespMessage) goutils.ReqRespMessageHandler { 299 return func(ctxt context.Context, msg goutils.ReqRespMessage) error { 300 log.Debugf("Client %d received inbound REQUEST", idx) 301 rxChan <- msg 302 return nil 303 } 304 } 305 inboundResponseChans := []chan goutils.ReqRespMessage{} 306 genInboundResponseHandler := func(idx int, rxChan chan goutils.ReqRespMessage) goutils.ReqRespMessageHandler { 307 return func(ctxt context.Context, msg goutils.ReqRespMessage) error { 308 log.Debugf("Client %d received inbound RESPONSE", idx) 309 rxChan <- msg 310 return nil 311 } 312 } 313 responsesHandlers := []goutils.ReqRespMessageHandler{} 314 for itr := 0; itr < 2; itr++ { 315 inboundRequestChans = append(inboundRequestChans, make(chan goutils.ReqRespMessage)) 316 inboundResponseChans = append(inboundResponseChans, make(chan goutils.ReqRespMessage)) 317 assert.Nil(uuts[itr].SetInboundRequestHandler( 318 utCtxt, genInboundRequestHandler(itr, inboundRequestChans[itr]), 319 )) 320 responsesHandlers = append( 321 responsesHandlers, genInboundResponseHandler(itr, inboundResponseChans[itr]), 322 ) 323 } 324 325 // ========================================================================================= 326 327 // Case 0: send request 0 -> 1, no response 328 { 329 lclCtxt0, cancel0 := context.WithTimeout(utCtxt, time.Second*10) 330 defer cancel0() 331 reqPayload := []byte(uuid.NewString()) 332 333 timeoutIndication := make(chan bool) 334 timeoutHandler := func(ctxt context.Context) error { 335 log.Debugf("Request timed out") 336 timeoutIndication <- true 337 return nil 338 } 339 340 // Send request 341 _, err := uuts[0].Request(lclCtxt0, rrTopics[1], reqPayload, nil, goutils.RequestCallParam{ 342 RespHandler: responsesHandlers[0], 343 ExpectedResponsesCount: 1, 344 Blocking: false, 345 Timeout: time.Second * 5, 346 TimeoutHandler: timeoutHandler, 347 }) 348 assert.Nil(err) 349 350 // Check for request in other client 351 select { 352 case <-lclCtxt0.Done(): 353 assert.False(true, "Timed out waiting for uut[1] to receive request") 354 case msg, ok := <-inboundRequestChans[1]: 355 assert.True(ok) 356 assert.True(msg.IsRequest) 357 assert.Equal(rrTopics[1], msg.TargetID) 358 assert.Equal(rrTopics[0], msg.SenderID) 359 assert.Equal(reqPayload, msg.Payload) 360 } 361 362 // Wait for timeout 363 lclCtxt1, cancel1 := context.WithTimeout(utCtxt, time.Second*10) 364 defer cancel1() 365 select { 366 case <-lclCtxt1.Done(): 367 assert.False(true, "The request did not timeout") 368 case <-timeoutIndication: 369 // request did timeout 370 break 371 } 372 } 373 374 // Case 1: send request 0 -> 1. multiple responses, only one response 375 { 376 lclCtxt0, cancel0 := context.WithTimeout(utCtxt, time.Second*10) 377 defer cancel0() 378 reqPayload := []byte(uuid.NewString()) 379 380 timeoutIndication := make(chan bool) 381 timeoutHandler := func(ctxt context.Context) error { 382 log.Debugf("Request timed out") 383 timeoutIndication <- true 384 return nil 385 } 386 387 // Send request 388 _, err := uuts[0].Request(lclCtxt0, rrTopics[1], reqPayload, nil, goutils.RequestCallParam{ 389 RespHandler: responsesHandlers[0], 390 ExpectedResponsesCount: 2, 391 Blocking: false, 392 Timeout: time.Second * 5, 393 TimeoutHandler: timeoutHandler, 394 }) 395 assert.Nil(err) 396 397 // Check for request in other client 398 var rxMsg goutils.ReqRespMessage 399 select { 400 case <-lclCtxt0.Done(): 401 assert.False(true, "Timed out waiting for uut[1] to receive request") 402 case msg, ok := <-inboundRequestChans[1]: 403 assert.True(ok) 404 assert.True(msg.IsRequest) 405 assert.Equal(rrTopics[1], msg.TargetID) 406 assert.Equal(rrTopics[0], msg.SenderID) 407 assert.Equal(reqPayload, msg.Payload) 408 rxMsg = msg 409 } 410 411 lclCtxt1, cancel1 := context.WithTimeout(utCtxt, time.Second*10) 412 defer cancel1() 413 // Return a response 414 respPayload := []byte(uuid.NewString()) 415 assert.Nil(uuts[1].Respond(lclCtxt1, rxMsg, respPayload, nil, false)) 416 417 // Check for response in other client 418 select { 419 case <-lclCtxt0.Done(): 420 assert.False(true, "Timed out waiting for uut[0] to receive response") 421 case msg, ok := <-inboundResponseChans[0]: 422 assert.True(ok) 423 assert.False(msg.IsRequest) 424 assert.Equal(rrTopics[0], msg.TargetID) 425 assert.Equal(rrTopics[1], msg.SenderID) 426 assert.Equal(respPayload, msg.Payload) 427 } 428 429 // Wait for timeout 430 select { 431 case <-lclCtxt1.Done(): 432 assert.False(true, "The request did not timeout") 433 case <-timeoutIndication: 434 // request did timeout 435 break 436 } 437 } 438 439 // ========================================================================================= 440 441 // Clean up 442 { 443 // Stop the request-response clients 444 for itr := 0; itr < 2; itr++ { 445 assert.Nil(uuts[itr].Stop(utCtxt)) 446 } 447 448 for itr := 0; itr < 2; itr++ { 449 assert.Nil(pubsubClients[itr].Close(utCtxt)) 450 451 // Delete the created subscriptions 452 subscription := fmt.Sprintf("ut-client.%s", rrTopics[itr]) 453 assert.Nil(pubsubClients[itr].DeleteSubscription(utCtxt, subscription)) 454 455 // Delete the created topics 456 assert.Nil(pubsubClients[itr].DeleteTopic(utCtxt, rrTopics[itr])) 457 } 458 } 459 } 460 461 func TestReqRespSubscriptionReuse(t *testing.T) { 462 assert := assert.New(t) 463 log.SetLevel(log.DebugLevel) 464 465 utCtxt := context.Background() 466 467 // Create the PubSub clients 468 pubsubClients := []goutils.PubSubClient{} 469 for itr := 0; itr < 2; itr++ { 470 coreClient, err := createTestPubSubClient(utCtxt) 471 assert.Nil(err) 472 473 psClient, err := goutils.GetNewPubSubClientInstance( 474 coreClient, log.Fields{"instance": fmt.Sprintf("ut-ps-client-%d", itr)}, nil, 475 ) 476 assert.Nil(err) 477 478 assert.Nil(psClient.UpdateLocalTopicCache(utCtxt)) 479 assert.Nil(psClient.UpdateLocalSubscriptionCache(utCtxt)) 480 481 pubsubClients = append(pubsubClients, psClient) 482 } 483 484 // Create request-response clients 485 rrTopic := fmt.Sprintf("goutil-ut-rr-bo-topic-0-%s", uuid.NewString()) 486 uuts := []goutils.RequestResponseClient{} 487 for itr := 0; itr < 2; itr++ { 488 log.Debugf("Re-syncing %d PubSub client", itr) 489 assert.Nil(pubsubClients[itr].UpdateLocalTopicCache(utCtxt)) 490 assert.Nil(pubsubClients[itr].UpdateLocalSubscriptionCache(utCtxt)) 491 uut, err := goutils.GetNewPubSubRequestResponseClientInstance( 492 utCtxt, 493 goutils.PubSubRequestResponseClientParam{ 494 TargetID: rrTopic, 495 Name: "ut-client", 496 PSClient: pubsubClients[itr], 497 MsgRetentionTTL: time.Minute * 10, 498 LogTags: log.Fields{"instance": fmt.Sprintf("ut-rr-client-%d", itr)}, 499 CustomLogModifiers: nil, 500 SupportWorkerCount: 2, 501 TimeoutEnforceInt: time.Minute, 502 }, 503 ) 504 assert.Nil(err) 505 uuts = append(uuts, uut) 506 } 507 508 // ========================================================================================= 509 510 // Clean up 511 { 512 // Stop the request-response clients 513 for itr := 0; itr < 2; itr++ { 514 assert.Nil(uuts[itr].Stop(utCtxt)) 515 } 516 517 for itr := 0; itr < 2; itr++ { 518 assert.Nil(pubsubClients[itr].Close(utCtxt)) 519 } 520 521 // Delete the created subscriptions 522 subscription := fmt.Sprintf("ut-client.%s", rrTopic) 523 assert.Nil(pubsubClients[0].DeleteSubscription(utCtxt, subscription)) 524 525 // Delete the created topics 526 assert.Nil(pubsubClients[0].DeleteTopic(utCtxt, rrTopic)) 527 } 528 }