From 8da7407b1c5e03bb34af6ee80f9243b6bc71e9b1 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 3 Nov 2025 13:07:04 -0500 Subject: [PATCH 1/3] test: add empty string subject key validation test Adds test case to validate SDK behavior when subject key is an empty string. This is a separate policy decision from the falsy value assignment bug - it tests whether SDKs should accept empty string as a valid subject identifier. Test validates: - Empty string subject can get legitimate assignments (not just defaults) - Normal subjects with real keys continue to work - Proper default fallback when no rules match --- ufc/flags-v1.json | 64 ++++++++++ .../test-case-empty-string-subject-key.json | 118 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 ufc/tests/test-case-empty-string-subject-key.json diff --git a/ufc/flags-v1.json b/ufc/flags-v1.json index 40e48a9..6bcc8e4 100644 --- a/ufc/flags-v1.json +++ b/ufc/flags-v1.json @@ -481,6 +481,70 @@ ], "totalShards": 10000 }, + "empty-string-subject-key-flag": { + "key": "empty-string-subject-key-flag", + "enabled": true, + "variationType": "BOOLEAN", + "variations": { + "disabled": { + "key": "disabled", + "value": false + }, + "enabled": { + "key": "enabled", + "value": true + } + }, + "allocations": [ + { + "key": "disable-for-anonymous", + "rules": [ + { + "conditions": [ + { + "attribute": "user_type", + "operator": "ONE_OF", + "value": [ + "anonymous" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "disabled", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "enable-for-users", + "rules": [ + { + "conditions": [ + { + "attribute": "user_type", + "operator": "ONE_OF", + "value": [ + "registered" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "enabled", + "shards": [] + } + ], + "doLog": true + } + ], + "totalShards": 10000 + }, "kill-switch": { "key": "kill-switch", "enabled": true, diff --git a/ufc/tests/test-case-empty-string-subject-key.json b/ufc/tests/test-case-empty-string-subject-key.json new file mode 100644 index 0000000..76c65f9 --- /dev/null +++ b/ufc/tests/test-case-empty-string-subject-key.json @@ -0,0 +1,118 @@ +{ + "flag": "empty-string-subject-key-flag", + "variationType": "BOOLEAN", + "defaultValue": true, + "subjects": [ + { + "subjectKey": "", + "subjectAttributes": { + "user_type": "anonymous" + }, + "assignment": false, + "evaluationDetails": { + "environmentName": "Test", + "flagEvaluationCode": "MATCH", + "flagEvaluationDescription": "Supplied attributes match rules defined in allocation \"disable-for-anonymous\".", + "banditKey": null, + "banditAction": null, + "variationKey": "disabled", + "variationValue": false, + "matchedRule": { + "conditions": [ + { + "attribute": "user_type", + "operator": "ONE_OF", + "value": [ + "anonymous" + ] + } + ] + }, + "matchedAllocation": { + "key": "disable-for-anonymous", + "allocationEvaluationCode": "MATCH", + "orderPosition": 1 + }, + "unmatchedAllocations": [], + "unevaluatedAllocations": [ + { + "key": "enable-for-users", + "allocationEvaluationCode": "UNEVALUATED", + "orderPosition": 2 + } + ] + } + }, + { + "subjectKey": "user123", + "subjectAttributes": { + "user_type": "registered" + }, + "assignment": true, + "evaluationDetails": { + "environmentName": "Test", + "flagEvaluationCode": "MATCH", + "flagEvaluationDescription": "Supplied attributes match rules defined in allocation \"enable-for-users\".", + "banditKey": null, + "banditAction": null, + "variationKey": "enabled", + "variationValue": true, + "matchedRule": { + "conditions": [ + { + "attribute": "user_type", + "operator": "ONE_OF", + "value": [ + "registered" + ] + } + ] + }, + "matchedAllocation": { + "key": "enable-for-users", + "allocationEvaluationCode": "MATCH", + "orderPosition": 2 + }, + "unmatchedAllocations": [ + { + "key": "disable-for-anonymous", + "allocationEvaluationCode": "FAILING_RULE", + "orderPosition": 1 + } + ], + "unevaluatedAllocations": [] + } + }, + { + "subjectKey": "guest_user", + "subjectAttributes": { + "user_type": "unknown" + }, + "assignment": true, + "evaluationDetails": { + "environmentName": "Test", + "flagEvaluationCode": "DEFAULT_ALLOCATION_NULL", + "flagEvaluationDescription": "No allocations matched. Falling back to \"Default Allocation\", serving NULL", + "banditKey": null, + "banditAction": null, + "variationKey": null, + "variationValue": null, + "matchedRule": null, + "matchedAllocation": null, + "unmatchedAllocations": [ + { + "key": "disable-for-anonymous", + "allocationEvaluationCode": "FAILING_RULE", + "orderPosition": 1 + }, + { + "key": "enable-for-users", + "allocationEvaluationCode": "FAILING_RULE", + "orderPosition": 2 + } + ], + "unevaluatedAllocations": [] + } + } + ] +} \ No newline at end of file From 7b16d34cb3bf4e43d2d36735d6284f5af007f8fa Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 3 Nov 2025 13:47:52 -0500 Subject: [PATCH 2/3] chore: update obfuscated flags config for empty subject key test --- ufc/flags-v1-obfuscated.json | 78 ++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/ufc/flags-v1-obfuscated.json b/ufc/flags-v1-obfuscated.json index 555ab23..f3a9a03 100644 --- a/ufc/flags-v1-obfuscated.json +++ b/ufc/flags-v1-obfuscated.json @@ -480,6 +480,70 @@ } ] }, + "aca57f6611d05b7ac7fe76a60220101d": { + "key": "aca57f6611d05b7ac7fe76a60220101d", + "enabled": true, + "variationType": "BOOLEAN", + "totalShards": 10000, + "variations": { + "ZGlzYWJsZWQ=": { + "key": "ZGlzYWJsZWQ=", + "value": "ZmFsc2U=" + }, + "ZW5hYmxlZA==": { + "key": "ZW5hYmxlZA==", + "value": "dHJ1ZQ==" + } + }, + "allocations": [ + { + "key": "ZGlzYWJsZS1mb3ItYW5vbnltb3Vz", + "doLog": true, + "rules": [ + { + "conditions": [ + { + "operator": "27457ce369f2a74203396a35ef537c0b", + "attribute": "d7b5164029f944313b08c6b778b7b178", + "value": [ + "294de3557d9d00b3d2d8a1e6aab028cf" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "ZGlzYWJsZWQ=", + "shards": [] + } + ] + }, + { + "key": "ZW5hYmxlLWZvci11c2Vycw==", + "doLog": true, + "rules": [ + { + "conditions": [ + { + "operator": "27457ce369f2a74203396a35ef537c0b", + "attribute": "d7b5164029f944313b08c6b778b7b178", + "value": [ + "a2b8ed9c305b8ea86116b603cca78a97" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "ZW5hYmxlZA==", + "shards": [] + } + ] + } + ] + }, "ea55330f6cbbaf8f57556df645c037e2": { "key": "ea55330f6cbbaf8f57556df645c037e2", "enabled": true, @@ -1207,14 +1271,14 @@ }, "allocations": [ { - "doLog": true, "key": "T3B0aW9uYWxseSBGb3JjZSBFbXB0eQ==", + "doLog": true, "rules": [ { "conditions": [ { - "attribute": "4e1d9d66e21dde70a7dfd7b26a9aa007", "operator": "27457ce369f2a74203396a35ef537c0b", + "attribute": "4e1d9d66e21dde70a7dfd7b26a9aa007", "value": [ "b326b5062b2f0e69046810717534cb09" ] @@ -1224,18 +1288,18 @@ ], "splits": [ { + "variationKey": "ZW1wdHk=", "shards": [ { "ranges": [ { - "end": 10000, - "start": 0 + "start": 0, + "end": 10000 } ], "salt": "ZnVsbC1yYW5nZS1zYWx0" } - ], - "variationKey": "ZW1wdHk=" + ] } ] }, @@ -3185,4 +3249,4 @@ ] } } -} +} \ No newline at end of file From cac0082c9d4a92418da79caee445f3e31d155417 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 3 Nov 2025 14:18:39 -0500 Subject: [PATCH 3/3] codify current behavior for programmatic default --- .../test-case-empty-string-subject-key.json | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/ufc/tests/test-case-empty-string-subject-key.json b/ufc/tests/test-case-empty-string-subject-key.json index 76c65f9..7c77834 100644 --- a/ufc/tests/test-case-empty-string-subject-key.json +++ b/ufc/tests/test-case-empty-string-subject-key.json @@ -8,39 +8,30 @@ "subjectAttributes": { "user_type": "anonymous" }, - "assignment": false, + "assignment": true, "evaluationDetails": { "environmentName": "Test", - "flagEvaluationCode": "MATCH", - "flagEvaluationDescription": "Supplied attributes match rules defined in allocation \"disable-for-anonymous\".", + "flagEvaluationCode": "DEFAULT_ALLOCATION_NULL", + "flagEvaluationDescription": "Empty subject key provided. Falling back to \"Default Allocation\", serving NULL", "banditKey": null, "banditAction": null, - "variationKey": "disabled", - "variationValue": false, - "matchedRule": { - "conditions": [ - { - "attribute": "user_type", - "operator": "ONE_OF", - "value": [ - "anonymous" - ] - } - ] - }, - "matchedAllocation": { - "key": "disable-for-anonymous", - "allocationEvaluationCode": "MATCH", - "orderPosition": 1 - }, - "unmatchedAllocations": [], - "unevaluatedAllocations": [ + "variationKey": null, + "variationValue": null, + "matchedRule": null, + "matchedAllocation": null, + "unmatchedAllocations": [ + { + "key": "disable-for-anonymous", + "allocationEvaluationCode": "UNEVALUATED", + "orderPosition": 1 + }, { "key": "enable-for-users", "allocationEvaluationCode": "UNEVALUATED", "orderPosition": 2 } - ] + ], + "unevaluatedAllocations": [] } }, {