Skip to content

test(ffe): apply server flag selection in the mocked backend - #8304

Open
mark-caii wants to merge 8 commits into
mainfrom
mark.cai/FFL-3567
Open

mark-caii wants to merge 8 commits into
mainfrom
mark.cai/FFL-3567

Conversation

@mark-caii

@mark-caii mark-caii commented Nov 2, 2026 •

Copy link
Copy Markdown

Motivation

Server SDKs send a flag selection (tags and key patterns) on the agentless GET /api/v2/feature-flagging/config/rules-based/server request, and serve only the flags the backend returns. The mocked backend can't exercise this today: any query parameter besides dd_env gets a 404, the UFC is never narrowed, and DD-Flag-Selection-Ignored is never sent.

Jira: FFL-3567

Depends on DataDog/ffe-system-test-data#63, which adds the flag-selection evaluation suite this test runs.

Changes

  • Mocked backend (JSON rules-based/server endpoint only; the protobuf helpers are unchanged):
    • Accepts selected_flag_tags and selected_flag_key_patterns alongside dd_env=test. Any other query still gets a 404.
    • Parses the selection the way ffe-service does (domains/ffe/libs/flagging/selection/selection.go):
      • Go QueryUnescape decoding;
      • trimming Unicode White_Space, removing duplicates and sorting by UTF-8 bytes;
      • limits of 31 values per list and 200 bytes per value, with the precedence malformed_selection > too_many_values > invalid_key_pattern > invalid_tag.
    • client.ffe_use_suite(name) serves that ffe-system-test-data evaluation suite's ufc-config.json instead of the root UFC, and matches tags from the suite's flag-tags.json. It accepts only suites in the snapshot; reset restores the root UFC, which has no tags. Suites otherwise reach SDKs through Remote Config, which ignores selections.
    • Serves only the flags that match a key pattern or carry a selected tag, with an ETag specific to the suite and the selection (ufc-v1-<suite>-<digest>), as ffe-service folds the selection into its ETag. A request carrying a selection, applied or ignored, gets a scripted not_modified 304 only when If-None-Match carries the ETag of the body it would otherwise get, and gets that body otherwise. Without a selection, the scripted 304 is unchanged.
    • For an invalid selection, serves every flag with DD-Flag-Selection-Ignored: <reason>.
    • client.ffe_set_flag_selection_ignored_reason(...) forces an ignored reason (for example tag_load_failed) on valid selections. Reset clears it.
    • The mocked-backend container mounts the snapshot's evaluation-suites/.
  • Fixture sync: update_ffe_fixtures.py vendors flag-selection/selection-vectors.json (the same way [FFL-3364] Test standalone Node rules flag selection #8266 does) and accepts an optional flag-tags.json in a suite. It checks the entries of both files before replacing the snapshot. evaluation-suites/flag-selection/ arrives through the existing suite copy. The snapshot comes from DataDog/ffe-system-test-data@be2ab83, the head of Some clean up on decorators #63.
  • Tests in tests/parametric/test_ffe/test_flag_selection.py:
    • Test_FFE_Flag_Selection::test_flag_selection_suite runs once per suite case file. It selects the suite and any forced ignored reason on the mock, starts the provider in agentless mode with the file's raw selection, checks the first configuration request against the file's expectations.configurationRequest, then evaluates each case that applies to the platform.
    • The raw selection reaches the SDK through ffe_start(flag_selection=...), which sends it as is in its own /ffe/start field, flag_selection, separate from configuration (which the PHP app loads as a UFC). The Node.js test app builds its provider at boot, so the test also sets SYSTEM_TESTS_FFE_FLAG_SELECTION (the selection as JSON) in library_env for it. Test apps pass the value untouched; only the SDK canonicalizes and validates it. No test app reads either yet; FFL-3576 wires them.
    • Test_FFE_Flag_Selection_Revalidation checks that the SDK's next poll sends its selection's ETag back in If-None-Match.
    • The 44 shared selection vectors run as mock unit tests in tests/test_the_test/test_mock_ffe_flag_selection.py, with tests for ffe_use_suite.
  • Feature and manifests: feature_flags_configuration_filtering uses the not-reported placeholder ID (NOT_REPORTED_ID, -1) until someone with Feature Parity Dashboard access, such as the system-tests team, creates the entry; the real ID and dashboard link then replace it. Every parametric manifest gates test_flag_selection.py: missing_feature for the SDKs with FFE, irrelevant for cpp and rust.
  • docs/mocked_backend/README.md documents the selection behavior and ffe_use_suite.

After DataDog/ffe-system-test-data#63 merges, the vendored snapshot and SOURCE.md get re-synced with update_ffe_fixtures.py from the merged commit.

Overlap with open PRs: #8266 also vendors selection-vectors.json and edits update_ffe_fixtures.py and its tests. Whichever of the two merges second merges main in (no force-push) and resolves the overlap. #8266's protobuf rules tests stay unfiltered.

Validation:

  • ./run.sh TEST_THE_TEST on the FFE mock, agentless, rules-backend, mock-backend, fixture-sync, FFE, feature and manifest suites: 347 passed.
  • ./format.sh --check passes.
  • Against released Go 2.11.2 with -F: the six case files that send a selection and the revalidation test fail at the request assertion, because Go sends only dd_env. The three files where the SDK must send no selection (empty selection, too many values, long URL) pass: an SDK without the option sends none and gets every flag. Without -F: 7 xfailed, 3 xpassed.

Workflow

  1. ⚠️ Create your PR as draft ⚠️
  2. Work on you PR until the CI passes
  3. Mark it as ready for review
    • Tests, manifest, weblog are modified -> you'll need a review from system-tests-reviewers: ask to one of youre co-worker familiar with the tested feature.
    • Framework is modified, or non obvious usage of it -> get a review from system-tests-core (slack)

🚀 Once your PR is reviewed and the CI green, you can merge it!

🛟 #apm-shared-testing 🛟

The JSON rules-based/server endpoint of the mocked backend now accepts
selected_flag_tags and selected_flag_key_patterns, applies them to the shared
ufc-config.json with the shared tags file, and sends DD-Flag-Selection-Ignored
with the full configuration for an invalid selection, following ffe-service
semantics. A test-only control forces an ignored reason.

The fixture sync vendors flag-selection/ from ffe-system-test-data. A new
parametric test, under the new feature_flags_configuration_filtering feature,
checks that a server SDK sends a tag selection and serves only the selected
flags. Every manifest gates it until each SDK ships the provider option.
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/canonical-form.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/empty-selection.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/interior-whitespace-tag.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/key-patterns.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/sdk-drops-long-url.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/sdk-drops-too-many-values.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/server-ignores-selection.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/tags-or-key-patterns.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/cases/tags.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/flag-tags.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/evaluation-suites/flag-selection/ufc-config.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/ffe-system-test-data/flag-selection/selection-vectors.json  @DataDog/feature-flagging-and-experimentation-sdk
tests/parametric/test_ffe/test_flag_selection.py                        @DataDog/feature-flagging-and-experimentation-sdk
tests/test_the_test/test_mock_ffe_flag_selection.py                     @DataDog/system-tests-reviewers
utils/mocked_backend/_ffe_flag_selection.py                             @DataDog/system-tests-core
docs/mocked_backend/README.md                                           @DataDog/system-tests-core
manifests/cpp.yml                                                       @DataDog/system-tests-reviewers
manifests/dotnet.yml                                                    @DataDog/system-tests-reviewers
manifests/golang.yml                                                    @DataDog/system-tests-reviewers
manifests/java.yml                                                      @DataDog/system-tests-reviewers
manifests/nodejs.yml                                                    @DataDog/system-tests-reviewers
manifests/php.yml                                                       @DataDog/system-tests-reviewers
manifests/python.yml                                                    @DataDog/system-tests-reviewers
manifests/ruby.yml                                                      @DataDog/system-tests-reviewers
manifests/rust.yml                                                      @DataDog/system-tests-reviewers
tests/parametric/test_ffe/ffe-system-test-data/SOURCE.md                @DataDog/feature-flagging-and-experimentation-sdk
tests/test_the_test/test_mock_backend_v2.py                             @DataDog/system-tests-reviewers
tests/test_the_test/test_update_ffe_fixtures.py                         @DataDog/feature-flagging-and-experimentation-sdk
utils/_context/containers.py                                            @DataDog/system-tests-core
utils/_features.py                                                      @DataDog/system-tests-core
utils/docker_fixtures/_test_clients/_test_client_parametric.py          @DataDog/system-tests-core
utils/mocked_backend/_backend_ffe.py                                    @DataDog/feature-flagging-and-experimentation-sdk @DataDog/system-tests-core
utils/mocked_backend/_client.py                                         @DataDog/system-tests-core
utils/mocked_backend/ffe.py                                             @DataDog/system-tests-core
utils/scripts/update_ffe_fixtures.py                                    @DataDog/feature-flagging-and-experimentation-sdk @DataDog/system-tests-core

The fixture sync copies flag-selection/selection-vectors.json and
flag-selection/ufc-config-tags.json instead of the whole directory, so the
upstream README stays out of the snapshot. The copy stays outside
_copy_fixture_snapshot.
A valid selection whose value decodes to bytes that are not UTF-8, such as
selected_flag_tags=%85, made the selection ETag digest raise, so the mock
dropped the connection. ffe-service applies such a selection. The digest now
hashes the values' wire bytes.

The flag selection test also waits while the provider reports no
configuration through providerState.hasConfig, like the other FFE tests.
@datadog-datadog-us1-prod

datadog-datadog-us1-prod Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Pipelines  Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 4b7487c | Docs | View more details | Give us feedback!

The PHP parametric app loads ffe_start's configuration as a UFC, so a
selection nested in it would collide with tests that pass a UFC there.
ffe_start now takes flag_selection and sends it as its own field, as is.
The test app passes it untouched to the SDK's provider option; only the
SDK canonicalizes and validates it.
ffe-service folds the selection into the ETag, so a 304 for a selection
carries the same validator as its 200. The mocked backend's scripted 304
now does the same: under a valid selection it answers 304 only when
If-None-Match carries that selection's ETag, and serves the selected flags
otherwise. Without a selection the scripted 304 is unchanged.

The flag selection test also checks that the SDK's next poll sends the
selection's ETag back.
@mark-caii
mark-caii marked this pull request as ready for review November 3, 2026 18:26
@mark-caii
mark-caii requested review from a team as code owners November 3, 2026 18:26
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T23:50:04.000973Z 058ed8b Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed97cad3f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread utils/scripts/update_ffe_fixtures.py Resolved
Comment thread utils/mocked_backend/_backend_ffe.py Resolved
Comment thread utils/scripts/update_ffe_fixtures.py Resolved
@mark-caii
mark-caii marked this pull request as draft November 3, 2026 18:58
The flag selection test now runs the ffe-system-test-data flag-selection
suite, one test per case file: tags, key patterns, both, canonical form,
a tag with interior whitespace, an empty selection, two SDK drops and a
selection the server ignores. Each file starts the provider with its raw
selection, checks the configuration request against the file's expected
canonical selection, then evaluates its cases. The ETag revalidation
check stays its own test.

The suite is served by the mocked backend: client.ffe_use_suite(name)
switches the agentless JSON configuration to that suite's UFC and filters
selections with its flag-tags.json. Suites otherwise reach SDKs through
Remote Config, which ignores selections. The root UFC no longer has tags.

The fixture sync vendors only selection-vectors.json under flag-selection/
and accepts flag-tags.json in a suite. The snapshot is refreshed from
DataDog/ffe-system-test-data@be2ab83.
@mark-caii
mark-caii marked this pull request as ready for review November 3, 2026 23:47
… data

A request carrying a selection the mocked backend ignores got a scripted
304 whatever its If-None-Match, so an SDK that had cached a filtered body
kept it instead of receiving every flag and the ignored reason. Any request
with selection parameters now gets a scripted 304 only for the ETag of the
body it would otherwise receive. Requests without a selection keep the
unconditional scripted 304.

The fixture sync also checks the entries of selection-vectors.json and a
suite's flag-tags.json, not only their top-level keys, before replacing
the snapshot.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants