diff --git a/CHANGES.txt b/CHANGES.txt index 2ad00a32..33219edd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,9 @@ CHANGES +8.6.0 (Jun 17, 2025) +- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK. +- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. + 8.5.0 (Jan 17, 2025) - Fixed high cpu usage when unique keys are cleared every 24 hours. - Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs. diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index 94386cf8..b4ffcdeb 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -23,6 +23,7 @@ require 'splitclient-rb/cache/repositories/splits_repository' require 'splitclient-rb/cache/repositories/events_repository' require 'splitclient-rb/cache/repositories/impressions_repository' +require 'splitclient-rb/cache/repositories/rule_based_segments_repository' require 'splitclient-rb/cache/repositories/events/memory_repository' require 'splitclient-rb/cache/repositories/events/redis_repository' require 'splitclient-rb/cache/repositories/flag_sets/memory_repository' @@ -47,6 +48,7 @@ require 'splitclient-rb/helpers/decryption_helper' require 'splitclient-rb/helpers/util' require 'splitclient-rb/helpers/repository_helper' +require 'splitclient-rb/helpers/evaluator_helper' require 'splitclient-rb/split_factory' require 'splitclient-rb/split_factory_builder' require 'splitclient-rb/split_config' @@ -96,13 +98,17 @@ require 'splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher' require 'splitclient-rb/engine/matchers/between_semver_matcher' require 'splitclient-rb/engine/matchers/in_list_semver_matcher' +require 'splitclient-rb/engine/matchers/rule_based_segment_matcher' +require 'splitclient-rb/engine/matchers/prerequisites_matcher' require 'splitclient-rb/engine/evaluator/splitter' require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker' require 'splitclient-rb/engine/impressions/unique_keys_tracker' require 'splitclient-rb/engine/metrics/binary_search_latency_tracker' require 'splitclient-rb/engine/models/split' require 'splitclient-rb/engine/models/label' +require 'splitclient-rb/engine/models/segment_type' require 'splitclient-rb/engine/models/treatment' +require 'splitclient-rb/engine/models/split_http_response' require 'splitclient-rb/engine/auth_api_client' require 'splitclient-rb/engine/back_off' require 'splitclient-rb/engine/push_manager' diff --git a/lib/splitclient-rb/cache/fetchers/split_fetcher.rb b/lib/splitclient-rb/cache/fetchers/split_fetcher.rb index d39b1aea..c1cba9dc 100644 --- a/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +++ b/lib/splitclient-rb/cache/fetchers/split_fetcher.rb @@ -2,10 +2,11 @@ module SplitIoClient module Cache module Fetchers class SplitFetcher - attr_reader :splits_repository + attr_reader :splits_repository, :rule_based_segments_repository - def initialize(splits_repository, api_key, config, telemetry_runtime_producer) + def initialize(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) @splits_repository = splits_repository + @rule_based_segments_repository = rule_based_segments_repository @api_key = api_key @config = config @semaphore = Mutex.new @@ -23,10 +24,11 @@ def call def fetch_splits(fetch_options = { cache_control_headers: false, till: nil }) @semaphore.synchronize do - data = splits_since(@splits_repository.get_change_number, fetch_options) - - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:splits], data[:till], @config) + data = splits_since(@splits_repository.get_change_number, @rule_based_segments_repository.get_change_number, fetch_options) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:ff][:d], data[:ff][:t], @config, @splits_api.clear_storage) + SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segments_repository, data[:rbs][:d], data[:rbs][:t], @config) @splits_repository.set_segment_names(data[:segment_names]) + @rule_based_segments_repository.set_segment_names(data[:segment_names]) @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled { segment_names: data[:segment_names], success: true } @@ -55,8 +57,8 @@ def splits_thread end end - def splits_since(since, fetch_options = { cache_control_headers: false, till: nil }) - splits_api.since(since, fetch_options) + def splits_since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil }) + splits_api.since(since, since_rbs, fetch_options) end def splits_api diff --git a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb new file mode 100644 index 00000000..79264b8f --- /dev/null +++ b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb @@ -0,0 +1,122 @@ +require 'concurrent' + +module SplitIoClient + module Cache + module Repositories + class RuleBasedSegmentsRepository < Repository + attr_reader :adapter + DEFAULT_CONDITIONS_TEMPLATE = [{ + conditionType: "ROLLOUT", + matcherGroup: { + combiner: "AND", + matchers: [ + { + keySelector: nil, + matcherType: "ALL_KEYS", + negate: false, + userDefinedSegmentMatcherData: nil, + whitelistMatcherData: nil, + unaryNumericMatcherData: nil, + betweenMatcherData: nil, + dependencyMatcherData: nil, + booleanMatcherData: nil, + stringMatcherData: nil + }] + } + }] + TILL_PREFIX = '.rbsegments.till' + RB_SEGMENTS_PREFIX = '.rbsegment.' + REGISTERED_PREFIX = '.segments.registered' + + def initialize(config) + super(config) + @adapter = case @config.cache_adapter.class.to_s + when 'SplitIoClient::Cache::Adapters::RedisAdapter' + SplitIoClient::Cache::Adapters::CacheAdapter.new(@config) + else + @config.cache_adapter + end + unless @config.mode.equal?(:consumer) + @adapter.set_string(namespace_key(TILL_PREFIX), '-1') + @adapter.initialize_map(namespace_key(REGISTERED_PREFIX)) + end + end + + def update(to_add, to_delete, new_change_number) + to_add.each{ |rule_based_segment| add_rule_based_segment(rule_based_segment) } + to_delete.each{ |rule_based_segment| remove_rule_based_segment(rule_based_segment) } + set_change_number(new_change_number) + end + + def get_rule_based_segment(name) + rule_based_segment = @adapter.string(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}")) + + JSON.parse(rule_based_segment, symbolize_names: true) if rule_based_segment + end + + def rule_based_segment_names + @adapter.find_strings_by_prefix(namespace_key(RB_SEGMENTS_PREFIX)) + .map { |rule_based_segment_names| rule_based_segment_names.gsub(namespace_key(RB_SEGMENTS_PREFIX), '') } + end + + def set_change_number(since) + @adapter.set_string(namespace_key(TILL_PREFIX), since) + end + + def get_change_number + @adapter.string(namespace_key(TILL_PREFIX)) + end + + def set_segment_names(names) + return if names.nil? || names.empty? + + names.each do |name| + @adapter.add_to_set(namespace_key(REGISTERED_PREFIX), name) + end + end + + def exists?(name) + @adapter.exists?(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}")) + end + + def clear + @adapter.clear(namespace_key) + end + + def contains?(segment_names) + return false if rule_based_segment_names.empty? + return set(segment_names).subset?(rule_based_segment_names) + end + + private + + def add_rule_based_segment(rule_based_segment) + return unless rule_based_segment[:name] + + if check_undefined_matcher(rule_based_segment) + @config.logger.warn("Rule based segment #{rule_based_segment[:name]} has undefined matcher, setting conditions to default template.") + rule_based_segment[:conditions] = RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE + end + + @adapter.set_string(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"), rule_based_segment.to_json) + end + + def check_undefined_matcher(rule_based_segment) + for condition in rule_based_segment[:conditions] + for matcher in condition[:matcherGroup][:matchers] + if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}") + @config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{rule_based_segment[:name]}") + return true + end + end + end + return false + end + + def remove_rule_based_segment(rule_based_segment) + @adapter.delete(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}")) + end + end + end + end +end diff --git a/lib/splitclient-rb/cache/repositories/segments_repository.rb b/lib/splitclient-rb/cache/repositories/segments_repository.rb index 953c18c7..a45ee167 100644 --- a/lib/splitclient-rb/cache/repositories/segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/segments_repository.rb @@ -83,6 +83,13 @@ def segment_keys_count 0 end + def contains?(segment_names) + if segment_names.empty? + return false + end + return segment_names.to_set.subset?(used_segment_names.to_set) + end + private def segment_data(name) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index e98e0d84..9fcae0e0 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -31,6 +31,9 @@ class SplitsRepository < Repository ], label: "targeting rule type unsupported by sdk" }] + TILL_PREFIX = '.splits.till' + SPLIT_PREFIX = '.split.' + READY_PREFIX = '.splits.ready' def initialize(config, flag_sets_repository, flag_set_filter) super(config) @@ -43,10 +46,7 @@ def initialize(config, flag_sets_repository, flag_set_filter) end @flag_sets = flag_sets_repository @flag_set_filter = flag_set_filter - unless @config.mode.equal?(:consumer) - @adapter.set_string(namespace_key('.splits.till'), '-1') - @adapter.initialize_map(namespace_key('.segments.registered')) - end + initialize_keys end def update(to_add, to_delete, new_change_number) @@ -87,16 +87,16 @@ def traffic_type_exists(tt_name) # Return an array of Split Names excluding control keys like splits.till def split_names - @adapter.find_strings_by_prefix(namespace_key('.split.')) - .map { |split| split.gsub(namespace_key('.split.'), '') } + @adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX)) + .map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') } end def set_change_number(since) - @adapter.set_string(namespace_key('.splits.till'), since) + @adapter.set_string(namespace_key(TILL_PREFIX), since) end def get_change_number - @adapter.string(namespace_key('.splits.till')) + @adapter.string(namespace_key(TILL_PREFIX)) end def set_segment_names(names) @@ -112,21 +112,22 @@ def exists?(name) end def ready? - @adapter.string(namespace_key('.splits.ready')).to_i != -1 + @adapter.string(namespace_key(READY_PREFIX)).to_i != -1 end def not_ready! - @adapter.set_string(namespace_key('.splits.ready'), -1) + @adapter.set_string(namespace_key(READY_PREFIX), -1) end def ready! - @adapter.set_string(namespace_key('.splits.ready'), Time.now.utc.to_i) + @adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i) end def clear @tt_cache.clear @adapter.clear(namespace_key) + initialize_keys end def kill(change_number, split_name, default_treatment) @@ -167,6 +168,13 @@ def flag_set_filter private + def initialize_keys + unless @config.mode.equal?(:consumer) + @adapter.set_string(namespace_key(TILL_PREFIX), '-1') + @adapter.initialize_map(namespace_key('.segments.registered')) + end + end + def add_feature_flag(split) return unless split[:name] existing_split = get_split(split[:name]) diff --git a/lib/splitclient-rb/cache/stores/localhost_split_builder.rb b/lib/splitclient-rb/cache/stores/localhost_split_builder.rb index b8f209ab..1fc2f949 100644 --- a/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +++ b/lib/splitclient-rb/cache/stores/localhost_split_builder.rb @@ -22,7 +22,8 @@ def build_split(feature, treatments) seed: 2_089_907_429, defaultTreatment: 'control_treatment', configurations: build_configurations(treatments), - conditions: build_conditions(treatments) + conditions: build_conditions(treatments), + prerequisites: [] } end diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index b81fbfb2..ed0e1021 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -22,6 +22,7 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage @api_key = sdk_key @splits_repository = repositories[:splits] @segments_repository = repositories[:segments] + @rule_based_segments_repository = repositories[:rule_based_segments] @impressions_repository = repositories[:impressions] @events_repository = repositories[:events] @status_manager = status_manager @@ -115,6 +116,7 @@ def destroy @splits_repository.clear @segments_repository.clear + @rule_based_segments_repository.clear SplitIoClient.load_factory_registry SplitIoClient.split_factory_registry.remove(@api_key) diff --git a/lib/splitclient-rb/engine/api/client.rb b/lib/splitclient-rb/engine/api/client.rb index 65dcd027..7906443e 100644 --- a/lib/splitclient-rb/engine/api/client.rb +++ b/lib/splitclient-rb/engine/api/client.rb @@ -20,6 +20,11 @@ def get_api(url, api_key, params = {}, cache_control_headers = false) @config.split_logger.log_if_debug("GET #{url} proxy: #{api_client.proxy}") end + + rescue Zlib::GzipFile::Error => e + @config.logger.warn("Incorrect formatted response exception: #{e}\n") + SplitIoClient::Engine::Models::SplitHttpResponse.new(400, '', true) + rescue StandardError => e @config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}") raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace @@ -50,6 +55,9 @@ def post_api(url, api_key, data, headers = {}, params = {}) raise e, 'Split SDK failed to connect to backend to post information', e.backtrace end + def sdk_url_overriden? + @config.sdk_url_overriden? + end private def api_client diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index b4d17bda..887adb26 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -5,17 +5,40 @@ module Api # Retrieves split definitions from the Split Backend class Splits < Client + PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60 + SPEC_1_1 = "1.1" + def initialize(api_key, config, telemetry_runtime_producer) super(config) @api_key = api_key @telemetry_runtime_producer = telemetry_runtime_producer @flag_sets_filter = @config.flag_sets_filter + @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION + @last_proxy_check_timestamp = 0 + @clear_storage = false + @old_spec_since = nil end - def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil}) + def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil}) start = Time.now + + if check_last_proxy_check_timestamp + @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION + @config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") + @old_spec_since = since + since = -1 + since_rbs = -1 + fetch_options = { cache_control_headers: false, till: nil, sets: nil} + end + + if @spec_version == Splits::SPEC_1_1 + since = @old_spec_since unless @old_spec_since.nil? + params = { s: @spec_version, since: since } + @old_spec_since = nil + else + params = { s: @spec_version, since: since, rbSince: since_rbs } + end - params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since } params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty? params[:till] = fetch_options[:till] unless fetch_options[:till].nil? @config.logger.debug("Fetching from splitChanges with #{params}: ") @@ -24,39 +47,92 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.") raise ApiException.new response.body, 414 end + + if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION + @config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.") + @spec_version = Splits::SPEC_1_1 + @last_proxy_check_timestamp = Time.now + return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till], + sets: fetch_options[:sets]}) + end if response.success? - result = splits_with_segment_names(response.body) - unless result[:splits].empty? - @config.split_logger.log_if_debug("#{result[:splits].length} feature flags retrieved. since=#{since}") - end - @config.split_logger.log_if_transport("Feature flag changes response: #{result.to_s}") + result = JSON.parse(response.body, symbolize_names: true) - bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0) - @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket) - @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i) + return process_result(result, since, since_rbs, start) + end + @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status) - result - else - @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status) + @config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \ + 'Check your API key and base URI') - @config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \ - 'Check your API key and base URI') + raise 'Split SDK failed to connect to backend to fetch feature flags definitions' + end - raise 'Split SDK failed to connect to backend to fetch feature flags definitions' - end + def clear_storage + @clear_storage end private - def splits_with_segment_names(splits_json) - parsed_splits = JSON.parse(splits_json, symbolize_names: true) + def process_result(result, since, since_rbs, start) + if @spec_version == Splits::SPEC_1_1 + result = convert_to_new_spec(result) + end + + result[:rbs][:d] = check_rbs_data(result[:rbs][:d]) + result = objects_with_segment_names(result) + + if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION + @clear_storage = @last_proxy_check_timestamp != 0 + @last_proxy_check_timestamp = 0 + end + + unless result[:ff][:d].empty? + @config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}") + end + @config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}") + + unless result[:rbs][:d].empty? + @config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}") + end + @config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}") + + bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0) + @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket) + @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i) + + result + end + + def check_rbs_data(rbs_data) + rbs_data.each do |rb_segment| + rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil? + rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil? + rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil? + end + rbs_data + end - parsed_splits[:segment_names] = - parsed_splits[:splits].each_with_object(Set.new) do |split, splits| - splits << Helpers::Util.segment_names_by_feature_flag(split) + def objects_with_segment_names(parsed_objects) + parsed_objects[:segment_names] = Set.new + parsed_objects[:segment_names] = + parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits| + splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT") end.flatten - parsed_splits + parsed_objects[:rbs][:d].each do |rule_based_segment| + parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT") + end + + parsed_objects + end + + def check_last_proxy_check_timestamp + @spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS) + end + + def convert_to_new_spec(body) + {:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}} end end end diff --git a/lib/splitclient-rb/engine/matchers/combining_matcher.rb b/lib/splitclient-rb/engine/matchers/combining_matcher.rb index 9a54b75a..7a77d8b3 100644 --- a/lib/splitclient-rb/engine/matchers/combining_matcher.rb +++ b/lib/splitclient-rb/engine/matchers/combining_matcher.rb @@ -56,7 +56,9 @@ def eval_and(args) @matchers.all? do |matcher| if match_with_key?(matcher) - matcher.match?(value: args[:matching_key]) + key = args[:value] + key = args[:matching_key] unless args[:matching_key].nil? + matcher.match?(value: key) else matcher.match?(args) end diff --git a/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb b/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb new file mode 100644 index 00000000..7970b63a --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SplitIoClient + class PrerequisitesMatcher + def initialize(prerequisites, logger) + @prerequisites = prerequisites + @logger = logger + end + + def match?(args) + keys = { matching_key: args[:matching_key], bucketing_key: args[:bucketing_key] } + + match = true + @prerequisites.each do |prerequisite| + evaluate = args[:evaluator].evaluate_feature_flag(keys, prerequisite[:n], args[:attributes]) + next if prerequisite[:ts].include?(evaluate[:treatment]) + + @logger.log_if_debug("[PrerequisitesMatcher] feature flag #{prerequisite[:n]} evaluated to #{evaluate[:treatment]} \ + that does not exist in prerequisited treatments.") + match = false + break + end + + match + end + + def string_type? + false + end + end +end diff --git a/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb b/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb new file mode 100644 index 00000000..a5463d0a --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module SplitIoClient + # + # class to implement the user defined matcher + # + class RuleBasedSegmentMatcher < Matcher + MATCHER_TYPE = 'IN_RULE_BASED_SEGMENT' + + def initialize(segments_repository, rule_based_segments_repository, segment_name, config) + super(config.logger) + @rule_based_segments_repository = rule_based_segments_repository + @segments_repository = segments_repository + @segment_name = segment_name + @config = config + end + + # + # evaluates if the key matches the matcher + # + # @param key [string] key value to be matched + # + # @return [boolean] evaluation of the key against the segment + def match?(args) + rule_based_segment = @rule_based_segments_repository.get_rule_based_segment(@segment_name) + return false if rule_based_segment.nil? + + key = update_key(args) + return false if rule_based_segment[:excluded][:keys].include?(key) + + return false unless check_excluded_segments(rule_based_segment, key, args) + + matches = false + rule_based_segment[:conditions].each do |c| + condition = SplitIoClient::Condition.new(c, @config) + next if condition.empty? + + matches = Helpers::EvaluatorHelper.matcher_type(condition, @segments_repository, @rule_based_segments_repository).match?(args) + end + @logger.debug("[InRuleSegmentMatcher] #{@segment_name} is in rule based segment -> #{matches}") + matches + end + + private + + def check_excluded_segments(rule_based_segment, key, args) + rule_based_segment[:excluded][:segments].each do |segment| + if segment[:type] == SplitIoClient::Engine::Models::SegmentType::STANDARD && + @segments_repository.in_segment?(segment[:name], key) + return false + end + return false if segment[:type] == SplitIoClient::Engine::Models::SegmentType::RULE_BASED_SEGMENT && match_rbs( + @rule_based_segments_repository.get_rule_based_segment(segment[:name]), args + ) + end + true + end + + def update_key(args) + if args[:value].nil? || args[:value].empty? + args[:matching_key] + else + args[:value] + end + end + + def match_rbs(rule_based_segment, args) + rbs_matcher = RuleBasedSegmentMatcher.new(@segments_repository, @rule_based_segments_repository, + rule_based_segment[:name], @config) + rbs_matcher.match?(matching_key: args[:matching_key], + bucketing_key: args[:value], + attributes: args[:attributes]) + end + end +end 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/models/segment_type.rb b/lib/splitclient-rb/engine/models/segment_type.rb new file mode 100644 index 00000000..48cfae9e --- /dev/null +++ b/lib/splitclient-rb/engine/models/segment_type.rb @@ -0,0 +1,4 @@ +class SplitIoClient::Engine::Models::SegmentType + STANDARD = 'standard' + RULE_BASED_SEGMENT = 'rule-based' +end diff --git a/lib/splitclient-rb/engine/models/split_http_response.rb b/lib/splitclient-rb/engine/models/split_http_response.rb new file mode 100644 index 00000000..961b37f6 --- /dev/null +++ b/lib/splitclient-rb/engine/models/split_http_response.rb @@ -0,0 +1,19 @@ +module SplitIoClient + module Engine + module Models + class SplitHttpResponse + attr_accessor :status, :body + + def initialize(status, body, success) + @status = status + @body = body + @success = success + end + + def success? + @success + end + end + end + end +end diff --git a/lib/splitclient-rb/engine/parser/condition.rb b/lib/splitclient-rb/engine/parser/condition.rb index b5d7567b..0caaabe8 100644 --- a/lib/splitclient-rb/engine/parser/condition.rb +++ b/lib/splitclient-rb/engine/parser/condition.rb @@ -230,6 +230,13 @@ def matcher_in_list_semver(params) ) end + def matcher_in_rule_based_segment(params) + matcher = params[:matcher] + segment_name = matcher[:userDefinedSegmentMatcherData] && matcher[:userDefinedSegmentMatcherData][:segmentName] + + RuleBasedSegmentMatcher.new(params[:segments_repository], params[:rule_based_segments_repository], segment_name, @config) + end + # # @return [object] the negate value for this condition def negate @@ -246,6 +253,8 @@ def negate # @return [void] def set_partitions partitions_list = [] + return partitions_list unless @data.key?(:partitions) or @data.key?('partitions') + @data[:partitions].each do |p| partition = SplitIoClient::Partition.new(p) partitions_list << partition diff --git a/lib/splitclient-rb/engine/parser/evaluator.rb b/lib/splitclient-rb/engine/parser/evaluator.rb index afefd364..49e850a6 100644 --- a/lib/splitclient-rb/engine/parser/evaluator.rb +++ b/lib/splitclient-rb/engine/parser/evaluator.rb @@ -2,9 +2,10 @@ module SplitIoClient module Engine module Parser class Evaluator - def initialize(segments_repository, splits_repository, config) + def initialize(segments_repository, splits_repository, rb_segment_repository, config) @splits_repository = splits_repository @segments_repository = segments_repository + @rb_segment_repository = rb_segment_repository @config = config end @@ -37,6 +38,9 @@ def split_configurations(treatment, split) end def match(split, keys, attributes) + + return treatment_hash(Models::Label::PREREQUISITES_NOT_MET, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) unless check_prerequisites_matcher(split, keys, attributes) + in_rollout = false key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key] legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false @@ -58,8 +62,7 @@ def match(split, keys, attributes) in_rollout = true end - - condition_matched = matcher_type(condition).match?( + condition_matched = Helpers::EvaluatorHelper::matcher_type(condition, @segments_repository, @rb_segment_repository).match?( matching_key: keys[:matching_key], bucketing_key: keys[:bucketing_key], evaluator: self, @@ -70,35 +73,19 @@ def match(split, keys, attributes) result = splitter.get_treatment(key, split[:seed], condition.partitions, split[:algo]) - if result.nil? - return treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) - else - return treatment_hash(c[:label], result, split[:changeNumber],split_configurations(result, split)) - end + return treatment_from_result(result, split, c) end treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) end - def matcher_type(condition) - matchers = [] - - @segments_repository.adapter.pipelined do - condition.matchers.each do |matcher| - matchers << if matcher[:negate] - condition.negation_matcher(matcher_instance(matcher[:matcherType], condition, matcher)) - else - matcher_instance(matcher[:matcherType], condition, matcher) - end - end - end - - final_matcher = condition.create_condition_matcher(matchers) + private - if final_matcher.nil? - @logger.error('Invalid matcher type') + def treatment_from_result(result, split, condition) + if result.nil? + return treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) else - final_matcher + return treatment_hash(condition[:label], result, split[:changeNumber],split_configurations(result, split)) end end @@ -106,11 +93,18 @@ def treatment_hash(label, treatment, change_number = nil, configurations = nil) { label: label, treatment: treatment, change_number: change_number, config: configurations } end - def matcher_instance(type, condition, matcher) - condition.send( - "matcher_#{type.downcase}", - matcher: matcher, segments_repository: @segments_repository - ) + def check_prerequisites_matcher(split, keys, attributes) + if split.key?(:prerequisites) && !split[:prerequisites].nil? + prerequisites_matcher = SplitIoClient::PrerequisitesMatcher.new(split[:prerequisites], @config.split_logger) + return prerequisites_matcher.match?( + matching_key: keys[:matching_key], + bucketing_key: keys[:bucketing_key], + evaluator: self, + attributes: attributes + ) + end + + true end end end diff --git a/lib/splitclient-rb/engine/synchronizer.rb b/lib/splitclient-rb/engine/synchronizer.rb index d2e0b319..841aff6d 100644 --- a/lib/splitclient-rb/engine/synchronizer.rb +++ b/lib/splitclient-rb/engine/synchronizer.rb @@ -15,6 +15,7 @@ def initialize( ) @splits_repository = repositories[:splits] @segments_repository = repositories[:segments] + @rule_based_segments_repository = repositories[:rule_based_segments] @impressions_repository = repositories[:impressions] @events_repository = repositories[:events] @config = config @@ -63,12 +64,12 @@ def stop_periodic_fetch @segment_fetcher.stop_segments_thread end - def fetch_splits(target_change_number) - return if target_change_number <= @splits_repository.get_change_number.to_i + def fetch_splits(target_change_number, rbs_target_change_number) + return if check_exit_conditions(target_change_number, rbs_target_change_number) fetch_options = { cache_control_headers: true, till: nil } - result = attempt_splits_sync(target_change_number, + result = attempt_splits_sync(target_change_number, rbs_target_change_number, fetch_options, @config.on_demand_fetch_max_retries, @config.on_demand_fetch_retry_delay_seconds, @@ -82,8 +83,13 @@ def fetch_splits(target_change_number) return end - fetch_options[:till] = target_change_number - result = attempt_splits_sync(target_change_number, + if target_change_number != 0 + fetch_options[:till] = target_change_number + else + fetch_options[:till] = rbs_target_change_number + end + + result = attempt_splits_sync(target_change_number, rbs_target_change_number, fetch_options, ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES, nil, @@ -91,12 +97,7 @@ def fetch_splits(target_change_number) attempts = ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - result[:remaining_attempts] - if result[:success] - @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty? - @config.logger.debug("Refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled - else - @config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled - end + process_result(result, attempts) rescue StandardError => e @config.log_found_exception(__method__.to_s, e) end @@ -139,6 +140,15 @@ def fetch_segment(name, target_change_number) private + def process_result(result, attempts) + if result[:success] + @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty? + @config.logger.debug("Refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled + else + @config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled + end + end + def attempt_segment_sync(name, target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff) remaining_attempts = max_retries @segments_sync_backoff.reset @@ -156,7 +166,7 @@ def attempt_segment_sync(name, target_cn, fetch_options, max_retries, retry_dela end end - def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff) + def attempt_splits_sync(target_cn, rbs_target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff) remaining_attempts = max_retries @splits_sync_backoff.reset @@ -165,7 +175,7 @@ def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_secon result = @split_fetcher.fetch_splits(fetch_options) - return sync_result(true, remaining_attempts, result[:segment_names]) if target_cn <= @splits_repository.get_change_number + return sync_result(true, remaining_attempts, result[:segment_names]) if check_exit_conditions(target_cn, rbs_target_cn) return sync_result(false, remaining_attempts, result[:segment_names]) if remaining_attempts <= 0 delay = with_backoff ? @splits_sync_backoff.interval : retry_delay_seconds @@ -206,6 +216,16 @@ def sync_splits_and_segments splits_result[:success] && @segment_fetcher.fetch_segments end + + def check_exit_conditions(target_change_number, rbs_target_change_number) + return true if rbs_target_change_number == 0 and target_change_number == 0 + + return target_change_number <= @splits_repository.get_change_number.to_i if rbs_target_change_number == 0 + + return rbs_target_change_number <= @rule_based_segments_repository.get_change_number.to_i if target_change_number == 0 + + return (target_change_number <= @splits_repository.get_change_number.to_i and rbs_target_change_number <= @rule_based_segments_repository.get_change_number.to_i) + end end end end diff --git a/lib/splitclient-rb/helpers/evaluator_helper.rb b/lib/splitclient-rb/helpers/evaluator_helper.rb new file mode 100644 index 00000000..2b16e094 --- /dev/null +++ b/lib/splitclient-rb/helpers/evaluator_helper.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module SplitIoClient + module Helpers + class EvaluatorHelper + def self.matcher_type(condition, segments_repository, rb_segment_repository) + matchers = [] + segments_repository.adapter.pipelined do + condition.matchers.each do |matcher| + matchers << if matcher[:negate] + condition.negation_matcher(matcher_instance(matcher[:matcherType], condition, + matcher, segments_repository, + rb_segment_repository)) + else + matcher_instance(matcher[:matcherType], condition, matcher, segments_repository, rb_segment_repository) + end + end + end + final_matcher = condition.create_condition_matcher(matchers) + + if final_matcher.nil? + config.logger.error('Invalid matcher type') + else + final_matcher + end + final_matcher + end + + def self.matcher_instance(type, condition, matcher, segments_repository, rb_segment_repository) + condition.send( + "matcher_#{type.downcase}", + matcher: matcher, segments_repository: segments_repository, rule_based_segments_repository: rb_segment_repository + ) + end + end + end +end diff --git a/lib/splitclient-rb/helpers/repository_helper.rb b/lib/splitclient-rb/helpers/repository_helper.rb index 11f42416..b21fabba 100644 --- a/lib/splitclient-rb/helpers/repository_helper.rb +++ b/lib/splitclient-rb/helpers/repository_helper.rb @@ -3,7 +3,7 @@ module SplitIoClient module Helpers class RepositoryHelper - def self.update_feature_flag_repository(feature_flag_repository, feature_flags, change_number, config) + def self.update_feature_flag_repository(feature_flag_repository, feature_flags, change_number, config, clear_storage) to_add = [] to_delete = [] feature_flags.each do |feature_flag| @@ -13,18 +13,49 @@ def self.update_feature_flag_repository(feature_flag_repository, feature_flags, next end - unless feature_flag.key?(:impressionsDisabled) - feature_flag[:impressionsDisabled] = false - if config.debug_enabled - config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false") - end - end + feature_flag = check_missing_elements(feature_flag, config) config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled to_add.push(feature_flag) end + feature_flag_repository.clear if clear_storage feature_flag_repository.update(to_add, to_delete, change_number) end + + def self.check_missing_elements(feature_flag, config) + unless feature_flag.key?(:impressionsDisabled) + feature_flag[:impressionsDisabled] = false + if config.debug_enabled + config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false") + end + end + + unless feature_flag.key?(:prerequisites) + feature_flag[:prerequisites] = [] + if config.debug_enabled + config.logger.debug("feature flag (#{feature_flag[:name]}) does not have prerequisites field, setting it to empty array") + end + end + + feature_flag + end + + def self.update_rule_based_segment_repository(rule_based_segment_repository, rule_based_segments, change_number, config) + to_add = [] + to_delete = [] + rule_based_segments.each do |rule_based_segment| + if Engine::Models::Split.archived?(rule_based_segment) + config.logger.debug("removing rule based segment from store(#{rule_based_segment})") if config.debug_enabled + to_delete.push(rule_based_segment) + next + end + + config.logger.debug("storing rule based segment (#{rule_based_segment[:name]})") if config.debug_enabled + to_add.push(rule_based_segment) + end + + rule_based_segment_repository.update(to_add, to_delete, change_number) + end end end end diff --git a/lib/splitclient-rb/helpers/util.rb b/lib/splitclient-rb/helpers/util.rb index 0cf9aad2..070ee475 100644 --- a/lib/splitclient-rb/helpers/util.rb +++ b/lib/splitclient-rb/helpers/util.rb @@ -3,15 +3,24 @@ module SplitIoClient module Helpers class Util - def self.segment_names_by_feature_flag(feature_flag) - feature_flag[:conditions].each_with_object(Set.new) do |condition, names| + def self.segment_names_by_object(object, matcher_type) + object[:conditions].each_with_object(Set.new) do |condition, names| condition[:matcherGroup][:matchers].each do |matcher| - next if matcher[:userDefinedSegmentMatcherData].nil? + next if matcher[:userDefinedSegmentMatcherData].nil? || matcher[:matcherType] != matcher_type names << matcher[:userDefinedSegmentMatcherData][:segmentName] end end end + + def self.segment_names_in_rb_segment(object, matcher_type) + names = Set.new + names.merge segment_names_by_object(object, matcher_type) + object[:excluded][:segments].each do |segment| + names.add(segment[:name]) if segment[:type] == SplitIoClient::Engine::Models::SegmentType::STANDARD + end + names + end end end end diff --git a/lib/splitclient-rb/spec.rb b/lib/splitclient-rb/spec.rb index bd991ccc..641e3e63 100644 --- a/lib/splitclient-rb/spec.rb +++ b/lib/splitclient-rb/spec.rb @@ -3,7 +3,7 @@ module SplitIoClient module Spec class FeatureFlags - SPEC_VERSION = "1.1" + SPEC_VERSION = "1.3" end end end diff --git a/lib/splitclient-rb/split_config.rb b/lib/splitclient-rb/split_config.rb index da434c91..de7f3e4d 100644 --- a/lib/splitclient-rb/split_config.rb +++ b/lib/splitclient-rb/split_config.rb @@ -645,6 +645,10 @@ def consumer? @mode.equal?(:consumer) end + def sdk_url_overriden? + return @base_uri != SplitConfig.default_base_uri + end + # # gets the hostname where the sdk gem is running # diff --git a/lib/splitclient-rb/split_factory.rb b/lib/splitclient-rb/split_factory.rb index 442298e7..7c8a4306 100644 --- a/lib/splitclient-rb/split_factory.rb +++ b/lib/splitclient-rb/split_factory.rb @@ -55,7 +55,7 @@ def initialize(api_key, config_hash = {}) @status_manager = Engine::StatusManager.new(@config) @split_validator = SplitIoClient::Validators.new(@config) - @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config) + @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @rule_based_segment_repository, @config) start! @@ -154,6 +154,7 @@ def repositories segments: @segments_repository, impressions: @impressions_repository, events: @events_repository, + rule_based_segments: @rule_based_segment_repository } end @@ -178,7 +179,7 @@ def build_telemetry_components end def build_fetchers - @split_fetcher = SplitFetcher.new(@splits_repository, @api_key, @config, @runtime_producer) + @split_fetcher = SplitFetcher.new(@splits_repository, @rule_based_segment_repository, @api_key, @config, @runtime_producer) @segment_fetcher = SegmentFetcher.new(@segments_repository, @api_key, @config, @runtime_producer) end @@ -198,7 +199,7 @@ def build_synchronizer def build_streaming_components @push_status_queue = Queue.new - splits_worker = SSE::Workers::SplitsWorker.new(@synchronizer, @config, @splits_repository, @runtime_producer, @segment_fetcher) + splits_worker = SSE::Workers::SplitsWorker.new(@synchronizer, @config, @splits_repository, @runtime_producer, @segment_fetcher, @rule_based_segment_repository) segments_worker = SSE::Workers::SegmentsWorker.new(@synchronizer, @config, @segments_repository) notification_manager_keeper = SSE::NotificationManagerKeeper.new(@config, @runtime_producer, @push_status_queue) notification_processor = SSE::NotificationProcessor.new(@config, splits_worker, segments_worker) @@ -220,6 +221,7 @@ def build_repositories end @splits_repository = SplitsRepository.new(@config, @flag_sets_repository, @flag_sets_filter) @segments_repository = SegmentsRepository.new(@config) + @rule_based_segment_repository = RuleBasedSegmentsRepository.new(@config) @impressions_repository = ImpressionsRepository.new(@config) @events_repository = EventsRepository.new(@config, @api_key, @runtime_producer) end diff --git a/lib/splitclient-rb/sse/event_source/event_types.rb b/lib/splitclient-rb/sse/event_source/event_types.rb index dbe5361d..5396417d 100644 --- a/lib/splitclient-rb/sse/event_source/event_types.rb +++ b/lib/splitclient-rb/sse/event_source/event_types.rb @@ -8,6 +8,7 @@ class EventTypes SPLIT_KILL = 'SPLIT_KILL' SEGMENT_UPDATE = 'SEGMENT_UPDATE' CONTROL = 'CONTROL' + RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE' end end end diff --git a/lib/splitclient-rb/sse/notification_processor.rb b/lib/splitclient-rb/sse/notification_processor.rb index 0c137f59..af5563c3 100644 --- a/lib/splitclient-rb/sse/notification_processor.rb +++ b/lib/splitclient-rb/sse/notification_processor.rb @@ -13,6 +13,8 @@ def process(incoming_notification) case incoming_notification.data['type'] when SSE::EventSource::EventTypes::SPLIT_UPDATE process_split_update(incoming_notification) + when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE + process_split_update(incoming_notification) when SSE::EventSource::EventTypes::SPLIT_KILL process_split_kill(incoming_notification) when SSE::EventSource::EventTypes::SEGMENT_UPDATE @@ -25,7 +27,7 @@ def process(incoming_notification) private def process_split_update(notification) - @config.logger.debug("SPLIT UPDATE notification received: #{notification}") if @config.debug_enabled + @config.logger.debug("#{notification.event_type} notification received: #{notification}") if @config.debug_enabled @splits_worker.add_to_queue(notification) end diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index 7971bce8..dc15eb2c 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -4,7 +4,8 @@ module SplitIoClient module SSE module Workers class SplitsWorker - def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime_producer, segment_fetcher) + def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime_producer, + segment_fetcher, rule_based_segment_repository) @synchronizer = synchronizer @config = config @feature_flags_repository = feature_flags_repository @@ -12,6 +13,7 @@ def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime @running = Concurrent::AtomicBoolean.new(false) @telemetry_runtime_producer = telemetry_runtime_producer @segment_fetcher = segment_fetcher + @rule_based_segment_repository = rule_based_segment_repository end def start @@ -54,7 +56,10 @@ def perform case notification.data['type'] when SSE::EventSource::EventTypes::SPLIT_UPDATE success = update_feature_flag(notification) - @synchronizer.fetch_splits(notification.data['changeNumber']) unless success + @synchronizer.fetch_splits(notification.data['changeNumber'], 0) unless success + when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE + success = update_rule_based_segment(notification) + @synchronizer.fetch_splits(0, notification.data['changeNumber']) unless success when SSE::EventSource::EventTypes::SPLIT_KILL kill_feature_flag(notification) end @@ -65,11 +70,12 @@ def update_feature_flag(notification) return true if @feature_flags_repository.get_change_number.to_i >= notification.data['changeNumber'] return false unless !notification.data['d'].nil? && @feature_flags_repository.get_change_number == notification.data['pcn'] - new_split = return_split_from_json(notification) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository, - [new_split], - notification.data['changeNumber'], @config) - fetch_segments_if_not_exists(new_split) + new_split = update_feature_flag_repository(notification) + fetch_segments_if_not_exists(Helpers::Util.segment_names_by_object(new_split, 'IN_SEGMENT'), @feature_flags_repository) + if fetch_rule_based_segments_if_not_exists(Helpers::Util.segment_names_by_object(new_split, 'IN_RULE_BASED_SEGMENT'), + notification.data['changeNumber']) + return true + end @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS) @@ -80,30 +86,63 @@ def update_feature_flag(notification) false end + def update_feature_flag_repository(notification) + new_split = return_object_from_json(notification) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository, [new_split], + notification.data['changeNumber'], @config, false) + new_split + end + + def update_rule_based_segment(notification) + return true if @rule_based_segment_repository.get_change_number.to_i >= notification.data['changeNumber'] + return false unless !notification.data['d'].nil? && + @rule_based_segment_repository.get_change_number == notification.data['pcn'] + + new_rb_segment = return_object_from_json(notification) + SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segment_repository, + [new_rb_segment], + notification.data['changeNumber'], @config) + fetch_segments_if_not_exists(Helpers::Util.segment_names_in_rb_segment(new_rb_segment, 'IN_SEGMENT'), + @rule_based_segment_repository) + + # @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS) + + true + rescue StandardError => e + @config.logger.debug("Failed to update Split: #{e.inspect}") if @config.debug_enabled + + false + end + def kill_feature_flag(notification) return if @feature_flags_repository.get_change_number.to_i > notification.data['changeNumber'] @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") - @feature_flags_repository.kill( - notification.data['changeNumber'], - notification.data['splitName'], - notification.data['defaultTreatment'] - ) - @synchronizer.fetch_splits(notification.data['changeNumber']) + @feature_flags_repository.kill(notification.data['changeNumber'], + notification.data['splitName'], + notification.data['defaultTreatment']) + @synchronizer.fetch_splits(notification.data['changeNumber'], 0) end - def return_split_from_json(notification) - split_json = Helpers::DecryptionHelper.get_encoded_definition(notification.data['c'], notification.data['d']) - JSON.parse(split_json, symbolize_names: true) + def return_object_from_json(notification) + object_json = Helpers::DecryptionHelper.get_encoded_definition(notification.data['c'], notification.data['d']) + JSON.parse(object_json, symbolize_names: true) end - def fetch_segments_if_not_exists(feature_flag) - segment_names = Helpers::Util.segment_names_by_feature_flag(feature_flag) + def fetch_segments_if_not_exists(segment_names, object_repository) return if segment_names.nil? - @feature_flags_repository.set_segment_names(segment_names) + object_repository.set_segment_names(segment_names) @segment_fetcher.fetch_segments_if_not_exists(segment_names) end + + def fetch_rule_based_segments_if_not_exists(segment_names, change_number) + return false if segment_names.nil? || segment_names.empty? || @rule_based_segment_repository.contains?(segment_names.to_a) + + @synchronizer.fetch_splits(0, change_number) + + true + end end end end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index bc0e4bd5..d0431b85 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.5.0' + VERSION = '8.6.0' end diff --git a/spec/allocations/splitclient-rb/clients/split_client_spec.rb b/spec/allocations/splitclient-rb/clients/split_client_spec.rb index 0dd15231..5d38f377 100644 --- a/spec/allocations/splitclient-rb/clients/split_client_spec.rb +++ b/spec/allocations/splitclient-rb/clients/split_client_spec.rb @@ -10,6 +10,7 @@ let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:impression_counter) { SplitIoClient::Engine::Common::ImpressionCounter.new } let(:evaluation_producer) { SplitIoClient::Telemetry::EvaluationProducer.new(config) } let(:impression_observer) { SplitIoClient::Observers::ImpressionObserver.new } @@ -19,7 +20,7 @@ let(:api_key) { 'SplitClient-key' } let(:telemetry_api) { SplitIoClient::Api::TelemetryApi.new(config, api_key, runtime_producer) } let(:impressions_api) { SplitIoClient::Api::Impressions.new(api_key, config, runtime_producer) } - let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) } + let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) } let(:sender_adapter) do SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, diff --git a/spec/cache/fetchers/segment_fetch_spec.rb b/spec/cache/fetchers/segment_fetch_spec.rb index f816f445..6280866b 100644 --- a/spec/cache/fetchers/segment_fetch_spec.rb +++ b/spec/cache/fetchers/segment_fetch_spec.rb @@ -26,7 +26,7 @@ stub_request(:get, 'https://sdk.split.io/api/segmentChanges/employees?since=1473863075059') .to_return(status: 200, body: segments_json2) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_with_segments_json) end @@ -36,10 +36,11 @@ 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(config, flag_sets_repository, flag_set_filter) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:segment_fetcher) { described_class.new(segments_repository, '', config, telemetry_runtime_producer) } let(:split_fetcher) do - SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, '', config, telemetry_runtime_producer) + SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) end it 'fetch segments' do @@ -72,10 +73,11 @@ let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(config) } let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:segment_fetcher) { described_class.new(segments_repository, '', config, telemetry_runtime_producer) } let(:split_fetcher) do - SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, '', config, telemetry_runtime_producer) + SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) end it 'fetch segments' do diff --git a/spec/cache/fetchers/split_fetch_spec.rb b/spec/cache/fetchers/split_fetch_spec.rb index 957d4101..abe65d01 100644 --- a/spec/cache/fetchers/split_fetch_spec.rb +++ b/spec/cache/fetchers/split_fetch_spec.rb @@ -11,7 +11,7 @@ end before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: active_splits_json) end @@ -26,20 +26,23 @@ 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(config, flag_sets_repository, flag_set_filter) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } + let(:store) { described_class.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) } it 'returns splits since' do - splits = store.send(:splits_since, -1) + splits = store.send(:splits_since, -1, -1) - expect(splits[:splits].count).to eq(2) + expect(splits[:ff][:d].count).to eq(2) + expect(splits[:rbs][:d].count).to eq(1) end it 'fetch data in the cache' do store.send(:fetch_splits) - expect(store.splits_repository.splits.size).to eq(2) - expect(store.splits_repository.get_change_number).to eq(store.send(:splits_since, -1)[:till]) + expect(store.splits_repository.get_change_number).to eq(store.send(:splits_since, -1, -1)[:ff][:t]) + expect(store.rule_based_segments_repository.rule_based_segment_names.size).to eq(1) + expect(store.rule_based_segments_repository.get_change_number).to eq(store.send(:splits_since, -1, -1)[:rbs][:t]) end it 'refreshes splits' do @@ -48,7 +51,7 @@ active_split = store.splits_repository.splits['test_1_ruby'] expect(active_split[:status]).to eq('ACTIVE') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1473413807667') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1473413807667&rbSince=1457726098069') .to_return(status: 200, body: archived_splits_json) store.send(:fetch_splits) @@ -77,25 +80,26 @@ let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(['set_2']) } let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new(['set_2']) } let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } + let(:store) { described_class.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) } before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1&sets=set_2') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_2') .to_return(status: 200, body: active_splits_json) end it 'returns splits since' do - splits = store.send(:splits_since, -1) + splits = store.send(:splits_since, -1, -1) - expect(splits[:splits].count).to eq(2) + expect(splits[:ff][:d].count).to eq(2) end it 'fetch data in the cache' do store.send(:fetch_splits) expect(store.splits_repository.splits.size).to eq(1) - expect(store.splits_repository.get_change_number).to eq(store.send(:splits_since, -1)[:till]) + expect(store.splits_repository.get_change_number).to eq(store.send(:splits_since, -1, -1)[:ff][:t]) end it 'refreshes splits' do @@ -103,14 +107,15 @@ expect(store.splits_repository.get_split('sample_feature')[:name]).to eq('sample_feature') expect(store.splits_repository.get_split('test_1_ruby')).to eq(nil) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1473413807667&sets=set_2') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1473413807667&rbSince=1457726098069&sets=set_2') .to_return(status: 200, body: archived_splits_json) store.send(:fetch_splits) expect(store.splits_repository.get_split('sample_feature')).to eq(nil) store.splits_repository.set_change_number(-1) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1&sets=set_2') + store.rule_based_segments_repository.set_change_number(-1) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_2') .to_return(status: 200, body: active_splits_json) store.send(:fetch_splits) @@ -129,18 +134,19 @@ let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(config) } let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } + let(:store) { described_class.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) } it 'returns splits since' do - splits = store.send(:splits_since, -1) - expect(splits[:splits].count).to eq(2) + splits = store.send(:splits_since, -1, -1) + expect(splits[:ff][:d].count).to eq(2) end it 'fetch data in the cache' do store.send(:fetch_splits) expect(store.splits_repository.splits.size).to eq(2) - expect(store.splits_repository.get_change_number).to eq(store.send(:splits_since, -1)[:till].to_s) + expect(store.splits_repository.get_change_number).to eq(store.send(:splits_since, -1, -1)[:ff][:t].to_s) end it 'refreshes splits' do @@ -149,7 +155,7 @@ active_split = store.splits_repository.splits['test_1_ruby'] expect(active_split[:status]).to eq('ACTIVE') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1473413807667') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1473413807667&rbSince=1457726098069') .to_return(status: 200, body: archived_splits_json) store.send(:fetch_splits) diff --git a/spec/cache/repositories/rule_based_segments_repository_spec.rb b/spec/cache/repositories/rule_based_segments_repository_spec.rb new file mode 100644 index 00000000..a4e2b9ba --- /dev/null +++ b/spec/cache/repositories/rule_based_segments_repository_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'set' + +describe SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository do + RSpec.shared_examples 'RuleBasedSegments Repository' do |cache_adapter| + let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: cache_adapter) } + let(:repository) { described_class.new(config) } + + before :all do + redis = Redis.new + redis.flushall + end + + before do + # in memory setup + repository.update([{name: 'foo', trafficTypeName: 'tt_name_1', conditions: []}, + {name: 'bar', trafficTypeName: 'tt_name_2', conditions: []}, + {name: 'baz', trafficTypeName: 'tt_name_1', conditions: []}], [], -1) + end + + after do + repository.update([], [{name: 'foo', trafficTypeName: 'tt_name_1', conditions: []}, + {name: 'bar', trafficTypeName: 'tt_name_2', conditions: []}, + {name: 'bar', trafficTypeName: 'tt_name_2', conditions: []}, + {name: 'qux', trafficTypeName: 'tt_name_3', conditions: []}, + {name: 'quux', trafficTypeName: 'tt_name_4', conditions: []}, + {name: 'corge', trafficTypeName: 'tt_name_5', conditions: []}, + {name: 'corge', trafficTypeName: 'tt_name_6', conditions: []}], -1) + end + + it 'returns rule_based_segments names' do + expect(Set.new(repository.rule_based_segment_names)).to eq(Set.new(%w[foo bar baz])) + end + + it 'returns rule_based_segment data' do + expect(repository.get_rule_based_segment('foo')).to eq( + { conditions: [] , name: 'foo', trafficTypeName: 'tt_name_1' }, + ) + end + + it 'remove undefined matcher with template condition' do + rule_based_segment = { name: 'corge', trafficTypeName: 'tt_name_5', conditions: [ + { + contitionType: 'WHITELIST', + label: 'some_label', + matcherGroup: { + matchers: [ + { + matcherType: 'UNDEFINED', + whitelistMatcherData: { + whitelist: ['k1', 'k2', 'k3'] + }, + negate: false, + } + ], + combiner: 'AND' + } + }] + } + + repository.update([rule_based_segment], [], -1) + expect(repository.get_rule_based_segment('corge')[:conditions]).to eq SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE + + # test with multiple conditions + rule_based_segment2 = { + name: 'corge2', + trafficTypeName: 'tt_name_5', + conditions: [ + { + contitionType: 'WHITELIST', + label: 'some_label', + matcherGroup: { + matchers: [ + { + matcherType: 'UNDEFINED', + whitelistMatcherData: { + whitelist: ['k1', 'k2', 'k3'] + }, + negate: false, + } + ], + combiner: 'AND' + } + }, + { + contitionType: 'WHITELIST', + label: 'some_other_label', + matcherGroup: { + matchers: [{matcherType: 'ALL_KEYS', negate: false}], + combiner: 'AND' + } + }] + } + + repository.update([rule_based_segment2], [], -1) + expect(repository.get_rule_based_segment('corge2')[:conditions]).to eq SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE + end + end + + describe 'with Memory Adapter' do + it_behaves_like 'RuleBasedSegments Repository', :memory + end + + describe 'with Redis Adapter' do + it_behaves_like 'RuleBasedSegments Repository', :redis + end +end diff --git a/spec/engine/api/splits_spec.rb b/spec/engine/api/splits_spec.rb index 63e00943..e5c4a47f 100644 --- a/spec/engine/api/splits_spec.rb +++ b/spec/engine/api/splits_spec.rb @@ -5,7 +5,7 @@ describe SplitIoClient::Api::Splits do let(:splits) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../../test_data/splits/splits.json'))) } - context '#splits_with_segment_names' do + context '#objects_with_segment_names' do let(:config) do SplitIoClient::SplitConfig.new( logger: Logger.new(log), @@ -18,10 +18,10 @@ let(:splits_api) { described_class.new('', config, telemetry_runtime_producer) } it 'returns splits with segment names' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits) - parsed_splits = splits_api.send(:splits_with_segment_names, splits) + parsed_splits = splits_api.send(:objects_with_segment_names, JSON.parse(splits, symbolize_names: true)) expect(parsed_splits[:segment_names]).to eq(Set.new(%w[demo employees])) end @@ -41,7 +41,7 @@ let(:splits_api) { described_class.new('', config, telemetry_runtime_producer) } it 'returns the splits - with 2 sets param' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1&sets=set_1,set_2') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_1,set_2') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -53,15 +53,15 @@ .to_return(status: 200, body: splits) fetch_options = { cache_control_headers: false, till: nil, sets: ['set_1','set_2'] } - returned_splits = splits_api.since(-1, fetch_options) + returned_splits = splits_api.since(-1, -1, fetch_options) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) expect(log.string).to include '2 feature flags retrieved. since=-1' - expect(log.string).to include returned_splits.to_s + expect(log.string).to include returned_splits["ff"].to_s end it 'raise api exception when status 414' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1&sets=set_1,set_2') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_1,set_2') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -75,7 +75,7 @@ fetch_options = { cache_control_headers: false, till: nil, sets: ['set_1','set_2'] } captured = 0 begin - returned_splits = splits_api.since(-1, fetch_options) + returned_splits = splits_api.since(-1, -1, fetch_options) rescue SplitIoClient::ApiException => e captured = e.exception_code end @@ -96,7 +96,7 @@ let(:splits_api) { described_class.new('', config, telemetry_runtime_producer) } it 'returns the splits - checking headers when cache_control_headers is false' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -107,15 +107,15 @@ }) .to_return(status: 200, body: splits) - returned_splits = splits_api.since(-1) + returned_splits = splits_api.since(-1, -1) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) expect(log.string).to include '2 feature flags retrieved. since=-1' - expect(log.string).to include returned_splits.to_s + expect(log.string).to include returned_splits["ff"].to_s end it 'returns the splits - with till param' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1&till=123123') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&&rbSince=-1&till=123123') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -127,15 +127,15 @@ .to_return(status: 200, body: splits) fetch_options = { cache_control_headers: false, till: 123_123, sets: nil } - returned_splits = splits_api.since(-1, fetch_options) + returned_splits = splits_api.since(-1, -1, fetch_options) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) expect(log.string).to include '2 feature flags retrieved. since=-1' - expect(log.string).to include returned_splits.to_s + expect(log.string).to include returned_splits["ff"].to_s end it 'returns the splits - checking headers when cache_control_headers is true' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -148,39 +148,115 @@ .to_return(status: 200, body: splits) fetch_options = { cache_control_headers: true, till: nil, sets: nil } - returned_splits = splits_api.since(-1, fetch_options) + returned_splits = splits_api.since(-1, -1, fetch_options) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) expect(log.string).to include '2 feature flags retrieved. since=-1' - expect(log.string).to include returned_splits.to_s + expect(log.string).to include returned_splits["ff"].to_s end it 'throws exception if request to get splits from API returns unexpected status code' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 404) - expect { splits_api.since(-1) }.to raise_error( + expect { splits_api.since(-1, -1) }.to raise_error( 'Split SDK failed to connect to backend to fetch feature flags definitions' ) expect(log.string).to include 'Unexpected status code while fetching feature flags' end it 'throws exception if request to get splits from API fails' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_raise(StandardError) - expect { splits_api.since(-1) }.to raise_error( + expect { splits_api.since(-1, -1) }.to raise_error( 'Split SDK failed to connect to backend to retrieve information' ) end it 'throws exception if request to get splits from API times out' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_timeout - expect { splits_api.since(-1) }.to raise_error( + expect { splits_api.since(-1, -1) }.to raise_error( 'Split SDK failed to connect to backend to retrieve information' ) end end + + context 'old spec tests' do + let(:old_spec_splits) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../../test_data/rule_based_segments/split_old_spec.json'))) } + let(:config) do + SplitIoClient::SplitConfig.new( + logger: Logger.new(log), + debug_enabled: true, + transport_debug_enabled: true, + base_uri: "https://proxy-server/api" + ) + end + let(:log) { StringIO.new } + let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } + let(:splits_api) { described_class.new('', config, telemetry_runtime_producer) } + + it 'switch to old spec url whith proper conditions' do + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return(status: 400, body: '') + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: old_spec_splits) + + parsed_splits = splits_api.since(-1, -1) + + expect(parsed_splits[:ff][:d].length()).to eq(6) + expect(parsed_splits[:ff][:t]).to eq(1457726098069) + expect(parsed_splits[:ff][:s]).to eq(-1) + expect(parsed_splits[:rbs]).to eq({:d => [], :s => -1, :t => -1}) + expect(splits_api.clear_storage).to eq(false) + end + + it 'check new spec after last proxy timestamp expires' do + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return({status: 400, body: ''}, {status: 200, body: splits}) + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: old_spec_splits) + + parsed_splits = splits_api.since(-1, -1) + expect(parsed_splits[:ff][:d].length()).to eq(6) + expect(splits_api.instance_variable_get(:@spec_version)).to eq(SplitIoClient::Api::Splits::SPEC_1_1) + + SplitIoClient::Api::Splits::PROXY_CHECK_INTERVAL_SECONDS = 1 + sleep 1 + parsed_splits = splits_api.since(1457726098069, -1) + expect(splits_api.clear_storage).to eq(true) + expect(parsed_splits[:ff][:d].length()).to eq(2) + expect(parsed_splits[:rbs][:d].length()).to eq(1) + expect(splits_api.instance_variable_get(:@spec_version)).to eq(SplitIoClient::Spec::FeatureFlags::SPEC_VERSION) + expect(splits_api.instance_variable_get(:@old_spec_since)).to eq(1457726098069) + end + + it 'check using old_spec_since variable' do + old_spec_splits2 = File.read(File.expand_path(File.join(File.dirname(__FILE__), '../../test_data/rule_based_segments/split_old_spec2.json'))) + + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return({status: 400, body: ''}, {status: 400, body: ''}) + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: old_spec_splits) + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.1&since=1457726098069') + .to_return(status: 200, body: old_spec_splits2) + + parsed_splits = splits_api.since(-1, -1) + expect(parsed_splits[:ff][:d].length()).to eq(6) + expect(splits_api.instance_variable_get(:@spec_version)).to eq(SplitIoClient::Api::Splits::SPEC_1_1) + + SplitIoClient::Api::Splits::PROXY_CHECK_INTERVAL_SECONDS = 1 + sleep 1 + parsed_splits = splits_api.since(1457726098069, -1) + SplitIoClient::Api::Splits::PROXY_CHECK_INTERVAL_SECONDS = 100000 + + sleep 1 + expect(splits_api.instance_variable_get(:@spec_version)).to eq(SplitIoClient::Api::Splits::SPEC_1_1) + expect(splits_api.instance_variable_get(:@old_spec_since)).to eq(nil) + expect(parsed_splits[:ff][:d].length()).to eq(1) + expect(log.string).to include 'Switching to new Feature flag spec 1.3 and fetching.' + end + end end diff --git a/spec/engine/auth_api_client_spec.rb b/spec/engine/auth_api_client_spec.rb index 0235c130..eaa5ba70 100644 --- a/spec/engine/auth_api_client_spec.rb +++ b/spec/engine/auth_api_client_spec.rb @@ -15,7 +15,7 @@ let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } it 'authenticate success' do - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 200, body: body_response) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 200, body: body_response) auth_api_client = subject.new(config, telemetry_runtime_producer) response = auth_api_client.authenticate(api_key) @@ -26,7 +26,7 @@ end it 'auth server return 500' do - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 500) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 500) auth_api_client = subject.new(config, telemetry_runtime_producer) response = auth_api_client.authenticate(api_key) @@ -36,7 +36,7 @@ end it 'auth server return 401' do - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 401) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 401) auth_api_client = subject.new(config, telemetry_runtime_producer) response = auth_api_client.authenticate(api_key) diff --git a/spec/engine/matchers/between_matcher_spec.rb b/spec/engine/matchers/between_matcher_spec.rb index 9e16e550..765ea2d6 100644 --- a/spec/engine/matchers/between_matcher_spec.rb +++ b/spec/engine/matchers/between_matcher_spec.rb @@ -41,9 +41,7 @@ before do # stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since/) # .to_return(status: 200, body: number_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges') - .to_return(status: 200, body: number_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: number_matcher_splits) subject.block_until_ready sleep 1 @@ -69,7 +67,9 @@ let(:non_matching_low_value_negative_attributes) { { income: -999 } } before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since.*/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since.*/) + .to_return(status: 200, body: negative_number_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: negative_number_matcher_splits) subject.block_until_ready sleep 1 @@ -97,7 +97,9 @@ let(:non_matching_high_value_attributes) { { created: 1_459_775_460 } } # "2016/04/04T13:11Z" before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since/) + .to_return(status: 200, body: datetime_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: datetime_matcher_splits) subject.block_until_ready sleep 1 diff --git a/spec/engine/matchers/combining_matcher_spec.rb b/spec/engine/matchers/combining_matcher_spec.rb index bdaccaa7..ab04b2a3 100644 --- a/spec/engine/matchers/combining_matcher_spec.rb +++ b/spec/engine/matchers/combining_matcher_spec.rb @@ -19,7 +19,7 @@ before do stub_request(:get, 'https://sdk.split.io/api/segmentChanges/employees?since=-1') .to_return(status: 200, body: segments_json) - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: splits_json) stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') diff --git a/spec/engine/matchers/equal_to_matcher_spec.rb b/spec/engine/matchers/equal_to_matcher_spec.rb index 043b39df..6f249c6e 100644 --- a/spec/engine/matchers/equal_to_matcher_spec.rb +++ b/spec/engine/matchers/equal_to_matcher_spec.rb @@ -37,7 +37,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: 'ok') @@ -64,7 +64,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: zero_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: 'ok') @@ -90,7 +90,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: negative_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: 'ok') @@ -117,7 +117,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: date_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: 'ok') diff --git a/spec/engine/matchers/greater_than_or_equal_to_matcher_spec.rb b/spec/engine/matchers/greater_than_or_equal_to_matcher_spec.rb index 5283bfb9..9503b5c7 100644 --- a/spec/engine/matchers/greater_than_or_equal_to_matcher_spec.rb +++ b/spec/engine/matchers/greater_than_or_equal_to_matcher_spec.rb @@ -35,7 +35,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) @@ -61,7 +61,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: negative_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) @@ -92,7 +92,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: date_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) diff --git a/spec/engine/matchers/less_than_or_equal_to_matcher_spec.rb b/spec/engine/matchers/less_than_or_equal_to_matcher_spec.rb index 60936dc0..13bee2cd 100644 --- a/spec/engine/matchers/less_than_or_equal_to_matcher_spec.rb +++ b/spec/engine/matchers/less_than_or_equal_to_matcher_spec.rb @@ -37,7 +37,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) @@ -64,7 +64,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: negative_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) @@ -93,7 +93,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: date_splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) @@ -119,7 +119,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: date_splits2_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) diff --git a/spec/engine/matchers/prerequisites_matcher_spec.rb b/spec/engine/matchers/prerequisites_matcher_spec.rb new file mode 100644 index 00000000..fcb366c6 --- /dev/null +++ b/spec/engine/matchers/prerequisites_matcher_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::PrerequisitesMatcher do + let(:evaluator) { double } + + it 'matches with empty prerequisites' do + expect(described_class.new([], @split_logger) + .match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(true) + end + + it 'matches with prerequisite treatments' do + allow(evaluator).to receive(:evaluate_feature_flag).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'flag1', nil) + .and_return(treatment: 'on') + + expect(described_class.new([:n => 'flag1', :ts => ['on']], @split_logger) + .match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(true) + expect(described_class.new([:n => 'flag1', :ts => ['off']], @split_logger) + .match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(false) + end + + it 'is not string type matcher' do + expect(described_class.new([], @split_logger).string_type?).to be false + end +end diff --git a/spec/engine/matchers/rule_based_segment_matcher_spec.rb b/spec/engine/matchers/rule_based_segment_matcher_spec.rb new file mode 100644 index 00000000..4f7786e4 --- /dev/null +++ b/spec/engine/matchers/rule_based_segment_matcher_spec.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::RuleBasedSegmentMatcher do + let(:config) { SplitIoClient::SplitConfig.new(debug_enabled: true) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.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(config, flag_sets_repository, flag_set_filter) } + + context '#string_type' do + it 'is not string type matcher' do + expect(described_class.new(nil, nil, nil, config).string_type?).to be false + end + end + + context 'test_matcher' do + it 'return false if excluded key is passed' do + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy.update([{name: 'foo', trafficTypeName: 'tt_name_1', conditions: [], excluded: {keys: ['key1'], segments: []}}], [], -1) + matcher = described_class.new(segments_repository, rbs_repositoy, 'foo', config) + expect(matcher.match?(value: 'key1')).to be false + end + + it 'return false if excluded segment is passed' do + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rbs_repositoy, true) + segments_repository.add_to_segment({:name => 'segment1', :added => [], :removed => []}) + rbs_repositoy.update([{:name => 'foo', :trafficTypeName => 'tt_name_1', :conditions => [], :excluded => {:keys => ['key1'], :segments => [{:name => 'segment1', :type => 'standard'}]}}], [], -1) + matcher = described_class.new(segments_repository, rbs_repositoy, 'foo', config) + expect(matcher.match?(value: 'key2')).to be false + end + + it 'return false if excluded rb segment is matched' do + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs = {:name => 'sample_rule_based_segment', :trafficTypeName => 'tt_name_1', :conditions => [{ + :matcherGroup => { + :combiner => "AND", + :matchers => [ + { + :matcherType => "WHITELIST", + :negate => false, + :userDefinedSegmentMatcherData => nil, + :whitelistMatcherData => { + :whitelist => [ + "bilal@split.io", + "bilal" + ] + }, + :unaryNumericMatcherData => nil, + :betweenMatcherData => nil + } + ] + } + }], :excluded => {:keys => [], :segments => [{:name => 'no_excludes', :type => 'rule-based'}]}} + rbs2 = {:name => 'no_excludes', :trafficTypeName => 'tt_name_1', + :conditions => [{ + :matcherGroup => { + :combiner => "AND", + :matchers => [ + { + :keySelector => { + :trafficType => "user", + :attribute => "email" + }, + :matcherType => "ENDS_WITH", + :negate => false, + :whitelistMatcherData => { + :whitelist => [ + "@split.io" + ] + } + } + ] + } + } + ], :excluded => {:keys => [], :segments => []}} + + rbs_repositoy.update([rbs, rbs2], [], -1) + matcher = described_class.new(segments_repository, rbs_repositoy, 'sample_rule_based_segment', config) + expect(matcher.match?(value: 'bilal@split.io', attributes: {'email': 'bilal@split.io'})).to be false + expect(matcher.match?(value: 'bilal', attributes: {'email': 'bilal'})).to be true + end + + it 'return true if condition matches' do + rule_based_segment = { :name => 'corge', :trafficTypeName => 'tt_name_5', + :excluded => {:keys => [], :segments => []}, + :conditions => [ + { + :contitionType => 'WHITELIST', + :label => 'some_label', + :matcherGroup => { + :matchers => [ + { + :keySelector => nil, + :matcherType => 'WHITELIST', + :whitelistMatcherData => { + :whitelist => ['k1', 'k2', 'k3'] + }, + :negate => false, + } + ], + :combiner => 'AND' + } + }] + } + + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy.update([rule_based_segment], [], -1) + matcher = described_class.new(segments_repository, rbs_repositoy, 'corge', config) + expect(matcher.match?({:matching_key => 'user', :attributes => {}})).to be false + expect(matcher.match?({:matching_key => 'k1', :attributes => {}})).to be true + end + + it 'return true if dependent rb segment matches' do + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs = { + :changeNumber => 5, + :name => "dependent_rbs", + :status => "ACTIVE", + :trafficTypeName => "user", + :excluded =>{:keys =>["mauro@split.io","gaston@split.io"],:segments =>[]}, + :conditions => [ + { + :matcherGroup => { + :combiner => "AND", + :matchers => [ + { + :keySelector => { + :trafficType => "user", + :attribute => "email" + }, + :matcherType => "ENDS_WITH", + :negate => false, + :whitelistMatcherData => { + :whitelist => [ + "@split.io" + ] + } + } + ] + } + } + ]} + rbs2 = { + :changeNumber => 5, + :name => "sample_rule_based_segment", + :status => "ACTIVE", + :trafficTypeName => "user", + :excluded => { + :keys => [], + :segments => [] + }, + :conditions => [ + { + :conditionType => "ROLLOUT", + :matcherGroup => { + :combiner => "AND", + :matchers => [ + { + :keySelector => { + :trafficType => "user" + }, + :matcherType => "IN_RULE_BASED_SEGMENT", + :negate => false, + :userDefinedSegmentMatcherData => { + :segmentName => "dependent_rbs" + } + } + ] + } + } + ] + } + rbs_repositoy.update([rbs, rbs2], [], -1) + matcher = described_class.new(segments_repository, rbs_repositoy, 'sample_rule_based_segment', config) + expect(matcher.match?(value: 'bilal@split.io', attributes: {'email': 'bilal@split.io'})).to be true + expect(matcher.match?(value: 'bilal', attributes: {'email': 'bilal'})).to be false + end + end +end \ No newline at end of file diff --git a/spec/engine/matchers/semver_matchers_integration_spec.rb b/spec/engine/matchers/semver_matchers_integration_spec.rb index 86909af5..04a76af4 100644 --- a/spec/engine/matchers/semver_matchers_integration_spec.rb +++ b/spec/engine/matchers/semver_matchers_integration_spec.rb @@ -51,11 +51,11 @@ context 'equal to matcher' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since\.*/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since\.*/) .to_return(status: 200, body: semver_equalto_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: semver_equalto_matcher_splits) - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1675259356568") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1675259356568&rbSince=-1") .to_return(status: 200, body: semver_equalto_matcher_splits) sleep 1 subject.block_until_ready @@ -75,9 +75,9 @@ context 'greater than or equal to matcher' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since/) .to_return(status: 200, body: semver_greater_or_equalto_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: semver_greater_or_equalto_matcher_splits) sleep 1 subject.block_until_ready @@ -98,9 +98,9 @@ context 'less than or equal to matcher' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since=-1&rbSince=-1/) .to_return(status: 200, body: semver_less_or_equalto_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: semver_less_or_equalto_matcher_splits) sleep 1 subject.block_until_ready @@ -121,9 +121,9 @@ context 'in list matcher' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since=-1&rbSince=-1/) .to_return(status: 200, body: semver_inlist_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: semver_inlist_matcher_splits) sleep 1 subject.block_until_ready @@ -144,9 +144,9 @@ context 'between matcher' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since=-1&rbSince=-1/) .to_return(status: 200, body: semver_between_matcher_splits) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: semver_between_matcher_splits) sleep 1 subject.block_until_ready diff --git a/spec/engine/matchers/whitelist_matcher_spec.rb b/spec/engine/matchers/whitelist_matcher_spec.rb index 25f4a4ba..90f40f65 100644 --- a/spec/engine/matchers/whitelist_matcher_spec.rb +++ b/spec/engine/matchers/whitelist_matcher_spec.rb @@ -22,7 +22,7 @@ before do stub_request(:any, /https:\/\/telemetry.*/) .to_return(status: 200, body: 'ok') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) stub_request(:any, /https:\/\/events.*/) .to_return(status: 200, body: "", headers: {}) diff --git a/spec/engine/parser/evaluator_spec.rb b/spec/engine/parser/evaluator_spec.rb index b3af7d57..a6beabe9 100644 --- a/spec/engine/parser/evaluator_spec.rb +++ b/spec/engine/parser/evaluator_spec.rb @@ -4,10 +4,11 @@ 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(: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, true) } + let(:evaluator) { described_class.new(segments_repository, splits_repository, rule_based_segments_repository, true) } let(:killed_split) { { killed: true, defaultTreatment: 'default' } } let(:archived_split) { { status: 'ARCHIVED' } } @@ -31,12 +32,12 @@ context 'dependency matcher' do it 'uses cache' do allow(evaluator.instance_variable_get(:@splits_repository)) - .to receive(:get_split).and_return(split_data[:splits][0]) + .to receive(:get_split).and_return(split_data[:ff][:d][0]) expect(evaluator).to receive(:match).exactly(2).times - evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user_id_1' }, split_data[:splits][0]) + evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user_id_1' }, split_data[:ff][:d][0]) - evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user_id_1' }, split_data[:splits][1]) + evaluator.evaluate_feature_flag({ bucketing_key: nil, matching_key: 'fake_user_id_1' }, split_data[:ff][:d][1]) end end end diff --git a/spec/engine/push_manager_spec.rb b/spec/engine/push_manager_spec.rb index a1a6d9e7..4cc3f9db 100644 --- a/spec/engine/push_manager_spec.rb +++ b/spec/engine/push_manager_spec.rb @@ -14,14 +14,15 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, runtime_producer) } + let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, runtime_producer) } - let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(split_fetcher, config, splits_repository, runtime_producer, segment_fetcher) } + let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(split_fetcher, config, splits_repository, runtime_producer, segment_fetcher, rule_based_segments_repository) } let(:segments_worker) { SplitIoClient::SSE::Workers::SegmentsWorker.new(segment_fetcher, config, segments_repository) } let(:push_status_queue) { Queue.new } let(:notification_manager_keeper) { SplitIoClient::SSE::NotificationManagerKeeper.new(config, runtime_producer, push_status_queue) } - let(:repositories) { { splits: splits_repository, segments: segments_repository } } + let(:repositories) { { splits: splits_repository, segments: segments_repository, rule_based_segments: rule_based_segments_repository } } let(:impression_counter) { SplitIoClient::Engine::Common::ImpressionCounter.new } let(:params) do { @@ -43,7 +44,7 @@ send_mock_content(res, 'content') end - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 200, body: body_response) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 200, body: body_response) config.streaming_service_url = server.base_uri sse_handler = SplitIoClient::SSE::SSEHandler.new(config, splits_worker, segments_worker, sse_client) @@ -51,7 +52,7 @@ push_manager = subject.new(config, sse_handler, api_key, runtime_producer) connected = push_manager.start_sse - expect(a_request(:get, config.auth_service_url + "?s=1.1")).to have_been_made.times(1) + expect(a_request(:get, config.auth_service_url + "?s=1.3")).to have_been_made.times(1) sleep(1.5) expect(connected).to eq(true) @@ -61,14 +62,14 @@ end it 'must not connect to server. Auth server return 500' do - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 500) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 500) sse_handler = SplitIoClient::SSE::SSEHandler.new(config, splits_worker, segments_worker, sse_client) push_manager = subject.new(config, sse_handler, api_key, runtime_producer) connected = push_manager.start_sse - expect(a_request(:get, config.auth_service_url + "?s=1.1")).to have_been_made.times(1) + expect(a_request(:get, config.auth_service_url + "?s=1.3")).to have_been_made.times(1) sleep(1.5) @@ -77,14 +78,14 @@ end it 'must not connect to server. Auth server return 401' do - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 401) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 401) sse_handler = SplitIoClient::SSE::SSEHandler.new(config, splits_worker, segments_worker, sse_client) push_manager = subject.new(config, sse_handler, api_key, runtime_producer) connected = push_manager.start_sse - expect(a_request(:get, config.auth_service_url + "?s=1.1")).to have_been_made.times(1) + expect(a_request(:get, config.auth_service_url + "?s=1.3")).to have_been_made.times(1) sleep(1.5) @@ -100,14 +101,14 @@ send_mock_content(res, 'content') end - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 200, body: body_response) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 200, body: body_response) config.streaming_service_url = server.base_uri sse_handler = SplitIoClient::SSE::SSEHandler.new(config, splits_worker, segments_worker, sse_client) push_manager = subject.new(config, sse_handler, api_key, runtime_producer) connected = push_manager.start_sse - expect(a_request(:get, config.auth_service_url + "?s=1.1")).to have_been_made.times(1) + expect(a_request(:get, config.auth_service_url + "?s=1.3")).to have_been_made.times(1) sleep(1.5) diff --git a/spec/engine/sync_manager_spec.rb b/spec/engine/sync_manager_spec.rb index d472de01..68169a7c 100644 --- a/spec/engine/sync_manager_spec.rb +++ b/spec/engine/sync_manager_spec.rb @@ -20,6 +20,7 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer) } @@ -29,12 +30,13 @@ splits: splits_repository, segments: segments_repository, impressions: impressions_repository, - events: events_repository + events: events_repository, + rule_based_segments: rule_based_segments_repository } end let(:sync_params) do { - split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer), + split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer), segment_fetcher: SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer), imp_counter: impression_counter, telemetry_runtime_producer: telemetry_runtime_producer, @@ -50,7 +52,7 @@ let(:telemetry_api) { SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) } let(:telemetry_synchronizer) { SplitIoClient::Telemetry::Synchronizer.new(config, telemetry_consumers, init_producer, repositories, telemetry_api, 0, 0) } let(:status_manager) { SplitIoClient::Engine::StatusManager.new(config) } - let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, splits_repository, telemetry_runtime_producer, sync_params[:segment_fetcher]) } + let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, splits_repository, telemetry_runtime_producer, sync_params[:segment_fetcher], rule_based_segments_repository) } let(:segments_worker) { SplitIoClient::SSE::Workers::SegmentsWorker.new(synchronizer, config, segments_repository) } let(:notification_processor) { SplitIoClient::SSE::NotificationProcessor.new(config, splits_worker, segments_worker) } let(:event_parser) { SplitIoClient::SSE::EventSource::EventParser.new(config) } @@ -68,7 +70,7 @@ mock_segment_changes('segment2', segment2, '-1') mock_segment_changes('segment2', segment2, '1470947453878') mock_segment_changes('segment3', segment3, '-1') - stub_request(:get, config.auth_service_url + "?s=1.1").to_return(status: 200, body: body_response) + stub_request(:get, config.auth_service_url + "?s=1.3").to_return(status: 200, body: body_response) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') end @@ -84,7 +86,7 @@ sync_manager.start sleep(2) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once expect(config.threads.size).to eq(11) config.threads.values.each { |thread| Thread.kill(thread) } @@ -104,7 +106,7 @@ sync_manager.start sleep(2) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once expect(config.threads.size).to eq(8) config.threads.values.each { |thread| Thread.kill(thread) } @@ -136,7 +138,7 @@ private def mock_split_changes_with_since(splits_json, since) - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=#{since}") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=#{since}&rbSince=-1") .to_return(status: 200, body: splits_json) end diff --git a/spec/engine/synchronizer_spec.rb b/spec/engine/synchronizer_spec.rb index 4a920b55..cef2020d 100644 --- a/spec/engine/synchronizer_spec.rb +++ b/spec/engine/synchronizer_spec.rb @@ -18,16 +18,18 @@ flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) repositories = { splits: splits_repository, segments: segments_repository, impressions: SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config), - events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, runtime_producer) + events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, runtime_producer), + rule_based_segments: rule_based_segments_repository } parameters = { - split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, runtime_producer), + split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, runtime_producer), segment_fetcher: SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, runtime_producer), telemetry_runtime_producer: runtime_producer, unique_keys_tracker: SplitIoClient::Engine::Impressions::NoopUniqueKeysTracker.new @@ -59,7 +61,7 @@ sleep(2) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=-1')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment2?since=-1')).to have_been_made.once @@ -73,7 +75,7 @@ sleep(2) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=-1')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment2?since=-1')).to have_been_made.once @@ -91,7 +93,7 @@ it 'start_periodic_fetch' do synchronizer.start_periodic_fetch - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=-1')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.once expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment2?since=-1')).to have_been_made.once @@ -101,7 +103,7 @@ end it 'sync_all synchronous - should return false' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').to_return(status: 500) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').to_return(status: 500) result = synchronizer.sync_all(false) @@ -118,40 +120,40 @@ mock_segment_changes('segment1', segment1, '-1') mock_segment_changes('segment1', segment1, '1470947453877') - synchronizer.fetch_splits(0) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once + synchronizer.fetch_splits(1506703262916, 0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once end it 'fetch_splits - with CDN bypassed' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: - '{ - "splits": [], - "since": -1, - "till": 1506703262918 - }') + '{"ff":{ + "d": [], + "s": -1, + "t": 1506703262918 + }, "rbs":{"d":[],"s":-1,"t":-1}}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1') .to_return(status: 200, body: - '{ - "splits": [], - "since": 1506703262918, - "till": 1506703262918 - }') + '{"ff":{ + "d": [], + "s": 1506703262918, + "t": 1506703262918 + }, "rbs":{"d":[],"s":-1,"t":-1}}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918&till=1506703262920') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1&till=1506703262920') .to_return(status: 200, body: - '{ - "splits": [], - "since": 1506703262918, - "till": 1506703262921 - }') + '{"ff":{ + "d": [], + "s": 1506703262918, + "t": 1506703262921 + }, "rbs":{"d":[],"s":-1,"t":-1}}') - synchronizer.fetch_splits(1_506_703_262_920) + synchronizer.fetch_splits(1_506_703_262_920, 0) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.once - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918')).to have_been_made.times(9) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918&till=1506703262920')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1')).to have_been_made.times(9) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1&till=1506703262920')).to have_been_made.once end it 'fetch_segment' do @@ -234,7 +236,7 @@ private def mock_split_changes(splits_json) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').to_return(status: 200, body: splits_json) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').to_return(status: 200, body: splits_json) end def mock_segment_changes(segment_name, segment_json, since) diff --git a/spec/engine_spec.rb b/spec/engine_spec.rb index b9999044..7909d64d 100644 --- a/spec/engine_spec.rb +++ b/spec/engine_spec.rb @@ -46,7 +46,7 @@ @mode = cache_adapter.equal?(:memory) ? :standalone : :consumer stub_request(:any, /https:\/\/telemetry.*/).to_return(status: 200, body: 'ok') stub_request(:any, /https:\/\/events.*/).to_return(status: 200, body: '') - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since=-1&rbSince=-1/) .to_return(status: 200, body: '') stub_request(:post, "https://telemetry.split.io/api/v1/metrics/config") .to_return(status: 200, body: '') @@ -128,7 +128,7 @@ context '#get_treatment' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/).to_return(status: 200, body: '') + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.3&since=-1&rbSince=-1/).to_return(status: 200, body: '') load_splits(all_keys_matcher_json, flag_sets_json) subject.block_until_ready @@ -928,7 +928,7 @@ end it 'returns control' do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: all_keys_matcher_json) subject.block_until_ready @@ -942,7 +942,7 @@ describe 'redis outage' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: all_keys_matcher_json) end @@ -955,7 +955,7 @@ describe 'events' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: all_keys_matcher_json) subject.block_until_ready end @@ -985,7 +985,7 @@ context '#track' do before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: all_keys_matcher_json) subject.block_until_ready end @@ -1192,7 +1192,7 @@ end before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since.*/) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: all_keys_matcher_json) end @@ -1270,7 +1270,7 @@ def load_splits(splits_json, flag_sets_json) if @mode.equal?(:standalone) # stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since.*/) - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=-1") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1") .to_return(status: 200, body: splits_json) else add_splits_to_repository(splits_json) @@ -1288,7 +1288,7 @@ def load_segments(segments_json) end def add_splits_to_repository(splits_json) - splits = JSON.parse(splits_json, symbolize_names: true)[:splits] + splits = JSON.parse(splits_json, symbolize_names: true)[:ff][:d] splits_repository = subject.instance_variable_get(:@splits_repository) diff --git a/spec/integrations/dedupe_impression_spec.rb b/spec/integrations/dedupe_impression_spec.rb index 399d3045..eed8588e 100644 --- a/spec/integrations/dedupe_impression_spec.rb +++ b/spec/integrations/dedupe_impression_spec.rb @@ -33,8 +33,8 @@ context 'checking logic impressions' do it 'get_treament should post 7 impressions - debug mode' do stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '') - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=-1").to_return(status: 200, body: splits, headers: {}) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916').to_return(status: 200, body: '') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1").to_return(status: 200, body: splits, headers: {}) factory = SplitIoClient::SplitFactory.new('test_api_key_debug-1', streaming_enabled: false, impressions_mode: :debug) debug_client = factory.client @@ -51,17 +51,14 @@ expect(debug_client.get_treatments_with_config_by_flag_sets('admin', ['set_3'])).to eq treatments expect(debug_client.get_treatment('24', 'Test_Save_1')).to eq 'off' expect(debug_client.get_treatment('24', 'Test_Save_1')).to eq 'off' - + sleep 1 impressions = debug_client.instance_variable_get(:@impressions_repository).batch - - sleep 0.5 - expect(impressions.size).to eq 7 end it 'get_treaments should post 9 impressions - debug mode' do stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1').to_return(status: 200, body: '') factory = SplitIoClient::SplitFactory.new('test_api_key_debug-2', streaming_enabled: false, impressions_mode: :debug) debug_client = factory.client @@ -74,14 +71,14 @@ impressions = debug_client.instance_variable_get(:@impressions_repository).batch - sleep 0.5 + sleep 1 expect(impressions.size).to eq 9 end it 'get_treament should post 3 impressions - optimized mode' do stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1').to_return(status: 200, body: '') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage').to_return(status: 200, body: '') factory = SplitIoClient::SplitFactory.new('test_api_key-1', streaming_enabled: false, impressions_mode: :optimized, impressions_refresh_rate: 60) @@ -113,7 +110,7 @@ it 'get_treaments should post 8 impressions - optimized mode' do stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1').to_return(status: 200, body: '') factory = SplitIoClient::SplitFactory.new('test_api_key-2', streaming_enabled: false, impressions_mode: :optimized) client = factory.client @@ -149,7 +146,7 @@ private def mock_split_changes_v2(splits_json) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) end diff --git a/spec/integrations/in_memory_client_spec.rb b/spec/integrations/in_memory_client_spec.rb index d70e6c76..209bd3ac 100644 --- a/spec/integrations/in_memory_client_spec.rb +++ b/spec/integrations/in_memory_client_spec.rb @@ -52,7 +52,7 @@ context '#get_treatment' do it 'returns CONTROL when server return 500' do - # stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + # stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916").to_return(status: 200, body: 'ok') mock_split_changes_error expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'control' @@ -67,7 +67,7 @@ end it 'returns treatments with FACUNDO_TEST feature and check impressions' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' expect(client.get_treatment('mauro_test', 'FACUNDO_TEST')).to eq 'off' @@ -90,8 +90,32 @@ expect(impressions[1][:treatment][:change_number]).to eq(1_506_703_262_916) end + it 'returns treatments with prereq_flag feature and check impressions' do + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') + client.block_until_ready + expect(client.get_treatment('nico_test', 'prereq_flag')).to eq 'on' + expect(client.get_treatment('bla', 'prereq_flag')).to eq 'off_default' + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 2 + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq('prereq_flag') + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('in segment all') + expect(impressions[0][:treatment][:change_number]).to eq(1494593336752) + + expect(impressions[1][:matching_key]).to eq('bla') + expect(impressions[1][:split_name]).to eq('prereq_flag') + expect(impressions[1][:treatment][:treatment]).to eq('off_default') + expect(impressions[1][:treatment][:label]).to eq('prerequisites not met') + expect(impressions[1][:treatment][:change_number]).to eq(1494593336752) + end + it 'returns treatments with Test_Save_1 feature and check impressions' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment('1', 'Test_Save_1')).to eq 'on' expect(client.get_treatment('24', 'Test_Save_1')).to eq 'off' @@ -115,7 +139,7 @@ end it 'returns treatments with input validations' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' expect(client.get_treatment('', 'FACUNDO_TEST')).to eq 'control' @@ -143,7 +167,7 @@ end it 'returns CONTROL with treatment doesnt exist' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') expect(client.get_treatment('nico_test', 'random_treatment')).to eq 'control' impressions = custom_impression_listener.queue @@ -151,7 +175,7 @@ end it 'with multiple factories returns on' do -# stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') +# stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916").to_return(status: 200, body: 'ok') local_log = StringIO.new logger = Logger.new(local_log) @@ -231,7 +255,7 @@ context '#get_treatment_with_config' do it 'returns treatments and configs with FACUNDO_TEST treatment and check impressions' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment_with_config('nico_test', 'FACUNDO_TEST')).to eq( treatment: 'on', @@ -261,7 +285,7 @@ end it 'returns treatments and configs with MAURO_TEST treatment and check impressions' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment_with_config('mauro', 'MAURO_TEST')).to eq( @@ -292,7 +316,7 @@ end it 'returns treatments with input validations' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment_with_config('nico_test', 'FACUNDO_TEST')).to eq( @@ -339,7 +363,7 @@ end it 'returns CONTROL with treatment doesnt exist' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready expect(client.get_treatment_with_config('nico_test', 'random_treatment')).to eq( @@ -353,7 +377,7 @@ end it 'returns CONTROL when server return 500' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') mock_split_changes_error expect(client.get_treatment_with_config('nico_test', 'FACUNDO_TEST')).to eq( @@ -375,7 +399,7 @@ context '#get_treatments' do before do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') end it 'returns treatments and check impressions' do @@ -479,7 +503,8 @@ context '#get_treatments_by_flag_set' do before do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + mock_split_changes(splits) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') end it 'returns treatments and check impressions' do @@ -564,7 +589,7 @@ context '#get_treatments_by_flag_sets' do before do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') end it 'returns treatments and check impressions' do @@ -647,7 +672,8 @@ context '#get_treatments_with_config_by_flag_set' do before do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') + stub_request(:post, "https://events.split.io/api/testImpressions/bulk").to_return(status: 200, body: "", headers: {}) end it 'returns treatments and check impressions' do @@ -757,7 +783,7 @@ context '#get_treatments_with_config_by_flag_sets' do before do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') end it 'returns treatments and check impressions' do @@ -861,7 +887,7 @@ context '#get_treatments_with_config' do it 'returns treatments and check impressions' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready result = client.get_treatments_with_config('nico_test', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) expect(result[:FACUNDO_TEST]).to eq( @@ -901,7 +927,7 @@ end it 'returns treatments with input validation' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready result1 = client.get_treatments_with_config('nico_test', %w[FACUNDO_TEST "" nil]) result2 = client.get_treatments_with_config('', %w["" MAURO_TEST Test_Save_1]) @@ -940,7 +966,7 @@ end it 'returns CONTROL with treatment doesnt exist' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') client.block_until_ready result = client.get_treatments_with_config('nico_test', %w[FACUNDO_TEST random_treatment]) @@ -965,7 +991,7 @@ end it 'returns CONTROL when server return 500' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') mock_split_changes_error result = client.get_treatments_with_config('nico_test', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) @@ -1085,7 +1111,7 @@ context '#track' do it 'returns true' do stub_request(:post, 'https://events.split.io/api/events/bulk').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1').to_return(status: 200, body: '') properties = { property_1: 1, @@ -1144,7 +1170,7 @@ flag_sets_filter: ['set_3', '@3we']) end before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1&sets=set_3') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_3') .to_return(status: 200, body: splits) mock_segment_changes('segment1', segment1, '-1') mock_segment_changes('segment1', segment1, '1470947453877') @@ -1204,7 +1230,7 @@ context 'impressions toggle' do it 'optimized mode' do splits_imp_toggle = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_imp_toggle) factory_imp_toggle = SplitIoClient::SplitFactory.new('test_api_key', @@ -1236,9 +1262,10 @@ expect(imp_count.keys()[0].include? ('with_track_disabled')).to eq(true) expect(imp_count.length).to eq(1) end + it 'debug mode' do splits_imp_toggle = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_imp_toggle) factory_imp_toggle = SplitIoClient::SplitFactory.new('test_api_key', @@ -1272,7 +1299,7 @@ end it 'none mode' do splits_imp_toggle = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_imp_toggle) factory_imp_toggle = SplitIoClient::SplitFactory.new('test_api_key', @@ -1306,17 +1333,127 @@ expect(imp_count.length).to eq(3) end end + + context 'rule based segments' do + it 'using segment in excluded' do + splits_rbs = File.read(File.join(SplitIoClient.root, 'spec/test_data/rule_based_segments/rule_base_segments3.json')) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return(status: 200, body: splits_rbs) + stub_request(:get, "https://sdk.split.io/api/splitChanges?rbSince=1506703262916&s=1.3&since=1506703262916") + .to_return(status: 200, body: "") + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=-1') + .to_return(status: 200, body: '{"name": "segment1","added": ["pato@split.io"],"removed": [],"since": -1,"till": 1470947453877}') + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877') + .to_return(status: 200, body: '{"name": "segment1","added": [],"removed": [],"since": 1470947453877,"till": 1470947453877}') + factory_rbs = + SplitIoClient::SplitFactory.new('test_api_key', + impressions_mode: :optimized, + features_refresh_rate: 9999, + telemetry_refresh_rate: 99999, + impressions_refresh_rate: 99999, + streaming_enabled: false) + + client_rbs = factory_rbs.client + client_rbs.block_until_ready + + expect(client_rbs.get_treatment('bilal@split.io', 'rbs_feature_flag', {:email => 'bilal@split.io'})).to eq('on') + expect(client_rbs.get_treatment('mauro@split.io', 'rbs_feature_flag', {:email => 'mauro@split.io'})).to eq('off') + expect(client_rbs.get_treatment('pato@split.io', 'rbs_feature_flag', {:email => 'pato@split.io'})).to eq('off') + end + + it 'using rb segment in excluded' do + splits_rbs = File.read(File.join(SplitIoClient.root, 'spec/test_data/rule_based_segments/rule_base_segments2.json')) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return(status: 200, body: splits_rbs) + stub_request(:get, "https://sdk.split.io/api/splitChanges?rbSince=1506703262916&s=1.3&since=1506703262916") + .to_return(status: 200, body: "") + factory_rbs = + SplitIoClient::SplitFactory.new('test_api_key', + impressions_mode: :optimized, + features_refresh_rate: 9999, + telemetry_refresh_rate: 99999, + impressions_refresh_rate: 99999, + streaming_enabled: false) + + client_rbs = factory_rbs.client + client_rbs.block_until_ready + + expect(client_rbs.get_treatment('bilal@split.io', 'rbs_feature_flag', {:email => 'bilal@split.io'})).to eq('off') + expect(client_rbs.get_treatment('mauro@split.io', 'rbs_feature_flag', {:email => 'mauro@split.io'})).to eq('off') + expect(client_rbs.get_treatment('bilal', 'rbs_feature_flag', {:email => 'bilal'})).to eq('on') + expect(client_rbs.get_treatment('bilal2@split.io', 'rbs_feature_flag', {:email => 'bilal2split.io'})).to eq('on') + end + + it 'using rb segment in condition' do + splits_rbs = File.read(File.join(SplitIoClient.root, 'spec/test_data/rule_based_segments/rule_base_segments.json')) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return(status: 200, body: splits_rbs) + stub_request(:get, "https://sdk.split.io/api/splitChanges?rbSince=1506703262916&s=1.3&since=1506703262916") + .to_return(status: 200, body: "") + factory_rbs = + SplitIoClient::SplitFactory.new('test_api_key', + impressions_mode: :optimized, + features_refresh_rate: 9999, + telemetry_refresh_rate: 99999, + impressions_refresh_rate: 99999, + streaming_enabled: false) + + client_rbs = factory_rbs.client + client_rbs.block_until_ready + + expect(client_rbs.get_treatment('bilal@split.io', 'rbs_feature_flag', {:email => 'bilal@split.io'})).to eq('on') + expect(client_rbs.get_treatment('mauro@split.io', 'rbs_feature_flag', {:email => 'mauro@split.io'})).to eq('off') + end + end + + context 'old spec tests' do + let(:old_spec_splits) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../test_data/rule_based_segments/split_old_spec.json'))) } + + it 'check new spec after last proxy timestamp expires' do + splits_rbs = File.read(File.join(SplitIoClient.root, 'spec/test_data/rule_based_segments/rule_base_segments.json')) + + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .to_return({status: 400, body: ''}, {status: 200, body: splits_rbs}) + stub_request(:get, "https://sdk.split.io/api/splitChanges?rbSince=1506703262916&s=1.3&since=1506703262916") + .to_return(status: 200, body: '') + stub_request(:get, 'https://proxy-server/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: old_spec_splits) + stub_request(:get, "https://proxy-server/api/splitChanges?s=1.1&since=1457726098069") + .to_return(status: 200, body: '') + stub_request(:post, "https://telemetry.split.io/api/v1/metrics/config") + .to_return(status: 200, body: '') + + factory_old_spec = + SplitIoClient::SplitFactory.new('test_api_key', + {impressions_mode: :none, + features_refresh_rate: 2, + base_uri: "https://proxy-server/api", + streaming_enabled: false}) + + SplitIoClient::Api::Splits::PROXY_CHECK_INTERVAL_SECONDS = 1 + client_old_spec = factory_old_spec.client + client_old_spec.block_until_ready + expect(client_old_spec.get_treatment('whitelisted_user', 'whitelist_feature')).to eq('on') + + sleep 1 + split_fetcher = factory_old_spec.instance_variable_get(:@split_fetcher) + split_fetcher.fetch_splits + sleep 1 + expect(client_old_spec.get_treatment('bilal@split.io', 'rbs_feature_flag', {:email => 'bilal@split.io'})).to eq('on') + expect(client_old_spec.get_treatment('whitelisted_user', 'whitelist_feature')).to eq('control') + end + end end private def mock_split_changes(splits_json) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) end def mock_split_changes_error - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 500) end diff --git a/spec/integrations/push_client_spec.rb b/spec/integrations/push_client_spec.rb index 721340b2..f62d6d60 100644 --- a/spec/integrations/push_client_spec.rb +++ b/spec/integrations/push_client_spec.rb @@ -51,14 +51,14 @@ mock_splits_request(splits2, '1585948850109') mock_splits_request(splits3, '1585948850110') mock_segment_changes('segment3', segment3, '-1') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_split_update_must_fetch) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -74,9 +74,9 @@ client.block_until_ready sleep(2) expect(client.get_treatment('admin', 'push_test')).to eq('after_fetch') - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.at_least_times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850109')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850110')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.at_least_times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850109&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1')).to have_been_made.times(1) client.destroy end end @@ -91,7 +91,7 @@ send_content(res, event_split_update_must_not_fetch) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -105,9 +105,9 @@ client.block_until_ready(1) sleep(1) expect(client.get_treatment('admin', 'push_test')).to eq('on') - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850109')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850110')).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850109&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1')).to have_been_made.times(0) client.destroy end end @@ -123,7 +123,7 @@ send_content(res, event_split_update_missing_change_number) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -137,9 +137,9 @@ client.block_until_ready(1) sleep(1) expect(client.get_treatment('admin', 'push_test')).to eq('on') - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850109')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850110')).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850109&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1')).to have_been_made.times(0) end end @@ -149,9 +149,9 @@ mock_splits_request(splits3, '1585948850110') mock_segment_changes('segment3', segment3, '-1') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111").to_return(status: 200, body: '') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1").to_return(status: 200, body: '') stub_request(:get, "https://sdk.split.io/api/segmentChanges/bilal_segment?since=-1").to_return(status: 200, body: '') - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) mock_server do |server| @@ -186,13 +186,13 @@ mock_splits_request(splits3, '1585948850110') mock_segment_changes('segment3', segment3, '-1') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_split_iff_update_incorrect_pcn) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -215,13 +215,13 @@ mock_splits_request(splits3, '1585948850110') mock_segment_changes('segment3', segment3, '-1') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_split_iff_update_missing_definition) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -244,13 +244,13 @@ mock_splits_request(splits3, '1585948850110') mock_segment_changes('segment3', segment3, '-1') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_split_iff_update_incorrect_compression) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -280,7 +280,7 @@ send_content(res, event_split_kill_must_fetch) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -293,9 +293,9 @@ client.block_until_ready sleep(2) expect(client.get_treatment('admin', 'push_test')).to eq('after_fetch') - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850109')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850110')).to have_been_made.at_least_times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850109&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1')).to have_been_made.at_least_times(1) client.destroy end end @@ -304,14 +304,14 @@ mock_splits_request(splits, -1) mock_splits_request(splits2, 1_585_948_850_109) mock_segment_changes('segment3', segment3, '-1') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850110').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_split_kill_must_not_fetch) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -325,9 +325,9 @@ client.block_until_ready(1) sleep(2) expect(client.get_treatment('admin', 'push_test')).to eq('on') - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850109')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850110')).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850109&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1')).to have_been_made.times(0) client.destroy end end @@ -346,7 +346,7 @@ send_content(res, event_segment_update_must_fetch) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -375,7 +375,7 @@ send_content(res, event_segment_update_must_not_fetch) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -400,14 +400,14 @@ mock_splits_request(splits2, 1_585_948_850_109) mock_splits_request(splits3, 1_585_948_850_110) mock_segment_changes('segment3', segment3, '-1') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_occupancy_with_publishers) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -436,7 +436,7 @@ send_content(res, event_occupancy_without_publishers) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -467,7 +467,7 @@ send_content(res, event_control_STREAMING_PAUSED) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -496,7 +496,7 @@ send_content(res, event_control_STREAMING_RESUMED) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -519,14 +519,14 @@ mock_splits_request(splits2, 1_585_948_850_109) mock_splits_request(splits3, 1_585_948_850_110) mock_segment_changes('segment3', segment3, '-1') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1585948850111').to_return(status: 200, body: '') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850111&rbSince=-1').to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_control_STREAMING_DISABLED) end - stub_request(:get, auth_service_url + "?s=1.1").to_return(status: 200, body: auth_body_response) + stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( @@ -559,7 +559,7 @@ def send_content(res, content) end def mock_splits_request(splits_json, since) - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=#{since}") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=#{since}&rbSince=-1") .to_return(status: 200, body: splits_json) end diff --git a/spec/integrations/redis_client_spec.rb b/spec/integrations/redis_client_spec.rb index b19733db..e23c1525 100644 --- a/spec/integrations/redis_client_spec.rb +++ b/spec/integrations/redis_client_spec.rb @@ -997,7 +997,7 @@ private def load_splits_redis(splits_json, cli) - splits = JSON.parse(splits_json, symbolize_names: true)[:splits] + splits = JSON.parse(splits_json, symbolize_names: true)[:ff][:d] splits_repository = cli.instance_variable_get(:@splits_repository) diff --git a/spec/repository_helper.rb b/spec/repository_helper.rb index 0f091f78..37770a76 100644 --- a/spec/repository_helper.rb +++ b/spec/repository_helper.rb @@ -13,16 +13,16 @@ flag_sets_repository, flag_set_filter) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(true) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => ['set_3']}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => ['set_3']}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(true) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => ['set_1']}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => ['set_1']}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(false) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ARCHIVED', conditions: [], :sets => ['set_1']}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ARCHIVED', conditions: [], :sets => ['set_1']}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(true) end @@ -35,16 +35,16 @@ flag_sets_repository, flag_set_filter) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(false) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :sets => ['set_3']}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :sets => ['set_3']}], -1, config, false) expect(feature_flag_repository.get_split('split2').nil?).to eq(false) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split3', :status => 'ACTIVE', conditions: [], :sets => ['set_1']}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split3', :status => 'ACTIVE', conditions: [], :sets => ['set_1']}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(false) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ARCHIVED', conditions: [], :sets => ['set_1']}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ARCHIVED', conditions: [], :sets => ['set_1']}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(true) end @@ -57,16 +57,47 @@ flag_sets_repository, flag_set_filter) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1')[:impressionsDisabled]).to eq(false) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :impressionsDisabled => false, :sets => []}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :impressionsDisabled => false, :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split2')[:impressionsDisabled]).to eq(false) - SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :impressionsDisabled => true, :sets => []}], -1, config) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :impressionsDisabled => true, :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split2')[:impressionsDisabled]).to eq(true) + end + + it 'test clear cache flag' do + config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) + flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) + flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) + feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( + config, + flag_sets_repository, + flag_set_filter) + + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) + expect(feature_flag_repository.get_split('split1').nil?).to eq(false) + + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :sets => ['set_3']}], -1, config, true) + expect(feature_flag_repository.get_split('split2').nil?).to eq(false) + expect(feature_flag_repository.get_split('split1').nil?).to eq(true) + end + + it 'test prerequisites element' do + config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) + flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) + flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) + feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( + config, + flag_sets_repository, + flag_set_filter) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) + expect(feature_flag_repository.get_split('split1')[:prerequisites]).to eq([]) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :prerequisites => [{:n => 'flag', :ts => ['on']}], :sets => []}], -1, config, false) + expect(feature_flag_repository.get_split('split2')[:prerequisites]).to eq([{:n => 'flag', :ts => ['on']}]) end end end diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index d8d7123d..fd19dc0c 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -10,19 +10,20 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) } let(:impression_manager) { SplitIoClient::Engine::Common::ImpressionManager.new(config, impressions_repository, SplitIoClient::Engine::Common::NoopImpressionCounter.new, runtime_producer, SplitIoClient::Observers::NoopImpressionObserver.new, SplitIoClient::Engine::Impressions::NoopUniqueKeysTracker.new) } let(:evaluation_producer) { SplitIoClient::Telemetry::EvaluationProducer.new(config) } - let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) } - let(:split_client) { SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) } + let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) } + let(:split_client) { SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository, :rule_based_segments => rule_based_segments_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) } let(:splits) do File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) end before do - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][2]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][2]], [], -1) end it 'check getting treatments' do @@ -56,14 +57,14 @@ end it 'posting impressions and events' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits) stub_request(:post, 'https://events.split.io/api/events/bulk').to_return(status: 200, body: '') stub_request(:post, 'https://events.split.io/api/testImpressions/bulk').to_return(status: 200, body: '') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage').to_return(status: 200, body: '') - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916").to_return(status: 200, body: 'ok') - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916sets=set_3&").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1&sets=set_3&").to_return(status: 200, body: 'ok') mock_segment_changes('segment1', segment1, '-1') mock_segment_changes('segment1', segment1, '1470947453877') mock_segment_changes('segment2', segment2, '-1') @@ -100,6 +101,7 @@ flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -107,13 +109,13 @@ unique_keys_tracker = SplitIoClient::Engine::Impressions::UniqueKeysTracker.new(config, filter_adapter, nil, Concurrent::Hash.new) impression_manager = SplitIoClient::Engine::Common::ImpressionManager.new(config, impressions_repository, impressions_counter, runtime_producer, SplitIoClient::Observers::ImpressionObserver.new, unique_keys_tracker) evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) - evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) + evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) split_client = SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][0]], [], -1) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][1]], [], -1) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][2]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][1]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][2]], [], -1) expect(split_client.get_treatment('key1', 'with_track_disabled')).to eq('off') expect(split_client.get_treatment('key2', 'with_track_enabled')).to eq('off') @@ -139,6 +141,7 @@ flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -146,13 +149,13 @@ unique_keys_tracker = SplitIoClient::Engine::Impressions::UniqueKeysTracker.new(config, filter_adapter, nil, Concurrent::Hash.new) impression_manager = SplitIoClient::Engine::Common::ImpressionManager.new(config, impressions_repository, impressions_counter, runtime_producer, SplitIoClient::Observers::ImpressionObserver.new, unique_keys_tracker) evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) - evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) + evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) split_client = SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][0]], [], -1) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][1]], [], -1) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][2]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][1]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][2]], [], -1) expect(split_client.get_treatment('key1', 'with_track_disabled')).to eq('off') expect(split_client.get_treatment('key2', 'with_track_enabled')).to eq('off') @@ -178,6 +181,7 @@ flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -185,13 +189,13 @@ unique_keys_tracker = SplitIoClient::Engine::Impressions::UniqueKeysTracker.new(config, filter_adapter, nil, Concurrent::Hash.new) impression_manager = SplitIoClient::Engine::Common::ImpressionManager.new(config, impressions_repository, impressions_counter, runtime_producer, SplitIoClient::Observers::ImpressionObserver.new, unique_keys_tracker) evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) - evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) + evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) split_client = SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][0]], [], -1) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][1]], [], -1) - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][2]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][1]], [], -1) + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][2]], [], -1) expect(split_client.get_treatment('key1', 'with_track_disabled')).to eq('off') expect(split_client.get_treatment('key2', 'with_track_enabled')).to eq('off') diff --git a/spec/splitclient/split_factory_spec.rb b/spec/splitclient/split_factory_spec.rb index 5cfec28c..dd0ef73a 100644 --- a/spec/splitclient/split_factory_spec.rb +++ b/spec/splitclient/split_factory_spec.rb @@ -69,7 +69,7 @@ let(:mode) { :standalone } it 'log an error stating Api Key is invalid' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: '') @@ -89,7 +89,7 @@ let(:mode) { :standalone } it 'log an error stating Api Key is invalid' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: '') @@ -112,9 +112,9 @@ end it 'log an error stating Api Key is invalid' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_with_segments_json) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1473863097220') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1473863097220&rbSince=-1') .to_return(status: 200, body: []) stub_request(:get, 'https://sdk.split.io/api/segmentChanges/employees?since=-1') .to_return(status: 403, body: []) @@ -140,7 +140,7 @@ let(:mode) { :standalone } it 'log an error' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage') @@ -174,7 +174,7 @@ end it 'logs warnings stating number of factories' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) described_class.new('API_KEY', options) @@ -188,7 +188,7 @@ end it 'decreases number of registered factories on client destroy' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) expect(SplitIoClient.split_factory_registry.number_of_factories_for('API_KEY')).to eq 0 @@ -203,7 +203,7 @@ end it 'active and redundant factories' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') @@ -239,7 +239,7 @@ .to_return(status: 200, body: 'ok') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: 'ok') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: '') @@ -260,7 +260,7 @@ .to_return(status: 200, body: 'ok') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: 'ok') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: '') @@ -281,7 +281,7 @@ .to_return(status: 200, body: 'ok') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: 'ok') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: '') @@ -302,7 +302,7 @@ .to_return(status: 200, body: 'ok') stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: 'ok') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: []) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: '') diff --git a/spec/splitclient/split_manager_spec.rb b/spec/splitclient/split_manager_spec.rb index 4714a40f..0f9bdd0e 100644 --- a/spec/splitclient/split_manager_spec.rb +++ b/spec/splitclient/split_manager_spec.rb @@ -10,7 +10,7 @@ let(:segments) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../test_data/segments/engine_segments.json'))) } before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits) stub_request(:get, 'https://sdk.split.io/api/segmentChanges/demo?since=-1') @@ -25,7 +25,7 @@ stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: 'ok') - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1473413807667") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1473413807667&rbSince=-1") .to_return(status: 200, body: "", headers: {}) end @@ -85,7 +85,7 @@ context '#splits' do it 'returns empty array and logs error when not ready' do - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=-1") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=-1") .to_return(status: 200, body: "", headers: {}) allow(subject).to receive(:ready?).and_return(false) @@ -109,10 +109,10 @@ let(:splits3) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../test_data/splits/splits3.json'))) } before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits3) - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1473863097220") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1473863097220&rbSince=-1") .to_return(status: 200, body: "", headers: {}) end @@ -137,10 +137,10 @@ let(:splits4) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../test_data/splits/splits4.json'))) } before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits4) - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since") + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since") .to_return(status: 200, body: "", headers: {}) end diff --git a/spec/splitclient_rb_corner_cases_spec.rb b/spec/splitclient_rb_corner_cases_spec.rb index b5d763ea..c4346f85 100644 --- a/spec/splitclient_rb_corner_cases_spec.rb +++ b/spec/splitclient_rb_corner_cases_spec.rb @@ -21,10 +21,10 @@ before do stub_request(:post, 'https://events.split.io/api/testImpressions/bulk').to_return(status: 200, body: '') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').to_return(status: 200, body: splits_json) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').to_return(status: 200, body: splits_json) stub_request(:get, 'https://sdk.split.io/api/segmentChanges/demo?since=-1').to_return(status: 200, body: segment_res) stub_request(:get, 'https://sdk.split.io/api/segmentChanges/employees?since=-1').to_return(status: 200, body: segment_res) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1473413807667').to_return(status: 200, body: segment_res) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1473413807667&rbSince=-1').to_return(status: 200, body: segment_res) stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: segment_res) stub_request(:post, 'https://events.split.io/api/testImpressions/count').to_return(status: 200, body: '') end diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 3547f6a1..1c199b09 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -20,19 +20,20 @@ splits: SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter), segments: SplitIoClient::Cache::Repositories::SegmentsRepository.new(config), impressions: SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config), - events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer) + events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer), + rule_based_segments: SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } end let(:parameters) do { - split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(repositories[:splits], api_key, config, telemetry_runtime_producer), + split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(repositories[:splits], repositories[:rule_based_segments], api_key, config, telemetry_runtime_producer), segment_fetcher: SplitIoClient::Cache::Fetchers::SegmentFetcher.new(repositories[:segments], api_key, config, telemetry_runtime_producer), imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, telemetry_runtime_producer: telemetry_runtime_producer } end let(:synchronizer) { SplitIoClient::Engine::Synchronizer.new(repositories, config, parameters) } - let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, repositories[:splits], telemetry_runtime_producer, parameters[:segment_fetcher]) } + let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, repositories[:splits], telemetry_runtime_producer, parameters[:segment_fetcher], repositories[:rule_based_segments]) } let(:segments_worker) { SplitIoClient::SSE::Workers::SegmentsWorker.new(synchronizer, config, repositories[:segments]) } let(:push_status_queue) { Queue.new } let(:notification_manager_keeper) { SplitIoClient::SSE::NotificationManagerKeeper.new(config, telemetry_runtime_producer, push_status_queue) } @@ -48,12 +49,12 @@ context 'tests' do it 'receive split update event' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .with(headers: { 'Authorization' => 'Bearer client-spec-key' }) - .to_return(status: 200, body: '{"splits":[],"since":-1,"till":5564531221}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=5564531221') + .to_return(status: 200, body: '{"ff":{"d":[],"s":-1,"t":5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=5564531221&rbSince=-1') .with(headers: { 'Authorization' => 'Bearer client-spec-key' }) - .to_return(status: 200, body: '{"splits":[],"since":5564531221,"till":5564531221}') + .to_return(status: 200, body: '{"ff":{"d":[],"s":5564531221,"t":5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') mock_server do |server| server.setup_response('/') do |_, res| @@ -68,7 +69,7 @@ expect(sse_client.connected?).to eq(true) expect(push_status_queue.pop(true)).to eq(SplitIoClient::Constants::PUSH_CONNECTED) sleep 1 - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(1) sse_client.close @@ -79,12 +80,12 @@ end it 'receive split kill event' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .with(headers: { 'Authorization' => 'Bearer client-spec-key' }) - .to_return(status: 200, body: '{"splits":[],"since":-1,"till":5564531221}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=5564531221') + .to_return(status: 200, body: '{"ff":{"d":[],"since":-1,"till":5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=5564531221&rbSince=-1') .with(headers: { 'Authorization' => 'Bearer client-spec-key' }) - .to_return(status: 200, body: '{"splits":[],"since":5564531221,"till":5564531221}') + .to_return(status: 200, body: '{"ff":{"d":[],"since":5564531221,"till":5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') mock_server do |server| server.setup_response('/') do |_, res| @@ -99,7 +100,7 @@ expect(sse_client.connected?).to eq(true) expect(push_status_queue.pop(true)).to eq(SplitIoClient::Constants::PUSH_CONNECTED) sleep 1 - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(1) sse_client.close @@ -150,7 +151,7 @@ expect(connected).to eq(true) expect(sse_client.connected?).to eq(true) expect(push_status_queue.pop(true)).to eq(SplitIoClient::Constants::PUSH_CONNECTED) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(0) expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment-test?since=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(0) sse_client.close @@ -172,7 +173,7 @@ expect(connected).to eq(true) expect(sse_client.connected?).to eq(true) expect(push_status_queue.pop(true)).to eq(SplitIoClient::Constants::PUSH_CONNECTED) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(0) expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment-test?since=-1').with(headers: { 'Authorization' => 'Bearer client-spec-key' })).to have_been_made.times(0) sse_client.close diff --git a/spec/sse/sse_handler_spec.rb b/spec/sse/sse_handler_spec.rb index e6a314dc..099f7932 100644 --- a/spec/sse/sse_handler_spec.rb +++ b/spec/sse/sse_handler_spec.rb @@ -21,19 +21,20 @@ splits: splits_repository, segments: segments_repository, impressions: SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config), - events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer) + events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer), + rule_based_segments: SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } end let(:parameters) do { - split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer), + split_fetcher: SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, repositories[:rule_based_segments], api_key, config, telemetry_runtime_producer), segment_fetcher: SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer), imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, telemetry_runtime_producer: telemetry_runtime_producer } end let(:synchronizer) { SplitIoClient::Engine::Synchronizer.new(repositories, config, parameters) } - let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, splits_repository, telemetry_runtime_producer, parameters[:segment_fetcher]) } + let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, splits_repository, telemetry_runtime_producer, parameters[:segment_fetcher], repositories[:rule_based_segments]) } let(:segments_worker) { SplitIoClient::SSE::Workers::SegmentsWorker.new(synchronizer, config, segments_repository) } let(:notification_processor) { SplitIoClient::SSE::NotificationProcessor.new(config, splits_worker, segments_worker) } let(:event_parser) { SplitIoClient::SSE::EventSource::EventParser.new(config) } diff --git a/spec/sse/workers/segments_worker_spec.rb b/spec/sse/workers/segments_worker_spec.rb index aad5db85..8e3405bc 100644 --- a/spec/sse/workers/segments_worker_spec.rb +++ b/spec/sse/workers/segments_worker_spec.rb @@ -17,10 +17,11 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer) } - let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer) } + let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } let(:repositories) { { splits: splits_repository, segments: segments_repository } } let(:impression_counter) { SplitIoClient::Engine::Common::ImpressionCounter.new } @@ -101,7 +102,7 @@ private def mock_split_changes(splits_json) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) end diff --git a/spec/sse/workers/splits_worker_spec.rb b/spec/sse/workers/splits_worker_spec.rb index b45aef73..74e00c51 100644 --- a/spec/sse/workers/splits_worker_spec.rb +++ b/spec/sse/workers/splits_worker_spec.rb @@ -21,13 +21,16 @@ let(:event_split_archived_no_compression) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c": 0,"d":"eyJ0cmFmZmljVHlwZU5hbWUiOiAidXNlciIsICJpZCI6ICIzM2VhZmE1MC0xYTY1LTExZWQtOTBkZi1mYTMwZDk2OTA0NDUiLCAibmFtZSI6ICJiaWxhbF9zcGxpdCIsICJ0cmFmZmljQWxsb2NhdGlvbiI6IDEwMCwgInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6IC0xMzY0MTE5MjgyLCAic2VlZCI6IC02MDU5Mzg4NDMsICJzdGF0dXMiOiAiQVJDSElWRUQiLCAia2lsbGVkIjogZmFsc2UsICJkZWZhdWx0VHJlYXRtZW50IjogIm9mZiIsICJjaGFuZ2VOdW1iZXIiOiAxNjg0Mjc1ODM5OTUyLCAiYWxnbyI6IDIsICJjb25maWd1cmF0aW9ucyI6IHt9LCAiY29uZGl0aW9ucyI6IFt7ImNvbmRpdGlvblR5cGUiOiAiUk9MTE9VVCIsICJtYXRjaGVyR3JvdXAiOiB7ImNvbWJpbmVyIjogIkFORCIsICJtYXRjaGVycyI6IFt7ImtleVNlbGVjdG9yIjogeyJ0cmFmZmljVHlwZSI6ICJ1c2VyIn0sICJtYXRjaGVyVHlwZSI6ICJJTl9TRUdNRU5UIiwgIm5lZ2F0ZSI6IGZhbHNlLCAidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOiB7InNlZ21lbnROYW1lIjogImJpbGFsX3NlZ21lbnQifX1dfSwgInBhcnRpdGlvbnMiOiBbeyJ0cmVhdG1lbnQiOiAib24iLCAic2l6ZSI6IDB9LCB7InRyZWF0bWVudCI6ICJvZmYiLCAic2l6ZSI6IDEwMH1dLCAibGFiZWwiOiAiaW4gc2VnbWVudCBiaWxhbF9zZWdtZW50In0sIHsiY29uZGl0aW9uVHlwZSI6ICJST0xMT1VUIiwgIm1hdGNoZXJHcm91cCI6IHsiY29tYmluZXIiOiAiQU5EIiwgIm1hdGNoZXJzIjogW3sia2V5U2VsZWN0b3IiOiB7InRyYWZmaWNUeXBlIjogInVzZXIifSwgIm1hdGNoZXJUeXBlIjogIkFMTF9LRVlTIiwgIm5lZ2F0ZSI6IGZhbHNlfV19LCAicGFydGl0aW9ucyI6IFt7InRyZWF0bWVudCI6ICJvbiIsICJzaXplIjogMH0sIHsidHJlYXRtZW50IjogIm9mZiIsICJzaXplIjogMTAwfV0sICJsYWJlbCI6ICJkZWZhdWx0IHJ1bGUifV19"}'), 'test') } let(:event_split_update_no_definition) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c": 0, "d":null}'), 'test') } let(:event_split_update_segments) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c":2,"d":"eJzcVFtr20wQ/SvhPK9AsnzTvpnPJp+po0DlppRgzFga2dusJLNapaRG/73Id7sOoU+FvmluZ3TOGXYDayhNVTx9W3NIGUOiKtlAQCWQSNq+FyeJ6yzcBTuex+T0qe86XrfrUkJBzH4AgXw3mVFlivl3eiWIA/BA6yImq4oc0nPdG/mIOYF0gpYfeO3AEyh3Ca/XDfxer+u2BUpLtiohMfhvOn4aQeBFad20paRLFkg4pUrbqWGyGecWEvbwPQ9cCMQrypccVtmCDaTX7feCnu+7nY7nCZBeFpAtgbjIU7WszPbPSshNvc0lah8/b05hoxkkvv4/no4m42gKgYxsvGJzb4pqDdn0ZguVNwsxCIenhh3SPriBk/OSLB/Z/Vgpy1qV9mE3MSRLDfwxD/kMSjKVb1dUpmgwVFxgVtezWmBNxp5RsDdlavkdCJTqJ2+tqmcCmhasIU+LOEEtftfg8+Nk8vjlzxV44beINce2ME3z2TEeDrEWVzKNw3k0un8YhTd0aiaGnKqck4iXDakrwcpdNjzdq9PChxIV+VEXt2F/UUvTC9Guyk/t90dfO+/Xro73w65z7y6cU/ndnvTdge7f9W8wmcw/jb5F1+79yybsX6c7U2lGPat/BQAA//9ygdKB"}'), 'test') } + let(:event_split_update_rb_segments) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c":0,"d":"eyJjaGFuZ2VOdW1iZXIiOiAxMCwgInRyYWZmaWNUeXBlTmFtZSI6ICJ1c2VyIiwgIm5hbWUiOiAicmJzX2ZsYWciLCAidHJhZmZpY0FsbG9jYXRpb24iOiAxMDAsICJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOiAxODI4Mzc3MzgwLCAic2VlZCI6IC0yODY2MTc5MjEsICJzdGF0dXMiOiAiQUNUSVZFIiwgImtpbGxlZCI6IGZhbHNlLCAiZGVmYXVsdFRyZWF0bWVudCI6ICJvZmYiLCAiYWxnbyI6IDIsICJjb25kaXRpb25zIjogW3siY29uZGl0aW9uVHlwZSI6ICJST0xMT1VUIiwgIm1hdGNoZXJHcm91cCI6IHsiY29tYmluZXIiOiAiQU5EIiwgIm1hdGNoZXJzIjogW3sia2V5U2VsZWN0b3IiOiB7InRyYWZmaWNUeXBlIjogInVzZXIifSwgIm1hdGNoZXJUeXBlIjogIklOX1JVTEVfQkFTRURfU0VHTUVOVCIsICJuZWdhdGUiOiBmYWxzZSwgInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjogeyJzZWdtZW50TmFtZSI6ICJzYW1wbGVfcnVsZV9iYXNlZF9zZWdtZW50In19XX0sICJwYXJ0aXRpb25zIjogW3sidHJlYXRtZW50IjogIm9uIiwgInNpemUiOiAxMDB9LCB7InRyZWF0bWVudCI6ICJvZmYiLCAic2l6ZSI6IDB9XSwgImxhYmVsIjogImluIHJ1bGUgYmFzZWQgc2VnbWVudCBzYW1wbGVfcnVsZV9iYXNlZF9zZWdtZW50In0sIHsiY29uZGl0aW9uVHlwZSI6ICJST0xMT1VUIiwgIm1hdGNoZXJHcm91cCI6IHsiY29tYmluZXIiOiAiQU5EIiwgIm1hdGNoZXJzIjogW3sia2V5U2VsZWN0b3IiOiB7InRyYWZmaWNUeXBlIjogInVzZXIifSwgIm1hdGNoZXJUeXBlIjogIkFMTF9LRVlTIiwgIm5lZ2F0ZSI6IGZhbHNlfV19LCAicGFydGl0aW9ucyI6IFt7InRyZWF0bWVudCI6ICJvbiIsICJzaXplIjogMH0sIHsidHJlYXRtZW50IjogIm9mZiIsICJzaXplIjogMTAwfV0sICJsYWJlbCI6ICJkZWZhdWx0IHJ1bGUifV0sICJjb25maWd1cmF0aW9ucyI6IHt9LCAic2V0cyI6IFtdLCAiaW1wcmVzc2lvbnNEaXNhYmxlZCI6IGZhbHNlfQ=="}'), 'test') } + let(:event_rb_segment_update) { SplitIoClient::SSE::EventSource::StreamData.new("data", 12345, JSON.parse('{"type":"RB_SEGMENT_UPDATE","changeNumber":5564531221,"pcn":1234,"c":0,"d":"eyJjaGFuZ2VOdW1iZXIiOiA1LCAibmFtZSI6ICJzYW1wbGVfcnVsZV9iYXNlZF9zZWdtZW50IiwgInN0YXR1cyI6ICJBQ1RJVkUiLCAidHJhZmZpY1R5cGVOYW1lIjogInVzZXIiLCAiZXhjbHVkZWQiOiB7ImtleXMiOiBbIm1hdXJvQHNwbGl0LmlvIl0sICJzZWdtZW50cyI6IFt7InR5cGUiOiAic3RhbmRhcmQiLCAibmFtZSI6ICJzZWdtZW50MSJ9XX0sICJjb25kaXRpb25zIjogW3sibWF0Y2hlckdyb3VwIjogeyJjb21iaW5lciI6ICJBTkQiLCAibWF0Y2hlcnMiOiBbeyJrZXlTZWxlY3RvciI6IHsidHJhZmZpY1R5cGUiOiAidXNlciIsICJhdHRyaWJ1dGUiOiAiZW1haWwifSwgIm1hdGNoZXJUeXBlIjogIklOX1NFR01FTlQiLCAibmVnYXRlIjogZmFsc2UsICJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6IHsic2VnbWVudE5hbWUiOiAiZGVtbyJ9fV19fV19"}'), 'test') } context 'add change number to queue' do 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(config, flag_sets_repository, flag_set_filter) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:synchronizer) do @@ -36,7 +39,8 @@ repositories = { splits: splits_repository, - segments: segments_repository + segments: segments_repository, + rule_based_segments: rule_based_segments_repository } params = { @@ -51,49 +55,49 @@ end it 'add change number - must tigger fetch - with retries' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').to_return(status: 200, body: '{"splits": [],"since": -1,"till": 1506703262918}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918').to_return(status: 200, body: '{"splits": [],"since": 1506703262918,"till": 1506703262918}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918&till=1506703262919').to_return(status: 200, body: '{"splits": [],"since": 1506703262919,"till": 1506703262919}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": -1,"t": 1506703262918}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 1506703262918,"t": 1506703262918}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1&till=1506703262919').to_return(status: 200, body: '{"ff":{"d": [],"s": 1506703262919,"t": 1506703262919}, "rbs":{"d":[],"s":-1,"t":-1}}') - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start worker.add_to_queue(SplitIoClient::SSE::EventSource::StreamData.new("SPLIT_UPDATE", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":1506703262919}'), 'test')) sleep 1 - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1')).to have_been_made.times(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918')).to have_been_made.at_least_times(2) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262918&till=1506703262919')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1')).to have_been_made.at_least_times(2) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262918&rbSince=-1&till=1506703262919')).to have_been_made.times(1) end it 'must trigger fetch' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').to_return(status: 200, body: '{"splits": [],"since": -1,"till": 1506703262916}') - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '{"splits": [],"since": 1506703262916,"till": 1506703262918}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": -1,"t": 1506703262916}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 1506703262916,"t": 1506703262918}, "rbs":{"d":[],"s":-1,"t":-1}}') - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start worker.add_to_queue(SplitIoClient::SSE::EventSource::StreamData.new("SPLIT_UPDATE", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":1506703262918}'), 'test')) sleep 1 - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1')).to have_been_made.once end it 'must not trigger fetch' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1').to_return(status: 200, body: '{"splits": [],"since": -1,"till": 1506703262916}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": -1,"t": 1506703262916}, "rbs":{"d":[],"s":-1,"t":-1}}') - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start worker.add_to_queue(SplitIoClient::SSE::EventSource::StreamData.new("SPLIT_UPDATE", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":1506703262916}'), 'test')) sleep 1 - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916')).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1')).to have_been_made.times(0) end it 'without start, must not fetch' do - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.add_to_queue(SplitIoClient::SSE::EventSource::StreamData.new("SPLIT_UPDATE", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":1506703262918}'), 'test')) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916')).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1')).to have_been_made.times(0) end end @@ -102,16 +106,18 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer) } + let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) repositories = { splits: splits_repository, - segments: segments_repository + segments: segments_repository, + rule_based_segments: rule_based_segments_repository } params = { @@ -136,9 +142,9 @@ end it 'must kill split and trigger fetch' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916').to_return(status: 200, body: '{"splits": [],"since": 1506703262916,"till": 1506703262918}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 1506703262916,"t": 1506703262918}, "rbs":{"d":[],"s":-1,"t":-1}}') - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start worker.send :kill_feature_flag, SplitIoClient::SSE::EventSource::StreamData.new("SPLIT_KILL", 123, JSON.parse('{"splitName":"FACUNDO_TEST", "defaultTreatment":"on", "type":"SPLIT_KILL","changeNumber":1506703262918}'), 'test') @@ -148,11 +154,11 @@ expect(split[:killed]).to be_truthy expect(split[:defaultTreatment]).to eq('on') expect(split[:changeNumber]).to eq(1_506_703_262_918) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1')).to have_been_made.once end it 'must kill split and must not trigger fetch' do - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start worker.send :kill_feature_flag, SplitIoClient::SSE::EventSource::StreamData.new("SPLIT_KILL", 123, JSON.parse('{"splitName":"FACUNDO_TEST", "defaultTreatment":"on", "type":"SPLIT_KILL","changeNumber":1506703262916}'), 'test') @@ -163,7 +169,7 @@ expect(split[:killed]).to be_truthy expect(split[:defaultTreatment]).to eq('on') expect(split[:changeNumber]).to eq(1_506_703_262_916) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1506703262916')).to have_been_made.times(0) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1')).to have_been_made.times(0) end end @@ -172,16 +178,18 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new(["set_1"])} let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer) } + let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) repositories = { splits: splits_repository, - segments: segments_repository + segments: segments_repository, + rule_based_segments: rule_based_segments_repository } params = { @@ -195,10 +203,10 @@ SplitIoClient::Engine::Synchronizer.new(repositories, config, params) end it 'update split with and without flagset' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1234').to_return(status: 200, body: '{"splits": [],"since": 1234,"till": 1234}') - stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1234&till=5564531221").to_return(status: 200, body: '{"splits": [],"since": 1234,"till": 5564531221}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1234&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 1234,"t": 1234}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1234&rbSince=-1&till=5564531221").to_return(status: 200, body: '{"ff":{"d": [],"s": 1234,"t": 5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') stub_request(:get, "https://sdk.split.io/api/segmentChanges/bilal_segment?since=-1").to_return(status: 200, body: "") - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start splits_repository.set_change_number(1234) @@ -224,16 +232,18 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, api_key, config, telemetry_runtime_producer) } + let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) repositories = { splits: splits_repository, - segments: segments_repository + segments: segments_repository, + rule_based_segments: rule_based_segments_repository } params = { @@ -246,9 +256,10 @@ SplitIoClient::Engine::Synchronizer.new(repositories, config, params) end + it 'decode and decompress split update data' do stub_request(:get, "https://sdk.split.io/api/segmentChanges/bilal_segment?since=-1").to_return(status: 200, body: "") - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start splits_repository.set_change_number(1234) @@ -276,9 +287,9 @@ end it 'instant ff update split notification with segment matcher.' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1234').to_return(status: 200, body: '{"splits": [],"since": 1234,"till": 1234}') - stub_request(:get, 'https://sdk.split.io/api/segmentChanges/maur-2?since=-1').to_return(status: 200, body: '{"name":"maur-2","added":["admin"],"removed":[],"since":-1,"till":-1}') - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1234&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 1234,"t": 1234}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/maur-2?since=-1').to_return(status: 200, body: '{"name":"maur-2","added":["admin"],"removed":[],"since":-1,"till":-1}}') + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start splits_repository.set_change_number(1234) @@ -289,9 +300,53 @@ expect(segments_repository.used_segment_names[0]).to eq('maur-2') end + it 'instant ff update split notification with rule based segment matcher.' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1234&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 1234,"t": 1234}, "rbs":{"d":[],"s":-1,"t":5564531221}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=5564531221&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": 5564531221,"t": 5564531221}, "rbs":{"d":[{"{ + "changeNumber": 123, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["mauro@split.io","gaston@split.io"], + "segments":[{ "type": "standard", "name": "segment1"}] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData":{ + "segmentName":"demo" + }, + } + ] + } + } + ] + }"}],"s":-1,"t":5564531221}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=5564531221&rbSince=5564531221').to_return(status: 200, body: '{"ff":{"d": [],"s": 5564531221,"t": 5564531221}, "rbs":{"d":[],"s":5564531221,"t":5564531221}}') + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) + worker.start + + splits_repository.set_change_number(1234) + expect(rule_based_segments_repository.get_rule_based_segment("sample_rule_based_segment") == nil) + + worker.add_to_queue(event_split_update_rb_segments) + sleep 1 + expect(rule_based_segments_repository.get_rule_based_segment("sample_rule_based_segment") != nil) + end + it 'should not update if definition is nil' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=1234').to_return(status: 200, body: '{"splits": [],"since": -1,"till": 1506703262918}') - worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1234&rbSince=-1').to_return(status: 200, body: '{"ff":{"d": [],"s": -1,"t": 1506703262918}, "rbs":{"d":[],"s":-1,"t":-1}}') + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) worker.start splits_repository.set_change_number(1234) @@ -299,12 +354,30 @@ sleep 1 expect(splits_repository.exists?('bilal_split') == false) end + + it 'process rb segment update' do + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=-1').to_return(status: 200, body: '{"name":"maur-2","added":["admin"],"removed":[],"since":-1,"till":-1}}') + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/demo?since=-1').to_return(status: 200, body: '{"name":"maur-2","added":["admin"],"removed":[],"since":-1,"till":-1}}') + worker = subject.new(synchronizer, config, splits_repository, telemetry_runtime_producer, segment_fetcher, rule_based_segments_repository) + worker.start + + rule_based_segments_repository.set_change_number(1234) + worker.add_to_queue(event_rb_segment_update) + sleep 2 + rb_segment = rule_based_segments_repository.get_rule_based_segment("sample_rule_based_segment") + expect(rb_segment[:name] == 'sample_rule_based_segment') + + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/demo?since=-1')).to have_been_made.once + expect(segments_repository.used_segment_names[0]).to eq('demo') + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=-1')).to have_been_made.once + expect(segments_repository.used_segment_names[1]).to eq('segment1') + end end private def mock_split_changes(splits_json) - stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') .to_return(status: 200, body: splits_json) end diff --git a/spec/test_data/integrations/splits.json b/spec/test_data/integrations/splits.json index 4361b6bb..1347a248 100644 --- a/spec/test_data/integrations/splits.json +++ b/spec/test_data/integrations/splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{"ff": { + "d": [ { "impressionsDisabled": false, "trafficTypeName": "account", @@ -2521,8 +2521,62 @@ "label": "in segment all" } ] + }, + { + "impressionsDisabled": false, + "trafficTypeName": "account", + "name": "prereq_flag", + "prerequisites": [ + {"n": "MAURO_TEST", "ts": ["off"]}, + {"n": "FACUNDO_TEST", "ts": ["on"]} + ], + "trafficAllocation": 100, + "trafficAllocationSeed": -285565213, + "seed": -1992295819, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off_default", + "changeNumber": 1494593336752, + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "in segment all" + } + ] } ], - "since": -1, - "till": 1506703262916 - } \ No newline at end of file + "s": -1, + "t": 1506703262916 + }, "rbs": {"d":[], "t":-1, "s":-1} +} \ No newline at end of file diff --git a/spec/test_data/integrations/splits_push.json b/spec/test_data/integrations/splits_push.json index 833a41a9..72962e8b 100644 --- a/spec/test_data/integrations/splits_push.json +++ b/spec/test_data/integrations/splits_push.json @@ -1,6 +1,6 @@ -{ - "splits": [ - { +{"ff": { + "d": [ + { "impressionsDisabled": false, "trafficTypeName": "user", "name": "push_test", @@ -225,6 +225,7 @@ ] } ], - "since": -1, - "till": 1585948850109 + "s": -1, + "t": 1585948850109 + }, "rbs": {"d":[], "t":-1, "s":-1} } \ No newline at end of file diff --git a/spec/test_data/integrations/splits_push2.json b/spec/test_data/integrations/splits_push2.json index c4f7d93d..701b7880 100644 --- a/spec/test_data/integrations/splits_push2.json +++ b/spec/test_data/integrations/splits_push2.json @@ -1,5 +1,6 @@ -{ - "splits": [], - "since": 1585948850109, - "till": 1585948850110 +{"ff": { + "d": [], + "s": 1585948850109, + "t": 1585948850110 + }, "rbs": {"d":[], "t":-1, "s":-1} } \ No newline at end of file diff --git a/spec/test_data/integrations/splits_push3.json b/spec/test_data/integrations/splits_push3.json index 8984c5ff..bfade789 100644 --- a/spec/test_data/integrations/splits_push3.json +++ b/spec/test_data/integrations/splits_push3.json @@ -1,6 +1,6 @@ -{ - "splits": [ - { +{"ff": { + "d": [ + { "impressionsDisabled": false, "trafficTypeName": "user", "name": "push_test", @@ -87,6 +87,7 @@ ] } ], - "since": 1585948850110, - "till": 1585948850111 + "s": 1585948850110, + "t": 1585948850111 + }, "rbs": {"d":[], "t":-1, "s":-1} } \ No newline at end of file diff --git a/spec/test_data/rule_based_segments/rule_base_segments.json b/spec/test_data/rule_based_segments/rule_base_segments.json new file mode 100644 index 00000000..deafbdc2 --- /dev/null +++ b/spec/test_data/rule_based_segments/rule_base_segments.json @@ -0,0 +1,135 @@ +{"ff": {"d": [ + { + "changeNumber": 10, + "trafficTypeName": "user", + "name": "rbs_feature_flag", + "trafficAllocation": 100, + "trafficAllocationSeed": 1828377380, + "seed": -286617921, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "sample_rule_based_segment" + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "in rule based segment sample_rule_based_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ALL_KEYS", + "negate": false + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ], + "configurations": {}, + "sets": [], + "impressionsDisabled": false + } + ], "t": 1506703262916, "s": -1}, +"rbs": {"t": 1506703262916, "s": -1, "d": + [{ + "changeNumber": 5, + "name": "dependent_rbs", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{"keys":["mauro@split.io","gaston@split.io"],"segments":[]}, + "conditions": [ + { + "conditionType": "WHITELIST", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ]}, + { + "changeNumber": 5, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded": { + "keys": [], + "segments": [] + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "dependent_rbs" + } + } + ] + } + } + ] + }] +}} diff --git a/spec/test_data/rule_based_segments/rule_base_segments2.json b/spec/test_data/rule_based_segments/rule_base_segments2.json new file mode 100644 index 00000000..f0ab0e1e --- /dev/null +++ b/spec/test_data/rule_based_segments/rule_base_segments2.json @@ -0,0 +1,140 @@ +{"ff": {"d": [ + { + "changeNumber": 10, + "trafficTypeName": "user", + "name": "rbs_feature_flag", + "trafficAllocation": 100, + "trafficAllocationSeed": 1828377380, + "seed": -286617921, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "sample_rule_based_segment" + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "in rule based segment sample_rule_based_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ALL_KEYS", + "negate": false + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ], + "configurations": {}, + "sets": [], + "impressionsDisabled": false + } +], "t": 1506703262916, "s": -1}, +"rbs": {"t": 1506703262916, "s": -1, "d": [ + { + "changeNumber": 5, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["mauro@split.io","gaston@split.io"], + "segments":[{"type":"rule-based", "name":"no_excludes"}] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "START_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "bilal" + ] + } + } + ] + } + } + ] + }, + { + "changeNumber": 5, + "name": "no_excludes", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["bilal2@split.io"], + "segments":[] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + } +]}} diff --git a/spec/test_data/rule_based_segments/rule_base_segments3.json b/spec/test_data/rule_based_segments/rule_base_segments3.json new file mode 100644 index 00000000..fa7af16f --- /dev/null +++ b/spec/test_data/rule_based_segments/rule_base_segments3.json @@ -0,0 +1,108 @@ +{"ff": {"d": [ + { + "changeNumber": 10, + "trafficTypeName": "user", + "name": "rbs_feature_flag", + "trafficAllocation": 100, + "trafficAllocationSeed": 1828377380, + "seed": -286617921, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "sample_rule_based_segment" + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "in rule based segment sample_rule_based_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ALL_KEYS", + "negate": false + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ], + "configurations": {}, + "sets": [], + "impressionsDisabled": false + } +], "t": 1506703262916, "s": -1}, +"rbs": {"t": 1506703262916, "s": -1, "d": [ + { + "changeNumber": 5, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["mauro@split.io","gaston@split.io"], + "segments":[{"type":"standard", "name":"segment1"}] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + } +]}} diff --git a/spec/test_data/rule_based_segments/split_old_spec.json b/spec/test_data/rule_based_segments/split_old_spec.json new file mode 100644 index 00000000..2d0aef1e --- /dev/null +++ b/spec/test_data/rule_based_segments/split_old_spec.json @@ -0,0 +1,264 @@ +{ + "splits": [ + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "whitelist_feature", + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "changeNumber": 123, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "WHITELIST", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": { + "whitelist": [ + "whitelisted_user" + ] + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + } + ] + }, + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ] + } + ], + "sets": ["set1", "set2"] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "all_feature", + "seed": 1699838640, + "status": "ACTIVE", + "killed": false, + "changeNumber": 123, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ] + } + ], + "sets": ["set4"] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "killed_feature", + "seed": -480091424, + "status": "ACTIVE", + "killed": true, + "changeNumber": 123, + "defaultTreatment": "defTreatment", + "configurations": { + "off": "{\"size\":15,\"test\":20}", + "defTreatment": "{\"size\":15,\"defTreatment\":true}" + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "defTreatment", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ] + } + ], + "sets": ["set3"] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "dependency_test", + "seed": 1222652054, + "status": "ACTIVE", + "killed": false, + "changeNumber": 123, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "IN_SPLIT_TREATMENT", + "negate": false, + "userDefinedSegmentMatcherData": null, + "dependencyMatcherData": { + "split": "all_feature", + "treatments": ["on"] + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ] + } + ], + "sets": [] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "regex_test", + "seed": 1222652051, + "status": "ACTIVE", + "killed": false, + "changeNumber": 123, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "MATCHES_STRING", + "negate": false, + "userDefinedSegmentMatcherData": null, + "stringMatcherData": "abc[0-9]" + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ] + } + ], + "sets": [] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "boolean_test", + "status": "ACTIVE", + "killed": false, + "changeNumber": 123, + "seed": 12321809, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "EQUAL_TO_BOOLEAN", + "negate": false, + "userDefinedSegmentMatcherData": null, + "booleanMatcherData": true + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ] + } + ], + "sets": [] + } + ], + "since": -1, + "till": 1457726098069 +} \ No newline at end of file diff --git a/spec/test_data/rule_based_segments/split_old_spec2.json b/spec/test_data/rule_based_segments/split_old_spec2.json new file mode 100644 index 00000000..74aff1ed --- /dev/null +++ b/spec/test_data/rule_based_segments/split_old_spec2.json @@ -0,0 +1,66 @@ +{ + "splits": [ + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "new_feature", + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "changeNumber": 123, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "WHITELIST", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": { + "whitelist": [ + "whitelisted_user" + ] + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + } + ] + }, + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ] + } + ], + "sets": ["set1", "set2"] + } ], + "since": -1, + "till": 1457726098069 +} \ No newline at end of file diff --git a/spec/test_data/splits/between_matcher/datetime_matcher_splits.json b/spec/test_data/splits/between_matcher/datetime_matcher_splits.json index d1e3b3df..73e7ffe2 100644 --- a/spec/test_data/splits/between_matcher/datetime_matcher_splits.json +++ b/spec/test_data/splits/between_matcher/datetime_matcher_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -50,5 +50,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/between_matcher/negate_number_matcher_splits.json b/spec/test_data/splits/between_matcher/negate_number_matcher_splits.json index 46d8bab1..9d93d964 100644 --- a/spec/test_data/splits/between_matcher/negate_number_matcher_splits.json +++ b/spec/test_data/splits/between_matcher/negate_number_matcher_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -50,5 +50,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/between_matcher/negative_number_matcher_splits.json b/spec/test_data/splits/between_matcher/negative_number_matcher_splits.json index 64f4182f..105ca471 100644 --- a/spec/test_data/splits/between_matcher/negative_number_matcher_splits.json +++ b/spec/test_data/splits/between_matcher/negative_number_matcher_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -50,5 +50,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/between_matcher/number_matcher_splits.json b/spec/test_data/splits/between_matcher/number_matcher_splits.json index bef6b87a..f5e98ff0 100644 --- a/spec/test_data/splits/between_matcher/number_matcher_splits.json +++ b/spec/test_data/splits/between_matcher/number_matcher_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -50,5 +50,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/boolean_matcher/splits.json b/spec/test_data/splits/boolean_matcher/splits.json index 6cfd69e7..9f81228e 100644 --- a/spec/test_data/splits/boolean_matcher/splits.json +++ b/spec/test_data/splits/boolean_matcher/splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -46,5 +46,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/combining_matcher_splits.json b/spec/test_data/splits/combining_matcher_splits.json index fc2ddcc1..86c258ea 100644 --- a/spec/test_data/splits/combining_matcher_splits.json +++ b/spec/test_data/splits/combining_matcher_splits.json @@ -1,5 +1,6 @@ +{ "ff":{ + "d": [ { - "splits": [{ "trafficTypeName": "user", "name": "PASSENGER_anding", "trafficAllocation": 100, @@ -111,5 +112,6 @@ }], "label": "in segment employees" }] - }] + }],"s":-1, "t":-1}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/engine/all_keys_matcher.json b/spec/test_data/splits/engine/all_keys_matcher.json index 107e5e2d..70c425f8 100644 --- a/spec/test_data/splits/engine/all_keys_matcher.json +++ b/spec/test_data/splits/engine/all_keys_matcher.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -36,5 +36,8 @@ ], "sets": ["set_1"] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/configurations.json b/spec/test_data/splits/engine/configurations.json index 05ccef7a..edaecd8d 100644 --- a/spec/test_data/splits/engine/configurations.json +++ b/spec/test_data/splits/engine/configurations.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -172,5 +172,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/dependency_matcher.json b/spec/test_data/splits/engine/dependency_matcher.json index 5a85fa41..ba4f1c4d 100644 --- a/spec/test_data/splits/engine/dependency_matcher.json +++ b/spec/test_data/splits/engine/dependency_matcher.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -73,5 +73,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/equal_to_set_matcher.json b/spec/test_data/splits/engine/equal_to_set_matcher.json index b7c07f23..83f7cc6e 100644 --- a/spec/test_data/splits/engine/equal_to_set_matcher.json +++ b/spec/test_data/splits/engine/equal_to_set_matcher.json @@ -1,5 +1,5 @@ -{ - "splits":[ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "mauro_test", @@ -119,5 +119,8 @@ ], "sets": ["set_2"] } - ] -} \ No newline at end of file + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} +} diff --git a/spec/test_data/splits/engine/impressions_test.json b/spec/test_data/splits/engine/impressions_test.json index c4743e68..79c1524c 100644 --- a/spec/test_data/splits/engine/impressions_test.json +++ b/spec/test_data/splits/engine/impressions_test.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -85,5 +85,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/killed.json b/spec/test_data/splits/engine/killed.json index 2a0315cf..be6f02ba 100644 --- a/spec/test_data/splits/engine/killed.json +++ b/spec/test_data/splits/engine/killed.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -32,5 +32,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/segment_deleted_matcher.json b/spec/test_data/splits/engine/segment_deleted_matcher.json index a558e5f0..e7816f47 100644 --- a/spec/test_data/splits/engine/segment_deleted_matcher.json +++ b/spec/test_data/splits/engine/segment_deleted_matcher.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -38,5 +38,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/segment_matcher.json b/spec/test_data/splits/engine/segment_matcher.json index 079049d2..9c7980f1 100644 --- a/spec/test_data/splits/engine/segment_matcher.json +++ b/spec/test_data/splits/engine/segment_matcher.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -39,5 +39,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/segment_matcher2.json b/spec/test_data/splits/engine/segment_matcher2.json index 62737bbe..d5e25dfc 100644 --- a/spec/test_data/splits/engine/segment_matcher2.json +++ b/spec/test_data/splits/engine/segment_matcher2.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -117,5 +117,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/engine/whitelist_matcher.json b/spec/test_data/splits/engine/whitelist_matcher.json index c9e756ad..1c491172 100644 --- a/spec/test_data/splits/engine/whitelist_matcher.json +++ b/spec/test_data/splits/engine/whitelist_matcher.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -37,5 +37,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/equal_to_matcher/date_splits.json b/spec/test_data/splits/equal_to_matcher/date_splits.json index fa5fa89b..5267517b 100644 --- a/spec/test_data/splits/equal_to_matcher/date_splits.json +++ b/spec/test_data/splits/equal_to_matcher/date_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -46,5 +46,8 @@ ], "sets": ["set_1"] } - ] + ], + "s": -1, + "t": -1}, + "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/equal_to_matcher/negative_splits.json b/spec/test_data/splits/equal_to_matcher/negative_splits.json index 84f5fc33..548bcee8 100644 --- a/spec/test_data/splits/equal_to_matcher/negative_splits.json +++ b/spec/test_data/splits/equal_to_matcher/negative_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -46,5 +46,8 @@ ], "sets": ["set_1"] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/equal_to_matcher/splits.json b/spec/test_data/splits/equal_to_matcher/splits.json index 13f2cae6..5f603999 100644 --- a/spec/test_data/splits/equal_to_matcher/splits.json +++ b/spec/test_data/splits/equal_to_matcher/splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -46,5 +46,8 @@ ], "sets": ["set_1"] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/equal_to_matcher/zero_splits.json b/spec/test_data/splits/equal_to_matcher/zero_splits.json index 17c63578..da231d90 100644 --- a/spec/test_data/splits/equal_to_matcher/zero_splits.json +++ b/spec/test_data/splits/equal_to_matcher/zero_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -46,5 +46,8 @@ ], "sets": ["set_1"] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/greater_than_or_equal_to_matcher/date_splits.json b/spec/test_data/splits/greater_than_or_equal_to_matcher/date_splits.json index 59f29335..643e07a9 100644 --- a/spec/test_data/splits/greater_than_or_equal_to_matcher/date_splits.json +++ b/spec/test_data/splits/greater_than_or_equal_to_matcher/date_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -45,5 +45,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/greater_than_or_equal_to_matcher/negative_splits.json b/spec/test_data/splits/greater_than_or_equal_to_matcher/negative_splits.json index 755948b3..0d5ccde7 100644 --- a/spec/test_data/splits/greater_than_or_equal_to_matcher/negative_splits.json +++ b/spec/test_data/splits/greater_than_or_equal_to_matcher/negative_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -45,5 +45,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/greater_than_or_equal_to_matcher/splits.json b/spec/test_data/splits/greater_than_or_equal_to_matcher/splits.json index e8a7f72f..730aa286 100644 --- a/spec/test_data/splits/greater_than_or_equal_to_matcher/splits.json +++ b/spec/test_data/splits/greater_than_or_equal_to_matcher/splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -45,5 +45,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/imp-toggle.json b/spec/test_data/splits/imp-toggle.json index 77d7a4e9..b70d6684 100644 --- a/spec/test_data/splits/imp-toggle.json +++ b/spec/test_data/splits/imp-toggle.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "with_track_disabled", @@ -152,4 +152,6 @@ ], "since": -1, "till": 1675259356568 + }, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits.json b/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits.json index 6faf61c3..0f74fa5b 100644 --- a/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits.json +++ b/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -45,5 +45,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits2.json b/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits2.json index 7630b564..eab2e7e2 100644 --- a/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits2.json +++ b/spec/test_data/splits/less_than_or_equal_to_matcher/date_splits2.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":null, "environment":null, @@ -49,5 +49,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/less_than_or_equal_to_matcher/negative_splits.json b/spec/test_data/splits/less_than_or_equal_to_matcher/negative_splits.json index ca725e3c..db1a51f5 100644 --- a/spec/test_data/splits/less_than_or_equal_to_matcher/negative_splits.json +++ b/spec/test_data/splits/less_than_or_equal_to_matcher/negative_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -45,5 +45,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/less_than_or_equal_to_matcher/splits.json b/spec/test_data/splits/less_than_or_equal_to_matcher/splits.json index 76850210..cc2e6dc3 100644 --- a/spec/test_data/splits/less_than_or_equal_to_matcher/splits.json +++ b/spec/test_data/splits/less_than_or_equal_to_matcher/splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -45,5 +45,8 @@ } ] } - ] + ], + "s": -1, + "t": -1 +}, "rbs": {"d":[], "s":-1, "t": -1} } diff --git a/spec/test_data/splits/semver_matchers/semver_between.json b/spec/test_data/splits/semver_matchers/semver_between.json index 44edc2b6..9a0a3c7b 100644 --- a/spec/test_data/splits/semver_matchers/semver_between.json +++ b/spec/test_data/splits/semver_matchers/semver_between.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "semver_between", @@ -82,5 +82,6 @@ } ], "since": -1, - "till": 1675259356568 + "till": 1675259356568}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/semver_matchers/semver_equalto.json b/spec/test_data/splits/semver_matchers/semver_equalto.json index c3daa9ea..e7699e16 100644 --- a/spec/test_data/splits/semver_matchers/semver_equalto.json +++ b/spec/test_data/splits/semver_matchers/semver_equalto.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "semver_equalto", @@ -81,5 +81,6 @@ } ], "since": -1, - "till": 1675259356568 + "till": 1675259356568}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json b/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json index 40f0f036..1fde9c28 100644 --- a/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json +++ b/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "semver_greater_or_equalto", @@ -81,5 +81,6 @@ } ], "since": -1, - "till": 1675259356568 + "till": 1675259356568}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/semver_matchers/semver_inlist.json b/spec/test_data/splits/semver_matchers/semver_inlist.json index 9f1e6246..8feafc58 100644 --- a/spec/test_data/splits/semver_matchers/semver_inlist.json +++ b/spec/test_data/splits/semver_matchers/semver_inlist.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "semver_inlist", @@ -82,5 +82,6 @@ } ], "since": -1, - "till": 1675259356568 + "till": 1675259356568}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json b/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json index 9a46807f..6704a6c3 100644 --- a/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json +++ b/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "user", "name": "semver_less_or_equalto", @@ -81,5 +81,6 @@ } ], "since": -1, - "till": 1675259356568 + "till": 1675259356568}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/splits.json b/spec/test_data/splits/splits.json index 7bd101db..0c08e949 100644 --- a/spec/test_data/splits/splits.json +++ b/spec/test_data/splits/splits.json @@ -1,5 +1,5 @@ -{ - "splits":[ +{"ff": { + "d":[ { "trafficTypeName":"user", "name":"test_1_ruby", @@ -261,6 +261,37 @@ "sets":["set_1","set_2"] } ], - "since":-1, - "till":1473413807667 -} + "s":-1, + "t":1473413807667 + }, "rbs": {"t": 1457726098069, "s": -1, "d": [{ + "changeNumber": 123, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["mauro@split.io","gaston@split.io"], + "segments":[] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + }]}} \ No newline at end of file diff --git a/spec/test_data/splits/splits2.json b/spec/test_data/splits/splits2.json index d591990a..d688a19d 100644 --- a/spec/test_data/splits/splits2.json +++ b/spec/test_data/splits/splits2.json @@ -1,5 +1,5 @@ -{ - "splits":[ +{"ff":{ + "d":[ { "trafficTypeName":"user", "name":"test_1_ruby", @@ -259,6 +259,37 @@ "sets":["set_1"] } ], - "since":-1, - "till":1473413807667 -} + "s":-1, + "t":1473413807667 + }, "rbs": {"t": 1457726098069, "s": -1, "d": [{ + "changeNumber": 123, + "name": "sample_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded":{ + "keys":["mauro@split.io","gaston@split.io"], + "segments":[] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + }]}} \ No newline at end of file diff --git a/spec/test_data/splits/splits3.json b/spec/test_data/splits/splits3.json index c31f3510..baae0c98 100644 --- a/spec/test_data/splits/splits3.json +++ b/spec/test_data/splits/splits3.json @@ -1,5 +1,5 @@ -{ - "splits":[ +{"ff": { + "d":[ { "trafficTypeName":"user", "name":"test_1_ruby", @@ -260,6 +260,7 @@ ] } ], - "since":-1, - "till":1473863097220 + "s":-1, + "t":1473863097220 + }, "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/splits4.json b/spec/test_data/splits/splits4.json index 17f0ec88..f9d3f993 100644 --- a/spec/test_data/splits/splits4.json +++ b/spec/test_data/splits/splits4.json @@ -1,5 +1,6 @@ -{ - "splits": [{ +{"ff": { + "d":[ + { "trafficTypeName": "user", "name": "uber_feature", "trafficAllocation": 100, @@ -83,4 +84,6 @@ "label": "category in list [expert] and last_ride on or after 2017-07-27 01:18AM" }] } -]} +] +}, "rbs": {"d":[], "s":-1, "t":-1} +} diff --git a/spec/test_data/splits/splits_traffic_allocation.json b/spec/test_data/splits/splits_traffic_allocation.json index ce35c5e9..e1a58647 100644 --- a/spec/test_data/splits/splits_traffic_allocation.json +++ b/spec/test_data/splits/splits_traffic_allocation.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "trafficTypeName": "account", "name": "Traffic_Allocation_UI", @@ -355,4 +355,5 @@ ], "since": -1, "till": 1470855828956 +}, "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/splits_traffic_allocation_one_percent.json b/spec/test_data/splits/splits_traffic_allocation_one_percent.json index 5b523c21..d480a521 100644 --- a/spec/test_data/splits/splits_traffic_allocation_one_percent.json +++ b/spec/test_data/splits/splits_traffic_allocation_one_percent.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "name": "Traffic_Allocation_One_Percent", "algo": 1, @@ -37,5 +37,6 @@ } ] } - ] + ], "s":-1, "t":-1}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/spec/test_data/splits/whitelist_matcher_splits.json b/spec/test_data/splits/whitelist_matcher_splits.json index 1a517656..81259de9 100644 --- a/spec/test_data/splits/whitelist_matcher_splits.json +++ b/spec/test_data/splits/whitelist_matcher_splits.json @@ -1,5 +1,5 @@ -{ - "splits": [ +{ "ff":{ + "d": [ { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", "environment":"cf2d09f0-b3eb-11e5-855f-4eacec19f7bf", @@ -52,5 +52,6 @@ } ] } - ] + ], "s":-1, "t":-1}, + "rbs": {"d":[], "s":-1, "t":-1} } diff --git a/splitclient-rb.gemspec b/splitclient-rb.gemspec index a8adca6a..0a86d725 100644 --- a/splitclient-rb.gemspec +++ b/splitclient-rb.gemspec @@ -47,8 +47,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'simplecov', '~> 0.20' spec.add_development_dependency 'simplecov-json', '~> 0.2' spec.add_development_dependency 'timecop', '~> 0.9' - spec.add_development_dependency 'webmock', '~> 3.14' - spec.add_development_dependency 'webrick', '~> 1.7' + spec.add_development_dependency 'webmock', '~> 3.24' + spec.add_development_dependency 'webrick', '~> 1.8.2' spec.add_runtime_dependency 'bitarray', '~> 1.3' spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'