feat: Add test harness support for flag change listeners in Client SDKs#320
Draft
aaron-zeisler wants to merge 10 commits intofeat/fdv2from
Draft
feat: Add test harness support for flag change listeners in Client SDKs#320aaron-zeisler wants to merge 10 commits intofeat/fdv2from
aaron-zeisler wants to merge 10 commits intofeat/fdv2from
Conversation
I performed some research to understand the problem better, and to come up with some ideas for how to implement these types of tests. This is the research that I performed: * What are flag change listeners? * How are they implemented in some of our SDKs? * How are other features tested using the SDK test harness? * What would it take to support tests for change listeners?
Added the "flag-change-listeners" capability constant to service_params.go and documented it in service_spec.md. This capability indicates that an SDK supports registering listeners for flag changes and can notify the test harness via callbacks. The capability supports testing both general flag change listeners (notified on any flag configuration change) and flag value change listeners (notified when a specific flag's evaluated value changes for a given context).
Define the command constants and parameter structs needed for the test harness to instruct SDK test services to register and unregister flag change listeners at runtime. Three new commands are added to servicedef/command_params.go: - registerFlagChangeListener: registers a general flag change listener that notifies when any flag's configuration changes. Params include a listener ID, an optional flag key to filter on, and a callback URI where the test service will POST notifications. - registerFlagValueChangeListener: registers a value-specific listener that notifies when a flag's evaluated value changes for a given context. Params include a listener ID, flag key, evaluation context, default value, and callback URI. - unregisterListener: removes a previously registered listener by ID. Unlike existing commands (evaluate, migrate, etc.), these commands are stateful — the test service must maintain a map of active listeners between commands. This is the first example of dynamic, post-init registration in the test service protocol. Co-authored-by: Cursor <cursoragent@cursor.com>
Define the callback infrastructure needed for flag change listener tests: - servicedef/command_params.go: add ListenerNotification, the JSON payload POSTed by the SDK test service to the callback URI when a listener fires. OldValue/NewValue are present only for value change notifications. - mockld/listener_callback_service.go: add ListenerCallbackService, a mock HTTP endpoint that receives POSTed notifications and delivers them to a Go channel for consumption by tests. Mirrors HookCallbackService. - sdktests/testapi_listeners.go: add ListenerCallback, the test API layer wrapping ListenerCallbackService with three assertion helpers: ExpectFlagChangeNotification, ExpectValueChangeNotification, and ExpectNoNotification. Co-authored-by: Cursor <cursoragent@cursor.com>
Add three methods to SDKClient in sdktests/testapi_sdk_client.go: - RegisterFlagChangeListener: sends a registerFlagChangeListener command to the test service, registering a listener for general flag config changes on an optional specific flag key. - RegisterFlagValueChangeListener: sends a registerFlagValueChangeListener command, registering a listener for evaluated value changes for a specific flag key and context. - UnregisterListener: sends an unregisterListener command to remove a previously registered listener by ID. All three follow the existing SDKClient command pattern: a single params struct argument, SendCommandWithParams, and nil response (registration and unregistration produce no response body). Co-authored-by: Cursor <cursoragent@cursor.com>
Expand the listener test suite with additional coverage for both listener types. For general flag change listeners: verify that notifications fire on any configuration change (not only value changes), and that registering with an empty flag key subscribes to changes for all flags. For value change listeners: verify that multiple independent listeners for the same flag both receive notifications, and that listener notifications are scoped to the specific evaluation context they were registered with. Co-authored-by: Cursor <cursoragent@cursor.com>
I performed some research about two scenarios that I'd like to create test cases for: * Is the change listener fired when the Identify action is taken? * Is the change listener fired when a stale version of a flag or a stale version of the data is reported? I've captured my findings in a couple .md files. I don't expect these will be committed to the main branch: they're just for my reference.
While researching specific test cases, I found that all of the code I'd written so far only works on server-side SDKs. I want these tests to work for client-side SDKs as well. This commit updates my development plan to include the work for testing client- side SDKs.
Converts the standalone functions in common_tests_listeners.go to methods on a new CommonListenerTests struct that embeds commonTestsBase. No test logic or behavior changes; all eight existing tests pass unchanged against the Go Server SDK. The struct pattern (also used by CommonStreamingTests, CommonEventTests, etc.) gives the listener tests SDK-kind awareness via isClientSide and sdkKind, and ensures createClient uses baseSDKConfigurationPlus so that client-side configurers (e.g. WithClientSideInitialContext) are automatically included. This is the prerequisite for a follow-up commit that extends the tests to client-side SDKs.
- Make CommonListenerTests SDK-kind-aware: createClient builds ClientSDKData for client-side SDKs and pushFlagUpdate serialises ClientSDKFlagWithKey instead of a full FeatureFlag model. - Add setupListenerDataSystems, which always forces a streaming synchronizer (so pushFlagUpdate can push SSE updates). For JS-based SDKs, the streaming SDKDataSystem is applied first and a polling synchronizer is appended with WithPollingSynchronizer rather than via a second SDKDataSystem (which would overwrite the streaming URL). - Gate flagChangeListenerFiresOnConfigChange and valueListenerIsContextSpecific with RequireCapability(CapabilityServerSide): client-side SDKs receive pre-evaluated values and evaluate for a single fixed context, so these tests do not apply. - Re-enable doCommonListenerTests in doAllClientSideTests now that the shared tests are SDK-kind-aware. - Fix client-side polling to serve FDv1 flat-flag-map format rather than the FDv2 PollingPayload envelope; client-side SDKs parse the polling response through their streaming put handler which expects the simpler format.
This was referenced Feb 24, 2026
35a6b80 to
f19469f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request is part of a larger set of PRs that implement Flag Change Listener tests for both the server SDKs and the client SDKs. Here's the list of pull requests that I currently have open:
Requirements
Related issues
Provide links to any issues in this repository or elsewhere relating to this pull request.
Describe the solution you've provided
Provide a clear and concise description of what you expect to happen.
Describe alternatives you've considered
Provide a clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context about the pull request here.