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  }