From 417338a0e7959d8e63a9b54d4cf52f026492a65d Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Fri, 22 Mar 2024 11:50:43 +0000 Subject: [PATCH] fix(flags): Handle bool value matching --- lib/FeatureFlag.php | 23 +++++--- test/FeatureFlagTest.php | 73 +++++++++++++++++++++++++ test/assests/MockedResponses.php | 92 ++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 7 deletions(-) diff --git a/lib/FeatureFlag.php b/lib/FeatureFlag.php index 2b12f63..0346fb1 100644 --- a/lib/FeatureFlag.php +++ b/lib/FeatureFlag.php @@ -35,11 +35,11 @@ public static function matchProperty($property, $propertyValues) } if ($operator == "icontains") { - return strpos(strtolower(strval($overrideValue)), strtolower(strval($value))) !== false; + return strpos(strtolower(FeatureFlag::valueToString($overrideValue)), strtolower(FeatureFlag::valueToString($value))) !== false; } - + if ($operator == "not_icontains") { - return strpos(strtolower(strval($overrideValue)), strtolower(strval($value))) == false; + return strpos(strtolower(FeatureFlag::valueToString($overrideValue)), strtolower(FeatureFlag::valueToString($value))) == false; } if (in_array($operator, ["regex", "not_regex"])) { @@ -65,12 +65,12 @@ public static function matchProperty($property, $propertyValues) if (!is_null($parsedValue) && !is_null($overrideValue)) { if (is_string($overrideValue)) { - return FeatureFlag::compare($overrideValue, strval($value), $operator); + return FeatureFlag::compare($overrideValue, FeatureFlag::valueToString($value), $operator); } else { return FeatureFlag::compare($overrideValue, $parsedValue, $operator, "numeric"); } } else { - return FeatureFlag::compare(strval($overrideValue), strval($value), $operator); + return FeatureFlag::compare(FeatureFlag::valueToString($overrideValue), FeatureFlag::valueToString($value), $operator); } } @@ -246,9 +246,18 @@ private static function convertToDateTime($value) { private static function computeExactMatch($value, $overrideValue) { if (is_array($value)) { - return in_array(strtolower(strval($overrideValue)), array_map('strtolower', $value)); + return in_array(strtolower(FeatureFlag::valueToString($overrideValue)), array_map('strtolower', array_map(fn($val) => FeatureFlag::valueToString($val) , $value))); + } + return strtolower(FeatureFlag::valueToString($value)) == strtolower(FeatureFlag::valueToString($overrideValue)); + } + + private static function valueToString($value) + { + if (is_bool($value)) { + return $value ? "true" : "false"; + } else { + return strval($value); } - return strtolower(strval($value)) == strtolower(strval($overrideValue)); } private static function compare($lhs, $rhs, $operator, $type = "string") diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 0e26270..5a679a6 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -1058,6 +1058,79 @@ public function testFlagPersonProperties() $this->checkEmptyErrorLogs(); } + public function testFlagPersonBooleanProperties() + { + $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_BOOLEAN_REQUEST); + + $this->client = new Client( + self::FAKE_API_KEY, + [ + "debug" => true, + ], + $this->http_client, + "test" + ); + + PostHog::init(null, null, $this->client); + + $this->assertTrue(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => "true", "region_array" => "true"], [], true, false)); + + PostHog::flush(); + $this->assertEquals( + $this->http_client->calls, + array( + 0 => array( + "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "payload" => null, + "extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'), + "requestOptions" => array(), + ), + // no decide or capture calls + ) + ); + + $this->checkEmptyErrorLogs(); + + // reset calls + $this->http_client->calls = array(); + + $this->assertTrue(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => "true", "region_array" => true], [], true, false)); + $this->assertTrue(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => true, "region_array" => true], [], true, false)); + $this->assertTrue(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => true, "region_array" => "true"], [], true, false)); + $this->assertFalse(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => 1, "region_array" => "1"], [], true, false)); + $this->assertFalse(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => true, "region_array" => "1"], [], true, false)); + $this->assertFalse(PostHog::getFeatureFlag('person-flag', 'some-distinct-id', [], ["region" => "1", "region_array" => "true"], [], true, false)); + + $this->assertEquals( + $this->http_client->calls, + array() + // no decide or capture calls + ); + + $this->assertTrue(PostHog::getFeatureFlag('person-flag-with-boolean', 'some-distinct-id', [], ["region" => "true", "region_array" => true], [], true, false)); + $this->assertTrue(PostHog::getFeatureFlag('person-flag-with-boolean', 'some-distinct-id', [], ["region" => "true", "region_array" => true], [], true, false)); + $this->assertTrue(PostHog::getFeatureFlag('person-flag-with-boolean', 'some-distinct-id', [], ["region" => true, "region_array" => true], [], true, false)); + $this->assertTrue(PostHog::getFeatureFlag('person-flag-with-boolean', 'some-distinct-id', [], ["region" => true, "region_array" => "true"], [], true, false)); + $this->assertFalse(PostHog::getFeatureFlag('person-flag-with-boolean', 'some-distinct-id', [], ["region" => true, "region_array" => "false"], [], true, false)); + $this->assertFalse(PostHog::getFeatureFlag('person-flag-with-boolean', 'some-distinct-id', [], ["region" => false, "region_array" => "true"], [], true, false)); + + $this->assertEquals( + $this->http_client->calls, + array() + // no decide or capture calls + ); + + $this->assertTrue(PostHog::getFeatureFlag('person-flag-with-boolean-icontains', 'some-distinct-id', [], ["region" => "true", "region_array" => true], [], true, false)); + $this->assertTrue(PostHog::getFeatureFlag('person-flag-with-boolean-icontains', 'some-distinct-id', [], ["region" => true, "region_array" => true], [], true, false)); + $this->assertFalse(PostHog::getFeatureFlag('person-flag-with-boolean-icontains', 'some-distinct-id', [], ["region" => false, "region_array" => "true"], [], true, false)); + + $this->assertEquals( + $this->http_client->calls, + array() + // no decide or capture calls + ); + } + public function testFlagGroupProperties() { $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_GROUP_PROPERTIES_REQUEST); diff --git a/test/assests/MockedResponses.php b/test/assests/MockedResponses.php index 669a73d..eef6a96 100644 --- a/test/assests/MockedResponses.php +++ b/test/assests/MockedResponses.php @@ -68,6 +68,98 @@ class MockedResponses ], ]; + public const LOCAL_EVALUATION_BOOLEAN_REQUEST = [ + 'count' => 1, + 'next' => null, + 'previous' => null, + 'flags' => [ + [ + "id" => 1, + "name" => "", + "key" => "person-flag", + "filters" => [ + "groups" => [ + [ + "properties" => [ + [ + "key" => "region_array", + "value" => ["true"], + "operator" => "exact", + "type" => "person" + ], + [ + "key" => "region", + "value" => "true", + "operator" => "exact", + "type" => "person" + ], + ], + "rollout_percentage" => 100 + ] + ] + ], + "deleted" => false, + "active" => true, + "is_simple_flag" => true, + "rollout_percentage" => null + ], + [ + "id" => 2, + "name" => "", + "key" => "person-flag-with-boolean", + "filters" => [ + "groups" => [ + [ + "properties" => [ + [ + "key" => "region_array", + "value" => [true], + "operator" => "exact", + "type" => "person" + ], + [ + "key" => "region", + "value" => true, + "operator" => "exact", + "type" => "person" + ], + ], + "rollout_percentage" => 100 + ] + ] + ], + "deleted" => false, + "active" => true, + "is_simple_flag" => true, + "rollout_percentage" => null + ], + [ + "id" => 2, + "name" => "", + "key" => "person-flag-with-boolean-icontains", + "filters" => [ + "groups" => [ + [ + "properties" => [ + [ + "key" => "region", + "value" => true, + "operator" => "icontains", + "type" => "person" + ], + ], + "rollout_percentage" => 100 + ] + ] + ], + "deleted" => false, + "active" => true, + "is_simple_flag" => true, + "rollout_percentage" => null + ] + ], + ]; + public const LOCAL_EVALUATION_MULTIPLE_REQUEST = [ 'count' => 2, 'next' => null,