github.com/vmware/transport-go@v1.3.4/bus/store_test.go (about) 1 // Copyright 2019-2020 VMware, Inc. 2 // SPDX-License-Identifier: BSD-2-Clause 3 4 package bus 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "github.com/go-stomp/stomp/v3/frame" 10 "github.com/stretchr/testify/assert" 11 "reflect" 12 "sync" 13 "sync/atomic" 14 "testing" 15 ) 16 17 type testItem struct { 18 name string 19 nameIndex int32 20 } 21 22 func testStore() BusStore { 23 store := newBusStore("testStore", newTestEventBus(), nil, nil) 24 store.Initialize() 25 return store 26 } 27 28 type mockGalacticStoreConnection struct { 29 messages []map[string]interface{} 30 topics []string 31 opts []func(fr *frame.Frame) error 32 } 33 34 func (con *mockGalacticStoreConnection) SendJSONMessage(destination string, payload []byte, opts ...func(*frame.Frame) error) error { 35 return con.SendMessage(destination, "application/json", payload, opts...) 36 } 37 38 func (con *mockGalacticStoreConnection) SendMessage(destination, contentType string, payload []byte, opts ...func(*frame.Frame) error) error { 39 var msgPayload map[string]interface{} 40 json.Unmarshal(payload, &msgPayload) 41 con.messages = append(con.messages, msgPayload) 42 con.topics = append(con.topics, destination) 43 con.opts = opts 44 return nil 45 } 46 47 func (con *mockGalacticStoreConnection) lastMessage() map[string]interface{} { 48 n := len(con.messages) 49 return con.messages[n-1] 50 } 51 52 func (con *mockGalacticStoreConnection) lastTopic() string { 53 n := len(con.topics) 54 return con.topics[n-1] 55 } 56 57 func testGalacticStore(itemType reflect.Type) (BusStore, *mockGalacticStoreConnection, EventBus) { 58 59 bus := newTestEventBus() 60 bus.GetChannelManager().CreateChannel("sync-channel") 61 62 conn := &mockGalacticStoreConnection{ 63 messages: make([]map[string]interface{}, 0), 64 topics: make([]string, 0), 65 opts: make([]func(*frame.Frame) error, 0), 66 } 67 68 conf := &galacticStoreConfig{ 69 syncChannelConfig: &storeSyncChannelConfig{ 70 syncChannelName: "sync-channel", 71 conn: conn, 72 pubPrefix: "/pub/", 73 }, 74 } 75 76 store := newBusStore("testStore", bus, itemType, conf) 77 return store, conn, bus 78 } 79 80 func TestBusStore_CreateStore(t *testing.T) { 81 store := testStore() 82 assert.Equal(t, store.GetName(), "testStore") 83 assert.False(t, store.IsGalactic()) 84 } 85 86 func TestBusStore_PutAndGet(t *testing.T) { 87 store := testStore() 88 store.Put("id1", 1, "ITEM_ADDED") 89 store.Put("id2", "value2", "ITEM_ADDED") 90 store.Put("id3", nil, "ITEM_ADDED") 91 92 v, ok := store.Get("id1") 93 assert.Equal(t, v, 1) 94 assert.True(t, ok) 95 96 v, _ = store.Get("id2") 97 assert.Equal(t, v, "value2") 98 99 v, ok = store.Get("id3") 100 assert.Equal(t, v, nil) 101 assert.True(t, ok) 102 103 v, ok = store.Get("invalid-id") 104 assert.False(t, ok) 105 assert.Nil(t, v) 106 } 107 108 func TestBusStore_Remove(t *testing.T) { 109 store := testStore() 110 store.Put("id1", "item1", "ITEM_ADDED") 111 112 var mutationEventsCounter int32 = 0 113 var successfulRemovesCounter int32 = 0 114 115 wg := sync.WaitGroup{} 116 wg.Add(1) 117 118 stream := store.OnAllChanges("ITEM_REMOVED") 119 stream.Subscribe(func(change *StoreChange) { 120 atomic.AddInt32(&mutationEventsCounter, 1) 121 wg.Done() 122 }) 123 124 for i := 0; i < 50; i++ { 125 wg.Add(1) 126 go func() { 127 if store.Remove("id1", "ITEM_REMOVED") { 128 atomic.AddInt32(&successfulRemovesCounter, 1) 129 } 130 wg.Done() 131 }() 132 } 133 134 wg.Wait() 135 136 assert.False(t, store.Remove("invalid-id", "ITEM_REMOVED")) 137 138 // Verify that only one of the Remove calls was successful (has returned true) 139 assert.Equal(t, successfulRemovesCounter, int32(1)) 140 assert.Equal(t, mutationEventsCounter, int32(1)) 141 } 142 143 func TestBusStore_AllValuesAndAllValuesAsMap(t *testing.T) { 144 store := testStore() 145 146 items := store.AllValues() 147 allItemsAsMap := store.AllValuesAsMap() 148 assert.Equal(t, len(items), 0) 149 assert.Equal(t, len(allItemsAsMap), 0) 150 151 store.Put("id1", testItem{name: "item1", nameIndex: 1}, "ITEM_ADDED") 152 store.Put("id2", testItem{name: "item2", nameIndex: 2}, "ITEM_ADDED") 153 store.Put("id3", testItem{name: "item3", nameIndex: 3}, "ITEM_ADDED") 154 155 items = store.AllValues() 156 allItemsAsMap = store.AllValuesAsMap() 157 158 assert.Equal(t, len(items), 3) 159 for _, item := range items { 160 assert.Equal(t, fmt.Sprintf("item%d", item.(testItem).nameIndex), item.(testItem).name) 161 } 162 163 assert.Equal(t, len(allItemsAsMap), 3) 164 assert.Equal(t, allItemsAsMap["id1"], testItem{name: "item1", nameIndex: 1}) 165 assert.Equal(t, allItemsAsMap["id2"], testItem{name: "item2", nameIndex: 2}) 166 assert.Equal(t, allItemsAsMap["id3"], testItem{name: "item3", nameIndex: 3}) 167 168 allItemsAsMapWithVer, version := store.AllValuesAndVersion() 169 assert.Equal(t, allItemsAsMap, allItemsAsMapWithVer) 170 assert.Equal(t, version, int64(4)) 171 } 172 173 func TestBusStore_OnChange(t *testing.T) { 174 store := testStore() 175 176 wg := sync.WaitGroup{} 177 178 allChangesStreams := make([]StoreStream, 0) 179 180 var allChangesCounter int32 = 0 181 for i := 0; i < 5; i++ { 182 stream := store.OnChange("id1") 183 allChangesStreams = append(allChangesStreams, stream) 184 stream.Subscribe(func(change *StoreChange) { 185 atomic.AddInt32(&allChangesCounter, 1) 186 wg.Done() 187 }) 188 } 189 190 var itemUpdateCounter int32 = 0 191 for i := 0; i < 5; i++ { 192 store.OnChange("id1", "ITEM_REMOVE", "ITEM_UPDATE").Subscribe( 193 func(change *StoreChange) { 194 atomic.AddInt32(&itemUpdateCounter, 1) 195 wg.Done() 196 }) 197 } 198 199 for i := 0; i < 200; i++ { 200 if i%2 == 0 { 201 wg.Add(10) 202 go func() { 203 store.Put("id1", "newValue", "ITEM_UPDATE") 204 }() 205 } else { 206 wg.Add(5) 207 go func() { 208 store.Put("id1", "newValue", "ITEM_ADD") 209 }() 210 } 211 } 212 213 wg.Wait() 214 215 assert.Equal(t, allChangesCounter, int32(1000)) 216 assert.Equal(t, itemUpdateCounter, int32(500)) 217 218 // Unsubscribe all changes listeners 219 for _, stream := range allChangesStreams { 220 stream.Unsubscribe() 221 } 222 223 wg.Add(5) 224 store.Put("id1", "newValue", "ITEM_REMOVE") 225 wg.Wait() 226 227 assert.Equal(t, allChangesCounter, int32(1000)) 228 assert.Equal(t, itemUpdateCounter, int32(505)) 229 } 230 231 func TestBusStore_OnChangeErrorHandling(t *testing.T) { 232 store := testStore() 233 stream := store.OnChange("id1") 234 e := stream.Unsubscribe() 235 assert.EqualError(t, e, "stream not subscribed") 236 237 subscribeErr := stream.Subscribe(func(change *StoreChange) { 238 }) 239 240 assert.Nil(t, subscribeErr) 241 subscribeErr = stream.Subscribe(func(change *StoreChange) { 242 }) 243 assert.EqualError(t, subscribeErr, "stream already subscribed") 244 245 e = stream.Subscribe(nil) 246 assert.EqualError(t, e, "invalid StoreChangeHandlerFunction") 247 } 248 249 func TestBusStore_OnAllChanges(t *testing.T) { 250 store := testStore() 251 252 wg := sync.WaitGroup{} 253 254 var allChangesCounter int32 = 0 255 allChangesStream := store.OnAllChanges() 256 allChangesStream.Subscribe(func(change *StoreChange) { 257 atomic.AddInt32(&allChangesCounter, 1) 258 wg.Done() 259 }) 260 261 itemUpdatedStream := store.OnAllChanges("ITEM_UPDATED", "ITEM_REMOVED") 262 var itemUpdateCounter int32 = 0 263 itemUpdatedStream.Subscribe( 264 func(change *StoreChange) { 265 atomic.AddInt32(&itemUpdateCounter, 1) 266 wg.Done() 267 }) 268 269 for i := 0; i < 200; i++ { 270 if i%2 == 0 { 271 wg.Add(2) 272 go func() { 273 store.Put("id1", "newValue", "ITEM_UPDATED") 274 }() 275 } else { 276 wg.Add(1) 277 go func() { 278 store.Put("id1", "newValue", "ITEM_ADD") 279 }() 280 } 281 } 282 283 wg.Wait() 284 285 assert.Equal(t, allChangesCounter, int32(200)) 286 assert.Equal(t, itemUpdateCounter, int32(100)) 287 288 allChangesStream.Unsubscribe() 289 290 wg.Add(1) 291 store.Put("id1", "newValue", "ITEM_REMOVED") 292 wg.Wait() 293 294 assert.Equal(t, allChangesCounter, int32(200)) 295 assert.Equal(t, itemUpdateCounter, int32(101)) 296 } 297 298 func TestBusStore_WhenReady(t *testing.T) { 299 store := newBusStore("testStore", newTestEventBus(), nil, nil) 300 301 wg := sync.WaitGroup{} 302 var counter int32 = 0 303 for i := 0; i < 100; i++ { 304 wg.Add(1) 305 store.WhenReady(func() { 306 atomic.AddInt32(&counter, 1) 307 wg.Done() 308 }) 309 } 310 311 store.Initialize() 312 313 wg.Wait() 314 assert.Equal(t, counter, int32(100)) 315 316 for i := 0; i < 100; i++ { 317 wg.Add(1) 318 store.WhenReady(func() { 319 atomic.AddInt32(&counter, 1) 320 wg.Done() 321 }) 322 } 323 324 wg.Wait() 325 assert.Equal(t, counter, int32(200)) 326 } 327 328 func TestBusStore_Populate(t *testing.T) { 329 store := newBusStore("testStore", newTestEventBus(), nil, nil) 330 331 wg := sync.WaitGroup{} 332 counter := 0 333 334 wg.Add(1) 335 store.WhenReady(func() { 336 counter++ 337 wg.Done() 338 }) 339 340 err := store.Populate(map[string]interface{}{ 341 "id1": 1, 342 "id2": 2, 343 "id3": 3, 344 "id4": 4, 345 }) 346 347 assert.Nil(t, err) 348 349 wg.Wait() 350 351 assert.Equal(t, counter, 1) 352 353 allValues := store.AllValuesAsMap() 354 assert.Equal(t, len(allValues), 4) 355 assert.Equal(t, allValues["id1"], 1) 356 assert.Equal(t, allValues["id2"], 2) 357 assert.Equal(t, allValues["id3"], 3) 358 assert.Equal(t, allValues["id4"], 4) 359 360 err = store.Populate(map[string]interface{}{ 361 "id1": 1, 362 }) 363 364 assert.EqualError(t, err, "store items already initialized") 365 assert.Equal(t, len(store.AllValues()), 4) 366 } 367 368 func TestBusStore_Reset(t *testing.T) { 369 store := newBusStore("testStore", newTestEventBus(), nil, nil) 370 wg := sync.WaitGroup{} 371 counter := 0 372 373 wg.Add(1) 374 store.WhenReady(func() { 375 counter++ 376 wg.Done() 377 }) 378 379 store.Populate(map[string]interface{}{ 380 "id1": 1, 381 "id2": 2, 382 "id3": 3, 383 }) 384 wg.Wait() 385 386 store.Reset() 387 388 assert.Equal(t, len(store.AllValues()), 0) 389 390 wg.Add(1) 391 store.WhenReady(func() { 392 counter++ 393 wg.Done() 394 }) 395 396 store.Initialize() 397 wg.Wait() 398 assert.Equal(t, counter, 2) 399 } 400 401 func TestBusStore_OnMutationRequest(t *testing.T) { 402 store := testStore() 403 404 var allMutationsCounter int32 = 0 405 var responseCount int32 = 0 406 var errorCount int32 = 0 407 408 allMutationsStream := store.OnMutationRequest() 409 allMutationsStream.Subscribe(func(mutationReq *MutationRequest) { 410 atomic.AddInt32(&allMutationsCounter, 1) 411 mutationReq.SuccessHandler(mutationReq.Request.(string) + "-response") 412 }) 413 414 var updateMutationsCounter int32 = 0 415 updateMutationStream := store.OnMutationRequest("UPDATE_ITEM", "REMOVE_ITEM") 416 updateMutationStream.Subscribe(func(mutationReq *MutationRequest) { 417 atomic.AddInt32(&updateMutationsCounter, 1) 418 mutationReq.ErrorHandler(mutationReq.Request.(string) + "-error") 419 }) 420 421 wg := sync.WaitGroup{} 422 423 for i := 0; i < 100; i++ { 424 req := fmt.Sprintf("req%d", i) 425 wg.Add(2) 426 go func() { 427 store.Mutate(req, "UPDATE_ITEM", 428 func(result interface{}) { 429 assert.Equal(t, result, req+"-response") 430 atomic.AddInt32(&responseCount, 1) 431 wg.Done() 432 }, 433 func(err interface{}) { 434 assert.Equal(t, err, req+"-error") 435 atomic.AddInt32(&errorCount, 1) 436 wg.Done() 437 }) 438 }() 439 440 wg.Add(1) 441 go func() { 442 store.Mutate(req, "MODIFY_ITEM", 443 func(result interface{}) { 444 assert.Equal(t, result, req+"-response") 445 atomic.AddInt32(&responseCount, 1) 446 wg.Done() 447 }, 448 nil) 449 }() 450 } 451 452 wg.Wait() 453 assert.Equal(t, allMutationsCounter, int32(200)) 454 assert.Equal(t, responseCount, int32(200)) 455 assert.Equal(t, updateMutationsCounter, int32(100)) 456 assert.Equal(t, errorCount, int32(100)) 457 458 allMutationsStream.Unsubscribe() 459 460 wg.Add(1) 461 store.Mutate("req", "UPDATE_ITEM", 462 nil, 463 func(err interface{}) { 464 assert.Equal(t, err, "req-error") 465 atomic.AddInt32(&errorCount, 1) 466 wg.Done() 467 }) 468 wg.Wait() 469 assert.Equal(t, allMutationsCounter, int32(200)) 470 assert.Equal(t, responseCount, int32(200)) 471 assert.Equal(t, updateMutationsCounter, int32(101)) 472 assert.Equal(t, errorCount, int32(101)) 473 } 474 475 func TestBusStore_OnMutationRequest_ErrorHandling(t *testing.T) { 476 store := testStore() 477 478 ms := store.OnMutationRequest() 479 err := ms.Unsubscribe() 480 481 assert.EqualError(t, err, "stream not subscribed") 482 483 err = ms.Subscribe(nil) 484 assert.EqualError(t, err, "invalid MutationRequestHandlerFunction") 485 486 ms.Subscribe(func(mutationReq *MutationRequest) {}) 487 488 err = ms.Subscribe(func(mutationReq *MutationRequest) {}) 489 assert.EqualError(t, err, "stream already subscribed") 490 } 491 492 func TestBusStore_InitGalacticStore(t *testing.T) { 493 store, conn, bus := testGalacticStore(nil) 494 495 assert.True(t, store.IsGalactic()) 496 assert.EqualError(t, store.Populate(nil), "populate() API is not supported for galactic stores") 497 498 assert.Equal(t, len(conn.messages), 1) 499 assert.Equal(t, conn.lastTopic(), "/pub/sync-channel") 500 assert.Equal(t, conn.lastMessage()["request"], "openStore") 501 502 rq := conn.lastMessage()["payload"].(map[string]interface{}) 503 assert.Equal(t, rq["storeId"], "testStore") 504 505 wg := sync.WaitGroup{} 506 wg.Add(1) 507 508 store.WhenReady(func() { 509 wg.Done() 510 }) 511 512 var jsonBlob = []byte(`{ 513 "storeId": "testStore", 514 "responseType": "storeContentResponse", 515 "items": { 516 "id3": "value3" 517 }, 518 "storeVersion": 12 519 }`) 520 bus.SendResponseMessage("sync-channel", jsonBlob, nil) 521 522 wg.Wait() 523 assert.Equal(t, len(store.AllValues()), 1) 524 525 store.Put("id1", "value1", "add") 526 assert.Equal(t, len(conn.messages), 2) 527 528 assert.Equal(t, conn.lastTopic(), "/pub/sync-channel") 529 assert.Equal(t, conn.lastMessage()["request"], "updateStore") 530 rq = conn.lastMessage()["payload"].(map[string]interface{}) 531 assert.Equal(t, rq["storeId"], "testStore") 532 assert.Equal(t, rq["itemId"], "id1") 533 assert.Equal(t, rq["newItemValue"], "value1") 534 assert.Equal(t, rq["clientStoreVersion"], float64(12)) 535 536 assert.False(t, store.Remove("id1", "removing")) 537 assert.Equal(t, len(conn.messages), 2) 538 539 assert.True(t, store.Remove("id3", "removing")) 540 assert.Equal(t, len(conn.messages), 3) 541 542 assert.Equal(t, conn.lastTopic(), "/pub/sync-channel") 543 assert.Equal(t, conn.lastMessage()["request"], "updateStore") 544 rq = conn.lastMessage()["payload"].(map[string]interface{}) 545 assert.Equal(t, rq["storeId"], "testStore") 546 assert.Equal(t, rq["itemId"], "id3") 547 assert.Equal(t, rq["newItemValue"], nil) 548 assert.Equal(t, rq["clientStoreVersion"], float64(12)) 549 550 store.Reset() 551 assert.Equal(t, len(conn.messages), 4) 552 assert.Equal(t, conn.lastTopic(), "/pub/sync-channel") 553 assert.Equal(t, conn.lastMessage()["request"], "openStore") 554 555 store.(*busStore).OnDestroy() 556 assert.Equal(t, len(conn.messages), 5) 557 assert.Equal(t, conn.lastTopic(), "/pub/sync-channel") 558 assert.Equal(t, conn.lastMessage()["request"], "closeStore") 559 } 560 561 func TestBusStore_GalacticStoreUpdates(t *testing.T) { 562 store, _, bus := testGalacticStore(reflect.TypeOf(MockStoreItem{})) 563 564 wg := sync.WaitGroup{} 565 wg.Add(1) 566 567 var lastStoreChange *StoreChange 568 569 store.OnAllChanges().Subscribe(func(change *StoreChange) { 570 lastStoreChange = change 571 wg.Done() 572 }) 573 574 var jsonBlob = []byte(`{ 575 "storeId": "testStore", 576 "responseType": "updateStoreResponse", 577 "itemId": "id1", 578 "newItemValue": { "from": "admin", "message": "value1"}, 579 "storeVersion": 54 580 }`) 581 bus.SendResponseMessage("sync-channel", jsonBlob, nil) 582 583 bus.SendResponseMessage("sync-channel", []byte("invalid-json}"), nil) 584 585 bus.SendResponseMessage("sync-channel", []byte(`{ 586 "storeId": "testStore2", 587 "responseType": "updateStoreResponse", 588 "itemId": "id1", 589 "newItemValue": "value4", 590 "storeVersion": 55 591 }`), nil) 592 593 wg.Wait() 594 595 assert.Equal(t, store.(*busStore).storeVersion, int64(54)) 596 assert.NotNil(t, lastStoreChange) 597 598 assert.Equal(t, lastStoreChange.Id, "id1") 599 assert.Equal(t, lastStoreChange.Value, MockStoreItem{From: "admin", Message: "value1"}) 600 assert.Equal(t, store.GetValue("id1"), MockStoreItem{From: "admin", Message: "value1"}) 601 602 wg.Add(1) 603 jsonBlob = []byte(`{ 604 "storeId": "testStore", 605 "responseType": "updateStoreResponse", 606 "itemId": "id1", 607 "storeVersion": 55 608 }`) 609 bus.SendResponseMessage("sync-channel", jsonBlob, nil) 610 611 bus.SendResponseMessage("sync-channel", []byte(`{ 612 "storeId": "testStore", 613 "responseType": "updateStoreResponse", 614 "itemId": "id1", 615 "newItemValue": "invalid-obj", 616 "storeVersion": 55 617 }`), nil) 618 619 wg.Wait() 620 621 assert.Equal(t, lastStoreChange.Id, "id1") 622 assert.Equal(t, lastStoreChange.Value, MockStoreItem{From: "admin", Message: "value1"}) 623 assert.Equal(t, lastStoreChange.IsDeleteChange, true) 624 assert.Nil(t, store.GetValue("id1")) 625 } 626 627 func TestBusStore_GalacticStoreContent(t *testing.T) { 628 store, _, bus := testGalacticStore(reflect.TypeOf(MockStoreItem{})) 629 630 wg := sync.WaitGroup{} 631 wg.Add(1) 632 633 store.WhenReady(func() { 634 wg.Done() 635 }) 636 637 var jsonBlob = []byte(`{ 638 "storeId": "testStore", 639 "responseType": "storeContentResponse", 640 "items": { 641 "id1": { "from": "admin", "message": "value1"}, 642 "id2": { "from": "admin", "message": "value2"}, 643 "id3": "invalid-obj" 644 }, 645 "storeVersion": 12 646 }`) 647 bus.SendResponseMessage("sync-channel", jsonBlob, nil) 648 649 wg.Wait() 650 651 allValues, version := store.AllValuesAndVersion() 652 assert.Equal(t, version, int64(12)) 653 assert.Equal(t, len(allValues), 2) 654 assert.Equal(t, allValues["id1"], MockStoreItem{From: "admin", Message: "value1"}) 655 assert.Equal(t, allValues["id2"], MockStoreItem{From: "admin", Message: "value2"}) 656 }