|
5 | 5 | "errors" |
6 | 6 | "regexp" |
7 | 7 | "testing" |
| 8 | + "time" |
8 | 9 |
|
9 | 10 | of "github.com/open-feature/go-sdk/openfeature" |
10 | 11 | imp "github.com/open-feature/go-sdk/openfeature/memprovider" |
@@ -344,3 +345,118 @@ func TestMultiProvider_statusEvaluation(t *testing.T) { |
344 | 345 | assert.Equal(t, of.ErrorState, multiProvider.evaluateState()) |
345 | 346 | }) |
346 | 347 | } |
| 348 | + |
| 349 | +func TestMultiProvider_StateUpdateWithSameTypeProviders(t *testing.T) { |
| 350 | + ctrl := gomock.NewController(t) |
| 351 | + t.Cleanup(ctrl.Finish) |
| 352 | + |
| 353 | + // Create two mock providers with EventHandler support |
| 354 | + primaryProvider := newMockProviderWithEvents(ctrl, "MockProvider") |
| 355 | + secondaryProvider := newMockProviderWithEvents(ctrl, "MockProvider") |
| 356 | + |
| 357 | + providers := ProviderMap{ |
| 358 | + "primary": primaryProvider, |
| 359 | + "secondary": secondaryProvider, |
| 360 | + } |
| 361 | + |
| 362 | + multiProvider, err := NewProvider(providers, StrategyFirstMatch) |
| 363 | + if err != nil { |
| 364 | + t.Fatalf("failed to create multi-provider: %v", err) |
| 365 | + } |
| 366 | + t.Cleanup(multiProvider.Shutdown) |
| 367 | + |
| 368 | + // Initialize the provider |
| 369 | + ctx := of.NewEvaluationContext("test", nil) |
| 370 | + if err := multiProvider.Init(ctx); err != nil { |
| 371 | + t.Fatalf("failed to initialize multi-provider: %v", err) |
| 372 | + } |
| 373 | + |
| 374 | + primaryProvider.EmitEvent(of.ProviderError, "fail to fetch data") |
| 375 | + secondaryProvider.EmitEvent(of.ProviderReady, "rev 1") |
| 376 | + |
| 377 | + time.Sleep(200 * time.Millisecond) |
| 378 | + |
| 379 | + // Check the state after the error event |
| 380 | + multiProvider.providerStatusLock.Lock() |
| 381 | + primaryState := multiProvider.providerStatus["primary"] |
| 382 | + secondaryState := multiProvider.providerStatus["secondary"] |
| 383 | + numProviders := len(multiProvider.providerStatus) |
| 384 | + multiProvider.providerStatusLock.Unlock() |
| 385 | + |
| 386 | + if primaryState != of.ErrorState { |
| 387 | + t.Errorf("Expected primary-mock state to be ERROR after emitting error event, got %s", primaryState) |
| 388 | + } |
| 389 | + |
| 390 | + if secondaryState != of.ReadyState { |
| 391 | + t.Errorf("Expected secondary-mock state to be READY, got %s", secondaryState) |
| 392 | + } |
| 393 | + |
| 394 | + if numProviders != 2 { |
| 395 | + t.Errorf("Expected 2 providers in status map, got %d", numProviders) |
| 396 | + } |
| 397 | +} |
| 398 | + |
| 399 | +var _ of.StateHandler = (*mockProviderWithEvents)(nil) |
| 400 | + |
| 401 | +// mockProviderWithEvents wraps a mock provider to add EventHandler capability |
| 402 | +type mockProviderWithEvents struct { |
| 403 | + *of.MockFeatureProvider |
| 404 | + *of.MockStateHandler |
| 405 | + eventChannel chan of.Event |
| 406 | + metadata of.Metadata |
| 407 | +} |
| 408 | + |
| 409 | +func newMockProviderWithEvents(ctrl *gomock.Controller, name string) *mockProviderWithEvents { |
| 410 | + mockProvider := of.NewMockFeatureProvider(ctrl) |
| 411 | + mockStateHandler := of.NewMockStateHandler(ctrl) |
| 412 | + eventChan := make(chan of.Event, 10) |
| 413 | + |
| 414 | + metadata := of.Metadata{Name: name} |
| 415 | + |
| 416 | + // Set up expectations |
| 417 | + mockProvider.EXPECT().Metadata().Return(metadata).AnyTimes() |
| 418 | + mockProvider.EXPECT().Hooks().Return([]of.Hook{}).AnyTimes() |
| 419 | + mockStateHandler.EXPECT().Init(gomock.Any()).DoAndReturn(func(ctx of.EvaluationContext) error { |
| 420 | + // Emit READY event on init |
| 421 | + eventChan <- of.Event{ |
| 422 | + ProviderName: name, |
| 423 | + EventType: of.ProviderReady, |
| 424 | + ProviderEventDetails: of.ProviderEventDetails{ |
| 425 | + EventMetadata: make(map[string]any), |
| 426 | + }, |
| 427 | + } |
| 428 | + return nil |
| 429 | + }).AnyTimes() |
| 430 | + mockStateHandler.EXPECT().Shutdown() |
| 431 | + |
| 432 | + return &mockProviderWithEvents{ |
| 433 | + MockFeatureProvider: mockProvider, |
| 434 | + MockStateHandler: mockStateHandler, |
| 435 | + eventChannel: eventChan, |
| 436 | + metadata: metadata, |
| 437 | + } |
| 438 | +} |
| 439 | + |
| 440 | +func (m *mockProviderWithEvents) Init(evalCtx of.EvaluationContext) error { |
| 441 | + return m.MockStateHandler.Init(evalCtx) |
| 442 | +} |
| 443 | + |
| 444 | +func (m *mockProviderWithEvents) Shutdown() { |
| 445 | + m.MockStateHandler.Shutdown() |
| 446 | + close(m.eventChannel) |
| 447 | +} |
| 448 | + |
| 449 | +func (m *mockProviderWithEvents) EventChannel() <-chan of.Event { |
| 450 | + return m.eventChannel |
| 451 | +} |
| 452 | + |
| 453 | +func (m *mockProviderWithEvents) EmitEvent(eventType of.EventType, message string) { |
| 454 | + m.eventChannel <- of.Event{ |
| 455 | + ProviderName: m.metadata.Name, |
| 456 | + EventType: eventType, |
| 457 | + ProviderEventDetails: of.ProviderEventDetails{ |
| 458 | + Message: message, |
| 459 | + EventMetadata: make(map[string]any), |
| 460 | + }, |
| 461 | + } |
| 462 | +} |
0 commit comments