From 389e82ba4eede52a80ae06f08714de38f8e26cb1 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sat, 27 Jun 2026 05:44:30 +0600 Subject: [PATCH] [AI-FSSDK] [FSSDK-12750] Use attribute id instead of key for CMAB prediction requests --- .../CmabTests/DecisionServiceCmabTest.cs | 16 ++--- .../CmabTests/DefaultCmabServiceTest.cs | 59 +++++++++++++++---- OptimizelySDK/Cmab/DefaultCmabService.cs | 2 +- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/OptimizelySDK.Tests/CmabTests/DecisionServiceCmabTest.cs b/OptimizelySDK.Tests/CmabTests/DecisionServiceCmabTest.cs index a08af152..53a42079 100644 --- a/OptimizelySDK.Tests/CmabTests/DecisionServiceCmabTest.cs +++ b/OptimizelySDK.Tests/CmabTests/DecisionServiceCmabTest.cs @@ -271,8 +271,8 @@ public void TestGetVariationWithCmabExperimentCacheHit() TEST_EXPERIMENT_ID, TEST_USER_ID, It.Is>(attrs => - attrs.Count == 1 && attrs.ContainsKey(AGE_ATTRIBUTE_KEY) && - (int)attrs[AGE_ATTRIBUTE_KEY] == 25), + attrs.Count == 1 && attrs.ContainsKey("age_attr_id") && + (int)attrs["age_attr_id"] == 25), It.IsAny(), It.IsAny())) .Returns(VARIATION_A_ID); @@ -300,7 +300,7 @@ public void TestGetVariationWithCmabExperimentCacheHit() TEST_EXPERIMENT_ID, TEST_USER_ID, It.Is>(attrs => - attrs.Count == 1 && (int)attrs[AGE_ATTRIBUTE_KEY] == 25), + attrs.Count == 1 && (int)attrs["age_attr_id"] == 25), It.IsAny(), It.IsAny()), Times.Once); @@ -334,7 +334,7 @@ public void TestGetVariationWithCmabExperimentCacheMissAttributesChanged() cmabClientMock.Setup(c => c.FetchDecision( TEST_EXPERIMENT_ID, TEST_USER_ID, - It.Is>(attrs => attrs.ContainsKey(AGE_ATTRIBUTE_KEY)), + It.Is>(attrs => attrs.ContainsKey("age_attr_id")), It.IsAny(), It.IsAny())) .Returns(VARIATION_A_ID); @@ -364,7 +364,7 @@ public void TestGetVariationWithCmabExperimentCacheMissAttributesChanged() TEST_EXPERIMENT_ID, TEST_USER_ID, It.Is>(attrs => - attrs.ContainsKey(AGE_ATTRIBUTE_KEY) && (int)attrs[AGE_ATTRIBUTE_KEY] == 25), + attrs.ContainsKey("age_attr_id") && (int)attrs["age_attr_id"] == 25), It.IsAny(), It.IsAny()), Times.Once); @@ -372,7 +372,7 @@ public void TestGetVariationWithCmabExperimentCacheMissAttributesChanged() TEST_EXPERIMENT_ID, TEST_USER_ID, It.Is>(attrs => - attrs.ContainsKey(AGE_ATTRIBUTE_KEY) && (int)attrs[AGE_ATTRIBUTE_KEY] == 30), + attrs.ContainsKey("age_attr_id") && (int)attrs["age_attr_id"] == 30), It.IsAny(), It.IsAny()), Times.Once); @@ -489,8 +489,8 @@ public void TestGetDecisionForCmabExperimentAttributeFiltering() TEST_EXPERIMENT_ID, TEST_USER_ID, It.Is>(attrs => - attrs.Count == 2 && (int)attrs["age"] == 25 && - (string)attrs["location"] == "USA" && + attrs.Count == 2 && (int)attrs["age_attr_id"] == 25 && + (string)attrs["location_attr_id"] == "USA" && !attrs.ContainsKey("extra")), It.IsAny(), It.IsAny())) diff --git a/OptimizelySDK.Tests/CmabTests/DefaultCmabServiceTest.cs b/OptimizelySDK.Tests/CmabTests/DefaultCmabServiceTest.cs index 2e101f26..2d71e070 100644 --- a/OptimizelySDK.Tests/CmabTests/DefaultCmabServiceTest.cs +++ b/OptimizelySDK.Tests/CmabTests/DefaultCmabServiceTest.cs @@ -71,7 +71,7 @@ public void ReturnsDecisionFromCacheWhenHashMatches() var userContext = CreateUserContext(TEST_USER_ID, new Dictionary { { "age", 25 } }); var filteredAttributes = - new UserAttributes(new Dictionary { { "age", 25 } }); + new UserAttributes(new Dictionary { { AGE_ATTRIBUTE_ID, 25 } }); var cacheKey = DefaultCmabService.GetCacheKey(TEST_USER_ID, TEST_RULE_ID); _cmabCache.Save(cacheKey, new CmabCacheEntry @@ -107,8 +107,8 @@ public void IgnoresCacheWhenOptionSpecified() _mockCmabClient.Setup(c => c.FetchDecision(TEST_RULE_ID, TEST_USER_ID, It.Is>(attrs => - attrs != null && attrs.Count == 1 && attrs.ContainsKey("age") && - (int)attrs["age"] == 25), + attrs != null && attrs.Count == 1 && attrs.ContainsKey(AGE_ATTRIBUTE_ID) && + (int)attrs[AGE_ATTRIBUTE_ID] == 25), It.IsAny(), It.IsAny())).Returns("varB"); @@ -143,7 +143,7 @@ public void ResetsCacheWhenOptionSpecified() _mockCmabClient.Setup(c => c.FetchDecision(TEST_RULE_ID, TEST_USER_ID, It.Is>(attrs => - attrs.Count == 1 && (int)attrs["age"] == 25), + attrs.Count == 1 && (int)attrs[AGE_ATTRIBUTE_ID] == 25), It.IsAny(), It.IsAny())).Returns("varNew"); @@ -190,7 +190,7 @@ public void InvalidatesUserEntryWhenOptionSpecified() _mockCmabClient.Setup(c => c.FetchDecision(TEST_RULE_ID, TEST_USER_ID, It.Is>(attrs => - attrs.Count == 1 && (int)attrs["age"] == 25), + attrs.Count == 1 && (int)attrs[AGE_ATTRIBUTE_ID] == 25), It.IsAny(), It.IsAny())).Returns("varNew"); @@ -232,7 +232,7 @@ public void FetchesNewDecisionWhenHashDiffers() _mockCmabClient.Setup(c => c.FetchDecision(TEST_RULE_ID, TEST_USER_ID, It.Is>(attrs => - attrs.Count == 1 && (int)attrs["age"] == 25), + attrs.Count == 1 && (int)attrs[AGE_ATTRIBUTE_ID] == 25), It.IsAny(), It.IsAny())).Returns("varUpdated"); @@ -270,8 +270,8 @@ public void FiltersAttributesBeforeCallingClient() _mockCmabClient.Setup(c => c.FetchDecision(TEST_RULE_ID, TEST_USER_ID, It.Is>(attrs => attrs.Count == 2 && - (int)attrs["age"] == 25 && - (string)attrs["location"] == "USA" && + (int)attrs[AGE_ATTRIBUTE_ID] == 25 && + (string)attrs[LOCATION_ATTRIBUTE_ID] == "USA" && !attrs.ContainsKey("extra")), It.IsAny(), It.IsAny())).Returns("varFiltered"); @@ -283,6 +283,45 @@ public void FiltersAttributesBeforeCallingClient() _mockCmabClient.VerifyAll(); } + [Test] + public void FilteredAttributesUseAttributeIdNotKey() + { + var experiment = CreateExperiment(TEST_RULE_ID, + new List { AGE_ATTRIBUTE_ID, LOCATION_ATTRIBUTE_ID }); + var attributeMap = new Dictionary + { + { AGE_ATTRIBUTE_ID, new AttributeEntity { Id = AGE_ATTRIBUTE_ID, Key = "age" } }, + { + LOCATION_ATTRIBUTE_ID, + new AttributeEntity { Id = LOCATION_ATTRIBUTE_ID, Key = "location" } + }, + }; + var projectConfig = CreateProjectConfig(TEST_RULE_ID, experiment, attributeMap); + var userContext = CreateUserContext(TEST_USER_ID, new Dictionary + { + { "age", 25 }, + { "location", "USA" }, + }); + + _mockCmabClient.Setup(c => c.FetchDecision(TEST_RULE_ID, TEST_USER_ID, + It.Is>(attrs => + attrs.Count == 2 && + attrs.ContainsKey(AGE_ATTRIBUTE_ID) && + !attrs.ContainsKey("age") && + attrs.ContainsKey(LOCATION_ATTRIBUTE_ID) && + !attrs.ContainsKey("location") && + (int)attrs[AGE_ATTRIBUTE_ID] == 25 && + (string)attrs[LOCATION_ATTRIBUTE_ID] == "USA"), + It.IsAny(), + It.IsAny())).Returns("varIdBased"); + + var decision = _cmabService.GetDecision(projectConfig, userContext, TEST_RULE_ID); + + Assert.IsNotNull(decision); + Assert.AreEqual("varIdBased", decision.VariationId); + _mockCmabClient.VerifyAll(); + } + [Test] public void HandlesMissingCmabConfiguration() { @@ -509,8 +548,8 @@ public void ConcurrentRequestsForSameUserUseCacheAfterFirstNetworkCall() TEST_RULE_ID, TEST_USER_ID, It.Is>(attrs => - attrs != null && attrs.Count == 1 && attrs.ContainsKey("age") && - (int)attrs["age"] == 25), + attrs != null && attrs.Count == 1 && attrs.ContainsKey(AGE_ATTRIBUTE_ID) && + (int)attrs[AGE_ATTRIBUTE_ID] == 25), It.IsAny(), It.IsAny())) .Returns(() => diff --git a/OptimizelySDK/Cmab/DefaultCmabService.cs b/OptimizelySDK/Cmab/DefaultCmabService.cs index 86b4d649..0f111e2c 100644 --- a/OptimizelySDK/Cmab/DefaultCmabService.cs +++ b/OptimizelySDK/Cmab/DefaultCmabService.cs @@ -248,7 +248,7 @@ private UserAttributes FilterAttributes(ProjectConfig projectConfig, if (attributeIdMap.TryGetValue(attributeId, out var attribute) && userAttributes.TryGetValue(attribute.Key, out var value)) { - filtered[attribute.Key] = value; + filtered[attribute.Id] = value; } }