From ec55ac1413023fd49e081a0cd021ad3942ccfb04 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Mon, 29 Apr 2024 18:00:11 -0300 Subject: [PATCH 1/5] GreaterThanOrEqualToSemver Matcher implementation --- src/SplitIO/Grammar/Condition/Matcher.php | 7 +++ .../Matcher/GreaterThanOrEqualToSemver.php | 42 +++++++++++++++++ tests/Suite/Matchers/MatchersTest.php | 29 ++++++++++++ tests/Suite/Sdk/SdkClientTest.php | 12 +++++ tests/Suite/Sdk/files/splitChanges.json | 46 +++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualToSemver.php diff --git a/src/SplitIO/Grammar/Condition/Matcher.php b/src/SplitIO/Grammar/Condition/Matcher.php index f2768a1..0d9cab2 100644 --- a/src/SplitIO/Grammar/Condition/Matcher.php +++ b/src/SplitIO/Grammar/Condition/Matcher.php @@ -7,6 +7,7 @@ use SplitIO\Grammar\Condition\Matcher\EqualTo; use SplitIO\Grammar\Condition\Matcher\EqualToSemver; use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualTo; +use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualToSemver; use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualTo; use SplitIO\Grammar\Condition\Matcher\Segment; use SplitIO\Grammar\Condition\Matcher\Whitelist; @@ -42,6 +43,7 @@ class Matcher const EQUAL_TO_BOOLEAN = 'EQUAL_TO_BOOLEAN'; const MATCHES_STRING = 'MATCHES_STRING'; const EQUAL_TO_SEMVER = 'EQUAL_TO_SEMVER'; + const GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'; public static function factory($matcher) { @@ -137,6 +139,11 @@ public static function factory($matcher) is_string($matcher['stringMatcherData']) ? $matcher['stringMatcherData'] : null; return new EqualToSemver($data, $negate, $attribute); + case self::GREATER_THAN_OR_EQUAL_TO_SEMVER: + $data = isset($matcher['stringMatcherData']) && + is_string($matcher['stringMatcherData']) ? + $matcher['stringMatcherData'] : null; + return new GreaterThanOrEqualToSemver($data, $negate, $attribute); // @codeCoverageIgnoreStart default: throw new UnsupportedMatcherException("Unable to create matcher for matcher type: " . $matcherType); diff --git a/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualToSemver.php b/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualToSemver.php new file mode 100644 index 0000000..6fe0074 --- /dev/null +++ b/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualToSemver.php @@ -0,0 +1,42 @@ +target = Semver::build($toCompare); + } + + /** + * + * @param mixed $key + */ + protected function evalKey($key) + { + if ($key == null || $this->target == null || !is_string($key)) { + return false; + } + + $keySemver = Semver::build($key); + if ($keySemver == null) { + return false; + } + + $result = SemverComparer::do($keySemver, $this->target) >= 0; + + SplitApp::logger()->debug($this->target->getVersion() . " >= " + . $keySemver->getVersion() . " | Result: " . $result); + + return $result; + } +} diff --git a/tests/Suite/Matchers/MatchersTest.php b/tests/Suite/Matchers/MatchersTest.php index b5ffc17..62275ac 100644 --- a/tests/Suite/Matchers/MatchersTest.php +++ b/tests/Suite/Matchers/MatchersTest.php @@ -532,6 +532,35 @@ public function testEqualToSemverMatcher() $this->assertFalse($matcher->evaluate('2.2.2')); } + public function testGreaterThanOrEqualToSemverMatcher() + { + $this->setupSplitApp(); + + $condition = array( + 'matcherType' => 'GREATER_THAN_OR_EQUAL_TO_SEMVER', + 'stringMatcherData' => '2.2.2' + ); + + $matcher = Matcher::factory($condition); + $this->assertTrue($matcher->evaluate('2.2.2')); + $this->assertTrue($matcher->evaluate('2.2.3')); + $this->assertTrue($matcher->evaluate('2.3.2')); + $this->assertTrue($matcher->evaluate('3.2.2')); + $this->assertFalse($matcher->evaluate('1.2.2')); + $this->assertFalse($matcher->evaluate('2.1.2')); + $this->assertFalse($matcher->evaluate('2.2.2-rc.1')); + $this->assertFalse($matcher->evaluate(null)); + $this->assertFalse($matcher->evaluate('')); + + $condition = array( + 'matcherType' => 'GREATER_THAN_OR_EQUAL_TO_SEMVER', + 'stringMatcherData' => null + ); + + $matcher = Matcher::factory($condition); + $this->assertFalse($matcher->evaluate('2.2.2')); + } + public static function tearDownAfterClass(): void { Utils\Utils::cleanCache(); diff --git a/tests/Suite/Sdk/SdkClientTest.php b/tests/Suite/Sdk/SdkClientTest.php index 2c2f6fa..c944e4e 100644 --- a/tests/Suite/Sdk/SdkClientTest.php +++ b/tests/Suite/Sdk/SdkClientTest.php @@ -276,6 +276,18 @@ public function testClientSemverMatchers() $this->validateLastImpression($redisClient, 'equal_to_semver_flag', 'user1', 'v1'); $this->assertEquals('off', $splitSdk->getTreatment('user2', 'equal_to_semver_flag', array('version' => '34.56.89'))); $this->validateLastImpression($redisClient, 'equal_to_semver_flag', 'user2', 'off'); + + //Assertions GreaterThanOrEqualToSemver + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'gtoet_semver_flag', array('version' => '34.56.89-rc.12.2.3.4+meta'))); + $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'gtoet_semver_flag', array('version' => '34.56.89-rc.12.3.3.4+meta'))); + $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'v1'); + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'gtoet_semver_flag', array('version' => '34.56.89-rc.12.2.1.4+meta'))); + $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'off'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'gtoet_semver_flag', array('version' => '34.56.89'))); + $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'gtoet_semver_flag', array('version' => '34.57.89'))); + $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'v1'); } public function testClientWithUnsupportedMatcher() diff --git a/tests/Suite/Sdk/files/splitChanges.json b/tests/Suite/Sdk/files/splitChanges.json index 8b27f99..685e7ba 100644 --- a/tests/Suite/Sdk/files/splitChanges.json +++ b/tests/Suite/Sdk/files/splitChanges.json @@ -548,6 +548,52 @@ ] } ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "gtoet_semver_flag", + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "sets": [], + "configurations": { + "on": "{\"size\":15,\"test\":20}", + "of": "{\"size\":15,\"defTreatment\":true}" + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType": "GREATER_THAN_OR_EQUAL_TO_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "stringMatcherData": "34.56.89-rc.12.2.3.4+meta" + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 100 + }, + { + "treatment": "v2", + "size": 0 + } + ] + } + ] } ], "since": -1, From 6f0b33d1b2c14a366f5b0820b03d993030dc8d68 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Mon, 29 Apr 2024 18:30:45 -0300 Subject: [PATCH 2/5] LessThanOrEqualToSemver Matcher implementation. --- src/SplitIO/Grammar/Condition/Matcher.php | 7 +++ .../Matcher/LessThanOrEqualToSemver.php | 42 +++++++++++++++++ tests/Suite/Matchers/MatchersTest.php | 31 +++++++++++++ tests/Suite/Sdk/SdkClientTest.php | 12 +++++ tests/Suite/Sdk/files/splitChanges.json | 46 +++++++++++++++++++ 5 files changed, 138 insertions(+) create mode 100644 src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php diff --git a/src/SplitIO/Grammar/Condition/Matcher.php b/src/SplitIO/Grammar/Condition/Matcher.php index 0d9cab2..d5b4fc2 100644 --- a/src/SplitIO/Grammar/Condition/Matcher.php +++ b/src/SplitIO/Grammar/Condition/Matcher.php @@ -9,6 +9,7 @@ use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualTo; use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualToSemver; use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualTo; +use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualToSemver; use SplitIO\Grammar\Condition\Matcher\Segment; use SplitIO\Grammar\Condition\Matcher\Whitelist; use SplitIO\Grammar\Condition\Matcher\StartsWith; @@ -44,6 +45,7 @@ class Matcher const MATCHES_STRING = 'MATCHES_STRING'; const EQUAL_TO_SEMVER = 'EQUAL_TO_SEMVER'; const GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'; + const LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'; public static function factory($matcher) { @@ -144,6 +146,11 @@ public static function factory($matcher) is_string($matcher['stringMatcherData']) ? $matcher['stringMatcherData'] : null; return new GreaterThanOrEqualToSemver($data, $negate, $attribute); + case self::LESS_THAN_OR_EQUAL_TO_SEMVER: + $data = isset($matcher['stringMatcherData']) && + is_string($matcher['stringMatcherData']) ? + $matcher['stringMatcherData'] : null; + return new LessThanOrEqualToSemver($data, $negate, $attribute); // @codeCoverageIgnoreStart default: throw new UnsupportedMatcherException("Unable to create matcher for matcher type: " . $matcherType); diff --git a/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php b/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php new file mode 100644 index 0000000..af7fc6e --- /dev/null +++ b/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php @@ -0,0 +1,42 @@ +target = Semver::build($toCompare); + } + + /** + * + * @param mixed $key + */ + protected function evalKey($key) + { + if ($key == null || $this->target == null || !is_string($key)) { + return false; + } + + $keySemver = Semver::build($key); + if ($keySemver == null) { + return false; + } + + $result = SemverComparer::do($keySemver, $this->target) <= 0; + + SplitApp::logger()->debug($this->target->getVersion() . " <= " + . $keySemver->getVersion() . " | Result: " . $result); + + return $result; + } +} diff --git a/tests/Suite/Matchers/MatchersTest.php b/tests/Suite/Matchers/MatchersTest.php index 62275ac..3f3bece 100644 --- a/tests/Suite/Matchers/MatchersTest.php +++ b/tests/Suite/Matchers/MatchersTest.php @@ -561,6 +561,37 @@ public function testGreaterThanOrEqualToSemverMatcher() $this->assertFalse($matcher->evaluate('2.2.2')); } + public function testLessThanOrEqualToSemverMatcher() + { + $this->setupSplitApp(); + + $condition = array( + 'matcherType' => 'LESS_THAN_OR_EQUAL_TO_SEMVER', + 'stringMatcherData' => '2.2.2' + ); + + $matcher = Matcher::factory($condition); + $this->assertTrue($matcher->evaluate('1.2.2')); + $this->assertTrue($matcher->evaluate('2.1.2')); + $this->assertTrue($matcher->evaluate('2.2.2-rc.1')); + $this->assertTrue($matcher->evaluate('0.2.2')); + $this->assertTrue($matcher->evaluate('2.2.1')); + $this->assertTrue($matcher->evaluate('2.2.2')); + $this->assertFalse($matcher->evaluate('2.2.3')); + $this->assertFalse($matcher->evaluate('2.3.2')); + $this->assertFalse($matcher->evaluate('3.2.2')); + $this->assertFalse($matcher->evaluate(null)); + $this->assertFalse($matcher->evaluate('')); + + $condition = array( + 'matcherType' => 'GREATER_THAN_OR_EQUAL_TO_SEMVER', + 'stringMatcherData' => null + ); + + $matcher = Matcher::factory($condition); + $this->assertFalse($matcher->evaluate('2.2.2')); + } + public static function tearDownAfterClass(): void { Utils\Utils::cleanCache(); diff --git a/tests/Suite/Sdk/SdkClientTest.php b/tests/Suite/Sdk/SdkClientTest.php index c944e4e..100ab7a 100644 --- a/tests/Suite/Sdk/SdkClientTest.php +++ b/tests/Suite/Sdk/SdkClientTest.php @@ -288,6 +288,18 @@ public function testClientSemverMatchers() $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'v1'); $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'gtoet_semver_flag', array('version' => '34.57.89'))); $this->validateLastImpression($redisClient, 'gtoet_semver_flag', 'user1', 'v1'); + + //Assertions LessThanOrEqualToSemver + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.22.33'))); + $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.1.33'))); + $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.1.3'))); + $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'v1'); + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.22.34'))); + $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'off'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.22.33-rc.1'))); + $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'v1'); } public function testClientWithUnsupportedMatcher() diff --git a/tests/Suite/Sdk/files/splitChanges.json b/tests/Suite/Sdk/files/splitChanges.json index 685e7ba..17e1620 100644 --- a/tests/Suite/Sdk/files/splitChanges.json +++ b/tests/Suite/Sdk/files/splitChanges.json @@ -594,6 +594,52 @@ ] } ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "ltoet_semver_flag", + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "sets": [], + "configurations": { + "on": "{\"size\":15,\"test\":20}", + "of": "{\"size\":15,\"defTreatment\":true}" + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType": "LESS_THAN_OR_EQUAL_TO_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "stringMatcherData": "11.22.33" + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 100 + }, + { + "treatment": "v2", + "size": 0 + } + ] + } + ] } ], "since": -1, From 163f5f0d7e1119631ece6a4eefde24591ecd76d2 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Mon, 29 Apr 2024 18:59:49 -0300 Subject: [PATCH 3/5] BetweenSemver Matcher implementation. --- src/SplitIO/Grammar/Condition/Matcher.php | 7 +++ .../Condition/Matcher/BetweenSemver.php | 45 +++++++++++++++++ .../Matcher/LessThanOrEqualToSemver.php | 2 +- tests/Suite/Matchers/MatchersTest.php | 30 ++++++++++++ tests/Suite/Sdk/SdkClientTest.php | 14 ++++++ tests/Suite/Sdk/files/splitChanges.json | 49 +++++++++++++++++++ 6 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php diff --git a/src/SplitIO/Grammar/Condition/Matcher.php b/src/SplitIO/Grammar/Condition/Matcher.php index d5b4fc2..e7771d9 100644 --- a/src/SplitIO/Grammar/Condition/Matcher.php +++ b/src/SplitIO/Grammar/Condition/Matcher.php @@ -4,6 +4,7 @@ use SplitIO\Exception\UnsupportedMatcherException; use SplitIO\Grammar\Condition\Matcher\All; use SplitIO\Grammar\Condition\Matcher\Between; +use SplitIO\Grammar\Condition\Matcher\BetweenSemver; use SplitIO\Grammar\Condition\Matcher\EqualTo; use SplitIO\Grammar\Condition\Matcher\EqualToSemver; use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualTo; @@ -46,6 +47,7 @@ class Matcher const EQUAL_TO_SEMVER = 'EQUAL_TO_SEMVER'; const GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'; const LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'; + const BETWEEN_SEMVER = 'BETWEEN_SEMVER'; public static function factory($matcher) { @@ -151,6 +153,11 @@ public static function factory($matcher) is_string($matcher['stringMatcherData']) ? $matcher['stringMatcherData'] : null; return new LessThanOrEqualToSemver($data, $negate, $attribute); + case self::BETWEEN_SEMVER: + $data = (isset($matcher['betweenStringMatcherData']) && + is_array($matcher['betweenStringMatcherData'])) + ? $matcher['betweenStringMatcherData'] : null; + return new BetweenSemver($data, $negate, $attribute); // @codeCoverageIgnoreStart default: throw new UnsupportedMatcherException("Unable to create matcher for matcher type: " . $matcherType); diff --git a/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php b/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php new file mode 100644 index 0000000..f10531a --- /dev/null +++ b/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php @@ -0,0 +1,45 @@ +startTarget = Semver::build($data['start']); + $this->endTarget = Semver::build($data['end']); + } + + /** + * + * @param mixed $key + */ + protected function evalKey($key) + { + if ($key == null || !is_string($key) || $this->startTarget == null || $this->endTarget == null) { + return false; + } + + $keySemver = Semver::build($key); + if ($keySemver == null) { + return false; + } + + $result = SemverComparer::do($keySemver, $this->startTarget) >= 0 && SemverComparer::do($keySemver, $this->endTarget) <= 0; + + SplitApp::logger()->debug($this->startTarget->getVersion() . " <= " + . $keySemver->getVersion() . " <= " . $this->endTarget->getVersion() + . " | Result: " . $result); + + return $result; + } +} diff --git a/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php b/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php index af7fc6e..4b537ba 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php +++ b/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php @@ -12,7 +12,7 @@ class LessThanOrEqualToSemver extends AbstractMatcher public function __construct($toCompare, $negate = false, $attribute = null) { - parent::__construct(Matcher::GREATER_THAN_OR_EQUAL_TO_SEMVER, $negate, $attribute); + parent::__construct(Matcher::LESS_THAN_OR_EQUAL_TO_SEMVER, $negate, $attribute); $this->target = Semver::build($toCompare); } diff --git a/tests/Suite/Matchers/MatchersTest.php b/tests/Suite/Matchers/MatchersTest.php index 3f3bece..3eefcb3 100644 --- a/tests/Suite/Matchers/MatchersTest.php +++ b/tests/Suite/Matchers/MatchersTest.php @@ -592,6 +592,36 @@ public function testLessThanOrEqualToSemverMatcher() $this->assertFalse($matcher->evaluate('2.2.2')); } + public function testBetweenSemverMatcher() + { + $this->setupSplitApp(); + + $condition = array( + 'matcherType' => 'BETWEEN_SEMVER', + 'betweenStringMatcherData' => array( + 'start' => '11.11.11', + 'end' => '22.22.22', + ) + ); + + $matcher = Matcher::factory($condition); + $this->assertTrue($matcher->evaluate('20.2.2')); + $this->assertTrue($matcher->evaluate('16.5.6')); + $this->assertTrue($matcher->evaluate('19.0.1')); + $this->assertFalse($matcher->evaluate(null)); + $this->assertFalse($matcher->evaluate('')); + $this->assertFalse($matcher->evaluate('22.22.25')); + $this->assertFalse($matcher->evaluate('10.0.0')); + + $condition = array( + 'matcherType' => 'BETWEEN_SEMVER', + 'betweenStringMatcherData' => null + ); + + $matcher = Matcher::factory($condition); + $this->assertFalse($matcher->evaluate('2.2.2')); + } + public static function tearDownAfterClass(): void { Utils\Utils::cleanCache(); diff --git a/tests/Suite/Sdk/SdkClientTest.php b/tests/Suite/Sdk/SdkClientTest.php index 100ab7a..48526ed 100644 --- a/tests/Suite/Sdk/SdkClientTest.php +++ b/tests/Suite/Sdk/SdkClientTest.php @@ -300,6 +300,20 @@ public function testClientSemverMatchers() $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'off'); $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.22.33-rc.1'))); $this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'v1'); + + //Assertions BetweenSemver + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '6.9.0'))); + $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '8.0.0-rc.22'))); + $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '9.0.0+metadata'))); + $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '10.4.4-rc.1+metadata'))); + $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1'); + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '10.4.7-rc.1+metadata'))); + $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'off'); + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '1.4.7'))); + $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'off'); } public function testClientWithUnsupportedMatcher() diff --git a/tests/Suite/Sdk/files/splitChanges.json b/tests/Suite/Sdk/files/splitChanges.json index 17e1620..e823d94 100644 --- a/tests/Suite/Sdk/files/splitChanges.json +++ b/tests/Suite/Sdk/files/splitChanges.json @@ -640,6 +640,55 @@ ] } ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "between_semver_flag", + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "sets": [], + "configurations": { + "on": "{\"size\":15,\"test\":20}", + "of": "{\"size\":15,\"defTreatment\":true}" + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType": "BETWEEN_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "betweenStringMatcherData": { + "start": "5.0.0", + "end": "10.4.6" + } + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 100 + }, + { + "treatment": "v2", + "size": 0 + } + ] + } + ] } ], "since": -1, From 89f55008c41c67b4293d70def260b1b0fad592e9 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Mon, 29 Apr 2024 19:27:55 -0300 Subject: [PATCH 4/5] InListSemver Matcher implementation. --- src/SplitIO/Grammar/Condition/Matcher.php | 7 +++ .../Condition/Matcher/BetweenSemver.php | 3 +- .../Condition/Matcher/InListSemver.php | 51 ++++++++++++++++++ tests/Suite/Matchers/MatchersTest.php | 33 ++++++++++++ tests/Suite/Sdk/SdkClientTest.php | 16 ++++++ tests/Suite/Sdk/files/splitChanges.json | 53 +++++++++++++++++++ 6 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/SplitIO/Grammar/Condition/Matcher/InListSemver.php diff --git a/src/SplitIO/Grammar/Condition/Matcher.php b/src/SplitIO/Grammar/Condition/Matcher.php index e7771d9..2ab7719 100644 --- a/src/SplitIO/Grammar/Condition/Matcher.php +++ b/src/SplitIO/Grammar/Condition/Matcher.php @@ -9,6 +9,7 @@ use SplitIO\Grammar\Condition\Matcher\EqualToSemver; use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualTo; use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualToSemver; +use SplitIO\Grammar\Condition\Matcher\InListSemver; use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualTo; use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualToSemver; use SplitIO\Grammar\Condition\Matcher\Segment; @@ -48,6 +49,7 @@ class Matcher const GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'; const LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'; const BETWEEN_SEMVER = 'BETWEEN_SEMVER'; + const IN_LIST_SEMVER = 'IN_LIST_SEMVER'; public static function factory($matcher) { @@ -158,6 +160,11 @@ public static function factory($matcher) is_array($matcher['betweenStringMatcherData'])) ? $matcher['betweenStringMatcherData'] : null; return new BetweenSemver($data, $negate, $attribute); + case self::IN_LIST_SEMVER: + $data = (isset($matcher['whitelistMatcherData']['whitelist']) && + is_array($matcher['whitelistMatcherData']['whitelist'])) + ? $matcher['whitelistMatcherData']['whitelist'] : null; + return new InListSemver($data, $negate, $attribute); // @codeCoverageIgnoreStart default: throw new UnsupportedMatcherException("Unable to create matcher for matcher type: " . $matcherType); diff --git a/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php b/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php index f10531a..fcffc1d 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php +++ b/src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php @@ -34,7 +34,8 @@ protected function evalKey($key) return false; } - $result = SemverComparer::do($keySemver, $this->startTarget) >= 0 && SemverComparer::do($keySemver, $this->endTarget) <= 0; + $result = SemverComparer::do($keySemver, $this->startTarget) >= 0 + && SemverComparer::do($keySemver, $this->endTarget) <= 0; SplitApp::logger()->debug($this->startTarget->getVersion() . " <= " . $keySemver->getVersion() . " <= " . $this->endTarget->getVersion() diff --git a/src/SplitIO/Grammar/Condition/Matcher/InListSemver.php b/src/SplitIO/Grammar/Condition/Matcher/InListSemver.php new file mode 100644 index 0000000..8eaf391 --- /dev/null +++ b/src/SplitIO/Grammar/Condition/Matcher/InListSemver.php @@ -0,0 +1,51 @@ +targetList = array(); + parent::__construct(Matcher::IN_LIST_SEMVER, $negate, $attribute); + + if (is_array($targetList)) { + foreach ($targetList as $item) { + $toAdd = Semver::build($item); + + if ($toAdd != null) { + array_push($this->targetList, $toAdd); + } + } + } + } + + /** + * + * @param mixed $key + */ + protected function evalKey($key) + { + if ($key == null || !is_string($key) || count($this->targetList) == 0) { + return false; + } + + $keySemver = Semver::build($key); + if ($keySemver == null) { + return false; + } + + foreach ($this->targetList as $item) { + if (SemverComparer::equals($keySemver, $item)) { + return true; + } + } + + return false; + } +} diff --git a/tests/Suite/Matchers/MatchersTest.php b/tests/Suite/Matchers/MatchersTest.php index 3eefcb3..9989ffc 100644 --- a/tests/Suite/Matchers/MatchersTest.php +++ b/tests/Suite/Matchers/MatchersTest.php @@ -622,6 +622,39 @@ public function testBetweenSemverMatcher() $this->assertFalse($matcher->evaluate('2.2.2')); } + public function testInListSemverMatcher() + { + $this->setupSplitApp(); + + $condition = array( + 'matcherType' => 'IN_LIST_SEMVER', + 'whitelistMatcherData' => array( + 'whitelist' => array( + '1.1.1', + '2.2.2', + '3.3.3', + ) + ) + ); + + $matcher = Matcher::factory($condition); + $this->assertTrue($matcher->evaluate('1.1.1')); + $this->assertTrue($matcher->evaluate('2.2.2')); + $this->assertTrue($matcher->evaluate('3.3.3')); + $this->assertFalse($matcher->evaluate(null)); + $this->assertFalse($matcher->evaluate('')); + $this->assertFalse($matcher->evaluate('4.22.25')); + $this->assertFalse($matcher->evaluate('10.0.0')); + + $condition = array( + 'matcherType' => 'IN_LIST_SEMVER', + 'whitelistMatcherData' => null + ); + + $matcher = Matcher::factory($condition); + $this->assertFalse($matcher->evaluate('2.2.2')); + } + public static function tearDownAfterClass(): void { Utils\Utils::cleanCache(); diff --git a/tests/Suite/Sdk/SdkClientTest.php b/tests/Suite/Sdk/SdkClientTest.php index 48526ed..69910df 100644 --- a/tests/Suite/Sdk/SdkClientTest.php +++ b/tests/Suite/Sdk/SdkClientTest.php @@ -314,6 +314,22 @@ public function testClientSemverMatchers() $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'off'); $this->assertEquals('off', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '1.4.7'))); $this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'off'); + + //Assertions InListSemver + // + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '6.7.8'))); + $this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '1.1.1-alpha'))); + $this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'v1'); + $this->assertEquals('v1', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '2.2.2+meta'))); + $this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'v1'); + + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '2.2.2'))); + $this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'off'); + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '1.1.1'))); + $this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'off'); + $this->assertEquals('off', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '8.6.0-rc.2'))); + $this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'off'); } public function testClientWithUnsupportedMatcher() diff --git a/tests/Suite/Sdk/files/splitChanges.json b/tests/Suite/Sdk/files/splitChanges.json index e823d94..55d66b7 100644 --- a/tests/Suite/Sdk/files/splitChanges.json +++ b/tests/Suite/Sdk/files/splitChanges.json @@ -689,6 +689,59 @@ ] } ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "inlist_semver_flag", + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "sets": [], + "configurations": { + "on": "{\"size\":15,\"test\":20}", + "of": "{\"size\":15,\"defTreatment\":true}" + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType": "IN_LIST_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": { + "whitelist": [ + "6.7.8", + "2.2.2+meta", + "1.1.1-alpha", + "8.6.0-rc.1", + "9.6.0-beta.1" + ] + } + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 100 + }, + { + "treatment": "v2", + "size": 0 + } + ] + } + ] } ], "since": -1, From 12d23a66d39d422895ee1e5046a1c268aed366e7 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Mon, 29 Apr 2024 19:32:39 -0300 Subject: [PATCH 5/5] polishing --- .../Condition/Matcher/EqualToSemver.php | 2 +- tests/Suite/Semver/ComparerTests.php | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/SplitIO/Grammar/Condition/Matcher/EqualToSemver.php b/src/SplitIO/Grammar/Condition/Matcher/EqualToSemver.php index 2b35512..5e8c494 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/EqualToSemver.php +++ b/src/SplitIO/Grammar/Condition/Matcher/EqualToSemver.php @@ -32,7 +32,7 @@ protected function evalKey($key) return false; } - $result = SemverComparer::Equals($this->toCompare, $keySemver); + $result = SemverComparer::equals($this->toCompare, $keySemver); SplitApp::logger()->debug($this->toCompare->getVersion() . " == " . $keySemver->getVersion() . " | Result: " . $result); diff --git a/tests/Suite/Semver/ComparerTests.php b/tests/Suite/Semver/ComparerTests.php index 976a755..93574f6 100644 --- a/tests/Suite/Semver/ComparerTests.php +++ b/tests/Suite/Semver/ComparerTests.php @@ -19,10 +19,10 @@ public function testGreaterThanOrEqualTo() $semver1 = Semver::build($v1); $semver2 = Semver::build($v2); - $this->assertTrue(SemverComparer::Do($semver1, $semver2) >= 0); - $this->assertTrue(SemverComparer::Do($semver1, $semver1) >= 0); - $this->assertTrue(SemverComparer::Do($semver2, $semver2) >= 0); - $this->assertFalse(SemverComparer::Do($semver2, $semver1) >= 0); + $this->assertTrue(SemverComparer::do($semver1, $semver2) >= 0); + $this->assertTrue(SemverComparer::do($semver1, $semver1) >= 0); + $this->assertTrue(SemverComparer::do($semver2, $semver2) >= 0); + $this->assertFalse(SemverComparer::do($semver2, $semver1) >= 0); } fclose($handle); @@ -44,10 +44,10 @@ public function testLessThanOrEqualTo() $semver1 = Semver::build($v1); $semver2 = Semver::build($v2); - $this->assertFalse(SemverComparer::Do($semver1, $semver2) <= 0); - $this->assertTrue(SemverComparer::Do($semver2, $semver1) <= 0); - $this->assertTrue(SemverComparer::Do($semver1, $semver1) <= 0); - $this->assertTrue(SemverComparer::Do($semver2, $semver2) <= 0); + $this->assertFalse(SemverComparer::do($semver1, $semver2) <= 0); + $this->assertTrue(SemverComparer::do($semver2, $semver1) <= 0); + $this->assertTrue(SemverComparer::do($semver1, $semver1) <= 0); + $this->assertTrue(SemverComparer::do($semver2, $semver2) <= 0); } fclose($handle); @@ -71,7 +71,7 @@ public function testEquals() $semver2 = Semver::build($c2); $expected = (bool) $c3; - $this->assertEquals($expected, SemverComparer::Equals($semver1, $semver2), $semver1->getVersion() . " - " . $semver2->getVersion() . " | " . $expected); + $this->assertEquals($expected, SemverComparer::equals($semver1, $semver2), $semver1->getVersion() . " - " . $semver2->getVersion() . " | " . $expected); } fclose($handle); @@ -96,7 +96,7 @@ public function testBetween() $semver2 = Semver::build($c2); $semver3 = Semver::build($c3); - $result = SemverComparer::Do($semver2, $semver1) >= 0 && SemverComparer::Do($semver2, $semver3) <= 0; + $result = SemverComparer::do($semver2, $semver1) >= 0 && SemverComparer::do($semver2, $semver3) <= 0; $this->assertEquals((bool) $c4, $result); }