@@ -193,6 +193,308 @@ public function testDecodeMulti(array $feature)
193193 self ::assertInstanceOf (FeatureFlag::class, $ featureFlag );
194194 }
195195
196+ public function testFlagReturnsOffVariationIfFlagIsOff ()
197+ {
198+ $ flagJson = array (
199+ 'key ' => 'feature ' ,
200+ 'version ' => 1 ,
201+ 'deleted ' => false ,
202+ 'on ' => false ,
203+ 'targets ' => array (),
204+ 'prerequisites ' => array (),
205+ 'rules ' => array (),
206+ 'offVariation ' => 1 ,
207+ 'fallthrough ' => array ('variation ' => 0 ),
208+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
209+ 'salt ' => ''
210+ );
211+ $ flag = FeatureFlag::decode ($ flagJson );
212+ $ ub = new LDUserBuilder ('x ' );
213+ $ user = $ ub ->build ();
214+
215+ $ result = $ flag ->evaluate ($ user , null );
216+ self ::assertEquals (1 , $ result ->getVariation ());
217+ self ::assertEquals ('off ' , $ result ->getValue ());
218+ self ::assertEquals (array (), $ result ->getPrerequisiteEvents ());
219+ }
220+
221+ public function testFlagReturnsNullIfFlagIsOffAndOffVariationIsUnspecified ()
222+ {
223+ $ flagJson = array (
224+ 'key ' => 'feature ' ,
225+ 'version ' => 1 ,
226+ 'deleted ' => false ,
227+ 'on ' => false ,
228+ 'targets ' => array (),
229+ 'prerequisites ' => array (),
230+ 'rules ' => array (),
231+ 'offVariation ' => null ,
232+ 'fallthrough ' => array ('variation ' => 0 ),
233+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
234+ 'salt ' => ''
235+ );
236+ $ flag = FeatureFlag::decode ($ flagJson );
237+ $ ub = new LDUserBuilder ('x ' );
238+ $ user = $ ub ->build ();
239+
240+ $ result = $ flag ->evaluate ($ user , null );
241+ self ::assertNull ($ result ->getVariation ());
242+ self ::assertNull ($ result ->getValue ());
243+ self ::assertEquals (array (), $ result ->getPrerequisiteEvents ());
244+ }
245+
246+ public function testFlagReturnsOffVariationIfPrerequisiteIsNotFound ()
247+ {
248+ $ flagJson = array (
249+ 'key ' => 'feature0 ' ,
250+ 'version ' => 1 ,
251+ 'deleted ' => false ,
252+ 'on ' => true ,
253+ 'targets ' => array (),
254+ 'prerequisites ' => array (
255+ array ('key ' => 'feature1 ' , 'variation ' => 1 )
256+ ),
257+ 'rules ' => array (),
258+ 'offVariation ' => 1 ,
259+ 'fallthrough ' => array ('variation ' => 0 ),
260+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
261+ 'salt ' => ''
262+ );
263+ $ flag = FeatureFlag::decode ($ flagJson );
264+ $ ub = new LDUserBuilder ('x ' );
265+ $ user = $ ub ->build ();
266+ $ requester = new MockFeatureRequesterForFeature ();
267+
268+ $ result = $ flag ->evaluate ($ user , $ requester );
269+ self ::assertEquals (1 , $ result ->getVariation ());
270+ self ::assertEquals ('off ' , $ result ->getValue ());
271+ self ::assertEquals (array (), $ result ->getPrerequisiteEvents ());
272+ }
273+
274+ public function testFlagReturnsOffVariationAndEventIfPrerequisiteIsNotMet ()
275+ {
276+ $ flag0Json = array (
277+ 'key ' => 'feature0 ' ,
278+ 'version ' => 1 ,
279+ 'deleted ' => false ,
280+ 'on ' => true ,
281+ 'targets ' => array (),
282+ 'prerequisites ' => array (
283+ array ('key ' => 'feature1 ' , 'variation ' => 1 )
284+ ),
285+ 'rules ' => array (),
286+ 'offVariation ' => 1 ,
287+ 'fallthrough ' => array ('variation ' => 0 ),
288+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
289+ 'salt ' => ''
290+ );
291+ $ flag1Json = array (
292+ 'key ' => 'feature1 ' ,
293+ 'version ' => 2 ,
294+ 'deleted ' => false ,
295+ 'on ' => true ,
296+ 'targets ' => array (),
297+ 'prerequisites ' => array (),
298+ 'rules ' => array (),
299+ 'offVariation ' => 1 ,
300+ 'fallthrough ' => array ('variation ' => 0 ),
301+ 'variations ' => array ('nogo ' , 'go ' ),
302+ 'salt ' => ''
303+ );
304+ $ flag0 = FeatureFlag::decode ($ flag0Json );
305+ $ flag1 = FeatureFlag::decode ($ flag1Json );
306+ $ ub = new LDUserBuilder ('x ' );
307+ $ user = $ ub ->build ();
308+ $ requester = new MockFeatureRequesterForFeature ();
309+ $ requester ->key = $ flag1 ->getKey ();
310+ $ requester ->val = $ flag1 ;
311+
312+ $ result = $ flag0 ->evaluate ($ user , $ requester );
313+ self ::assertEquals (1 , $ result ->getVariation ());
314+ self ::assertEquals ('off ' , $ result ->getValue ());
315+
316+ $ events = $ result ->getPrerequisiteEvents ();
317+ self ::assertEquals (1 , count ($ events ));
318+ $ event = $ events [0 ];
319+ self ::assertEquals ('feature ' , $ event ['kind ' ]);
320+ self ::assertEquals ($ flag1 ->getKey (), $ event ['key ' ]);
321+ self ::assertEquals ('nogo ' , $ event ['value ' ]);
322+ self ::assertEquals ($ flag1 ->getVersion (), $ event ['version ' ]);
323+ self ::assertEquals ($ flag0 ->getKey (), $ event ['prereqOf ' ]);
324+ }
325+
326+ public function testFlagReturnsFallthroughVariationAndEventIfPrerequisiteIsMetAndThereAreNoRules ()
327+ {
328+ $ flag0Json = array (
329+ 'key ' => 'feature0 ' ,
330+ 'version ' => 1 ,
331+ 'deleted ' => false ,
332+ 'on ' => true ,
333+ 'targets ' => array (),
334+ 'prerequisites ' => array (
335+ array ('key ' => 'feature1 ' , 'variation ' => 1 )
336+ ),
337+ 'rules ' => array (),
338+ 'offVariation ' => 1 ,
339+ 'fallthrough ' => array ('variation ' => 0 ),
340+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
341+ 'salt ' => ''
342+ );
343+ $ flag1Json = array (
344+ 'key ' => 'feature1 ' ,
345+ 'version ' => 2 ,
346+ 'deleted ' => false ,
347+ 'on ' => true ,
348+ 'targets ' => array (),
349+ 'prerequisites ' => array (),
350+ 'rules ' => array (),
351+ 'offVariation ' => 1 ,
352+ 'fallthrough ' => array ('variation ' => 1 ),
353+ 'variations ' => array ('nogo ' , 'go ' ),
354+ 'salt ' => ''
355+ );
356+ $ flag0 = FeatureFlag::decode ($ flag0Json );
357+ $ flag1 = FeatureFlag::decode ($ flag1Json );
358+ $ ub = new LDUserBuilder ('x ' );
359+ $ user = $ ub ->build ();
360+ $ requester = new MockFeatureRequesterForFeature ();
361+ $ requester ->key = $ flag1 ->getKey ();
362+ $ requester ->val = $ flag1 ;
363+
364+ $ result = $ flag0 ->evaluate ($ user , $ requester );
365+ self ::assertEquals (0 , $ result ->getVariation ());
366+ self ::assertEquals ('fall ' , $ result ->getValue ());
367+
368+ $ events = $ result ->getPrerequisiteEvents ();
369+ self ::assertEquals (1 , count ($ events ));
370+ $ event = $ events [0 ];
371+ self ::assertEquals ('feature ' , $ event ['kind ' ]);
372+ self ::assertEquals ($ flag1 ->getKey (), $ event ['key ' ]);
373+ self ::assertEquals ('go ' , $ event ['value ' ]);
374+ self ::assertEquals ($ flag1 ->getVersion (), $ event ['version ' ]);
375+ self ::assertEquals ($ flag0 ->getKey (), $ event ['prereqOf ' ]);
376+ }
377+
378+ public function testFlagMatchesUserFromTargets ()
379+ {
380+ $ flagJson = array (
381+ 'key ' => 'feature ' ,
382+ 'version ' => 1 ,
383+ 'deleted ' => false ,
384+ 'on ' => true ,
385+ 'targets ' => array (
386+ array ('values ' => array ('whoever ' , 'userkey ' ), 'variation ' => 2 )
387+ ),
388+ 'prerequisites ' => array (),
389+ 'rules ' => array (),
390+ 'offVariation ' => 1 ,
391+ 'fallthrough ' => array ('variation ' => 0 ),
392+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
393+ 'salt ' => ''
394+ );
395+ $ flag = FeatureFlag::decode ($ flagJson );
396+ $ ub = new LDUserBuilder ('userkey ' );
397+ $ user = $ ub ->build ();
398+
399+ $ result = $ flag ->evaluate ($ user , null );
400+ self ::assertEquals (2 , $ result ->getVariation ());
401+ self ::assertEquals ('on ' , $ result ->getValue ());
402+ self ::assertEquals (array (), $ result ->getPrerequisiteEvents ());
403+ }
404+
405+ public function testFlagMatchesUserFromRules ()
406+ {
407+ $ flagJson = array (
408+ 'key ' => 'feature ' ,
409+ 'version ' => 1 ,
410+ 'deleted ' => false ,
411+ 'on ' => true ,
412+ 'targets ' => array (),
413+ 'prerequisites ' => array (),
414+ 'rules ' => array (
415+ array (
416+ 'clauses ' => array (
417+ array (
418+ 'attribute ' => 'key ' ,
419+ 'op ' => 'in ' ,
420+ 'values ' => array ('userkey ' ),
421+ 'negate ' => false
422+ )
423+ ),
424+ 'variation ' => 2
425+ )
426+ ),
427+ 'offVariation ' => 1 ,
428+ 'fallthrough ' => array ('variation ' => 0 ),
429+ 'variations ' => array ('fall ' , 'off ' , 'on ' ),
430+ 'salt ' => ''
431+ );
432+ $ flag = FeatureFlag::decode ($ flagJson );
433+ $ ub = new LDUserBuilder ('userkey ' );
434+ $ user = $ ub ->build ();
435+
436+ $ result = $ flag ->evaluate ($ user , null );
437+ self ::assertEquals (2 , $ result ->getVariation ());
438+ self ::assertEquals ('on ' , $ result ->getValue ());
439+ self ::assertEquals (array (), $ result ->getPrerequisiteEvents ());
440+ }
441+
442+ public function clauseCanMatchBuiltInAttribute ()
443+ {
444+ $ clause = array ('attribute ' => 'name ' , 'op ' => 'in ' , 'values ' => array ('Bob ' ), 'negate ' => false );
445+ $ flag = $ this ->booleanFlagWithClauses (array ($ clause ));
446+ $ ub = new LDUserBuilder ('userkey ' );
447+ $ user = $ ub ->build ();
448+
449+ $ result = $ flag ->evaluate ($ user , null );
450+ self ::assertEquals (true , $ result ->getValue ());
451+ }
452+
453+ public function clauseCanMatchCustomAttribute ()
454+ {
455+ $ clause = array ('attribute ' => 'legs ' , 'op ' => 'in ' , 'values ' => array ('4 ' ), 'negate ' => false );
456+ $ flag = $ this ->booleanFlagWithClauses (array ($ clause ));
457+ $ ub = new LDUserBuilder ('userkey ' );
458+ $ ub ->customAttribute ('legs ' , 4 );
459+ $ user = $ ub ->build ();
460+
461+ $ result = $ flag ->evaluate ($ user , null );
462+ self ::assertEquals (true , $ result ->getValue ());
463+ }
464+
465+ public function clauseReturnsFalseForMissingAttribute ()
466+ {
467+ $ clause = array ('attribute ' => 'legs ' , 'op ' => 'in ' , 'values ' => array ('4 ' ), 'negate ' => false );
468+ $ flag = $ this ->booleanFlagWithClauses (array ($ clause ));
469+ $ ub = new LDUserBuilder ('userkey ' );
470+ $ user = $ ub ->build ();
471+
472+ $ result = $ flag ->evaluate ($ user , null );
473+ self ::assertEquals (false , $ result ->getValue ());
474+ }
475+
476+ public function clauseCanBeNegated ()
477+ {
478+ $ clause = array ('attribute ' => 'name ' , 'op ' => 'in ' , 'values ' => array ('Bob ' ), 'negate ' => true );
479+ $ flag = $ this ->booleanFlagWithClauses (array ($ clause ));
480+ $ ub = new LDUserBuilder ('userkey ' );
481+ $ user = $ ub ->build ();
482+
483+ $ result = $ flag ->evaluate ($ user , null );
484+ self ::assertEquals (false , $ result ->getValue ());
485+ }
486+
487+ public function clauseWithUnknownOperatorDoesNotMatch ()
488+ {
489+ $ clause = array ('attribute ' => 'name ' , 'op ' => 'doesSomethingUnsupported ' , 'values ' => array ('Bob ' ), 'negate ' => false );
490+ $ flag = $ this ->booleanFlagWithClauses (array ($ clause ));
491+ $ ub = new LDUserBuilder ('userkey ' );
492+ $ user = $ ub ->build ();
493+
494+ $ result = $ flag ->evaluate ($ user , null );
495+ self ::assertEquals (false , $ result ->getValue ());
496+ }
497+
196498 public function testSegmentMatchClauseRetrievesSegmentFromStore ()
197499 {
198500 $ segmentJson = array (
@@ -234,7 +536,7 @@ public function testSegmentMatchClauseFallsThroughWithNoErrorsIfSegmentNotFound(
234536 self ::assertFalse ($ result ->getValue ());
235537 }
236538
237- private function makeBooleanFeatureWithSegmentMatch ( $ segmentKey )
539+ private function booleanFlagWithClauses ( $ clauses )
238540 {
239541 $ featureJson = array (
240542 'key ' => 'test ' ,
@@ -244,17 +546,7 @@ private function makeBooleanFeatureWithSegmentMatch($segmentKey)
244546 'variations ' => array (false , true ),
245547 'fallthrough ' => array ('variation ' => 0 ),
246548 'rules ' => array (
247- array (
248- 'clauses ' => array (
249- array (
250- 'attribute ' => '' ,
251- 'op ' => 'segmentMatch ' ,
252- 'values ' => array ($ segmentKey ),
253- 'negate ' => false
254- )
255- ),
256- 'variation ' => 1
257- )
549+ array ('clauses ' => $ clauses , 'variation ' => 1 )
258550 ),
259551 'offVariation ' => 0 ,
260552 'prerequisites ' => array (),
@@ -263,8 +555,43 @@ private function makeBooleanFeatureWithSegmentMatch($segmentKey)
263555 );
264556 return FeatureFlag::decode ($ featureJson );
265557 }
558+
559+ private function makeBooleanFeatureWithSegmentMatch ($ segmentKey )
560+ {
561+ $ clause = array (
562+ 'attribute ' => '' ,
563+ 'op ' => 'segmentMatch ' ,
564+ 'values ' => array ($ segmentKey ),
565+ 'negate ' => false
566+ );
567+ return $ this ->booleanFlagWithClauses (array ($ clause ));
568+ }
266569}
267570
571+ class MockFeatureRequesterForFeature implements FeatureRequester
572+ {
573+ public $ key = null ;
574+ public $ val = null ;
575+
576+ function __construct ($ baseurl = null , $ key = null , $ options = null )
577+ {
578+ }
579+
580+ public function getFeature ($ key )
581+ {
582+ return ($ key == $ this ->key ) ? $ this ->val : null ;
583+ }
584+
585+ public function getSegment ($ key )
586+ {
587+ return null ;
588+ }
589+
590+ public function getAllFeatures ()
591+ {
592+ return null ;
593+ }
594+ }
268595
269596class MockFeatureRequesterForSegment implements FeatureRequester
270597{
0 commit comments