FFL-2783: add tests for observeFullEvaluationData PII protection (gated as missing_feature) - #7316
Conversation
Adds three non-blocking test classes covering the truth-table matrix for the new per-env UFC field: absent (backcompat), false (explicit disable), and true. Asserts hashed sha256_-prefixed targeting_key + omitted context.evaluation for the first two cells, and raw targeting_key + full context.evaluation for the third. Uses a canonical PII vector (jane.doe@datadoghq.com) so every SDK's L1 unit tests can assert against the same known hash output. All classes gated missing_feature (FFL-2783) on Go; other SDK manifests already skip the whole file. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
|
|
🎉 All green!🧪 All tests passed 🔄 Datadog auto-retried 1 job - 1 passed on retry 🔗 Commit SHA: 08669d5 | Docs | Datadog PR Page | Give us feedback! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1ad980b2f
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| rc.tracer_rc_state.reset().set_config( | ||
| f"{RC_PATH}/{config_id}/config", | ||
| make_ufc_fixture(self.flag_key), | ||
| ).apply() |
There was a problem hiding this comment.
Move fallible setup work into the test
These new setup_* methods perform remote-config application and the weblog request before the test body; if either path raises or times out in CI, pytest reports a setup error and the scenario can stop instead of recording a normal test failure. .cursor/rules/pr-review.mdc says setup methods must only trigger observability and failures/assertions belong in test_*, so keep setup minimal and move the fallible validation path into the matching test method.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not addressing this one. The other eight test classes in this file (Test_FFE_EVP_Flagevaluation_Basic, _Count, _Context_Bounds, _Runtime_Default, _Load_Aggregation, _Burst_Aggregation, _High_Cardinality_Aggregation, _Degradation) all place rc...apply() and evaluate_flag(...) in setup_* and only run assert self.r.status_code == 200 in test_*. Diverging in only the three new classes would create an inconsistency reviewers would flag; if the setup-vs-test split needs enforcement here, it belongs in a dedicated refactor across the whole file.
The three new missing_feature rows should reference FFL-2784 (Go SDK implementation) rather than FFL-2783 (this L3 test ticket) so an SDK dev lands directly on the work that unblocks activation. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
- Rename CANONICAL_* to PII_* and move constants + helpers into their existing sections (top-of-file constants, mid-file helpers) for consistency with the rest of the file. - Iterate every matching flagevaluation event in all three PII test classes so a clean first event cannot mask a leaky later event. - Add assert_no_raw_pii_in_event helper that walks the serialized event and asserts none of the raw PII values (targeting key or attributes) appear anywhere — guards against SDKs that route raw PII into unexpected fields (e.g., context.user_email) even when context.evaluation is correctly omitted. Applied to both hashed cells. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
cbeauchesne
left a comment
There was a problem hiding this comment.
From framework usage, just a small request change.
Cold you also get a review from someone familiar with the feature for the test logic ?
|
|
||
| @scenarios.feature_flagging_and_experimentation | ||
| @features.feature_flags_evp_flagevaluation | ||
| @pytest.mark.skip_if_xfail |
There was a problem hiding this comment.
There are semantic decorators in utils that allow to achieve the same, and also declare why we want to skip them : @slow and @scenario_crash, could you use them ?
There was a problem hiding this comment.
Thanks for raising this! You're right that using @pytest.mark.skip_if_xfail directly is too raw. We looked at @slow and @scenario_crash but neither quite fits — these tests aren't slow, and they don't crash the scenario. They're gated on SDK implementation status.
So we added a new alias @not_yet_implemented in utils/_decorators.py (same underlying skip_if_xfail mark, just with a clearer name) and used it consistently across all 11 classes in the file.
There was a problem hiding this comment.
Why is it not enough to mark the tests as xfails in that case?
…ly in test_flag_eval_evp.py @slow and @scenario_crash both alias skip_if_xfail but carry wrong semantics for tests gated on SDK implementation status. Add @not_yet_implemented as a clearer alias and use it throughout the FFE EVP test file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
leoromanovsky
left a comment
There was a problem hiding this comment.
logic is good, before merging please:
- add a env var=false case
- adjust labels on existing test cases
| @pytest.mark.skip_if_xfail | ||
| @not_yet_implemented |
There was a problem hiding this comment.
hehe @cbeauchesne skipped these because they are not set up correctly; I needed to break system test convention to get them to work with PHP, which has its own sidecar that needs an additional await to count received EVP events in the backend. It's on my backlog to address, but a more correct skip decorator would be flaky or bug
There was a problem hiding this comment.
Thanks for raising Leo! I looked into this more carefully. The existing @flaky/@bug decorators are all-or-nothing across SDKs. Per-SDK skipping lives in the manifests, so I think it would be best to wait until PHP gets enabled and then add class-level manifest entries in manifests/php.yml.
For now I've added a docstring note to Burst, HighCardinality, and Degradation pointing to FFL-2529 and the manifest pattern, so whenever we drop the file-level missing_feature for PHP we will know to add those entries at the same time.
I left @not_yet_implemented on all three for now since that's still the accurate for PHP and most SDKs. Let me know if this works for you or if you were looking for something different!
There was a problem hiding this comment.
The flaky and bug decorators accept arguments that let you filter based on several context elements, such as the library, library version, or weblog. See the (documentation) for more details.
However, you should prefer using manifests wherever possible. Also, these decorators do not behave the same way as skip_if_xfail. The skip_if_xfail decorator skips disabled tests and should only be used when running the test would have a significant downside, for example, if it is very slow or could crash the scenario.
The terminology can be a little confusing, so you may also want to review our glossary for more details.
I may be missing some context, but from what I can see, you do not need decorators here and should be able to put everything in the manifests.
Please do not hesitate to reach out if you have any questions.
There was a problem hiding this comment.
Ahhh thanks for the additional context and docs @nccatoni! This is my first PR in system-tests so those links are great context.
I tried removing the @pytest.mark.skip_if_xfail / @not_yet_implemented decorators entirely to let the manifest missing_feature (FFL-2446) gate do its job (and preserve xpass detection for the easy-win activation script). But CI showed running these tests against the PHP weblog crashes the /ffe endpoint mid-run (it starts returning HTTP 500 with empty body), which then breaks sibling tests in test_flag_eval_metrics.py that share the scenario.
- See the failure at job 90074003763 (
test_flag_eval_metricsstarts passing and then fails partway through once the weblog has been destabilized). Root cause is a PHP sidecar timing issue tracked in FFL-2676.
That's exactly the case @scenario_crash is documented for in docs/edit/skip-tests.md: "a failing test that brings down the whole scenario prevents other tests from running." So I've pushed a change (221e146) that replaces @not_yet_implemented with @scenario_crash on all EVP test classes (same force-skip behavior Leo originally added, but with the semantically-correct decorator name from utils).
The tradeoff (as @cbeauchesne noted): once the feature is implemented across SDKs, @scenario_crash continues to force-skip xpasses so we won't get automatic easy-win activation on this file. That's a conscious cost given how badly the PHP weblog crashes when we don't force-skip. Once FFL-2676 is fixed we can drop @scenario_crash entirely.
Let me know if I'm missing something! I think this is what Leo intended with his review but I misunderstood what @scenario_crash meant so thanks for helping clarify!
| assert "targeting_key" not in event, f"degraded event must omit targeting_key: {event}" | ||
| assert object_key(event.get("variant"), "variant") == "on" | ||
| assert object_key(event.get("allocation"), "allocation") == "default-allocation" | ||
|
|
There was a problem hiding this comment.
Please add a test for DD_FLAGGING_EVALUATION_COUNTS_ENABLED=false + observeFullEvaluationData=true - sorry I missed adding coverage for the false case.
There was a problem hiding this comment.
Happy to add coverage for DD_FLAGGING_EVALUATION_COUNTS_ENABLED =false + observeFullEvaluationData=true, but I think it lives better at L1 unit tests/L2 SDK integration layers.
The assertion is "SDK-level killswitch overrides UFC opt-in" which is purely internal to the SDK and doesn't exercise anything system-level. Adding an L3 scenario here means a new weblog boot per SDK (~minutes of CI × N languages) to verify the boolean killswitch.
I propose we cover this in each SDK's unit or integration suite instead. Let me know if you have strong feelings otherwise!
Per dd-source PR #22826, observeFullEvaluationData is a top-level field on UniversalFlagConfiguration, not nested under environment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses leoromanovsky review comment on tests/ffe/test_flag_eval_evp.py:26. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Degradation tests When PHP is enabled for test_flag_eval_evp.py, these three classes will need per-class flaky entries in manifests/php.yml due to the PHP sidecar's async flush hop making EVP event counts racy. Docstring notes point future enablers to FFL-2529 and the manifest pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Local system-tests validation — Java SDK ✅Ran the three new All three pass:
Run command: ./run.sh +l java FEATURE_FLAGGING_AND_EXPERIMENTATION \
-F tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_Absent_Hashed \
-F tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_False_Hashed \
-F tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_True_UnhashedNote: an earlier build ( |
Addressed the feedback and @cbeauchesne is OOO for several weeks, asking for another codeowner re-review in #apm-shared-testing
Force-skipping every class blocked xpass detection, preventing the easy-win activation script from promoting missing_feature entries to version gates once SDKs implement the feature. The underlying PHP sidecar flakiness that motivated the force-skip is now tracked in FFL-2864 and belongs in manifests/php.yml as a per-class flaky entry when PHP promotes past the file-level missing_feature gate. Also removes the not_yet_implemented alias since it has no remaining callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Running the EVP flag-evaluation tests against the PHP weblog crashes the /ffe endpoint (weblog begins returning HTTP 500), which breaks sibling tests in test_flag_eval_metrics.py that share the scenario. That is exactly what @scenario_crash exists for per skip-tests.md: "a failing test that brings down the whole scenario prevents other tests from running." Force-skipping preserves the safety net Leo originally added with @pytest.mark.skip_if_xfail, but now with the semantically-correct decorator name from utils. Root cause of the crash is tracked in FFL-2864 (PHP sidecar timing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Only the aggregation classes (Burst, High_Cardinality, Degradation) generate enough load on /ffe to crash the PHP weblog: Burst uses a 32-thread ThreadPoolExecutor and Degradation sends 12,000 evaluations per test. The other 8 classes send 1-60 evaluations and are unlikely to destabilize the sidecar on their own. Keeping @scenario_crash off those preserves xpass detection for the easy-win activation script. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds per-class bug (FFL-2676) entries in manifests/php.yml for the 3 scenario-crashing classes (Burst_Aggregation, High_Cardinality_Aggregation, Degradation) alongside the existing file-level missing_feature (FFL-2446). When FFL-2446 is resolved and the file-level entry is dropped, the per-class bug entries keep @scenario_crash force-skipping those 3 classes until the PHP sidecar timing issue in FFL-2676 is fixed. Removes now-stale docstring notes pointing to this future work since the manifest already reflects it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit added three PHP manifest entries in class-source order (Burst, HighCardinality, Degradation) but the manifest linter requires strict alphabetical key order (Burst < Degradation < High). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92cccbf
into
main
Motivation
FFL-2783 — part of FFL-2780 (Protecting PII in flagevaluations track)
See informal RFC for more context
Summary
tests/ffe/test_flag_eval_evp.pycovering the new per-env UFCobserveFullEvaluationDatafieldmake_ufc_fixturewith anobserve_full_evaluation_data: bool | None = Nonekwarg.Noneomits the field entirely, so existing tests are unaffectedmissing_feature (FFL-2783)on Go — the only SDK where the file-level gate would otherwise activate them. Other SDK manifests already deactivate the whole file viamissing_feature (FFL-2446)orirrelevant.Truth-table coverage
observeFullEvaluationDatatargeting_keycontext.evaluationsha256_-prefixed (71 chars, lowercase hex suffix)falsesha256_-prefixedtrueCanonical PII vector (shared across SDKs)
Every SDK's unit tests should assert against the same input/output. Input:
"jane.doe@datadoghq.com"→"sha256_b4698f9b6d186781fa8dc59e533578fa2d8379a46b1cf6db85cda6aa9c99e51b". Canonical attributes cover string, integer, dotted-key, and email PII shapes.Forward-compat for existing SDKs
Introducing a new field inside the UFC
environmentobject could theoretically break SDKs if their UFC parser rejects unknown fields. That risk is already covered bytests/ffe/test_dynamic_evaluation.py::Test_FFE_Unknown_Fields_Tolerance, which injects random unknown fields at every UFC level (including insideenvironment) and asserts each SDK continues to evaluate flags correctly. That test runs on every SDK wheretest_dynamic_evaluation.pyis active, so no per-fixture retrofit is needed here.Out of scope
DD_FLAGGING_EVALUATION_COUNTS_ENABLED) — per-SDK unit test territory, nosystem-testsscenario variant needed.Test plan
./format.shclean (mypy, ruff, node linters)missing_feature (FFL-2783)rows tov2.X.Y-devand confirm all three cells pass🤖 Generated with Claude Code