Skip to content

Commit 38386cd

Browse files
wip: add holdout model test cases
1 parent f5e60a7 commit 38386cd

File tree

3 files changed

+278
-4
lines changed

3 files changed

+278
-4
lines changed

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,8 @@
20122012
980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; };
20132013
98137C552A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */; };
20142014
98137C572A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */; };
2015+
982C071F2D8C82AE0068B1FF /* HoldoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */; };
2016+
982C07202D8C82AE0068B1FF /* HoldoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */; };
20152017
984E2FDC2B27199B001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; };
20162018
984E2FDD2B27199C001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; };
20172019
984E2FDE2B27199D001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; };
@@ -2472,6 +2474,7 @@
24722474
980CC9072D833F2800E07D24 /* ExperimentCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCore.swift; sourceTree = "<group>"; };
24732475
98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Init_Async_Await.swift; sourceTree = "<group>"; };
24742476
98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP_Aync_Await.swift; sourceTree = "<group>"; };
2477+
982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoldoutTests.swift; sourceTree = "<group>"; };
24752478
984FE5102CC8AA88004F6F41 /* UserProfileTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileTracker.swift; sourceTree = "<group>"; };
24762479
987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
24772480
BD6485812491474500F30986 /* Optimizely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Optimizely.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -3048,6 +3051,7 @@
30483051
6E7519A922C5211100B2B157 /* UserAttributeTests.swift */,
30493052
6E7519AA22C5211100B2B157 /* GroupTests.swift */,
30503053
6E7519AB22C5211100B2B157 /* VariationTests.swift */,
3054+
982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */,
30513055
6E7519AC22C5211100B2B157 /* ExperimentTests.swift */,
30523056
6E7519AD22C5211100B2B157 /* EventTests.swift */,
30533057
6E7519AE22C5211100B2B157 /* ConditionHolderTests.swift */,
@@ -4987,6 +4991,7 @@
49874991
84861809286CF33700B7F41B /* OdpEvent.swift in Sources */,
49884992
6E75171022C520D400B2B157 /* OptimizelyClient.swift in Sources */,
49894993
6E7516C822C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */,
4994+
982C071F2D8C82AE0068B1FF /* HoldoutTests.swift in Sources */,
49904995
980CC9112D833F2800E07D24 /* ExperimentCore.swift in Sources */,
49914996
84E2E9502852A378001114AB /* VuidManager.swift in Sources */,
49924997
6E75194622C520D500B2B157 /* OPTDecisionService.swift in Sources */,
@@ -5260,6 +5265,7 @@
52605265
84861804286CF33700B7F41B /* OdpEvent.swift in Sources */,
52615266
6E75170B22C520D400B2B157 /* OptimizelyClient.swift in Sources */,
52625267
6E7516C322C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */,
5268+
982C07202D8C82AE0068B1FF /* HoldoutTests.swift in Sources */,
52635269
980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */,
52645270
84E2E94B2852A378001114AB /* VuidManager.swift in Sources */,
52655271
6E75194122C520D500B2B157 /* OPTDecisionService.swift in Sources */,

Sources/Data Model/Holdout.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import Foundation
1818

1919
struct Holdout: Codable, ExperimentCore {
2020
enum Status: String, Codable {
21-
case running = "Running"
22-
case archived = "Archived"
2321
case draft = "Draft"
22+
case running = "Running"
2423
case concluded = "Concluded"
24+
case archived = "Archived"
2525
}
2626

2727
var id: String
@@ -32,8 +32,8 @@ struct Holdout: Codable, ExperimentCore {
3232
var trafficAllocation: [TrafficAllocation]
3333
var audienceIds: [String]
3434
var audienceConditions: ConditionHolder?
35-
var excludedFlags: [String]
36-
var includedFlags: [String]
35+
var includedFlags: [String]?
36+
var excludedFlags: [String]?
3737

3838
enum CodingKeys: String, CodingKey {
3939
case id, key, status, layerId, variations, trafficAllocation, audienceIds, audienceConditions, includedFlags, excludedFlags
@@ -43,6 +43,21 @@ struct Holdout: Codable, ExperimentCore {
4343
var audiences: String = ""
4444
}
4545

46+
extension Holdout: Equatable {
47+
static func == (lhs: Holdout, rhs: Holdout) -> Bool {
48+
return lhs.id == rhs.id &&
49+
lhs.key == rhs.key &&
50+
lhs.status == rhs.status &&
51+
lhs.layerId == rhs.layerId &&
52+
lhs.variations == rhs.variations &&
53+
lhs.trafficAllocation == rhs.trafficAllocation &&
54+
lhs.audienceIds == rhs.audienceIds &&
55+
lhs.audienceConditions == rhs.audienceConditions &&
56+
lhs.includedFlags == rhs.includedFlags &&
57+
lhs.excludedFlags == rhs.excludedFlags
58+
}
59+
}
60+
4661

4762
extension Holdout {
4863
var isActivated: Bool {
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
//
2+
// Copyright 2022, Optimizely, Inc. and contributors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
18+
import XCTest
19+
20+
// MARK: - Sample Data
21+
22+
class HoldoutTests: XCTestCase {
23+
/// Global holoout without included and excluded key
24+
static var sampleData: [String: Any] = ["id": "11111",
25+
"key": "background",
26+
"status": "Running",
27+
"layerId": "22222",
28+
"variations": [VariationTests.sampleData],
29+
"trafficAllocation": [TrafficAllocationTests.sampleData],
30+
"audienceIds": ["33333"],
31+
"audienceConditions": ConditionHolderTests.sampleData]
32+
33+
}
34+
35+
// MARK: - Decode
36+
37+
extension HoldoutTests {
38+
39+
func testDecodeSuccessWithJSONValid() {
40+
let data: [String: Any] = HoldoutTests.sampleData
41+
let model: Holdout = try! OTUtils.model(from: data)
42+
43+
XCTAssert(model.id == "11111")
44+
XCTAssert(model.key == "background")
45+
XCTAssert(model.status == .running)
46+
XCTAssert(model.layerId == "22222")
47+
XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)])
48+
XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)])
49+
XCTAssert(model.audienceIds == ["33333"])
50+
XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData)))
51+
}
52+
53+
func testDecodeSuccessWithIncludedFlags() {
54+
var data: [String: Any] = HoldoutTests.sampleData
55+
data["includedFlags"] = ["4444", "5555"]
56+
57+
let model: Holdout = try! OTUtils.model(from: data)
58+
59+
XCTAssert(model.id == "11111")
60+
XCTAssert(model.key == "background")
61+
XCTAssert(model.status == .running)
62+
XCTAssert(model.layerId == "22222")
63+
XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)])
64+
XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)])
65+
XCTAssert(model.audienceIds == ["33333"])
66+
XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData)))
67+
XCTAssertEqual(model.includedFlags, ["4444", "5555"])
68+
}
69+
70+
func testDecodeSuccessWithExcludedFlags() {
71+
var data: [String: Any] = HoldoutTests.sampleData
72+
data["excludedFlags"] = ["4444", "5555"]
73+
74+
let model: Holdout = try! OTUtils.model(from: data)
75+
76+
XCTAssert(model.id == "11111")
77+
XCTAssert(model.key == "background")
78+
XCTAssert(model.status == .running)
79+
XCTAssert(model.layerId == "22222")
80+
XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)])
81+
XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)])
82+
XCTAssert(model.audienceIds == ["33333"])
83+
XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData)))
84+
XCTAssertEqual(model.excludedFlags, ["4444", "5555"])
85+
}
86+
87+
88+
func testDecodeSuccessWithMissingAudienceConditions() {
89+
var data: [String: Any] = HoldoutTests.sampleData
90+
data["audienceConditions"] = nil
91+
92+
let model: Holdout = try! OTUtils.model(from: data)
93+
94+
XCTAssert(model.id == "11111")
95+
XCTAssert(model.key == "background")
96+
XCTAssert(model.status == .running)
97+
XCTAssert(model.layerId == "22222")
98+
XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)])
99+
XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)])
100+
XCTAssert(model.audienceIds == ["33333"])
101+
}
102+
103+
func testDecodeFailWithMissingId() {
104+
var data: [String: Any] = HoldoutTests.sampleData
105+
data["id"] = nil
106+
107+
let model: Holdout? = try? OTUtils.model(from: data)
108+
XCTAssertNil(model)
109+
}
110+
111+
func testDecodeFailWithMissingKey() {
112+
var data: [String: Any] = HoldoutTests.sampleData
113+
data["key"] = nil
114+
115+
let model: Holdout? = try? OTUtils.model(from: data)
116+
XCTAssertNil(model)
117+
}
118+
119+
func testDecodeFailWithMissingStatus() {
120+
var data: [String: Any] = HoldoutTests.sampleData
121+
data["status"] = nil
122+
123+
let model: Holdout? = try? OTUtils.model(from: data)
124+
XCTAssertNil(model)
125+
}
126+
127+
func testDecodeFailWithMissingLayerId() {
128+
var data: [String: Any] = HoldoutTests.sampleData
129+
data["layerId"] = nil
130+
131+
let model: Holdout? = try? OTUtils.model(from: data)
132+
XCTAssertNil(model)
133+
}
134+
135+
func testDecodeFailWithMissingVariations() {
136+
var data: [String: Any] = HoldoutTests.sampleData
137+
data["variations"] = nil
138+
139+
let model: Holdout? = try? OTUtils.model(from: data)
140+
XCTAssertNil(model)
141+
}
142+
143+
func testDecodeFailWithMissingTrafficAllocation() {
144+
var data: [String: Any] = HoldoutTests.sampleData
145+
data["trafficAllocation"] = nil
146+
147+
let model: Holdout? = try? OTUtils.model(from: data)
148+
XCTAssertNil(model)
149+
}
150+
151+
}
152+
153+
// MARK: - Encode
154+
155+
extension HoldoutTests {
156+
157+
func testEncodeJSON() {
158+
let data: [String: Any] = HoldoutTests.sampleData
159+
let modelGiven: Holdout = try! OTUtils.model(from: data)
160+
161+
XCTAssert(OTUtils.isEqualWithEncodeThenDecode(modelGiven))
162+
}
163+
164+
}
165+
166+
// MARK: - audiences serialization
167+
168+
extension HoldoutTests {
169+
170+
func testAudiencesSerialization() {
171+
let commonData: [String: Any] = ["id": "11111",
172+
"key": "background",
173+
"status": "Running",
174+
"layerId": "22222",
175+
"variations": [VariationTests.sampleData],
176+
"trafficAllocation": [TrafficAllocationTests.sampleData],
177+
"audienceIds": [],
178+
"audienceConditions": [],
179+
"forcedVariations": ["12345": "1234567890"]]
180+
181+
let audiencesMap = [
182+
"1": "us",
183+
"11": "fr",
184+
"2": "female",
185+
"12": "male",
186+
"3": "adult",
187+
"13": "kid"
188+
]
189+
190+
let audiencesInput: [Any] = [
191+
[],
192+
["or", "1", "2"],
193+
["and", "1", "2", "3"],
194+
["not", "1"],
195+
["or", "1"],
196+
["and", "1"],
197+
["1"],
198+
["1", "2"],
199+
["and", ["or", "1", "2"], "3"],
200+
["and", ["or", "1", ["and", "2", "3"]], ["and", "11", ["or", "12", "13"]]],
201+
["not", ["and", "1", "2"]],
202+
["or", "1", "100000"],
203+
["and", "and"]
204+
]
205+
206+
let audiencesOutput: [String] = [
207+
"",
208+
"\"us\" OR \"female\"",
209+
"\"us\" AND \"female\" AND \"adult\"",
210+
"NOT \"us\"",
211+
"\"us\"",
212+
"\"us\"",
213+
"\"us\"",
214+
"\"us\" OR \"female\"",
215+
"(\"us\" OR \"female\") AND \"adult\"",
216+
"(\"us\" OR (\"female\" AND \"adult\")) AND (\"fr\" AND (\"male\" OR \"kid\"))",
217+
"NOT (\"us\" AND \"female\")",
218+
"\"us\" OR \"100000\"",
219+
""
220+
]
221+
222+
for (idx, audience) in audiencesInput.enumerated() {
223+
var data = commonData
224+
data["audienceConditions"] = audience
225+
var model: Holdout = try! OTUtils.model(from: data)
226+
model.serializeAudiences(with: audiencesMap)
227+
XCTAssertEqual(model.audiences, audiencesOutput[idx])
228+
}
229+
}
230+
231+
}
232+
233+
// MARK: - Test Utils
234+
235+
extension HoldoutTests {
236+
237+
func testIsActivated() {
238+
let data: [String: Any] = HoldoutTests.sampleData
239+
var model: Holdout = try! OTUtils.model(from: data)
240+
241+
XCTAssertTrue(model.isActivated)
242+
243+
let allNotActiveStates: [Holdout.Status] = [.draft, .concluded, .archived]
244+
for status in allNotActiveStates {
245+
model.status = status
246+
XCTAssertFalse(model.isActivated)
247+
}
248+
249+
model.status = .running
250+
XCTAssertTrue(model.isActivated)
251+
}
252+
}
253+

0 commit comments

Comments
 (0)