@@ -106,35 +106,53 @@ extension OptimizelyUserContextTests_Decide_Reasons {
106
106
XCTAssert ( decision. reasons. contains ( OptimizelyError . conditionInvalidFormat ( " Empty condition array " ) . reason) )
107
107
}
108
108
109
- // func testDecideReasons_evaluateAttributeInvalidCondition() {
110
- // let featureKey = "feature_1"
111
- // let audienceId = "invalid_condition"
112
- // setAudienceForFeatureTest(featureKey: featureKey, audienceId: audienceId)
113
- //
114
- // let condition = "{\"match\":\"gt\",\"value\":\"US\",\"name\":\"age\",\"type\":\"custom_attribute\"}"
115
- // user.setAttribute(key: "age", value: 25)
116
- //
117
- // var decision = user.decide(key: featureKey)
118
- // XCTAssert(decision.reasons.isEmpty)
119
- // decision = user.decide(key: featureKey, options: [.includeReasons])
120
- // XCTAssert(decision.reasons.contains(OptimizelyError.evaluateAttributeInvalidCondition(condition).reason))
121
- // }
109
+ func testDecideReasons_evaluateAttributeInvalidCondition( ) {
110
+ let featureKey = " feature_1 "
111
+ let audienceId = " invalid_condition "
112
+ setAudienceForFeatureTest ( featureKey: featureKey, audienceId: audienceId)
113
+
114
+ let condition = " { \" match \" : \" gt \" , \" value \" : \" US \" , \" name \" : \" age \" , \" type \" : \" custom_attribute \" } "
115
+ user. setAttribute ( key: " age " , value: 25 )
116
+
117
+ var decision = user. decide ( key: featureKey)
118
+ XCTAssert ( decision. reasons. isEmpty)
119
+ decision = user. decide ( key: featureKey, options: [ . includeReasons] )
120
+
121
+ let expectedReason = OptimizelyError . evaluateAttributeInvalidCondition ( condition) . reason
122
+
123
+ // Look for a matching reason in the decision.reasons array
124
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
125
+
126
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
127
+
128
+ //XCTAssert(decision.reasons.contains(OptimizelyError.evaluateAttributeInvalidCondition(condition).reason))
129
+ }
122
130
123
- // func testDecideReasons_evaluateAttributeInvalidType() {
124
- // let featureKey = "feature_1"
125
- // let audienceId = "13389130056"
126
- // setAudienceForFeatureTest(featureKey: featureKey, audienceId: audienceId)
127
- //
128
- // let condition = "{\"match\":\"exact\",\"value\":\"US\",\"name\":\"country\",\"type\":\"custom_attribute\"}"
129
- // let attributeKey = "country"
130
- // let attributeValue = 25
131
- // user.setAttribute(key: attributeKey, value: attributeValue)
132
- //
133
- // var decision = user.decide(key: featureKey)
134
- // XCTAssert(decision.reasons.isEmpty)
131
+ func testDecideReasons_evaluateAttributeInvalidType( ) {
132
+ let featureKey = " feature_1 "
133
+ let audienceId = " 13389130056 "
134
+ setAudienceForFeatureTest ( featureKey: featureKey, audienceId: audienceId)
135
+
136
+ let condition = " { \" match \" : \" exact \" , \" value \" : \" US \" , \" name \" : \" country \" , \" type \" : \" custom_attribute \" } "
137
+ let attributeKey = " country "
138
+ let attributeValue = 25
139
+ user. setAttribute ( key: attributeKey, value: attributeValue)
140
+
141
+ var decision = user. decide ( key: featureKey)
142
+ XCTAssert ( decision. reasons. isEmpty)
143
+
144
+ decision = user. decide ( key: featureKey, options: [ . includeReasons] )
145
+
146
+ let expectedReason = OptimizelyError . evaluateAttributeInvalidType ( condition, attributeValue, attributeKey) . reason
147
+
148
+ // Look for a matching reason in the decision.reasons array
149
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
150
+
151
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
152
+
135
153
// decision = user.decide(key: featureKey, options: [.includeReasons])
136
154
// XCTAssert(decision.reasons.contains(OptimizelyError.evaluateAttributeInvalidType(condition, attributeValue, attributeKey).reason))
137
- // }
155
+ }
138
156
139
157
func testDecideReasons_evaluateAttributeValueOutOfRange( ) {
140
158
let featureKey = " feature_1 "
@@ -146,8 +164,17 @@ extension OptimizelyUserContextTests_Decide_Reasons {
146
164
147
165
var decision = user. decide ( key: featureKey)
148
166
XCTAssert ( decision. reasons. isEmpty)
167
+
149
168
decision = user. decide ( key: featureKey, options: [ . includeReasons] )
150
- XCTAssert ( decision. reasons. contains ( OptimizelyError . evaluateAttributeValueOutOfRange ( condition, " age " ) . reason) )
169
+
170
+ let expectedReason = OptimizelyError . evaluateAttributeValueOutOfRange ( condition, " age " ) . reason
171
+
172
+ // Look for a matching reason in the decision.reasons array
173
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
174
+
175
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
176
+
177
+ //XCTAssert(decision.reasons.contains(OptimizelyError.evaluateAttributeValueOutOfRange(condition, "age").reason))
151
178
}
152
179
153
180
func testDecideReasons_userAttributeInvalidType( ) {
@@ -161,7 +188,15 @@ extension OptimizelyUserContextTests_Decide_Reasons {
161
188
var decision = user. decide ( key: featureKey)
162
189
XCTAssert ( decision. reasons. isEmpty)
163
190
decision = user. decide ( key: featureKey, options: [ . includeReasons] )
164
- XCTAssert ( decision. reasons. contains ( OptimizelyError . userAttributeInvalidType ( condition) . reason) )
191
+
192
+ let expectedReason = OptimizelyError . userAttributeInvalidType ( condition) . reason
193
+
194
+ // Look for a matching reason in the decision.reasons array
195
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
196
+
197
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
198
+
199
+ //XCTAssert(decision.reasons.contains(OptimizelyError.userAttributeInvalidType(condition).reason))
165
200
}
166
201
167
202
func testDecideReasons_userAttributeInvalidMatch( ) {
@@ -175,7 +210,15 @@ extension OptimizelyUserContextTests_Decide_Reasons {
175
210
var decision = user. decide ( key: featureKey)
176
211
XCTAssert ( decision. reasons. isEmpty)
177
212
decision = user. decide ( key: featureKey, options: [ . includeReasons] )
178
- XCTAssert ( decision. reasons. contains ( OptimizelyError . userAttributeInvalidMatch ( condition) . reason) )
213
+
214
+ let expectedReason = OptimizelyError . userAttributeInvalidMatch ( condition) . reason
215
+
216
+ // Look for a matching reason in the decision.reasons array
217
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
218
+
219
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
220
+
221
+ //XCTAssert(decision.reasons.contains(OptimizelyError.userAttributeInvalidMatch(condition).reason))
179
222
}
180
223
181
224
func testDecideReasons_userAttributeNilValue( ) {
@@ -188,8 +231,17 @@ extension OptimizelyUserContextTests_Decide_Reasons {
188
231
189
232
var decision = user. decide ( key: featureKey)
190
233
XCTAssert ( decision. reasons. isEmpty)
234
+
191
235
decision = user. decide ( key: featureKey, options: [ . includeReasons] )
192
- XCTAssert ( decision. reasons. contains ( OptimizelyError . userAttributeNilValue ( condition) . reason) )
236
+
237
+ let expectedReason = OptimizelyError . userAttributeNilValue ( condition) . reason
238
+
239
+ // Look for a matching reason in the decision.reasons array
240
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
241
+
242
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
243
+
244
+ // XCTAssert(decision.reasons.contains(OptimizelyError.userAttributeNilValue(condition).reason))
193
245
}
194
246
195
247
func testDecideReasons_userAttributeInvalidName( ) {
@@ -202,8 +254,17 @@ extension OptimizelyUserContextTests_Decide_Reasons {
202
254
203
255
var decision = user. decide ( key: featureKey)
204
256
XCTAssert ( decision. reasons. isEmpty)
257
+
205
258
decision = user. decide ( key: featureKey, options: [ . includeReasons] )
206
- XCTAssert ( decision. reasons. contains ( OptimizelyError . userAttributeInvalidName ( condition) . reason) )
259
+
260
+ let expectedReason = OptimizelyError . userAttributeInvalidName ( condition) . reason
261
+
262
+ // Look for a matching reason in the decision.reasons array
263
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
264
+
265
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
266
+
267
+ //XCTAssert(decision.reasons.contains(OptimizelyError.userAttributeInvalidName(condition).reason))
207
268
}
208
269
209
270
func testDecideReasons_missingAttributeValue( ) {
@@ -215,8 +276,17 @@ extension OptimizelyUserContextTests_Decide_Reasons {
215
276
216
277
var decision = user. decide ( key: featureKey)
217
278
XCTAssert ( decision. reasons. isEmpty)
279
+
218
280
decision = user. decide ( key: featureKey, options: [ . includeReasons] )
219
- XCTAssert ( decision. reasons. contains ( OptimizelyError . missingAttributeValue ( condition, " age " ) . reason) )
281
+
282
+ let expectedReason = OptimizelyError . missingAttributeValue ( condition, " age " ) . reason
283
+
284
+ // Look for a matching reason in the decision.reasons array
285
+ let foundMatchingReason = matchReason ( expectedReason: expectedReason, in: decision. reasons)
286
+
287
+ XCTAssert ( foundMatchingReason, " Expected reason with matching condition not found in decision reasons " )
288
+
289
+ //XCTAssert(decision.reasons.contains(OptimizelyError.missingAttributeValue(condition, "age").reason))
220
290
}
221
291
222
292
}
@@ -462,7 +532,46 @@ extension OptimizelyUserContextTests_Decide_Reasons {
462
532
// Utils
463
533
464
534
extension OptimizelyUserContextTests_Decide_Reasons {
465
-
535
+ func matchReason( expectedReason: String , in reasons: [ String ] ) -> Bool {
536
+ // Extract the condition JSON and remaining strings from expectedReason
537
+ let ( expectedPrefix, expectedJsonCondition, expectedSuffix) = extractComponents ( from: expectedReason) ?? ( " " , [ : ] , " " )
538
+
539
+ // Look for a matching reason in the decision.reasons array
540
+ let foundMatchingReason = reasons. contains { reason in
541
+ guard let ( actualPrefix, actualJsonCondition, actualSuffix) = extractComponents ( from: reason) else {
542
+ return false
543
+ }
544
+
545
+ // Check if the prefix and suffix match exactly and the JSON conditions match ignoring key order
546
+ return expectedPrefix == actualPrefix &&
547
+ expectedSuffix == actualSuffix &&
548
+ NSDictionary ( dictionary: expectedJsonCondition) == NSDictionary ( dictionary: actualJsonCondition)
549
+ }
550
+ return foundMatchingReason
551
+ }
552
+
553
+ // Helper function to extract prefix, JSON condition, and suffix from the reason string
554
+ func extractComponents( from reason: String ) -> ( String , [ String : Any ] , String ) ? {
555
+ // Use regular expression to extract the parts of the string before, inside, and after the parentheses
556
+ let pattern = " ^(.*?) \\ ((.*?) \\ )(.*?)$ "
557
+ let regex = try ? NSRegularExpression ( pattern: pattern)
558
+ let nsString = reason as NSString
559
+ if let match = regex? . firstMatch ( in: reason, range: NSRange ( location: 0 , length: nsString. length) ) {
560
+ let prefix = nsString. substring ( with: match. range ( at: 1 ) ) // Part before the parentheses
561
+ let jsonString = nsString. substring ( with: match. range ( at: 2 ) ) // JSON inside parentheses
562
+ let suffix = nsString. substring ( with: match. range ( at: 3 ) ) // Part after the parentheses
563
+
564
+ // Convert the JSON string into a dictionary
565
+ if let data = jsonString. data ( using: . utf8) ,
566
+ let jsonObject = try ? JSONSerialization . jsonObject ( with: data, options: [ ] ) ,
567
+ let jsonDict = jsonObject as? [ String : Any ] {
568
+ return ( prefix, jsonDict, suffix)
569
+ }
570
+ }
571
+
572
+ return nil
573
+ }
574
+
466
575
func setAudienceForFeatureTest( featureKey: String , audienceId: String ) {
467
576
let experimentId = " 10390977673 " // "exp_with_audience"
468
577
var experiment = optimizely. config!. getExperiment ( id: experimentId) !
0 commit comments