Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions pkg/openfeature/event_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,23 +163,35 @@ func (e *eventExecutor) removeClientHandler(name string, t EventType, c EventCal
e.scopedRegistry[name].callbacks[t] = entrySlice
}

// emitOnRegistration fulfils the spec requirement to fire ready events if associated provider is ready
func (e *eventExecutor) emitOnRegistration(providerReference providerReference, t EventType, c EventCallback) {
if t != ProviderReady {
return
}

// emitOnRegistration fulfils the spec requirement to fire events if the
// event type and the state of the associated provider are compatible.
func (e *eventExecutor) emitOnRegistration(
providerReference providerReference,
eventType EventType,
callback EventCallback,
) {
s, ok := (providerReference.featureProvider).(StateHandler)
if !ok {
// not a state handler, hence ignore state emitting
return
}

if s.Status() == ReadyState {
(*c)(EventDetails{
providerName: (providerReference.featureProvider).Metadata().Name,
state := s.Status()

var message string
if state == ReadyState && eventType == ProviderReady {
message = "provider is in ready state"
} else if state == ErrorState && eventType == ProviderError {
message = "provider is in error state"
} else if state == StaleState && eventType == ProviderStale {
message = "provider is in stale state"
}

if message != "" {
(*callback)(EventDetails{
providerName: providerReference.featureProvider.Metadata().Name,
ProviderEventDetails: ProviderEventDetails{
Message: "provider is at ready state",
Message: message,
},
})
}
Expand Down
202 changes: 202 additions & 0 deletions pkg/openfeature/event_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,208 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
})
}

// Requirement 5.3.3, Spec version 0.7.0: Handlers attached after the
// provider is already in the associated state, MUST run immediately
func TestEventHandler_HandlersRunImmediately(t *testing.T) {
t.Run("ready handler runs when provider ready", func(t *testing.T) {
defer t.Cleanup(initSingleton)

provider := struct {
FeatureProvider
StateHandler
}{
NoopProvider{},
&stateHandlerForTests{
State: ReadyState,
},
}

if err := SetProvider(provider); err != nil {
t.Fatal(err)
}

rsp := make(chan EventDetails, 1)
callback := func(e EventDetails) {
rsp <- e
}

AddHandler(ProviderReady, &callback)

select {
case <-rsp:
break
case <-time.After(200 * time.Millisecond):
t.Errorf("timed out waiting for callback")
}
})

t.Run("error handler runs when provider error", func(t *testing.T) {
defer t.Cleanup(initSingleton)

provider := struct {
FeatureProvider
StateHandler
}{
NoopProvider{},
&stateHandlerForTests{
State: ErrorState,
},
}

if err := SetProvider(provider); err != nil {
t.Fatal(err)
}

rsp := make(chan EventDetails, 1)
callback := func(e EventDetails) {
rsp <- e
}

AddHandler(ProviderError, &callback)

select {
case <-rsp:
break
case <-time.After(200 * time.Millisecond):
t.Errorf("timed out waiting for callback")
}
})

t.Run("stale handler runs when provider stale", func(t *testing.T) {
defer t.Cleanup(initSingleton)

provider := struct {
FeatureProvider
StateHandler
}{
NoopProvider{},
&stateHandlerForTests{
State: StaleState,
},
}

if err := SetProvider(provider); err != nil {
t.Fatal(err)
}

rsp := make(chan EventDetails, 1)
callback := func(e EventDetails) {
rsp <- e
}

AddHandler(ProviderStale, &callback)

select {
case <-rsp:
break
case <-time.After(200 * time.Millisecond):
t.Errorf("timed out waiting for callback")
}
})

t.Run("non-ready handler does not run when provider ready", func(t *testing.T) {
defer t.Cleanup(initSingleton)

provider := struct {
FeatureProvider
StateHandler
}{
NoopProvider{},
&stateHandlerForTests{
State: ReadyState,
},
}

if err := SetProvider(provider); err != nil {
t.Fatal(err)
}

rsp := make(chan EventDetails, 3)
callback := func(e EventDetails) {
rsp <- e
}

AddHandler(ProviderError, &callback)
AddHandler(ProviderStale, &callback)
AddHandler(ProviderConfigChange, &callback)

select {
case <-rsp:
t.Errorf("event must not emit for this handler")
case <-time.After(200 * time.Millisecond):
break
}
})

t.Run("non-error handler does not run when provider error", func(t *testing.T) {
defer t.Cleanup(initSingleton)

provider := struct {
FeatureProvider
StateHandler
}{
NoopProvider{},
&stateHandlerForTests{
State: ErrorState,
},
}

if err := SetProvider(provider); err != nil {
t.Fatal(err)
}

rsp := make(chan EventDetails, 3)
callback := func(e EventDetails) {
rsp <- e
}

AddHandler(ProviderReady, &callback)
AddHandler(ProviderStale, &callback)
AddHandler(ProviderConfigChange, &callback)

select {
case <-rsp:
t.Errorf("event must not emit for this handler")
case <-time.After(200 * time.Millisecond):
break
}
})

t.Run("non-stale handler does not run when provider stale", func(t *testing.T) {
defer t.Cleanup(initSingleton)

provider := struct {
FeatureProvider
StateHandler
}{
NoopProvider{},
&stateHandlerForTests{
State: StaleState,
},
}

if err := SetProvider(provider); err != nil {
t.Fatal(err)
}

rsp := make(chan EventDetails, 3)
callback := func(e EventDetails) {
rsp <- e
}

AddHandler(ProviderReady, &callback)
AddHandler(ProviderError, &callback)
AddHandler(ProviderConfigChange, &callback)

select {
case <-rsp:
t.Errorf("event must not emit for this handler")
case <-time.After(200 * time.Millisecond):
break
}
})
}

// non-spec bound validations

func TestEventHandler_multiSubs(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pkg/openfeature/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
NotReadyState State = "NOT_READY"
ReadyState State = "READY"
ErrorState State = "ERROR"
StaleState State = "STALE"

ProviderReady EventType = "PROVIDER_READY"
ProviderConfigChange EventType = "PROVIDER_CONFIGURATION_CHANGED"
Expand Down