From e58a513569a8d55f966c63b7796f3d9731843354 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 4 Jun 2025 11:00:39 -0700 Subject: [PATCH] Updated evaluator --- lib/splitclient-rb/engine/models/label.rb | 1 + lib/splitclient-rb/engine/parser/evaluator.rb | 8 ++ spec/engine/parser/evaluator_spec.rb | 26 +++++- .../splits/engine/prerequisites_matcher.json | 79 +++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 spec/test_data/splits/engine/prerequisites_matcher.json diff --git a/lib/splitclient-rb/engine/models/label.rb b/lib/splitclient-rb/engine/models/label.rb index 750ad79d..d55d16b4 100644 --- a/lib/splitclient-rb/engine/models/label.rb +++ b/lib/splitclient-rb/engine/models/label.rb @@ -6,4 +6,5 @@ class SplitIoClient::Engine::Models::Label NOT_IN_SPLIT = 'not in split'.freeze NOT_READY = 'not ready'.freeze NOT_FOUND = 'definition not found'.freeze + PREREQUISITES_NOT_MET = 'prerequisites not met'.freeze end diff --git a/lib/splitclient-rb/engine/parser/evaluator.rb b/lib/splitclient-rb/engine/parser/evaluator.rb index ec7636cd..5e977632 100644 --- a/lib/splitclient-rb/engine/parser/evaluator.rb +++ b/lib/splitclient-rb/engine/parser/evaluator.rb @@ -38,6 +38,14 @@ def split_configurations(treatment, split) end def match(split, keys, attributes) + prerequisites_matcher = SplitIoClient::PrerequisitesMatcher.new(split[:prerequisites], @config.split_logger) + return treatment_hash(Models::Label::PREREQUISITES_NOT_MET, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) unless prerequisites_matcher.match?( + matching_key: keys[:matching_key], + bucketing_key: keys[:bucketing_key], + evaluator: self, + attributes: attributes + ) + in_rollout = false key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key] legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false diff --git a/spec/engine/parser/evaluator_spec.rb b/spec/engine/parser/evaluator_spec.rb index a6beabe9..8aa9e2d7 100644 --- a/spec/engine/parser/evaluator_spec.rb +++ b/spec/engine/parser/evaluator_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' describe SplitIoClient::Engine::Parser::Evaluator do - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(@default_config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(@default_config) } + let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([])} let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config, flag_sets_repository, flag_set_filter) } - let(:evaluator) { described_class.new(segments_repository, splits_repository, rule_based_segments_repository, true) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } + let(:evaluator) { described_class.new(segments_repository, splits_repository, rule_based_segments_repository, config) } let(:killed_split) { { killed: true, defaultTreatment: 'default' } } let(:archived_split) { { status: 'ARCHIVED' } } @@ -17,6 +18,11 @@ SplitIoClient.root, 'spec/test_data/splits/engine/dependency_matcher.json' )), symbolize_names: true) end + let(:split_data_prereq) do + JSON.parse(File.read(File.join( + SplitIoClient.root, 'spec/test_data/splits/engine/prerequisites_matcher.json' + )), symbolize_names: true) + end it 'returns killed treatment' do expect(evaluator.evaluate_feature_flag({ matching_key: 'foo' }, killed_split)) @@ -40,4 +46,16 @@ evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user_id_1' }, split_data[:ff][:d][1]) end end + + context 'prerequisites matcher' do + it 'test match' do + splits_repository.update([split_data_prereq[:ff][:d][0], split_data_prereq[:ff][:d][1]], [], 1234) + + result = evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user' }, 'test_prereq') + expect(result[:treatment]).to eq('off_default') + + result = evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user_id_1' }, 'test_prereq') + expect(result[:treatment]).to eq('on') + end + end end diff --git a/spec/test_data/splits/engine/prerequisites_matcher.json b/spec/test_data/splits/engine/prerequisites_matcher.json new file mode 100644 index 00000000..78474660 --- /dev/null +++ b/spec/test_data/splits/engine/prerequisites_matcher.json @@ -0,0 +1,79 @@ +{ "ff":{ + "d": [ + { + "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", + "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", + "name":"test_whitelist", + "prerequisites": [], + "trafficTypeId":"u", + "trafficTypeName":"User", + "seed":-1245274114, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off", + "conditions":[ + { + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "matcherType":"WHITELIST", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":{ + "whitelist":[ + "fake_user_id_1", + "fake_user_id_3" + ] + } + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + } + ] + } + ], + "sets": ["set_4"] + }, + { + "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", + "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", + "name":"test_prereq", + "prerequisites": [{"n": "test_whitelist", "ts": ["on"]}], + "trafficTypeId":"u", + "trafficTypeName":"User", + "seed":-1245274114, + "status":"ACTIVE", + "killed":false, + "defaultTreatment":"off_default", + "conditions":[ + { + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"on", + "size":100 + } + ] + } + ] + } + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} +}