Skip to content

Commit 92b988a

Browse files
authored
Merge pull request #21 from launchdarkly/eb/eval-tests
add more unit tests for flag evaluation
2 parents 9119093 + 639a0e7 commit 92b988a

File tree

1 file changed

+339
-12
lines changed

1 file changed

+339
-12
lines changed

tests/FeatureFlagTest.php

Lines changed: 339 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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

269596
class MockFeatureRequesterForSegment implements FeatureRequester
270597
{

0 commit comments

Comments
 (0)