github.com/alwitt/goutils@v0.6.4/pubsub_test.go (about) 1 package goutils_test 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "sync" 8 "testing" 9 "time" 10 11 "cloud.google.com/go/pubsub" 12 "github.com/alwitt/goutils" 13 "github.com/apex/log" 14 "github.com/google/uuid" 15 "github.com/stretchr/testify/assert" 16 "google.golang.org/api/option" 17 ) 18 19 func createPubSubClientUsingAPIToken( 20 ctxt context.Context, projectID string, token string, 21 ) (*pubsub.Client, error) { 22 return pubsub.NewClient(ctxt, projectID, option.WithAPIKey(token)) 23 } 24 25 func createTestPubSubClient(ctxt context.Context) (*pubsub.Client, error) { 26 testGCPProjectID := os.Getenv("UNITTEST_GCP_PROJECT_ID") 27 28 testToken := os.Getenv("UNITTEST_GCP_API_TOKEN") 29 if testToken != "" { 30 return createPubSubClientUsingAPIToken(ctxt, testGCPProjectID, testToken) 31 } 32 return goutils.CreateBasicGCPPubSubClient(ctxt, testGCPProjectID) 33 } 34 35 func TestPubSubTopicCRUD(t *testing.T) { 36 assert := assert.New(t) 37 log.SetLevel(log.DebugLevel) 38 39 utCtxt := context.Background() 40 41 coreClient, err := createTestPubSubClient(utCtxt) 42 assert.Nil(err) 43 44 uut, err := goutils.GetNewPubSubClientInstance( 45 coreClient, log.Fields{"instance": "unit-tester"}, nil, 46 ) 47 assert.Nil(err) 48 49 assert.Nil(uut.UpdateLocalTopicCache(utCtxt)) 50 51 // Case 0: unknown topic 52 { 53 _, err = uut.GetTopic(utCtxt, uuid.NewString()) 54 assert.NotNil(err) 55 } 56 57 // Case 1: create new topic 58 topic0 := fmt.Sprintf("goutil-ut-topic-0-%s", uuid.NewString()) 59 assert.Nil(uut.CreateTopic( 60 utCtxt, topic0, &pubsub.TopicConfig{RetentionDuration: time.Minute * 10}, 61 )) 62 { 63 config, err := uut.GetTopic(utCtxt, topic0) 64 assert.Nil(err) 65 assert.Equal(topic0, config.ID()) 66 assert.Equal(time.Minute*10, config.RetentionDuration) 67 } 68 69 // Case 2: create same topic again 70 assert.Nil(uut.CreateTopic(utCtxt, topic0, nil)) 71 72 // Case 3: update the topic 73 assert.Nil(uut.UpdateTopic( 74 utCtxt, topic0, pubsub.TopicConfigToUpdate{RetentionDuration: time.Minute * 30}, 75 )) 76 { 77 config, err := uut.GetTopic(utCtxt, topic0) 78 assert.Nil(err) 79 assert.Equal(topic0, config.ID()) 80 assert.Equal(time.Minute*30, config.RetentionDuration) 81 } 82 83 // Case 4: create another topic 84 topic1 := fmt.Sprintf("goutil-ut-topic-0-%s", uuid.NewString()) 85 assert.Nil(uut.CreateTopic( 86 utCtxt, topic1, &pubsub.TopicConfig{RetentionDuration: time.Minute * 15}, 87 )) 88 { 89 config, err := uut.GetTopic(utCtxt, topic1) 90 assert.Nil(err) 91 assert.Equal(topic1, config.ID()) 92 assert.Equal(time.Minute*15, config.RetentionDuration) 93 config, err = uut.GetTopic(utCtxt, topic0) 94 assert.Nil(err) 95 assert.Equal(topic0, config.ID()) 96 assert.Equal(time.Minute*30, config.RetentionDuration) 97 } 98 99 // Clean up 100 assert.Nil(uut.DeleteTopic(utCtxt, topic0)) 101 assert.Nil(uut.DeleteTopic(utCtxt, topic1)) 102 } 103 104 func TestPubSubTopicSync(t *testing.T) { 105 assert := assert.New(t) 106 log.SetLevel(log.DebugLevel) 107 108 utCtxt := context.Background() 109 110 coreClient0, err := createTestPubSubClient(utCtxt) 111 assert.Nil(err) 112 113 coreClient1, err := createTestPubSubClient(utCtxt) 114 assert.Nil(err) 115 116 uut0, err := goutils.GetNewPubSubClientInstance( 117 coreClient0, log.Fields{"instance": "unit-tester-0"}, nil, 118 ) 119 assert.Nil(err) 120 121 uut1, err := goutils.GetNewPubSubClientInstance( 122 coreClient1, log.Fields{"instance": "unit-tester-1"}, nil, 123 ) 124 assert.Nil(err) 125 126 // Create new topic 127 topic0 := fmt.Sprintf("goutil-ut-topic-1-%s", uuid.NewString()) 128 assert.Nil(uut0.CreateTopic( 129 utCtxt, topic0, &pubsub.TopicConfig{RetentionDuration: time.Minute * 33}, 130 )) 131 132 // Sync from the other client 133 assert.Nil(uut1.UpdateLocalTopicCache(utCtxt)) 134 { 135 config, err := uut1.GetTopic(utCtxt, topic0) 136 assert.Nil(err) 137 assert.Equal(topic0, config.ID()) 138 assert.Equal(time.Minute*33, config.RetentionDuration) 139 } 140 141 // Clean up 142 assert.Nil(uut0.DeleteTopic(utCtxt, topic0)) 143 } 144 145 func TestPubSubSubscriptionCRUD(t *testing.T) { 146 assert := assert.New(t) 147 log.SetLevel(log.DebugLevel) 148 149 utCtxt := context.Background() 150 151 coreClient, err := createTestPubSubClient(utCtxt) 152 assert.Nil(err) 153 154 uut, err := goutils.GetNewPubSubClientInstance( 155 coreClient, log.Fields{"instance": "unit-tester"}, nil, 156 ) 157 assert.Nil(err) 158 159 assert.Nil(uut.UpdateLocalTopicCache(utCtxt)) 160 assert.Nil(uut.UpdateLocalSubscriptionCache(utCtxt)) 161 162 // Case 0: unknown subscription 163 { 164 _, err := uut.GetSubscription(utCtxt, uuid.NewString()) 165 assert.NotNil(err) 166 } 167 168 // Create test topic 169 testTopic := fmt.Sprintf("goutil-ut-topic-2-%s", uuid.NewString()) 170 assert.Nil(uut.CreateTopic( 171 utCtxt, testTopic, &pubsub.TopicConfig{RetentionDuration: time.Minute * 10}, 172 )) 173 174 // Case 1: create subscription 175 subscribe0 := fmt.Sprintf("goutil-ut-sub-2-%s", uuid.NewString()) 176 assert.Nil(uut.CreateSubscription(utCtxt, testTopic, subscribe0, pubsub.SubscriptionConfig{ 177 AckDeadline: time.Second * 60, 178 RetentionDuration: time.Hour * 4, 179 })) 180 { 181 config, err := uut.GetSubscription(utCtxt, subscribe0) 182 assert.Nil(err) 183 assert.Equal(testTopic, config.Topic.ID()) 184 assert.Equal(subscribe0, config.ID()) 185 assert.Equal(time.Second*60, config.AckDeadline) 186 assert.Equal(time.Hour*4, config.RetentionDuration) 187 } 188 189 // Case 2: create subscription against unknown topic 190 assert.NotNil(uut.CreateSubscription( 191 utCtxt, 192 uuid.NewString(), 193 fmt.Sprintf("goutil-ut-sub-2-%s", uuid.NewString()), 194 pubsub.SubscriptionConfig{}, 195 )) 196 197 // Case 3: create using the same parameters 198 assert.Nil(uut.CreateSubscription(utCtxt, testTopic, subscribe0, pubsub.SubscriptionConfig{ 199 AckDeadline: time.Second * 60, 200 RetentionDuration: time.Hour * 4, 201 })) 202 203 // Case 4: update subscription config 204 assert.Nil(uut.UpdateSubscription(utCtxt, subscribe0, pubsub.SubscriptionConfigToUpdate{ 205 RetentionDuration: time.Hour * 72, 206 })) 207 { 208 config, err := uut.GetSubscription(utCtxt, subscribe0) 209 assert.Nil(err) 210 assert.Equal(testTopic, config.Topic.ID()) 211 assert.Equal(subscribe0, config.ID()) 212 assert.Equal(time.Second*60, config.AckDeadline) 213 assert.Equal(time.Hour*72, config.RetentionDuration) 214 } 215 216 // Clean up 217 assert.Nil(uut.DeleteSubscription(utCtxt, subscribe0)) 218 assert.Nil(uut.DeleteTopic(utCtxt, testTopic)) 219 } 220 221 func TestPubSubDataPassing(t *testing.T) { 222 assert := assert.New(t) 223 log.SetLevel(log.DebugLevel) 224 225 utCtxt := context.Background() 226 227 coreClient, err := createTestPubSubClient(utCtxt) 228 assert.Nil(err) 229 230 uut, err := goutils.GetNewPubSubClientInstance( 231 coreClient, log.Fields{"instance": "unit-tester"}, nil, 232 ) 233 assert.Nil(err) 234 235 assert.Nil(uut.UpdateLocalTopicCache(utCtxt)) 236 assert.Nil(uut.UpdateLocalSubscriptionCache(utCtxt)) 237 238 // Create test topic 239 testTopic := fmt.Sprintf("goutil-ut-topic-3-%s", uuid.NewString()) 240 assert.Nil(uut.CreateTopic( 241 utCtxt, testTopic, &pubsub.TopicConfig{RetentionDuration: time.Minute * 10}, 242 )) 243 244 // Create subscription 245 testSubscription := fmt.Sprintf("goutil-ut-sub-3-%s", uuid.NewString()) 246 assert.Nil(uut.CreateSubscription(utCtxt, testTopic, testSubscription, pubsub.SubscriptionConfig{ 247 AckDeadline: time.Second * 10, 248 RetentionDuration: time.Minute * 10, 249 })) 250 251 // support for receiving messages 252 type msgPkg struct { 253 msg []byte 254 meta map[string]string 255 } 256 rxMsg := make(chan msgPkg) 257 receiveMsg := func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error { 258 rxMsg <- msgPkg{msg: msg, meta: meta} 259 return nil 260 } 261 262 // Start receiving on subscription 263 log.Debug("Starting message subscription receive") 264 wg := sync.WaitGroup{} 265 wg.Add(1) 266 rxCtxt, rxCancel := context.WithCancel(utCtxt) 267 go func() { 268 defer wg.Done() 269 assert.Nil(uut.Subscribe(rxCtxt, testSubscription, receiveMsg)) 270 }() 271 log.Debug("Started message subscription receive") 272 273 // Send messages 274 log.Debug("Publishing test messages") 275 testMsgs := map[string]map[string]string{} 276 for itr := 0; itr < 3; itr++ { 277 msg := uuid.NewString() 278 meta := map[string]string{ 279 uuid.NewString(): uuid.NewString(), uuid.NewString(): uuid.NewString(), 280 } 281 _, err := uut.Publish(utCtxt, testTopic, []byte(msg), meta, true) 282 assert.Nil(err) 283 testMsgs[msg] = meta 284 } 285 log.Debug("Published test messages") 286 287 // Wait for message to come back 288 { 289 lclCtxt, cancel := context.WithTimeout(utCtxt, time.Second*5) 290 defer cancel() 291 292 processedMsgs := map[string]map[string]string{} 293 for itr := 0; itr < 3; itr++ { 294 select { 295 case <-lclCtxt.Done(): 296 assert.False(true, "PubSub receive timeout") 297 case msg, ok := <-rxMsg: 298 assert.True(ok) 299 processedMsgs[string(msg.msg)] = msg.meta 300 } 301 } 302 303 assert.EqualValues(testMsgs, processedMsgs) 304 } 305 306 // Clean up 307 rxCancel() 308 wg.Wait() 309 assert.Nil(uut.DeleteSubscription(utCtxt, testSubscription)) 310 assert.Nil(uut.DeleteTopic(utCtxt, testTopic)) 311 } 312 313 func TestPubSubMultiReaderOneSubcription(t *testing.T) { 314 assert := assert.New(t) 315 log.SetLevel(log.DebugLevel) 316 317 utCtxt := context.Background() 318 319 coreClient, err := createTestPubSubClient(utCtxt) 320 assert.Nil(err) 321 322 uut0, err := goutils.GetNewPubSubClientInstance( 323 coreClient, log.Fields{"instance": "unit-tester-0"}, nil, 324 ) 325 assert.Nil(err) 326 327 assert.Nil(uut0.UpdateLocalTopicCache(utCtxt)) 328 assert.Nil(uut0.UpdateLocalSubscriptionCache(utCtxt)) 329 330 // Create test topic 331 testTopic := fmt.Sprintf("goutil-ut-topic-4-%s", uuid.NewString()) 332 assert.Nil(uut0.CreateTopic( 333 utCtxt, testTopic, &pubsub.TopicConfig{RetentionDuration: time.Minute * 10}, 334 )) 335 336 // Create subscription 337 testSubscription := fmt.Sprintf("goutil-ut-sub-4-%s", uuid.NewString()) 338 assert.Nil(uut0.CreateSubscription(utCtxt, testTopic, testSubscription, pubsub.SubscriptionConfig{ 339 AckDeadline: time.Second * 10, 340 RetentionDuration: time.Minute * 10, 341 })) 342 343 // Create second client 344 uut1, err := goutils.GetNewPubSubClientInstance( 345 coreClient, log.Fields{"instance": "unit-tester-1"}, nil, 346 ) 347 assert.Nil(err) 348 assert.Nil(uut1.UpdateLocalTopicCache(utCtxt)) 349 assert.Nil(uut1.UpdateLocalSubscriptionCache(utCtxt)) 350 351 type msgWrap struct { 352 rxIdx int 353 msg []byte 354 meta map[string]string 355 } 356 357 // Support for receiving messages 358 rxMsg := make(chan msgWrap) 359 receiveMsg0 := func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error { 360 rxMsg <- msgWrap{rxIdx: 0, msg: msg, meta: meta} 361 return nil 362 } 363 receiveMsg1 := func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error { 364 rxMsg <- msgWrap{rxIdx: 1, msg: msg, meta: meta} 365 return nil 366 } 367 368 // Start receiving on subscription 369 log.Debug("Starting message subscription receive") 370 wg := sync.WaitGroup{} 371 wg.Add(2) 372 rxCtxt, rxCancel := context.WithCancel(utCtxt) 373 receiver := func( 374 idx int, 375 client goutils.PubSubClient, 376 handler func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error, 377 ) { 378 defer wg.Done() 379 log.Debugf("Start MSG Receiver %d", idx) 380 assert.Nil(client.Subscribe(rxCtxt, testSubscription, handler)) 381 } 382 go receiver(0, uut0, receiveMsg0) 383 go receiver(1, uut1, receiveMsg1) 384 log.Debug("Started message subscription receive") 385 386 // Send messages 387 log.Debug("Publishing test messages") 388 testMsgs := map[string]map[string]string{} 389 for itr := 0; itr < 3; itr++ { 390 msg := uuid.NewString() 391 meta := map[string]string{ 392 uuid.NewString(): uuid.NewString(), uuid.NewString(): uuid.NewString(), 393 } 394 _, err := uut0.Publish(utCtxt, testTopic, []byte(msg), meta, true) 395 assert.Nil(err) 396 testMsgs[msg] = meta 397 } 398 log.Debug("Published test messages") 399 400 // Wait for message to come back 401 { 402 lclCtxt, cancel := context.WithTimeout(utCtxt, time.Second*5) 403 defer cancel() 404 405 processedMsgs := map[string]map[string]string{} 406 for itr := 0; itr < 3; itr++ { 407 select { 408 case <-lclCtxt.Done(): 409 assert.False(true, "PubSub receive timeout") 410 case msg, ok := <-rxMsg: 411 assert.True(ok) 412 processedMsgs[string(msg.msg)] = msg.meta 413 } 414 } 415 416 assert.EqualValues(testMsgs, processedMsgs) 417 } 418 { 419 // Verify no more messages show up 420 lclCtxt, cancel := context.WithTimeout(utCtxt, time.Second*2) 421 defer cancel() 422 423 select { 424 case <-lclCtxt.Done(): 425 break 426 case <-rxMsg: 427 assert.False(true, "Received unexpected message") 428 } 429 } 430 431 // Clean up 432 rxCancel() 433 wg.Wait() 434 assert.Nil(uut0.DeleteSubscription(utCtxt, testSubscription)) 435 assert.Nil(uut0.DeleteTopic(utCtxt, testTopic)) 436 } 437 438 func TestPubSubMultiSubscriptionOneTopic(t *testing.T) { 439 assert := assert.New(t) 440 log.SetLevel(log.DebugLevel) 441 442 utCtxt := context.Background() 443 444 coreClient, err := createTestPubSubClient(utCtxt) 445 assert.Nil(err) 446 447 uut, err := goutils.GetNewPubSubClientInstance( 448 coreClient, log.Fields{"instance": "unit-tester"}, nil, 449 ) 450 assert.Nil(err) 451 452 assert.Nil(uut.UpdateLocalTopicCache(utCtxt)) 453 assert.Nil(uut.UpdateLocalSubscriptionCache(utCtxt)) 454 455 // Create test topic 456 testTopic := fmt.Sprintf("goutil-ut-topic-5-%s", uuid.NewString()) 457 assert.Nil(uut.CreateTopic( 458 utCtxt, testTopic, &pubsub.TopicConfig{RetentionDuration: time.Minute * 10}, 459 )) 460 461 // Create subscription 462 testSubscription0 := fmt.Sprintf("goutil-ut-sub-5-%s", uuid.NewString()) 463 assert.Nil(uut.CreateSubscription(utCtxt, testTopic, testSubscription0, pubsub.SubscriptionConfig{ 464 AckDeadline: time.Second * 10, 465 RetentionDuration: time.Minute * 10, 466 })) 467 testSubscription1 := fmt.Sprintf("goutil-ut-sub-5-%s", uuid.NewString()) 468 assert.Nil(uut.CreateSubscription(utCtxt, testTopic, testSubscription1, pubsub.SubscriptionConfig{ 469 AckDeadline: time.Second * 10, 470 RetentionDuration: time.Minute * 10, 471 })) 472 473 type msgWrap struct { 474 rxIdx int 475 msg []byte 476 meta map[string]string 477 } 478 479 // Support for receiving messages 480 rxMsg := make(chan msgWrap) 481 receiveMsg0 := func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error { 482 rxMsg <- msgWrap{rxIdx: 0, msg: msg, meta: meta} 483 return nil 484 } 485 receiveMsg1 := func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error { 486 rxMsg <- msgWrap{rxIdx: 1, msg: msg, meta: meta} 487 return nil 488 } 489 490 // Start receiving on subscription 491 log.Debug("Starting message subscription receive") 492 wg := sync.WaitGroup{} 493 wg.Add(2) 494 rxCtxt, rxCancel := context.WithCancel(utCtxt) 495 receiver := func( 496 idx int, 497 subscription string, 498 handler func(ctxt context.Context, ts time.Time, msg []byte, meta map[string]string) error, 499 ) { 500 defer wg.Done() 501 log.Debugf("Start MSG Receiver %d", idx) 502 assert.Nil(uut.Subscribe(rxCtxt, subscription, handler)) 503 } 504 go receiver(0, testSubscription0, receiveMsg0) 505 go receiver(1, testSubscription1, receiveMsg1) 506 log.Debug("Started message subscription receive") 507 508 // Send messages 509 log.Debug("Publishing test messages") 510 testMsgs := map[string]map[string]string{} 511 for itr := 0; itr < 3; itr++ { 512 msg := uuid.NewString() 513 meta := map[string]string{ 514 uuid.NewString(): uuid.NewString(), uuid.NewString(): uuid.NewString(), 515 } 516 _, err := uut.Publish(utCtxt, testTopic, []byte(msg), meta, true) 517 assert.Nil(err) 518 testMsgs[msg] = meta 519 } 520 log.Debug("Published test messages") 521 522 // Wait for message to come back 523 { 524 lclCtxt, cancel := context.WithTimeout(utCtxt, time.Second*5) 525 defer cancel() 526 527 perSubRXMsgs := map[int]map[string]map[string]string{} 528 529 for itr := 0; itr < 6; itr++ { 530 select { 531 case <-lclCtxt.Done(): 532 assert.False(true, "PubSub receive timeout") 533 case msg, ok := <-rxMsg: 534 assert.True(ok) 535 if _, ok := perSubRXMsgs[msg.rxIdx]; !ok { 536 perSubRXMsgs[msg.rxIdx] = make(map[string]map[string]string) 537 } 538 perSubRXMsgs[msg.rxIdx][string(msg.msg)] = msg.meta 539 } 540 } 541 542 assert.Len(perSubRXMsgs, 2) 543 assert.EqualValues(testMsgs, perSubRXMsgs[0]) 544 assert.EqualValues(testMsgs, perSubRXMsgs[1]) 545 } 546 547 // Clean up 548 rxCancel() 549 wg.Wait() 550 assert.Nil(uut.DeleteSubscription(utCtxt, testSubscription0)) 551 assert.Nil(uut.DeleteSubscription(utCtxt, testSubscription1)) 552 assert.Nil(uut.DeleteTopic(utCtxt, testTopic)) 553 } 554 555 func TestPubSubDynamicTopicCacheUpdate(t *testing.T) { 556 assert := assert.New(t) 557 log.SetLevel(log.DebugLevel) 558 559 utCtxt := context.Background() 560 561 coreClient, err := createTestPubSubClient(utCtxt) 562 assert.Nil(err) 563 564 uut0, err := goutils.GetNewPubSubClientInstance( 565 coreClient, log.Fields{"instance": "unit-tester-0"}, nil, 566 ) 567 assert.Nil(err) 568 569 // Create test topic 570 testTopic1 := fmt.Sprintf("goutil-ut-topic-6-%s", uuid.NewString()) 571 assert.Nil(uut0.CreateTopic( 572 utCtxt, testTopic1, &pubsub.TopicConfig{RetentionDuration: time.Minute * 10}, 573 )) 574 575 // Define a different client 576 uut1, err := goutils.GetNewPubSubClientInstance( 577 coreClient, log.Fields{"instance": "unit-tester-1"}, nil, 578 ) 579 assert.Nil(err) 580 581 // Publish on topic created by first client 582 _, err = uut1.Publish(utCtxt, testTopic1, []byte(uuid.NewString()), nil, true) 583 assert.Nil(err) 584 585 // Publish on completely unknown topic 586 testTopic2 := fmt.Sprintf("goutil-ut-topic-6-%s", uuid.NewString()) 587 _, err = uut1.Publish(utCtxt, testTopic2, []byte(uuid.NewString()), nil, true) 588 assert.NotNil(err) 589 590 // Clean up 591 assert.Nil(uut0.DeleteTopic(utCtxt, testTopic1)) 592 }