From f5e60a7be715daf7bae789af2ec1ae1be3a8aecd Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 20 Mar 2025 23:02:58 +0600 Subject: [PATCH 1/5] wip: Holdout Model created --- OptimizelySwiftSDK.xcodeproj/project.pbxproj | 70 +++++++++++++++- Sources/Data Model/Experiment.swift | 67 +-------------- Sources/Data Model/ExperimentCore.swift | 86 ++++++++++++++++++++ Sources/Data Model/Holdout.swift | 51 ++++++++++++ 4 files changed, 207 insertions(+), 67 deletions(-) create mode 100644 Sources/Data Model/ExperimentCore.swift create mode 100644 Sources/Data Model/Holdout.swift diff --git a/OptimizelySwiftSDK.xcodeproj/project.pbxproj b/OptimizelySwiftSDK.xcodeproj/project.pbxproj index 3df0de831..1dfcfdb6e 100644 --- a/OptimizelySwiftSDK.xcodeproj/project.pbxproj +++ b/OptimizelySwiftSDK.xcodeproj/project.pbxproj @@ -1978,6 +1978,38 @@ 84F6BAB427FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F6BAB227FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift */; }; 84F6BADD27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F6BADC27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift */; }; 84F6BADE27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F6BADC27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift */; }; + 980CC8F72D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8F82D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8F92D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FA2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FB2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FC2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FD2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FE2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FF2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9002D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9012D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9022D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9032D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9042D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9052D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9062D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9082D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9092D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90A2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90B2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90C2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90D2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90E2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90F2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9102D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9112D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9122D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9132D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9142D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9152D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9162D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; 98137C552A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */; }; 98137C572A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */; }; 984E2FDC2B27199B001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; @@ -2436,6 +2468,8 @@ 84E7ABBA27D2A1F100447CAE /* ThreadSafeLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeLogger.swift; sourceTree = ""; }; 84F6BAB227FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP.swift; sourceTree = ""; }; 84F6BADC27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP_Decide.swift; sourceTree = ""; }; + 980CC8F62D833F0D00E07D24 /* Holdout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Holdout.swift; sourceTree = ""; }; + 980CC9072D833F2800E07D24 /* ExperimentCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCore.swift; sourceTree = ""; }; 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Init_Async_Await.swift; sourceTree = ""; }; 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP_Aync_Await.swift; sourceTree = ""; }; 984FE5102CC8AA88004F6F41 /* UserProfileTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileTracker.swift; sourceTree = ""; }; @@ -2821,6 +2855,8 @@ 6E75169122C520D400B2B157 /* TrafficAllocation.swift */, 6E75169222C520D400B2B157 /* Project.swift */, 6E75169322C520D400B2B157 /* Experiment.swift */, + 980CC9072D833F2800E07D24 /* ExperimentCore.swift */, + 980CC8F62D833F0D00E07D24 /* Holdout.swift */, 6E75169422C520D400B2B157 /* FeatureFlag.swift */, 6E75169522C520D400B2B157 /* Group.swift */, 6E75169622C520D400B2B157 /* Variable.swift */, @@ -4209,7 +4245,9 @@ 6E14CD722423F96B00010234 /* OptimizelyClient+ObjC.swift in Sources */, 6E14CDA62423F9C300010234 /* HandlerRegistryService.swift in Sources */, 6E14CD7B2423F98D00010234 /* OPTEventDispatcher.swift in Sources */, + 980CC9022D833F0D00E07D24 /* Holdout.swift in Sources */, 6E14CD902423F9A700010234 /* Variation.swift in Sources */, + 980CC9162D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E14CD8E2423F9A700010234 /* FeatureVariable.swift in Sources */, 8464087728130D3200CCF97D /* Integration.swift in Sources */, 6E14CD8D2423F9A700010234 /* ProjectConfig.swift in Sources */, @@ -4272,6 +4310,7 @@ 6E424D0326324B620081004A /* AttributeValue.swift in Sources */, 84E2E9482852A378001114AB /* VuidManager.swift in Sources */, 6E424D0426324B620081004A /* ConditionLeaf.swift in Sources */, + 980CC90E2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E424D0526324B620081004A /* ConditionHolder.swift in Sources */, 6E424D5226324C4D0081004A /* OptimizelyClient+Decide.swift in Sources */, 6E424D0626324B620081004A /* UserAttribute.swift in Sources */, @@ -4300,6 +4339,7 @@ 6E424D1526324B620081004A /* DataStoreQueueStack.swift in Sources */, 6E424D1626324B620081004A /* OPTDataStore.swift in Sources */, 6E424D2F26324BBA0081004A /* OTUtils.swift in Sources */, + 980CC8F72D833F0D00E07D24 /* Holdout.swift in Sources */, 6E424D1726324B620081004A /* OPTDecisionService.swift in Sources */, 6E424D1826324B620081004A /* OPTBucketer.swift in Sources */, 6E424D1926324B620081004A /* ArrayEventForDispatch+Extension.swift in Sources */, @@ -4358,6 +4398,7 @@ 6EF8DE3224BF7D69008B9488 /* DecisionReasons.swift in Sources */, 6E75192522C520D500B2B157 /* DataStoreQueueStack.swift in Sources */, 6E7516FB22C520D400B2B157 /* OptimizelyLogLevel.swift in Sources */, + 980CC9042D833F0D00E07D24 /* Holdout.swift in Sources */, 84E7ABBC27D2A1F100447CAE /* ThreadSafeLogger.swift in Sources */, 6E75184D22C520D400B2B157 /* ProjectConfig.swift in Sources */, 8464087128130D3200CCF97D /* Integration.swift in Sources */, @@ -4404,6 +4445,7 @@ 6E75177F22C520D400B2B157 /* ArrayEventForDispatch+Extension.swift in Sources */, 6E75181122C520D400B2B157 /* DataStoreQueueStackImpl.swift in Sources */, 6EF8DE1B24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, + 980CC90D2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75173722C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E7517F922C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, C78CAF592445AD8D009FE876 /* OptimizelyJSON.swift in Sources */, @@ -4516,7 +4558,9 @@ 6E75174A22C520D400B2B157 /* HandlerRegistryService.swift in Sources */, 6E7516F622C520D400B2B157 /* OptimizelyError.swift in Sources */, 6E75188422C520D400B2B157 /* TrafficAllocation.swift in Sources */, + 980CC8F92D833F0D00E07D24 /* Holdout.swift in Sources */, 6EA2CC2C2345618E001E7531 /* OptimizelyConfig.swift in Sources */, + 980CC9152D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E7517D022C520D400B2B157 /* DefaultBucketer.swift in Sources */, 8464087C28130D3200CCF97D /* Integration.swift in Sources */, 6E75180022C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, @@ -4565,6 +4609,7 @@ 6E75181422C520D400B2B157 /* DataStoreQueueStackImpl.swift in Sources */, 6E593FB625BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift in Sources */, 6E7516C222C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 980CC8FC2D833F0D00E07D24 /* Holdout.swift in Sources */, 848617F22863E21200B7F41B /* OdpEventApiManager.swift in Sources */, 6E75188022C520D400B2B157 /* TrafficAllocation.swift in Sources */, 6E9B11DD22C548A200C22D81 /* OptimizelyClientTests_Valid.swift in Sources */, @@ -4600,6 +4645,7 @@ 6E75194C22C520D500B2B157 /* OPTDatafileHandler.swift in Sources */, 6E652308278E688B00954EA1 /* LruCache.swift in Sources */, 6E7517C022C520D400B2B157 /* DefaultDatafileHandler.swift in Sources */, + 980CC90F2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75183822C520D400B2B157 /* EventForDispatch.swift in Sources */, 6E75175222C520D400B2B157 /* LogMessage.swift in Sources */, 6E9B11DB22C548A200C22D81 /* OptimizelyClientTests_Variables.swift in Sources */, @@ -4702,6 +4748,7 @@ 6E75181722C520D400B2B157 /* DataStoreQueueStackImpl.swift in Sources */, 6E75185322C520D400B2B157 /* ProjectConfig.swift in Sources */, 84E2E94D2852A378001114AB /* VuidManager.swift in Sources */, + 980CC8F82D833F0D00E07D24 /* Holdout.swift in Sources */, 6E75173D22C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E7516E922C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E7518A722C520D400B2B157 /* FeatureFlag.swift in Sources */, @@ -4718,6 +4765,7 @@ 84B4D75B27E2A7550078CDA4 /* OptimizelySegmentOption.swift in Sources */, 6E7517E722C520D400B2B157 /* DefaultDecisionService.swift in Sources */, 6E424BE7263228E90081004A /* AtomicArray.swift in Sources */, + 980CC9102D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E7516DD22C520D400B2B157 /* OPTUserProfileService.swift in Sources */, 6E75188F22C520D400B2B157 /* Project.swift in Sources */, 6E75195B22C520D500B2B157 /* OPTBucketer.swift in Sources */, @@ -4807,6 +4855,7 @@ 84F6BAB427FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift in Sources */, 6E7516C722C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, 6E9B11B522C5489600C22D81 /* MockUrlSession.swift in Sources */, + 980CC9012D833F0D00E07D24 /* Holdout.swift in Sources */, 6EF8DE1724BD1BB2008B9488 /* OptimizelyDecision.swift in Sources */, 8428D3D12807337400D0FB0C /* LruCacheTests.swift in Sources */, 84861816286D0B8900B7F41B /* VuidManagerTests.swift in Sources */, @@ -4847,6 +4896,7 @@ 845945C8287758A500D13E11 /* OdpConfig.swift in Sources */, 6E9B116422C5487100C22D81 /* BucketTests_Others.swift in Sources */, 6E7518CD22C520D400B2B157 /* Audience.swift in Sources */, + 980CC90C2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E96E28540B5E001114AB /* OptimizelySdkSettings.swift in Sources */, 6E9B117322C5487100C22D81 /* BatchEventBuilderTests_Attributes.swift in Sources */, 6E9B11B622C5489600C22D81 /* OTUtils.swift in Sources */, @@ -4924,6 +4974,7 @@ 0B97DD9A249D332C003DE606 /* SemanticVersionTests.swift in Sources */, 6E65230E278E688B00954EA1 /* LruCache.swift in Sources */, 6E9B119C22C5488300C22D81 /* ProjectConfigTests.swift in Sources */, + 980CC8FF2D833F0D00E07D24 /* Holdout.swift in Sources */, 6E7518FE22C520D500B2B157 /* UserAttribute.swift in Sources */, 6E7517F622C520D400B2B157 /* DataStoreMemory.swift in Sources */, 6E9B119322C5488300C22D81 /* AttributeTests.swift in Sources */, @@ -4936,6 +4987,7 @@ 84861809286CF33700B7F41B /* OdpEvent.swift in Sources */, 6E75171022C520D400B2B157 /* OptimizelyClient.swift in Sources */, 6E7516C822C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 980CC9112D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E9502852A378001114AB /* VuidManager.swift in Sources */, 6E75194622C520D500B2B157 /* OPTDecisionService.swift in Sources */, 6E75185622C520D400B2B157 /* ProjectConfig.swift in Sources */, @@ -5095,7 +5147,7 @@ 6E75171522C520D400B2B157 /* OptimizelyClient+ObjC.swift in Sources */, 6E6522E3278E4F3800954EA1 /* OdpManager.swift in Sources */, 6EA2CC272345618E001E7531 /* OptimizelyConfig.swift in Sources */, - 84861815286D0B8900B7F41B /* OdpVuidManagerTests.swift in Sources */, + 84861815286D0B8900B7F41B /* VuidManagerTests.swift in Sources */, 984FE51E2CC8AA88004F6F41 /* UserProfileTracker.swift in Sources */, C78CAFA724486E0A009FE876 /* OptimizelyJSON+ObjC.swift in Sources */, 6E75185B22C520D400B2B157 /* FeatureVariable.swift in Sources */, @@ -5122,6 +5174,7 @@ 6E0A72D426C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift in Sources */, 6EF41A332522BE1900EAADF1 /* OptimizelyUserContextTests_Decide.swift in Sources */, 848617EF2863E21200B7F41B /* OdpEventApiManager.swift in Sources */, + 980CC9002D833F0D00E07D24 /* Holdout.swift in Sources */, 6E27ECBE266FD78600B4A6D4 /* DecisionReasonsTests.swift in Sources */, 6E9B115E22C5486E00C22D81 /* DataStoreTests.swift in Sources */, 6E4544AF270E67C800F2CEBC /* NetworkReachability.swift in Sources */, @@ -5153,6 +5206,7 @@ 6E7517E322C520D400B2B157 /* DefaultDecisionService.swift in Sources */, 6E75179922C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */, 6E9B115C22C5486E00C22D81 /* DatafileHandlerTests.swift in Sources */, + 980CC9082D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E9B114D22C5486E00C22D81 /* BatchEventBuilderTests_Events.swift in Sources */, 6E75188B22C520D400B2B157 /* Project.swift in Sources */, 6E75187F22C520D400B2B157 /* TrafficAllocation.swift in Sources */, @@ -5193,6 +5247,7 @@ 0B97DD99249D332C003DE606 /* SemanticVersionTests.swift in Sources */, 6E652309278E688B00954EA1 /* LruCache.swift in Sources */, 6E9B118622C5488100C22D81 /* ProjectConfigTests.swift in Sources */, + 980CC9032D833F0D00E07D24 /* Holdout.swift in Sources */, 6E7518F922C520D500B2B157 /* UserAttribute.swift in Sources */, 6E7517F122C520D400B2B157 /* DataStoreMemory.swift in Sources */, 6E9B117D22C5488100C22D81 /* AttributeTests.swift in Sources */, @@ -5205,6 +5260,7 @@ 84861804286CF33700B7F41B /* OdpEvent.swift in Sources */, 6E75170B22C520D400B2B157 /* OptimizelyClient.swift in Sources */, 6E7516C322C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E94B2852A378001114AB /* VuidManager.swift in Sources */, 6E75194122C520D500B2B157 /* OPTDecisionService.swift in Sources */, 6E75185122C520D400B2B157 /* ProjectConfig.swift in Sources */, @@ -5327,6 +5383,7 @@ 6EC6DD4924ABF89B0017D296 /* OptimizelyUserContext.swift in Sources */, 6E75188222C520D400B2B157 /* TrafficAllocation.swift in Sources */, 6E5D12252638DDF4000ABFC3 /* MockEventDispatcher.swift in Sources */, + 980CC9142D833F2800E07D24 /* ExperimentCore.swift in Sources */, 848617F42863E21200B7F41B /* OdpEventApiManager.swift in Sources */, 6ECB60D0234D5D9C00016D41 /* OptimizelyConfig+ObjC.swift in Sources */, 6E9B11E322C548AF00C22D81 /* ThrowableConditionListTest.swift in Sources */, @@ -5343,6 +5400,7 @@ 6E7516E822C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E75191222C520D500B2B157 /* BackgroundingCallbacks.swift in Sources */, 6E7518E222C520D400B2B157 /* ConditionLeaf.swift in Sources */, + 980CC9062D833F0D00E07D24 /* Holdout.swift in Sources */, 6E75182E22C520D400B2B157 /* BatchEvent.swift in Sources */, 6E7516DC22C520D400B2B157 /* OPTUserProfileService.swift in Sources */, 6E75182222C520D400B2B157 /* BatchEventBuilder.swift in Sources */, @@ -5429,6 +5487,7 @@ 6EC6DD4E24ABF89B0017D296 /* OptimizelyUserContext.swift in Sources */, 6E75188722C520D400B2B157 /* TrafficAllocation.swift in Sources */, 6E5D122A2638DDF4000ABFC3 /* MockEventDispatcher.swift in Sources */, + 980CC9122D833F2800E07D24 /* ExperimentCore.swift in Sources */, 848617F92863E21200B7F41B /* OdpEventApiManager.swift in Sources */, 6ECB60D5234D5D9C00016D41 /* OptimizelyConfig+ObjC.swift in Sources */, 6E9B11E522C548B100C22D81 /* ThrowableConditionListTest.swift in Sources */, @@ -5445,6 +5504,7 @@ 6E7516ED22C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E75191722C520D500B2B157 /* BackgroundingCallbacks.swift in Sources */, 6E7518E722C520D400B2B157 /* ConditionLeaf.swift in Sources */, + 980CC8FD2D833F0D00E07D24 /* Holdout.swift in Sources */, 6E75183322C520D400B2B157 /* BatchEvent.swift in Sources */, 6E7516E122C520D400B2B157 /* OPTUserProfileService.swift in Sources */, 6E75182722C520D400B2B157 /* BatchEventBuilder.swift in Sources */, @@ -5518,6 +5578,7 @@ 6EF8DE3124BF7D69008B9488 /* DecisionReasons.swift in Sources */, 6E7517BC22C520D400B2B157 /* DefaultDatafileHandler.swift in Sources */, 6E7516CA22C520D400B2B157 /* OPTLogger.swift in Sources */, + 980CC9052D833F0D00E07D24 /* Holdout.swift in Sources */, 84E7ABBB27D2A1F100447CAE /* ThreadSafeLogger.swift in Sources */, 6E75182822C520D400B2B157 /* BatchEvent.swift in Sources */, 8464087028130D3200CCF97D /* Integration.swift in Sources */, @@ -5564,6 +5625,7 @@ 6E75173622C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E75185822C520D400B2B157 /* FeatureVariable.swift in Sources */, 6EF8DE1A24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, + 980CC90B2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75186422C520D400B2B157 /* Rollout.swift in Sources */, 6E75179622C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */, C78CAF582445AD8D009FE876 /* OptimizelyJSON.swift in Sources */, @@ -5676,7 +5738,9 @@ 6E75174422C520D400B2B157 /* HandlerRegistryService.swift in Sources */, 6E7516F022C520D400B2B157 /* OptimizelyError.swift in Sources */, 6E75187E22C520D400B2B157 /* TrafficAllocation.swift in Sources */, + 980CC8FA2D833F0D00E07D24 /* Holdout.swift in Sources */, 6EA2CC262345618E001E7531 /* OptimizelyConfig.swift in Sources */, + 980CC90A2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E7517CA22C520D400B2B157 /* DefaultBucketer.swift in Sources */, 8464087428130D3200CCF97D /* Integration.swift in Sources */, 6E7517FA22C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, @@ -5707,6 +5771,7 @@ 8464087328130D3200CCF97D /* Integration.swift in Sources */, 75C71A0025E454460084187E /* OptimizelyError.swift in Sources */, 75C71A0125E454460084187E /* OptimizelyLogLevel.swift in Sources */, + 980CC8FB2D833F0D00E07D24 /* Holdout.swift in Sources */, 75C71A0225E454460084187E /* OptimizelyClient.swift in Sources */, 75C71A0325E454460084187E /* OptimizelyClient+ObjC.swift in Sources */, 75C71A0425E454460084187E /* OptimizelyResult.swift in Sources */, @@ -5748,6 +5813,7 @@ 75C71A2025E454460084187E /* BatchEvent.swift in Sources */, 75C71A2125E454460084187E /* EventForDispatch.swift in Sources */, 75C71A2225E454460084187E /* SemanticVersion.swift in Sources */, + 980CC9092D833F2800E07D24 /* ExperimentCore.swift in Sources */, 75C71A2325E454460084187E /* Audience.swift in Sources */, 84E2E9452852A378001114AB /* VuidManager.swift in Sources */, 6E6522E1278E4F3800954EA1 /* OdpManager.swift in Sources */, @@ -5810,6 +5876,7 @@ 6EF8DE3324BF7D69008B9488 /* DecisionReasons.swift in Sources */, BD6485432491474500F30986 /* DefaultDatafileHandler.swift in Sources */, BD6485442491474500F30986 /* OPTLogger.swift in Sources */, + 980CC8FE2D833F0D00E07D24 /* Holdout.swift in Sources */, 84E7ABBD27D2A1F100447CAE /* ThreadSafeLogger.swift in Sources */, BD6485452491474500F30986 /* BatchEvent.swift in Sources */, 8464087228130D3200CCF97D /* Integration.swift in Sources */, @@ -5856,6 +5923,7 @@ BD6485622491474500F30986 /* MurmurHash3.swift in Sources */, BD6485632491474500F30986 /* FeatureVariable.swift in Sources */, 6EF8DE1C24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, + 980CC9132D833F2800E07D24 /* ExperimentCore.swift in Sources */, BD6485642491474500F30986 /* Rollout.swift in Sources */, BD6485652491474500F30986 /* DataStoreQueueStackImpl+Extension.swift in Sources */, BD6485662491474500F30986 /* OptimizelyJSON.swift in Sources */, diff --git a/Sources/Data Model/Experiment.swift b/Sources/Data Model/Experiment.swift index 415bcb98c..0d2438476 100644 --- a/Sources/Data Model/Experiment.swift +++ b/Sources/Data Model/Experiment.swift @@ -16,7 +16,7 @@ import Foundation -struct Experiment: Codable, OptimizelyExperiment { +struct Experiment: Codable, OptimizelyExperiment, ExperimentCore { enum Status: String, Codable { case running = "Running" case launched = "Launched" @@ -64,74 +64,9 @@ extension Experiment: Equatable { // MARK: - Utils extension Experiment { - func getVariation(id: String) -> Variation? { - return variations.filter { $0.id == id }.first - } - - func getVariation(key: String) -> Variation? { - return variations.filter { $0.key == key }.first - } var isActivated: Bool { return status == .running } - mutating func serializeAudiences(with audiencesMap: [String: String]) { - guard let conditions = audienceConditions else { return } - - let serialized = conditions.serialized - audiences = replaceAudienceIdsWithNames(string: serialized, audiencesMap: audiencesMap) - } - - /// Replace audience ids with audience names - /// - /// example: - /// - string: "(AUDIENCE(1) OR AUDIENCE(2)) AND AUDIENCE(3)" - /// - replaced: "(\"us\" OR \"female\") AND \"adult\"" - /// - /// - Parameter string: before replacement - /// - Returns: string after replacement - func replaceAudienceIdsWithNames(string: String, audiencesMap: [String: String]) -> String { - let beginWord = "AUDIENCE(" - let endWord = ")" - var keyIdx = 0 - var audienceId = "" - var collect = false - - var replaced = "" - for ch in string { - // extract audience id in parenthesis (example: AUDIENCE("35") => "35") - if collect { - if String(ch) == endWord { - // output the extracted audienceId - replaced += "\"\(audiencesMap[audienceId] ?? audienceId)\"" - collect = false - audienceId = "" - } else { - audienceId += String(ch) - } - continue - } - - // walk-through until finding a matching keyword "AUDIENCE(" - if ch == Array(beginWord)[keyIdx] { - keyIdx += 1 - if keyIdx == beginWord.count { - keyIdx = 0 - collect = true - } - continue - } else { - if keyIdx > 0 { - replaced += Array(beginWord)[.. Variation? { + return variations.filter { $0.id == id }.first + } + + func getVariation(key: String) -> Variation? { + return variations.filter { $0.key == key }.first + } + + func replaceAudienceIdsWithNames(string: String, audiencesMap: [String: String]) -> String { + let beginWord = "AUDIENCE(" + let endWord = ")" + var keyIdx = 0 + var audienceId = "" + var collect = false + + var replaced = "" + for ch in string { + if collect { + if String(ch) == endWord { + replaced += "\"\(audiencesMap[audienceId] ?? audienceId)\"" + collect = false + audienceId = "" + } else { + audienceId += String(ch) + } + continue + } + + if ch == Array(beginWord)[keyIdx] { + keyIdx += 1 + if keyIdx == beginWord.count { + keyIdx = 0 + collect = true + } + continue + } else { + if keyIdx > 0 { + replaced += Array(beginWord)[.. Date: Thu, 20 Mar 2025 23:40:26 +0600 Subject: [PATCH 2/5] wip: add holdout model test cases --- OptimizelySwiftSDK.xcodeproj/project.pbxproj | 6 + Sources/Data Model/Holdout.swift | 23 +- .../HoldoutTests.swift | 253 ++++++++++++++++++ 3 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 Tests/OptimizelyTests-DataModel/HoldoutTests.swift diff --git a/OptimizelySwiftSDK.xcodeproj/project.pbxproj b/OptimizelySwiftSDK.xcodeproj/project.pbxproj index 1dfcfdb6e..9770d72b8 100644 --- a/OptimizelySwiftSDK.xcodeproj/project.pbxproj +++ b/OptimizelySwiftSDK.xcodeproj/project.pbxproj @@ -2012,6 +2012,8 @@ 980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; 98137C552A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */; }; 98137C572A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */; }; + 982C071F2D8C82AE0068B1FF /* HoldoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */; }; + 982C07202D8C82AE0068B1FF /* HoldoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */; }; 984E2FDC2B27199B001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; 984E2FDD2B27199C001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; 984E2FDE2B27199D001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; @@ -2472,6 +2474,7 @@ 980CC9072D833F2800E07D24 /* ExperimentCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCore.swift; sourceTree = ""; }; 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Init_Async_Await.swift; sourceTree = ""; }; 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP_Aync_Await.swift; sourceTree = ""; }; + 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoldoutTests.swift; sourceTree = ""; }; 984FE5102CC8AA88004F6F41 /* UserProfileTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileTracker.swift; sourceTree = ""; }; 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; BD6485812491474500F30986 /* Optimizely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Optimizely.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3048,6 +3051,7 @@ 6E7519A922C5211100B2B157 /* UserAttributeTests.swift */, 6E7519AA22C5211100B2B157 /* GroupTests.swift */, 6E7519AB22C5211100B2B157 /* VariationTests.swift */, + 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */, 6E7519AC22C5211100B2B157 /* ExperimentTests.swift */, 6E7519AD22C5211100B2B157 /* EventTests.swift */, 6E7519AE22C5211100B2B157 /* ConditionHolderTests.swift */, @@ -4987,6 +4991,7 @@ 84861809286CF33700B7F41B /* OdpEvent.swift in Sources */, 6E75171022C520D400B2B157 /* OptimizelyClient.swift in Sources */, 6E7516C822C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 982C071F2D8C82AE0068B1FF /* HoldoutTests.swift in Sources */, 980CC9112D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E9502852A378001114AB /* VuidManager.swift in Sources */, 6E75194622C520D500B2B157 /* OPTDecisionService.swift in Sources */, @@ -5260,6 +5265,7 @@ 84861804286CF33700B7F41B /* OdpEvent.swift in Sources */, 6E75170B22C520D400B2B157 /* OptimizelyClient.swift in Sources */, 6E7516C322C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 982C07202D8C82AE0068B1FF /* HoldoutTests.swift in Sources */, 980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E94B2852A378001114AB /* VuidManager.swift in Sources */, 6E75194122C520D500B2B157 /* OPTDecisionService.swift in Sources */, diff --git a/Sources/Data Model/Holdout.swift b/Sources/Data Model/Holdout.swift index 9fc455b36..aae89876e 100644 --- a/Sources/Data Model/Holdout.swift +++ b/Sources/Data Model/Holdout.swift @@ -18,10 +18,10 @@ import Foundation struct Holdout: Codable, ExperimentCore { enum Status: String, Codable { - case running = "Running" - case archived = "Archived" case draft = "Draft" + case running = "Running" case concluded = "Concluded" + case archived = "Archived" } var id: String @@ -32,8 +32,8 @@ struct Holdout: Codable, ExperimentCore { var trafficAllocation: [TrafficAllocation] var audienceIds: [String] var audienceConditions: ConditionHolder? - var excludedFlags: [String] - var includedFlags: [String] + var includedFlags: [String]? + var excludedFlags: [String]? enum CodingKeys: String, CodingKey { case id, key, status, layerId, variations, trafficAllocation, audienceIds, audienceConditions, includedFlags, excludedFlags @@ -43,6 +43,21 @@ struct Holdout: Codable, ExperimentCore { var audiences: String = "" } +extension Holdout: Equatable { + static func == (lhs: Holdout, rhs: Holdout) -> Bool { + return lhs.id == rhs.id && + lhs.key == rhs.key && + lhs.status == rhs.status && + lhs.layerId == rhs.layerId && + lhs.variations == rhs.variations && + lhs.trafficAllocation == rhs.trafficAllocation && + lhs.audienceIds == rhs.audienceIds && + lhs.audienceConditions == rhs.audienceConditions && + lhs.includedFlags == rhs.includedFlags && + lhs.excludedFlags == rhs.excludedFlags + } +} + extension Holdout { var isActivated: Bool { diff --git a/Tests/OptimizelyTests-DataModel/HoldoutTests.swift b/Tests/OptimizelyTests-DataModel/HoldoutTests.swift new file mode 100644 index 000000000..b065165b4 --- /dev/null +++ b/Tests/OptimizelyTests-DataModel/HoldoutTests.swift @@ -0,0 +1,253 @@ +// +// Copyright 2022, Optimizely, Inc. and contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +import XCTest + +// MARK: - Sample Data + +class HoldoutTests: XCTestCase { + /// Global holoout without included and excluded key + static var sampleData: [String: Any] = ["id": "11111", + "key": "background", + "status": "Running", + "layerId": "22222", + "variations": [VariationTests.sampleData], + "trafficAllocation": [TrafficAllocationTests.sampleData], + "audienceIds": ["33333"], + "audienceConditions": ConditionHolderTests.sampleData] + +} + +// MARK: - Decode + +extension HoldoutTests { + + func testDecodeSuccessWithJSONValid() { + let data: [String: Any] = HoldoutTests.sampleData + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData))) + } + + func testDecodeSuccessWithIncludedFlags() { + var data: [String: Any] = HoldoutTests.sampleData + data["includedFlags"] = ["4444", "5555"] + + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData))) + XCTAssertEqual(model.includedFlags, ["4444", "5555"]) + } + + func testDecodeSuccessWithExcludedFlags() { + var data: [String: Any] = HoldoutTests.sampleData + data["excludedFlags"] = ["4444", "5555"] + + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData))) + XCTAssertEqual(model.excludedFlags, ["4444", "5555"]) + } + + + func testDecodeSuccessWithMissingAudienceConditions() { + var data: [String: Any] = HoldoutTests.sampleData + data["audienceConditions"] = nil + + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + } + + func testDecodeFailWithMissingId() { + var data: [String: Any] = HoldoutTests.sampleData + data["id"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingKey() { + var data: [String: Any] = HoldoutTests.sampleData + data["key"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingStatus() { + var data: [String: Any] = HoldoutTests.sampleData + data["status"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingLayerId() { + var data: [String: Any] = HoldoutTests.sampleData + data["layerId"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingVariations() { + var data: [String: Any] = HoldoutTests.sampleData + data["variations"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingTrafficAllocation() { + var data: [String: Any] = HoldoutTests.sampleData + data["trafficAllocation"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + +} + +// MARK: - Encode + +extension HoldoutTests { + + func testEncodeJSON() { + let data: [String: Any] = HoldoutTests.sampleData + let modelGiven: Holdout = try! OTUtils.model(from: data) + + XCTAssert(OTUtils.isEqualWithEncodeThenDecode(modelGiven)) + } + +} + +// MARK: - audiences serialization + +extension HoldoutTests { + + func testAudiencesSerialization() { + let commonData: [String: Any] = ["id": "11111", + "key": "background", + "status": "Running", + "layerId": "22222", + "variations": [VariationTests.sampleData], + "trafficAllocation": [TrafficAllocationTests.sampleData], + "audienceIds": [], + "audienceConditions": [], + "forcedVariations": ["12345": "1234567890"]] + + let audiencesMap = [ + "1": "us", + "11": "fr", + "2": "female", + "12": "male", + "3": "adult", + "13": "kid" + ] + + let audiencesInput: [Any] = [ + [], + ["or", "1", "2"], + ["and", "1", "2", "3"], + ["not", "1"], + ["or", "1"], + ["and", "1"], + ["1"], + ["1", "2"], + ["and", ["or", "1", "2"], "3"], + ["and", ["or", "1", ["and", "2", "3"]], ["and", "11", ["or", "12", "13"]]], + ["not", ["and", "1", "2"]], + ["or", "1", "100000"], + ["and", "and"] + ] + + let audiencesOutput: [String] = [ + "", + "\"us\" OR \"female\"", + "\"us\" AND \"female\" AND \"adult\"", + "NOT \"us\"", + "\"us\"", + "\"us\"", + "\"us\"", + "\"us\" OR \"female\"", + "(\"us\" OR \"female\") AND \"adult\"", + "(\"us\" OR (\"female\" AND \"adult\")) AND (\"fr\" AND (\"male\" OR \"kid\"))", + "NOT (\"us\" AND \"female\")", + "\"us\" OR \"100000\"", + "" + ] + + for (idx, audience) in audiencesInput.enumerated() { + var data = commonData + data["audienceConditions"] = audience + var model: Holdout = try! OTUtils.model(from: data) + model.serializeAudiences(with: audiencesMap) + XCTAssertEqual(model.audiences, audiencesOutput[idx]) + } + } + +} + +// MARK: - Test Utils + +extension HoldoutTests { + + func testIsActivated() { + let data: [String: Any] = HoldoutTests.sampleData + var model: Holdout = try! OTUtils.model(from: data) + + XCTAssertTrue(model.isActivated) + + let allNotActiveStates: [Holdout.Status] = [.draft, .concluded, .archived] + for status in allNotActiveStates { + model.status = status + XCTAssertFalse(model.isActivated) + } + + model.status = .running + XCTAssertTrue(model.isActivated) + } +} + From 326adced4a13c30aae851782279c6ecf69dd25c4 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Mon, 24 Mar 2025 23:59:41 +0600 Subject: [PATCH 3/5] wip: included and excluded flags updated to required --- Sources/Data Model/Holdout.swift | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Sources/Data Model/Holdout.swift b/Sources/Data Model/Holdout.swift index aae89876e..b8f8ba504 100644 --- a/Sources/Data Model/Holdout.swift +++ b/Sources/Data Model/Holdout.swift @@ -32,8 +32,8 @@ struct Holdout: Codable, ExperimentCore { var trafficAllocation: [TrafficAllocation] var audienceIds: [String] var audienceConditions: ConditionHolder? - var includedFlags: [String]? - var excludedFlags: [String]? + var includedFlags: [String] + var excludedFlags: [String] enum CodingKeys: String, CodingKey { case id, key, status, layerId, variations, trafficAllocation, audienceIds, audienceConditions, includedFlags, excludedFlags @@ -41,6 +41,23 @@ struct Holdout: Codable, ExperimentCore { // replace with serialized string representation with audience names when ProjectConfig is ready var audiences: String = "" + + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + id = try container.decode(String.self, forKey: .id) + key = try container.decode(String.self, forKey: .key) + status = try container.decode(Status.self, forKey: .status) + layerId = try container.decode(String.self, forKey: .layerId) + variations = try container.decode([Variation].self, forKey: .variations) + trafficAllocation = try container.decode([TrafficAllocation].self, forKey: .trafficAllocation) + audienceIds = try container.decode([String].self, forKey: .audienceIds) + audienceConditions = try container.decodeIfPresent(ConditionHolder.self, forKey: .audienceConditions) + + includedFlags = try container.decodeIfPresent([String].self, forKey: .includedFlags) ?? [] + excludedFlags = try container.decodeIfPresent([String].self, forKey: .excludedFlags) ?? [] + } } extension Holdout: Equatable { From d1cc64d49b27201419d2d8a1ac1f7383e3483f1a Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Tue, 25 Mar 2025 21:32:00 +0600 Subject: [PATCH 4/5] wip: move OptimizelyExperiment compliance to OptmizelyConfig file --- Sources/Data Model/Experiment.swift | 2 +- Sources/Optimizely/OptimizelyConfig.swift | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Data Model/Experiment.swift b/Sources/Data Model/Experiment.swift index 0d2438476..ab75730ec 100644 --- a/Sources/Data Model/Experiment.swift +++ b/Sources/Data Model/Experiment.swift @@ -16,7 +16,7 @@ import Foundation -struct Experiment: Codable, OptimizelyExperiment, ExperimentCore { +struct Experiment: Codable, ExperimentCore { enum Status: String, Codable { case running = "Running" case launched = "Launched" diff --git a/Sources/Optimizely/OptimizelyConfig.swift b/Sources/Optimizely/OptimizelyConfig.swift index e04d3002c..7ffd3df52 100644 --- a/Sources/Optimizely/OptimizelyConfig.swift +++ b/Sources/Optimizely/OptimizelyConfig.swift @@ -42,6 +42,9 @@ public protocol OptimizelyExperiment { var variationsMap: [String: OptimizelyVariation] { get } } +// Experiment compliances OptimizelyExperiment +extension Experiment: OptimizelyExperiment { } + public protocol OptimizelyFeature { var id: String { get } var key: String { get } From 697546e1c02fb4cbb2527d8e0793bb839de0d653 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Fri, 4 Apr 2025 17:48:13 +0600 Subject: [PATCH 5/5] Experiment core protocol compliance OptimizelyExperiment protocol --- Sources/Data Model/ExperimentCore.swift | 4 +--- Sources/Data Model/Holdout.swift | 1 + Sources/Optimizely/OptimizelyConfig.swift | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Sources/Data Model/ExperimentCore.swift b/Sources/Data Model/ExperimentCore.swift index 73cfa3530..fc65b7cb4 100644 --- a/Sources/Data Model/ExperimentCore.swift +++ b/Sources/Data Model/ExperimentCore.swift @@ -16,9 +16,7 @@ import Foundation -protocol ExperimentCore { - var id: String { get } - var key: String { get } +protocol ExperimentCore: OptimizelyExperiment { var audiences: String { get set } var layerId: String { get } var variations: [Variation] { get } diff --git a/Sources/Data Model/Holdout.swift b/Sources/Data Model/Holdout.swift index b8f8ba504..16a24ba00 100644 --- a/Sources/Data Model/Holdout.swift +++ b/Sources/Data Model/Holdout.swift @@ -39,6 +39,7 @@ struct Holdout: Codable, ExperimentCore { case id, key, status, layerId, variations, trafficAllocation, audienceIds, audienceConditions, includedFlags, excludedFlags } + var variationsMap: [String : OptimizelyVariation] = [:] // replace with serialized string representation with audience names when ProjectConfig is ready var audiences: String = "" diff --git a/Sources/Optimizely/OptimizelyConfig.swift b/Sources/Optimizely/OptimizelyConfig.swift index 7ffd3df52..e04d3002c 100644 --- a/Sources/Optimizely/OptimizelyConfig.swift +++ b/Sources/Optimizely/OptimizelyConfig.swift @@ -42,9 +42,6 @@ public protocol OptimizelyExperiment { var variationsMap: [String: OptimizelyVariation] { get } } -// Experiment compliances OptimizelyExperiment -extension Experiment: OptimizelyExperiment { } - public protocol OptimizelyFeature { var id: String { get } var key: String { get }