-
Couldn't load subscription status.
- Fork 92
feat: Normalize selector handling across sync, evaluation, and OFREP services #1815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
46bd44f
2861db0
69a5afa
a4eb2d1
fe63d65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,8 +10,13 @@ import ( | |
| syncv1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/flagd/sync/v1" | ||
| "github.com/open-feature/flagd/core/pkg/logger" | ||
| "github.com/open-feature/flagd/core/pkg/store" | ||
| flagdService "github.com/open-feature/flagd/flagd/pkg/service" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| "go.uber.org/zap" | ||
| "go.uber.org/zap/zapcore" | ||
| "go.uber.org/zap/zaptest/observer" | ||
| "google.golang.org/grpc/metadata" | ||
| ) | ||
|
|
||
| func TestSyncHandler_SyncFlags(t *testing.T) { | ||
|
|
@@ -128,3 +133,220 @@ func (m *mockSyncFlagsServer) GetLastResponse() *syncv1.SyncFlagsResponse { | |
| defer m.mu.Unlock() | ||
| return m.lastResp | ||
| } | ||
|
|
||
| // TestSyncHandler_SelectorFromHeader tests that the selector is correctly extracted from the header | ||
| func TestSyncHandler_SelectorFromHeader(t *testing.T) { | ||
| flagStore, err := store.NewStore(logger.NewLogger(nil, false), []string{}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Create a logger with observer to capture log messages | ||
| observedZapCore, observedLogs := observer.New(zapcore.WarnLevel) | ||
| observedLogger := zap.New(observedZapCore) | ||
| log := logger.NewLogger(observedLogger, false) | ||
|
|
||
| handler := syncHandler{ | ||
| store: flagStore, | ||
| log: log, | ||
| contextValues: map[string]any{}, | ||
| } | ||
|
|
||
| // Create context with metadata containing the selector header | ||
| md := metadata.New(map[string]string{ | ||
| flagdService.FLAGD_SELECTOR_HEADER: "source:my-source", | ||
| }) | ||
| ctx := metadata.NewIncomingContext(context.Background(), md) | ||
|
|
||
| // Test with SyncFlags | ||
| stream := &mockSyncFlagsServer{ | ||
| ctx: ctx, | ||
| mu: sync.Mutex{}, | ||
| respReady: make(chan struct{}, 1), | ||
| } | ||
|
|
||
| go func() { | ||
| // Use empty request body selector to verify header is used | ||
| err := handler.SyncFlags(&syncv1.SyncFlagsRequest{Selector: ""}, stream) | ||
| assert.NoError(t, err) | ||
| }() | ||
|
|
||
| select { | ||
| case <-stream.respReady: | ||
| // Verify no deprecation warning was logged | ||
| logs := observedLogs.All() | ||
| for _, log := range logs { | ||
| assert.NotContains(t, log.Message, "deprecated", "Should not log deprecation warning when using header") | ||
| } | ||
|
Comment on lines
+175
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic to verify that no deprecation warning was logged is duplicated across For example: func assertNoDeprecationWarning(t *testing.T, logs *observer.ObservedLogs) {
t.Helper()
for _, entry := range logs.All() {
assert.NotContains(t, entry.Message, "deprecated", "Should not log deprecation warning")
}
}You could then call |
||
| case <-time.After(time.Second): | ||
| t.Fatal("timeout waiting for response") | ||
| } | ||
| } | ||
|
|
||
| // TestSyncHandler_SelectorFromRequestBody tests backward compatibility with request body selector | ||
| func TestSyncHandler_SelectorFromRequestBody(t *testing.T) { | ||
| flagStore, err := store.NewStore(logger.NewLogger(nil, false), []string{}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Create a logger with observer to capture log messages | ||
| observedZapCore, observedLogs := observer.New(zapcore.WarnLevel) | ||
| observedLogger := zap.New(observedZapCore) | ||
| log := logger.NewLogger(observedLogger, false) | ||
|
|
||
| handler := syncHandler{ | ||
| store: flagStore, | ||
| log: log, | ||
| contextValues: map[string]any{}, | ||
| } | ||
|
|
||
| // Create context without metadata (no header) | ||
| ctx := context.Background() | ||
|
|
||
| // Test with SyncFlags | ||
| stream := &mockSyncFlagsServer{ | ||
| ctx: ctx, | ||
| mu: sync.Mutex{}, | ||
| respReady: make(chan struct{}, 1), | ||
| } | ||
|
|
||
| go func() { | ||
| // Use request body selector | ||
| err := handler.SyncFlags(&syncv1.SyncFlagsRequest{Selector: "source:legacy-source"}, stream) | ||
| assert.NoError(t, err) | ||
| }() | ||
|
|
||
| select { | ||
| case <-stream.respReady: | ||
| // Verify deprecation warning was logged | ||
| logs := observedLogs.All() | ||
| require.Greater(t, len(logs), 0, "Expected at least one log entry") | ||
| found := false | ||
| for _, log := range logs { | ||
| if log.Level == zapcore.WarnLevel { | ||
| assert.Contains(t, log.Message, "deprecated", "Should log deprecation warning when using request body selector") | ||
| assert.Contains(t, log.Message, "Flagd-Selector", "Deprecation message should mention the header name") | ||
| found = true | ||
| break | ||
| } | ||
| } | ||
| assert.True(t, found, "Expected to find deprecation warning in logs") | ||
|
Comment on lines
+219
to
+230
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic to verify the deprecation warning is duplicated in For example: func assertDeprecationWarning(t *testing.T, logs *observer.ObservedLogs) {
t.Helper()
entries := logs.All()
require.NotEmpty(t, entries, "Expected at least one log entry")
found := false
for _, entry := range entries {
if entry.Level == zapcore.WarnLevel {
assert.Contains(t, entry.Message, "deprecated")
assert.Contains(t, entry.Message, "Flagd-Selector")
found = true
break
}
}
assert.True(t, found, "Expected to find deprecation warning in logs")
}You could then call |
||
| case <-time.After(time.Second): | ||
| t.Fatal("timeout waiting for response") | ||
| } | ||
| } | ||
|
|
||
| // TestSyncHandler_SelectorHeaderTakesPrecedence tests that header takes precedence over request body | ||
| func TestSyncHandler_SelectorHeaderTakesPrecedence(t *testing.T) { | ||
| flagStore, err := store.NewStore(logger.NewLogger(nil, false), []string{}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Create a logger with observer to capture log messages | ||
| observedZapCore, observedLogs := observer.New(zapcore.WarnLevel) | ||
| observedLogger := zap.New(observedZapCore) | ||
| log := logger.NewLogger(observedLogger, false) | ||
|
|
||
| handler := syncHandler{ | ||
| store: flagStore, | ||
| log: log, | ||
| contextValues: map[string]any{}, | ||
| } | ||
|
|
||
| // Create context with metadata containing the selector header | ||
| md := metadata.New(map[string]string{ | ||
| flagdService.FLAGD_SELECTOR_HEADER: "source:header-source", | ||
| }) | ||
| ctx := metadata.NewIncomingContext(context.Background(), md) | ||
|
|
||
| // Test with SyncFlags | ||
| stream := &mockSyncFlagsServer{ | ||
| ctx: ctx, | ||
| mu: sync.Mutex{}, | ||
| respReady: make(chan struct{}, 1), | ||
| } | ||
|
|
||
| go func() { | ||
| // Provide both header and request body selector | ||
| err := handler.SyncFlags(&syncv1.SyncFlagsRequest{Selector: "source:body-source"}, stream) | ||
| assert.NoError(t, err) | ||
| }() | ||
|
|
||
| select { | ||
| case <-stream.respReady: | ||
| // Verify no deprecation warning was logged (header was used) | ||
| logs := observedLogs.All() | ||
| for _, log := range logs { | ||
| assert.NotContains(t, log.Message, "deprecated", "Should not log deprecation warning when header is present") | ||
| } | ||
| case <-time.After(time.Second): | ||
| t.Fatal("timeout waiting for response") | ||
| } | ||
| } | ||
|
|
||
| // TestSyncHandler_FetchAllFlags_SelectorFromHeader tests FetchAllFlags with header selector | ||
| func TestSyncHandler_FetchAllFlags_SelectorFromHeader(t *testing.T) { | ||
| flagStore, err := store.NewStore(logger.NewLogger(nil, false), []string{}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Create a logger with observer to capture log messages | ||
| observedZapCore, observedLogs := observer.New(zapcore.WarnLevel) | ||
| observedLogger := zap.New(observedZapCore) | ||
| log := logger.NewLogger(observedLogger, false) | ||
|
|
||
| handler := syncHandler{ | ||
| store: flagStore, | ||
| log: log, | ||
| contextValues: map[string]any{}, | ||
| } | ||
|
|
||
| // Create context with metadata containing the selector header | ||
| md := metadata.New(map[string]string{ | ||
| flagdService.FLAGD_SELECTOR_HEADER: "source:my-source", | ||
| }) | ||
| ctx := metadata.NewIncomingContext(context.Background(), md) | ||
|
|
||
| // Call FetchAllFlags with empty request body selector | ||
| _, err = handler.FetchAllFlags(ctx, &syncv1.FetchAllFlagsRequest{Selector: ""}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Verify no deprecation warning was logged | ||
| logs := observedLogs.All() | ||
| for _, log := range logs { | ||
| assert.NotContains(t, log.Message, "deprecated", "Should not log deprecation warning when using header") | ||
| } | ||
| } | ||
|
|
||
| // TestSyncHandler_FetchAllFlags_SelectorFromRequestBody tests FetchAllFlags with request body selector | ||
| func TestSyncHandler_FetchAllFlags_SelectorFromRequestBody(t *testing.T) { | ||
| flagStore, err := store.NewStore(logger.NewLogger(nil, false), []string{}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Create a logger with observer to capture log messages | ||
| observedZapCore, observedLogs := observer.New(zapcore.WarnLevel) | ||
| observedLogger := zap.New(observedZapCore) | ||
| log := logger.NewLogger(observedLogger, false) | ||
|
|
||
| handler := syncHandler{ | ||
| store: flagStore, | ||
| log: log, | ||
| contextValues: map[string]any{}, | ||
| } | ||
|
|
||
| // Create context without metadata (no header) | ||
| ctx := context.Background() | ||
|
|
||
| // Call FetchAllFlags with request body selector | ||
| _, err = handler.FetchAllFlags(ctx, &syncv1.FetchAllFlagsRequest{Selector: "source:legacy-source"}) | ||
| require.NoError(t, err) | ||
|
|
||
| // Verify deprecation warning was logged | ||
| logs := observedLogs.All() | ||
| require.Greater(t, len(logs), 0, "Expected at least one log entry") | ||
| found := false | ||
| for _, log := range logs { | ||
| if log.Level == zapcore.WarnLevel { | ||
| assert.Contains(t, log.Message, "deprecated", "Should log deprecation warning when using request body selector") | ||
| assert.Contains(t, log.Message, "Flagd-Selector", "Deprecation message should mention the header name") | ||
| found = true | ||
| break | ||
| } | ||
| } | ||
| assert.True(t, found, "Expected to find deprecation warning in logs") | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.