From e9129d2aa896da7eba1c2588ea3b91e635063b1c Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 20 Oct 2023 16:32:17 -0700 Subject: [PATCH 01/27] updated engine.api.splits class --- lib/splitclient-rb/engine/api/splits.rb | 7 ++-- spec/engine/api/splits_spec.rb | 47 +++++++++++++++++++++++-- spec/test_data/splits/splits.json | 6 ++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index 10d4544c..3f5c4d30 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -10,15 +10,16 @@ def initialize(api_key, config, telemetry_runtime_producer) @telemetry_runtime_producer = telemetry_runtime_producer end - def since(since, fetch_options = { cache_control_headers: false, till: nil }) + def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil }) start = Time.now - + params = { since: since } params[:till] = fetch_options[:till] unless fetch_options[:till].nil? + params[:sets] = fetch_options[:sets].join(",") unless fetch_options[:sets].nil? response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) if response.success? - result = splits_with_segment_names(response.body) + 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 diff --git a/spec/engine/api/splits_spec.rb b/spec/engine/api/splits_spec.rb index 73264a96..eb12be45 100644 --- a/spec/engine/api/splits_spec.rb +++ b/spec/engine/api/splits_spec.rb @@ -27,6 +27,49 @@ end end + context '#sets' do + it 'returns the splits - with sets param' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1') + .with(headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip', + 'Authorization' => 'Bearer', + 'Connection' => 'keep-alive', + 'Keep-Alive' => '30', + 'Splitsdkversion' => "#{config.language}-#{config.version}" + }) + .to_return(status: 200, body: splits) + + fetch_options = { cache_control_headers: false, till: nil, sets: ['set_1'] } + returned_splits = splits_api.since(-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 + end + + it 'returns the splits - with 2 sets param' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1,set_2') + .with(headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip', + 'Authorization' => 'Bearer', + 'Connection' => 'keep-alive', + 'Keep-Alive' => '30', + 'Splitsdkversion' => "#{config.language}-#{config.version}" + }) + .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) + 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 + end + + end + context '#since' do it 'returns the splits - checking headers when cache_control_headers is false' do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') @@ -59,7 +102,7 @@ }) .to_return(status: 200, body: splits) - fetch_options = { cache_control_headers: false, till: 123_123 } + fetch_options = { cache_control_headers: false, till: 123_123, sets: nil } returned_splits = splits_api.since(-1, fetch_options) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) @@ -80,7 +123,7 @@ }) .to_return(status: 200, body: splits) - fetch_options = { cache_control_headers: true, till: nil } + fetch_options = { cache_control_headers: true, till: nil, sets: nil } returned_splits = splits_api.since(-1, fetch_options) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) diff --git a/spec/test_data/splits/splits.json b/spec/test_data/splits/splits.json index a57dfe77..7bd101db 100644 --- a/spec/test_data/splits/splits.json +++ b/spec/test_data/splits/splits.json @@ -99,7 +99,8 @@ } ] } - ] + ], + "sets":["set_1"] }, { "trafficTypeName":"user", @@ -256,7 +257,8 @@ } ] } - ] + ], + "sets":["set_1","set_2"] } ], "since":-1, From fdc88cc70731226c353b489a6f426fd6a34670b2 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 20 Oct 2023 20:23:22 -0700 Subject: [PATCH 02/27] added exception for 409 status code --- lib/splitclient-rb/engine/api/splits.rb | 5 ++++- lib/splitclient-rb/exceptions.rb | 11 +++++++++++ spec/engine/api/splits_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index 3f5c4d30..a061fd97 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -17,8 +17,11 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets params[:till] = fetch_options[:till] unless fetch_options[:till].nil? params[:sets] = fetch_options[:sets].join(",") unless fetch_options[:sets].nil? response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) + if response.status == 409 + @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, 409 + 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}") diff --git a/lib/splitclient-rb/exceptions.rb b/lib/splitclient-rb/exceptions.rb index 493045a1..f8b9c62d 100644 --- a/lib/splitclient-rb/exceptions.rb +++ b/lib/splitclient-rb/exceptions.rb @@ -12,4 +12,15 @@ def initialize(event) @event = event end end + + class ApiException < SplitIoError + def initialize(msg, exception_code) + @@exception_code = exception_code + super(msg) + end + def exception_code + @@exception_code + end + end + end diff --git a/spec/engine/api/splits_spec.rb b/spec/engine/api/splits_spec.rb index eb12be45..9ae5ca6f 100644 --- a/spec/engine/api/splits_spec.rb +++ b/spec/engine/api/splits_spec.rb @@ -68,6 +68,29 @@ expect(log.string).to include returned_splits.to_s end + it 'raise api exception when status 409' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1,set_2') + .with(headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip', + 'Authorization' => 'Bearer', + 'Connection' => 'keep-alive', + 'Keep-Alive' => '30', + 'Splitsdkversion' => "#{config.language}-#{config.version}" + }) + .to_return(status: 409, body: splits) + + fetch_options = { cache_control_headers: false, till: nil, sets: ['set_1','set_2'] } + captured = 0 + begin + returned_splits = splits_api.since(-1, fetch_options) + rescue SplitIoClient::ApiException => e + captured = e.exception_code + end + expect(captured).to eq(409) + end + + end context '#since' do From b4b43dc935ce2c700bba11bc878f3e5a55b24013 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 23 Oct 2023 14:39:49 -0700 Subject: [PATCH 03/27] added flag_set_filter config param and validation --- lib/splitclient-rb/split_config.rb | 23 ++++++++++++----- lib/splitclient-rb/validators.rb | 37 +++++++++++++++++++++++++++ spec/splitclient/split_config_spec.rb | 34 +++++++++++++++++++++++- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/lib/splitclient-rb/split_config.rb b/lib/splitclient-rb/split_config.rb index ba42db6f..786c7cf5 100644 --- a/lib/splitclient-rb/split_config.rb +++ b/lib/splitclient-rb/split_config.rb @@ -122,6 +122,7 @@ def initialize(opts = {}) @on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds @on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries + @flag_sets_filter = SplitConfig.sanitize_flag_set_filter(opts[:flag_sets_filter], @split_validator) startup_log end @@ -287,7 +288,7 @@ def initialize(opts = {}) attr_accessor :sdk_start_time - attr_accessor :on_demand_fetch_retry_delay_seconds + attr_accessor :on_demand_fetch_retry_delay_seconds attr_accessor :on_demand_fetch_max_retries attr_accessor :unique_keys_refresh_rate @@ -296,6 +297,12 @@ def initialize(opts = {}) attr_accessor :counter_refresh_rate + # + # Flagsets filter + # + # @return [Array] + attr_accessor :flag_sets_filter + def self.default_counter_refresh_rate(adapter) return 300 if adapter == :redis # Send bulk impressions count - Refresh rate: 5 min. @@ -325,9 +332,9 @@ def init_impressions_mode(impressions_mode, adapter) end end - def self.init_impressions_refresh_rate(impressions_mode, refresh_rate, default_rate) + def self.init_impressions_refresh_rate(impressions_mode, refresh_rate, default_rate) return (refresh_rate.nil? || refresh_rate <= 0 ? default_rate : refresh_rate) if impressions_mode == :debug - + return refresh_rate.nil? || refresh_rate <= 0 ? SplitConfig.default_impressions_refresh_rate_optimized : [default_rate, refresh_rate].max end @@ -482,7 +489,7 @@ def self.default_events_queue_size end def self.default_telemetry_refresh_rate - 3600 + 3600 end def self.default_unique_keys_refresh_rate(adapter) @@ -513,6 +520,10 @@ def self.default_offline_refresh_rate 5 end + def self.sanitize_flag_set_filter(flag_sets, validator) + return validator.valid_flag_sets(:config, flag_sets) + end + # # The default logger object # @@ -646,7 +657,7 @@ def self.machine_hostname(ip_addresses_enabled, machine_name, adapter) return 'NA'.freeze end end - + return ''.freeze end @@ -656,7 +667,7 @@ def self.machine_hostname(ip_addresses_enabled, machine_name, adapter) # @return [string] def self.machine_ip(ip_addresses_enabled, ip, adapter) if ip_addresses_enabled - begin + begin return ip unless ip.nil? || ip.to_s.empty? loopback_ip = Socket.ip_address_list.find { |ip| ip.ipv4_loopback? } diff --git a/lib/splitclient-rb/validators.rb b/lib/splitclient-rb/validators.rb index 39dbdd05..ad8d3a53 100644 --- a/lib/splitclient-rb/validators.rb +++ b/lib/splitclient-rb/validators.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true +require 'set' module SplitIoClient class Validators + Flagset_regex = /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/ + def initialize(config) @config = config end @@ -38,6 +41,25 @@ def valid_matcher_arguments(args) true end + def valid_flag_sets(method, flag_sets) + if flag_sets.nil? || !flag_sets.is_a?(Array) + return nil + end + valid_flag_sets = SortedSet[] + flag_sets.compact.uniq.select do |flag_set| + if !flag_set.is_a?(String) + log_invalid_type(:flag_set, method) + elsif flag_set.is_a?(String) && flag_set.empty? + log_nil(:flag_set, method) + elsif !flag_set.empty? && string_match?(flag_set.strip, method) + valid_flag_sets.add(flag_set.strip.downcase) + else + @config.logger.warn("#{method}: you passed an invalid flag set, flag set name must be a non-empty String") + end + end + !valid_flag_sets.empty? ? valid_flag_sets : nil + end + private def string?(value) @@ -52,6 +74,21 @@ def number_or_string?(value) (value.is_a?(Numeric) && !value.to_f.nan?) || string?(value) end + def string_match?(value, method) + if Flagset_regex.match(value) == nil + log_invalid_match(value, method) + false + else + true + end + end + + def log_invalid_match(key, method) + @config.logger.error("#{method}: you passed #{key}, flag set must adhere to the regular expression #{Flagset_regex}. " + + "This means flag set must be alphanumeric, cannot be more than 50 characters long, and can only include a dash, underscore, " + + "period, or colon as separators of alphanumeric characters.") + end + def log_nil(key, method) @config.logger.error("#{method}: you passed a nil #{key}, #{key} must be a non-empty String or a Symbol") end diff --git a/spec/splitclient/split_config_spec.rb b/spec/splitclient/split_config_spec.rb index 598587e8..d1cdc252 100644 --- a/spec/splitclient/split_config_spec.rb +++ b/spec/splitclient/split_config_spec.rb @@ -12,7 +12,8 @@ impressions_refresh_rate: 65, impressions_queue_size: 20, logger: Logger.new('/dev/null'), - debug_enabled: true } + debug_enabled: true, + flag_sets_filter: ["set_1"] } end describe 'split config object values' do @@ -33,6 +34,7 @@ expect(configs.machine_ip).to eq SplitIoClient::SplitConfig.machine_ip(default_ip, nil, :redis) expect(configs.on_demand_fetch_retry_delay_seconds).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_retry_delay_seconds expect(configs.on_demand_fetch_max_retries).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_max_retries + expect(configs.flag_sets_filter).to eq nil end it 'stores and retrieves correctly the customized values' do @@ -47,6 +49,7 @@ expect(configs.impressions_refresh_rate).to eq custom_options[:impressions_refresh_rate] expect(configs.impressions_queue_size).to eq custom_options[:impressions_queue_size] expect(configs.debug_enabled).to eq custom_options[:debug_enabled] + expect(configs.flag_sets_filter).to eq Set["set_1"] end it 'has the current default values for timeouts and intervals, with impressions_mode in :optimized' do @@ -148,5 +151,34 @@ expect(configs.impressions_refresh_rate).to eq 40 end + + it 'test flag sets filter validations' do + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: 0) + expect(configs.flag_sets_filter).to eq nil + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: []) + expect(configs.flag_sets_filter).to eq nil + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['set2 ', ' set1', 'set3']) + expect(configs.flag_sets_filter).to eq Set['set1', 'set2', 'set3'] + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['1set', '_set2']) + expect(configs.flag_sets_filter).to eq Set['1set'] + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['Set1', 'SET2']) + expect(configs.flag_sets_filter).to eq Set['set1', 'set2'] + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['se\t1', 's/et2', 's*et3', 's!et4', 'se@t5', 'se#t5', 'se$t5', 'se^t5', 'se%t5', 'se&t5']) + expect(configs.flag_sets_filter).to eq nil + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['set4', 'set1', 'set3', 'set1']) + expect(configs.flag_sets_filter).to eq Set['set1', 'set3', 'set4'] + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: '1set') + expect(configs.flag_sets_filter).to eq nil + + configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['1set', 12]) + expect(configs.flag_sets_filter).to eq Set['1set'] + end end end From 70b343617a68b81285e441acbcc30892ac219f4e Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 24 Oct 2023 08:33:36 -0700 Subject: [PATCH 04/27] updated error code --- lib/splitclient-rb/engine/api/splits.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index a061fd97..4d01a1bb 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -17,9 +17,9 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets params[:till] = fetch_options[:till] unless fetch_options[:till].nil? params[:sets] = fetch_options[:sets].join(",") unless fetch_options[:sets].nil? response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) - if response.status == 409 + if response.status == 414 @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, 409 + raise ApiException.new response.body, 414 end if response.success? result = splits_with_segment_names(response.body) From bd9e6691aaf8b457e1982d2b09c52bb340f1cdda Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 24 Oct 2023 09:03:08 -0700 Subject: [PATCH 05/27] fixed regex --- lib/splitclient-rb/validators.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/splitclient-rb/validators.rb b/lib/splitclient-rb/validators.rb index ad8d3a53..47eb2125 100644 --- a/lib/splitclient-rb/validators.rb +++ b/lib/splitclient-rb/validators.rb @@ -4,7 +4,7 @@ module SplitIoClient class Validators - Flagset_regex = /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/ + Flagset_regex = /^[a-z0-9][_a-z0-9]{0,49}$/ def initialize(config) @config = config @@ -42,9 +42,8 @@ def valid_matcher_arguments(args) end def valid_flag_sets(method, flag_sets) - if flag_sets.nil? || !flag_sets.is_a?(Array) - return nil - end + return Set[] if flag_sets.nil? || !flag_sets.is_a?(Array) + valid_flag_sets = SortedSet[] flag_sets.compact.uniq.select do |flag_set| if !flag_set.is_a?(String) @@ -57,7 +56,7 @@ def valid_flag_sets(method, flag_sets) @config.logger.warn("#{method}: you passed an invalid flag set, flag set name must be a non-empty String") end end - !valid_flag_sets.empty? ? valid_flag_sets : nil + !valid_flag_sets.empty? ? valid_flag_sets : Set[] end private From b838d782e5a35704edce3d5fa7eb7ec978e33800 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 24 Oct 2023 15:08:43 -0700 Subject: [PATCH 06/27] updated telemetry classes --- .../telemetry/domain/constants.rb | 4 ++ .../telemetry/domain/structs.rb | 8 +-- .../telemetry/memory/memory_synchronizer.rb | 16 ++++- .../telemetry/redis/redis_init_producer.rb | 2 +- .../telemetry/redis/redis_synchronizer.rb | 4 +- .../telemetry/storages/memory.rb | 8 +++ spec/telemetry/synchronizer_spec.rb | 40 +++++++----- spec/telemetry/telemetry_evaluation_spec.rb | 64 +++++++++++++++++-- spec/telemetry/telemetry_init_spec.rb | 4 +- 9 files changed, 119 insertions(+), 31 deletions(-) diff --git a/lib/splitclient-rb/telemetry/domain/constants.rb b/lib/splitclient-rb/telemetry/domain/constants.rb index c3ad34eb..44113b80 100644 --- a/lib/splitclient-rb/telemetry/domain/constants.rb +++ b/lib/splitclient-rb/telemetry/domain/constants.rb @@ -35,6 +35,10 @@ class Constants TREATMENTS = 'treatments' TREATMENT_WITH_CONFIG = 'treatmentWithConfig' TREATMENTS_WITH_CONFIG = 'treatmentsWithConfig' + TREATMENTS_BY_FLAG_SET = 'treatmentsByFlagSet' + TREATMENTS_BY_FLAG_SETS = 'treatmentsByFlagSets' + TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'treatmentWithConfigByFlagSet' + TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'treatmentsWithConfigByFlagSets' TRACK = 'track' SPLITS = 'splits' diff --git a/lib/splitclient-rb/telemetry/domain/structs.rb b/lib/splitclient-rb/telemetry/domain/structs.rb index c2d8b9d9..ab4297c0 100644 --- a/lib/splitclient-rb/telemetry/domain/structs.rb +++ b/lib/splitclient-rb/telemetry/domain/structs.rb @@ -16,8 +16,8 @@ module Telemetry # om: operationMode, st: storage, af: activeFactories, rf: redundantActiveFactories, t: tags, se: streamingEnabled, # rr: refreshRate, uo: urlOverrides, iq: impressionsQueueSize, eq: eventsQueueSize, im: impressionsMode, # il: impressionListenerEnabled, hp: httpProxyDetected, tr: timeUntilSdkReady, bt: burTimeouts, - # nr: sdkNotReadyUsage, i: integrations - ConfigInit = Struct.new(:om, :st, :af, :rf, :t, :se, :rr, :uo, :iq, :eq, :im, :il, :hp, :tr, :bt, :nr, :i) + # nr: sdkNotReadyUsage, i: integrations, fsT: Total flagsets, fsI: Invalid flagsets + ConfigInit = Struct.new(:om, :st, :af, :rf, :t, :fsT, :fsI, :se, :rr, :uo, :iq, :eq, :im, :il, :hp, :tr, :bt, :nr, :i) # ls: lastSynchronization, ml: clientMethodLatencies, me: clientMethodExceptions, he: httpErros, hl: httpLatencies, # tr: tokenRefreshes, ar: authRejections, iq: impressionsQueued, ide: impressionsDeduped, idr: impressionsDropped, @@ -26,8 +26,8 @@ module Telemetry Usage = Struct.new(:ls, :ml, :me, :he, :hl, :tr, :ar, :iq, :ide, :idr, :spc, :sec, :skc, :sl, :eq, :ed, :se, :t, :ufs) # t: treatment, ts: treatments, tc: treatmentWithConfig, tcs: treatmentsWithConfig, tr: track - ClientMethodLatencies = Struct.new(:t, :ts, :tc, :tcs, :tr) - ClientMethodExceptions = Struct.new(:t, :ts, :tc, :tcs, :tr) + ClientMethodLatencies = Struct.new(:t, :ts, :tc, :tcs, :tf, :tfs, :tcf, :tcfs, :tr) + ClientMethodExceptions = Struct.new(:t, :ts, :tc, :tcs, :tf, :tfs, :tcf, :tcfs, :tr) # sp: splits UpdatesFromSSE = Struct.new(:sp) diff --git a/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb b/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb index 45df5c59..8129a34e 100644 --- a/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +++ b/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb @@ -42,7 +42,7 @@ def synchronize_stats @config.log_found_exception(__method__.to_s, e) end - def synchronize_config(active_factories = nil, redundant_active_factories = nil, time_until_ready = nil) + def synchronize_config(active_factories = nil, redundant_active_factories = nil, time_until_ready = nil, flag_sets = nil, flag_sets_invalid = nil) rates = Rates.new(@config.features_refresh_rate, @config.segments_refresh_rate, @config.impressions_refresh_rate, @@ -64,6 +64,8 @@ def synchronize_config(active_factories = nil, redundant_active_factories = nil, active_factories, redundant_active_factories, @telemetry_runtime_consumer.pop_tags, + flag_sets, + flag_sets_invalid, @config.streaming_enabled, rates, url_overrides, @@ -113,7 +115,9 @@ def fornat_init_config(init) bT: init.bt, nR: init.nr, t: init.t, - i: init.i + i: init.i, + fsT: init.fsT, + fsI: init.fsI } end @@ -125,6 +129,10 @@ def format_stats(usage) ts: usage.ml[Telemetry::Domain::Constants::TREATMENTS], tc: usage.ml[Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG], tcs: usage.ml[Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG], + tf: usage.ml[Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET], + tfs: usage.ml[Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS], + tcf: usage.ml[Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET], + tcfs: usage.ml[Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS], tr: usage.ml[Telemetry::Domain::Constants::TRACK] }, mE: { @@ -132,6 +140,10 @@ def format_stats(usage) ts: usage.me[Telemetry::Domain::Constants::TREATMENTS], tc: usage.me[Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG], tcs: usage.me[Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG], + tf: usage.me[Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET], + tfs: usage.me[Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS], + tcf: usage.me[Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET], + tcfs: usage.me[Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS], tr: usage.me[Telemetry::Domain::Constants::TRACK] }, hE: { diff --git a/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb b/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb index 22f26fc3..a6f5a676 100644 --- a/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +++ b/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb @@ -11,7 +11,7 @@ def initialize(config) def record_config(config_data) return if config_data.nil? - data = { t: { oM: config_data.om, st: config_data.st, aF: config_data.af, rF: config_data.rf, t: config_data.t } } + data = { t: { oM: config_data.om, st: config_data.st, aF: config_data.af, rF: config_data.rf, t: config_data.t, fsT: config_data.fsT, fsI: config_data.fsI } } field = "#{@config.language}-#{@config.version}/#{@config.machine_name}/#{@config.machine_ip}" @adapter.add_to_map(config_key, field, data.to_json) diff --git a/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb b/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb index 38f879f0..d70e0019 100644 --- a/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +++ b/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb @@ -13,11 +13,11 @@ def synchronize_stats # No-op end - def synchronize_config(active_factories = nil, redundant_active_factories = nil, tags = nil) + def synchronize_config(active_factories = nil, redundant_active_factories = nil, tags = nil, flag_sets = nil, flag_sets_invalid = nil) active_factories ||= SplitIoClient.split_factory_registry.active_factories redundant_active_factories ||= SplitIoClient.split_factory_registry.redundant_active_factories - init_config = ConfigInit.new(@config.mode, 'redis', active_factories, redundant_active_factories, tags) + init_config = ConfigInit.new(@config.mode, 'redis', active_factories, redundant_active_factories, tags, flag_sets, flag_sets_invalid) @telemetry_init_producer.record_config(init_config) rescue StandardError => e diff --git a/lib/splitclient-rb/telemetry/storages/memory.rb b/lib/splitclient-rb/telemetry/storages/memory.rb index bbe243db..b7991258 100644 --- a/lib/splitclient-rb/telemetry/storages/memory.rb +++ b/lib/splitclient-rb/telemetry/storages/memory.rb @@ -44,6 +44,10 @@ def init_latencies @latencies << { method: Domain::Constants::TREATMENTS, latencies: Concurrent::Array.new(array_size, 0) } @latencies << { method: Domain::Constants::TREATMENT_WITH_CONFIG, latencies: Concurrent::Array.new(array_size, 0) } @latencies << { method: Domain::Constants::TREATMENTS_WITH_CONFIG, latencies: Concurrent::Array.new(array_size, 0) } + @latencies << { method: Domain::Constants::TREATMENTS_BY_FLAG_SET, latencies: Concurrent::Array.new(array_size, 0) } + @latencies << { method: Domain::Constants::TREATMENTS_BY_FLAG_SETS, latencies: Concurrent::Array.new(array_size, 0) } + @latencies << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, latencies: Concurrent::Array.new(array_size, 0) } + @latencies << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, latencies: Concurrent::Array.new(array_size, 0) } @latencies << { method: Domain::Constants::TRACK, latencies: Concurrent::Array.new(array_size, 0) } end @@ -54,6 +58,10 @@ def init_exceptions @exceptions << { method: Domain::Constants::TREATMENTS, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TREATMENT_WITH_CONFIG, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TREATMENTS_WITH_CONFIG, exceptions: Concurrent::AtomicFixnum.new(0) } + @exceptions << { method: Domain::Constants::TREATMENTS_BY_FLAG_SET, exceptions: Concurrent::AtomicFixnum.new(0) } + @exceptions << { method: Domain::Constants::TREATMENTS_BY_FLAG_SETS, exceptions: Concurrent::AtomicFixnum.new(0) } + @exceptions << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, exceptions: Concurrent::AtomicFixnum.new(0) } + @exceptions << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TRACK, exceptions: Concurrent::AtomicFixnum.new(0) } end diff --git a/spec/telemetry/synchronizer_spec.rb b/spec/telemetry/synchronizer_spec.rb index 94b4e716..e40a2439 100644 --- a/spec/telemetry/synchronizer_spec.rb +++ b/spec/telemetry/synchronizer_spec.rb @@ -8,9 +8,9 @@ context 'Redis' do let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log), cache_adapter: :redis, mode: :consumer, redis_namespace: 'synch-test') } let(:adapter) { config.telemetry_adapter } - let(:init_producer) { SplitIoClient::Telemetry::InitProducer.new(config) } + let(:init_producer) { SplitIoClient::Telemetry::InitProducer.new(config) } let(:synchronizer) { SplitIoClient::Telemetry::Synchronizer.new(config, nil, init_producer, nil, nil) } - let(:config_key) { 'synch-test.SPLITIO.telemetry.init' } + let(:config_key) { 'synch-test.SPLITIO.telemetry.init' } it 'synchronize_config with data' do adapter.redis.del(config_key) @@ -42,11 +42,11 @@ let(:init_producer) { SplitIoClient::Telemetry::InitProducer.new(config) } let(:telemetry_api) { SplitIoClient::Api::TelemetryApi.new(config, api_key, runtime_producer) } let(:telemetry_consumers) { { init: init_consumer, runtime: runtime_consumer, evaluation: evaluation_consumer } } - let(:body_usage) { "{\"lS\":{\"sp\":111111222,\"se\":111111222,\"im\":111111222,\"ic\":111111222,\"ev\":111111222,\"te\":111111222,\"to\":111111222},\"mL\":{\"t\":[0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ts\":[0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tc\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tr\":[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"mE\":{\"t\":2,\"ts\":1,\"tc\":1,\"tcs\":0,\"tr\":1},\"hE\":{\"sp\":{},\"se\":{\"400\":1},\"im\":{},\"ic\":{},\"ev\":{\"500\":2,\"501\":1},\"te\":{},\"to\":{}},\"hL\":{\"sp\":[0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"se\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"im\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ic\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ev\":[0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"te\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"to\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"tR\":1,\"aR\":1,\"iQ\":3,\"iDe\":1,\"iDr\":2,\"spC\":3,\"seC\":3,\"skC\":7,\"sL\":444555,\"eQ\":4,\"eD\":1,\"sE\":[{\"e\":50,\"d\":222222333,\"t\":222222333},{\"e\":70,\"d\":0,\"t\":222222333},{\"e\":70,\"d\":1,\"t\":222222333}],\"t\":[\"tag-1\",\"tag-2\"],\"ufs\":{\"sp\":5}}" } - let(:empty_body_usage) { "{\"lS\":{\"sp\":0,\"se\":0,\"im\":0,\"ic\":0,\"ev\":0,\"te\":0,\"to\":0},\"mL\":{\"t\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ts\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tc\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tr\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"mE\":{\"t\":0,\"ts\":0,\"tc\":0,\"tcs\":0,\"tr\":0},\"hE\":{\"sp\":{},\"se\":{},\"im\":{},\"ic\":{},\"ev\":{},\"te\":{},\"to\":{}},\"hL\":{\"sp\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"se\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"im\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ic\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ev\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"te\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"to\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"tR\":0,\"aR\":0,\"iQ\":0,\"iDe\":0,\"iDr\":0,\"spC\":0,\"seC\":0,\"skC\":0,\"sL\":0,\"eQ\":0,\"eD\":0,\"sE\":[],\"t\":[],\"ufs\":{\"sp\":0}}" } - let(:body_custom_config) { "{\"oM\":0,\"sE\":true,\"st\":\"memory\",\"rR\":{\"sp\":100,\"se\":110,\"im\":120,\"ev\":130,\"te\":140},\"iQ\":5000,\"eQ\":500,\"iM\":0,\"uO\":{\"s\":true,\"e\":true,\"a\":true,\"st\":false,\"t\":false},\"iL\":false,\"hP\":false,\"aF\":1,\"rF\":1,\"tR\":100,\"bT\":2,\"nR\":1,\"t\":[],\"i\":null}" } - let(:body_default_config) { "{\"oM\":0,\"sE\":true,\"st\":\"memory\",\"rR\":{\"sp\":60,\"se\":60,\"im\":300,\"ev\":60,\"te\":3600},\"iQ\":5000,\"eQ\":500,\"iM\":0,\"uO\":{\"s\":false,\"e\":false,\"a\":false,\"st\":false,\"t\":false},\"iL\":false,\"hP\":false,\"aF\":1,\"rF\":1,\"tR\":500,\"bT\":0,\"nR\":0,\"t\":[],\"i\":null}" } - let(:body_proxy_config) { "{\"oM\":0,\"sE\":true,\"st\":\"memory\",\"rR\":{\"sp\":60,\"se\":60,\"im\":300,\"ev\":60,\"te\":3600},\"iQ\":5000,\"eQ\":500,\"iM\":0,\"uO\":{\"s\":false,\"e\":false,\"a\":false,\"st\":false,\"t\":false},\"iL\":false,\"hP\":true,\"aF\":1,\"rF\":1,\"tR\":500,\"bT\":0,\"nR\":0,\"t\":[],\"i\":null}" } + let(:body_usage) { "{\"lS\":{\"sp\":111111222,\"se\":111111222,\"im\":111111222,\"ic\":111111222,\"ev\":111111222,\"te\":111111222,\"to\":111111222},\"mL\":{\"t\":[0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ts\":[0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tc\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tf\":[0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tfs\":[0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcf\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcfs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tr\":[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"mE\":{\"t\":2,\"ts\":1,\"tc\":1,\"tcs\":0,\"tf\":2,\"tfs\":1,\"tcf\":1,\"tcfs\":0,\"tr\":1},\"hE\":{\"sp\":{},\"se\":{\"400\":1},\"im\":{},\"ic\":{},\"ev\":{\"500\":2,\"501\":1},\"te\":{},\"to\":{}},\"hL\":{\"sp\":[0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"se\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"im\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ic\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ev\":[0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"te\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"to\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"tR\":1,\"aR\":1,\"iQ\":3,\"iDe\":1,\"iDr\":2,\"spC\":3,\"seC\":3,\"skC\":7,\"sL\":444555,\"eQ\":4,\"eD\":1,\"sE\":[{\"e\":50,\"d\":222222333,\"t\":222222333},{\"e\":70,\"d\":0,\"t\":222222333},{\"e\":70,\"d\":1,\"t\":222222333}],\"t\":[\"tag-1\",\"tag-2\"],\"ufs\":{\"sp\":5}}" } + let(:empty_body_usage) { "{\"lS\":{\"sp\":0,\"se\":0,\"im\":0,\"ic\":0,\"ev\":0,\"te\":0,\"to\":0},\"mL\":{\"t\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ts\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tc\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tf\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tfs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcf\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tcfs\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tr\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"mE\":{\"t\":0,\"ts\":0,\"tc\":0,\"tcs\":0,\"tf\":0,\"tfs\":0,\"tcf\":0,\"tcfs\":0,\"tr\":0},\"hE\":{\"sp\":{},\"se\":{},\"im\":{},\"ic\":{},\"ev\":{},\"te\":{},\"to\":{}},\"hL\":{\"sp\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"se\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"im\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ic\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"ev\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"te\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"to\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},\"tR\":0,\"aR\":0,\"iQ\":0,\"iDe\":0,\"iDr\":0,\"spC\":0,\"seC\":0,\"skC\":0,\"sL\":0,\"eQ\":0,\"eD\":0,\"sE\":[],\"t\":[],\"ufs\":{\"sp\":0}}" } + let(:body_custom_config) { "{\"oM\":0,\"sE\":true,\"st\":\"memory\",\"rR\":{\"sp\":100,\"se\":110,\"im\":120,\"ev\":130,\"te\":140},\"iQ\":5000,\"eQ\":500,\"iM\":0,\"uO\":{\"s\":true,\"e\":true,\"a\":true,\"st\":false,\"t\":false},\"iL\":false,\"hP\":false,\"aF\":1,\"rF\":1,\"tR\":100,\"bT\":2,\"nR\":1,\"t\":[],\"i\":null,\"fsT\":1,\"fsI\":0}" } + let(:body_default_config) { "{\"oM\":0,\"sE\":true,\"st\":\"memory\",\"rR\":{\"sp\":60,\"se\":60,\"im\":300,\"ev\":60,\"te\":3600},\"iQ\":5000,\"eQ\":500,\"iM\":0,\"uO\":{\"s\":false,\"e\":false,\"a\":false,\"st\":false,\"t\":false},\"iL\":false,\"hP\":false,\"aF\":1,\"rF\":1,\"tR\":500,\"bT\":0,\"nR\":0,\"t\":[],\"i\":null,\"fsT\":1,\"fsI\":0}" } + let(:body_proxy_config) { "{\"oM\":0,\"sE\":true,\"st\":\"memory\",\"rR\":{\"sp\":60,\"se\":60,\"im\":300,\"ev\":60,\"te\":3600},\"iQ\":5000,\"eQ\":500,\"iM\":0,\"uO\":{\"s\":false,\"e\":false,\"a\":false,\"st\":false,\"t\":false},\"iL\":false,\"hP\":true,\"aF\":1,\"rF\":1,\"tR\":500,\"bT\":0,\"nR\":0,\"t\":[],\"i\":null,\"fsT\":1,\"fsI\":0}" } context 'synchronize_stats' do before do @@ -111,12 +111,22 @@ evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT, 1) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS, 2) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS, 3) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 1) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 3) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 2) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 1) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, 2) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, 3) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TRACK, 3) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TRACK) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET) synchronizer.synchronize_stats @@ -136,10 +146,10 @@ before do stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .to_return(status: 200, body: 'ok') - + SplitIoClient.load_factory_registry end - + it 'with custom data' do config.features_refresh_rate = 100 config.segments_refresh_rate = 110 @@ -160,7 +170,7 @@ { splits: splits_repository, segments: segments_repository }, telemetry_api) - synchronizer.synchronize_config(1, 1, 100) + synchronizer.synchronize_config(1, 1, 100, 1, 0) expect(a_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .with(body: body_custom_config)).to have_been_made @@ -172,14 +182,14 @@ init_producer, { splits: splits_repository, segments: segments_repository }, telemetry_api) - - synchronizer.synchronize_config(1, 1, 500) - + + synchronizer.synchronize_config(1, 1, 500, 1, 0) + expect(a_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .with(body: body_default_config)).to have_been_made end - it 'with proxy' do + it 'with proxy' do ENV['HTTPS_PROXY'] = 'https://proxy.test.io/api/v1/metrics/config' synchronizer = SplitIoClient::Telemetry::Synchronizer.new(config, @@ -188,7 +198,7 @@ { splits: splits_repository, segments: segments_repository }, telemetry_api) - synchronizer.synchronize_config(1, 1, 500) + synchronizer.synchronize_config(1, 1, 500, 1, 0) expect(a_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .with(body: body_proxy_config)).to have_been_made diff --git a/spec/telemetry/telemetry_evaluation_spec.rb b/spec/telemetry/telemetry_evaluation_spec.rb index 6fc52e5c..dbd96f35 100644 --- a/spec/telemetry/telemetry_evaluation_spec.rb +++ b/spec/telemetry/telemetry_evaluation_spec.rb @@ -12,64 +12,95 @@ it 'record and pop latencies' do latencies = evaluation_consumer.pop_latencies - expect(latencies.length).to eq(5) + expect(latencies.length).to eq(9) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TRACK].length).to eq(23) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT, 1) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT, 2) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS, 3) evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, 4) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 3) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, 2) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, 1) + evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, 5) latencies = evaluation_consumer.pop_latencies - expect(latencies.length).to eq(5) + expect(latencies.length).to eq(9) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT][1]).to eq(1) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT][2]).to eq(1) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS][3]).to eq(1) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG][4]).to eq(1) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET][3]).to eq(1) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS][2]).to eq(1) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET][1]).to eq(1) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS][5]).to eq(1) latencies = evaluation_consumer.pop_latencies - expect(latencies.length).to eq(5) + expect(latencies.length).to eq(9) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET].length).to eq(23) + expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS].length).to eq(23) expect(latencies[SplitIoClient::Telemetry::Domain::Constants::TRACK].length).to eq(23) end it 'record and pop exceptions' do exceptions = evaluation_consumer.pop_exceptions - expect(exceptions.length).to eq(5) + expect(exceptions.length).to eq(9) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENT]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TRACK]).to eq(0) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT) evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS) + evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET) exceptions = evaluation_consumer.pop_exceptions - expect(exceptions.length).to eq(5) + expect(exceptions.length).to eq(9) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENT]).to eq(2) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS]).to eq(1) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET]).to eq(1) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS]).to eq(1) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET]).to eq(1) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TRACK]).to eq(0) exceptions = evaluation_consumer.pop_exceptions - expect(exceptions.length).to eq(5) + expect(exceptions.length).to eq(9) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENT]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET]).to eq(0) + expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS]).to eq(0) expect(exceptions[SplitIoClient::Telemetry::Domain::Constants::TRACK]).to eq(0) end end @@ -92,11 +123,18 @@ expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS, 4444)).to eq(1) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, 222)).to eq(1) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, 2)).to eq(1) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 2123)).to eq(1) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, 25555)).to eq(1) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, 24444)).to eq(1) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, 22)).to eq(1) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TRACK, 3)).to eq(1) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT, 123)).to eq(2) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT, 5555)).to eq(2) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS, 4444)).to eq(2) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, 2123)).to eq(2) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, 25555)).to eq(2) + expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, 24444)).to eq(2) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, 222)).to eq(2) expect(evaluation_producer.record_latency(SplitIoClient::Telemetry::Domain::Constants::TREATMENT, 123)).to eq(3) @@ -108,6 +146,10 @@ expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatments/4444").to_i).to eq(3) expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatmentWithConfig/222").to_i).to eq(3) expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatmentsWithConfig/2").to_i).to eq(1) + expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatmentsByFlagSet/2123").to_i).to eq(2) + expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatmentsByFlagSets/25555").to_i).to eq(2) + expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatmentWithConfigByFlagSet/24444").to_i).to eq(2) + expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/treatmentsWithConfigByFlagSets/22").to_i).to eq(1) expect(adapter.redis.hget(latency_key, "#{sdk_version}/#{name}/#{ip}/track/3").to_i).to eq(1) end @@ -122,12 +164,22 @@ expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT)).to eq(3) expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG)).to eq(1) expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG)).to eq(1) + expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET)).to eq(1) + expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET)).to eq(2) + expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS)).to eq(1) + expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET)).to eq(1) + expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)).to eq(1) + expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)).to eq(2) expect(evaluation_producer.record_exception(SplitIoClient::Telemetry::Domain::Constants::TRACK)).to eq(1) expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatment").to_i).to eq(3) expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatments").to_i).to eq(3) expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatmentWithConfig").to_i).to eq(1) expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatmentsWithConfig").to_i).to eq(1) + expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatmentsByFlagSet").to_i).to eq(2) + expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatmentsByFlagSets").to_i).to eq(1) + expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatmentWithConfigByFlagSet").to_i).to eq(1) + expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/treatmentsWithConfigByFlagSets").to_i).to eq(2) expect(adapter.redis.hget(exception_key, "#{sdk_version}/#{name}/#{ip}/track").to_i).to eq(1) end end diff --git a/spec/telemetry/telemetry_init_spec.rb b/spec/telemetry/telemetry_init_spec.rb index 95181439..333d7df3 100644 --- a/spec/telemetry/telemetry_init_spec.rb +++ b/spec/telemetry/telemetry_init_spec.rb @@ -61,7 +61,7 @@ it 'record config_init' do adapter.redis.del(telemetry_config_key) - config_init = SplitIoClient::Telemetry::ConfigInit.new('CONSUMER', 'REDIS', 1, 0, %w[t1 t2]) + config_init = SplitIoClient::Telemetry::ConfigInit.new('CONSUMER', 'REDIS', 1, 0, %w[t1 t2], 1, 0) init_producer.record_config(config_init) @@ -73,6 +73,8 @@ expect(result[:t][:aF]).to eq(1) expect(result[:t][:rF]).to eq(0) expect(result[:t][:t]).to eq(%w[t1 t2]) + expect(result[:t][:fsT]).to eq(1) + expect(result[:t][:fsI]).to eq(0) adapter.redis.del(telemetry_config_key) end From 06db54c6067ea0ec8fceefe7a129610609749ad6 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 3 Nov 2023 08:48:37 -0700 Subject: [PATCH 07/27] update client class and fixed tests --- .../cache/fetchers/split_fetcher.rb | 31 +---- .../repositories/flag_sets_repository.rb | 38 +++++ .../cache/repositories/splits_repository.rb | 130 +++++++++++++----- .../cache/stores/localhost_split_store.rb | 2 +- lib/splitclient-rb/clients/split_client.rb | 63 ++++++++- .../sse/workers/splits_worker.rb | 8 +- lib/splitclient-rb/validators.rb | 2 +- .../repositories/splits_repository_spec.rb | 33 +++-- spec/engine_spec.rb | 4 +- spec/integrations/dedupe_impression_spec.rb | 28 ++-- spec/integrations/redis_client_spec.rb | 2 +- spec/telemetry/synchronizer_spec.rb | 6 +- spec/test_data/integrations/splits.json | 12 +- 13 files changed, 242 insertions(+), 117 deletions(-) create mode 100644 lib/splitclient-rb/cache/repositories/flag_sets_repository.rb diff --git a/lib/splitclient-rb/cache/fetchers/split_fetcher.rb b/lib/splitclient-rb/cache/fetchers/split_fetcher.rb index 1ed99692..d39b1aea 100644 --- a/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +++ b/lib/splitclient-rb/cache/fetchers/split_fetcher.rb @@ -17,7 +17,7 @@ def call fetch_splits return end - + splits_thread end @@ -25,13 +25,8 @@ def fetch_splits(fetch_options = { cache_control_headers: false, till: nil }) @semaphore.synchronize do data = splits_since(@splits_repository.get_change_number, fetch_options) - data[:splits] && data[:splits].each do |split| - add_split_unless_archived(split) - end - + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:splits], data[:till], @config) @splits_repository.set_segment_names(data[:segment_names]) - @splits_repository.set_change_number(data[:till]) - @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 } @@ -64,28 +59,6 @@ def splits_since(since, fetch_options = { cache_control_headers: false, till: ni splits_api.since(since, fetch_options) end - def add_split_unless_archived(split) - if Engine::Models::Split.archived?(split) - @config.logger.debug("Seeing archived feature flag #{split[:name]}") if @config.debug_enabled - - remove_archived_split(split) - else - store_split(split) - end - end - - def remove_archived_split(split) - @config.logger.debug("removing feature flag from store(#{split})") if @config.debug_enabled - - @splits_repository.remove_split(split) - end - - def store_split(split) - @config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled - - @splits_repository.add_split(split) - end - def splits_api @splits_api ||= SplitIoClient::Api::Splits.new(@api_key, @config, @telemetry_runtime_producer) end diff --git a/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb b/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb new file mode 100644 index 00000000..4baf48d2 --- /dev/null +++ b/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb @@ -0,0 +1,38 @@ +require 'concurrent' + +module SplitIoClient + module Cache + module Repositories + class FlagSets + def initialize(flag_sets = []) + @sets_feature_flag_map = {} + flag_sets.each{ |flag_set| @sets_feature_flag_map[flag_set] = Set[] } + end + + def flag_set_exist?(flag_set) + @sets_feature_flag_map.key?(flag_set) + end + + def get_flag_set(flag_set) + @sets_feature_flag_map[flag_set] + end + + def add_flag_set(flag_set) + @sets_feature_flag_map[flag_set] = Set[] if !flag_set_exist?(flag_set) + end + + def remove_flag_set(flag_set) + @sets_feature_flag_map.delete(flag_set) if flag_set_exist?(flag_set) + end + + def add_feature_flag_to_flag_set(flag_set, feature_flag) + @sets_feature_flag_map[flag_set].add(feature_flag) if flag_set_exist?(flag_set) + end + + def remove_feature_flag_from_flag_set(flag_set, feature_flag) + @sets_feature_flag_map[flag_set].delete(feature_flag) if flag_set_exist?(flag_set) + end + end + end + end +end diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 383704b1..378174bc 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -6,7 +6,7 @@ module Repositories class SplitsRepository < Repository attr_reader :adapter - def initialize(config) + def initialize(config, flag_sets = []) super(config) @tt_cache = {} @adapter = case @config.cache_adapter.class.to_s @@ -15,48 +15,18 @@ def initialize(config) else @config.cache_adapter end + @flag_sets = SplitIoClient::Cache::Repositories::FlagSets.new(flag_sets) + @flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(flag_sets) unless @config.mode.equal?(:consumer) @adapter.set_string(namespace_key('.splits.till'), '-1') @adapter.initialize_map(namespace_key('.segments.registered')) end end - def add_split(split) - return unless split[:name] - existing_split = get_split(split[:name]) - - if(!existing_split) - increase_tt_name_count(split[:trafficTypeName]) - elsif(existing_split[:trafficTypeName] != split[:trafficTypeName]) - increase_tt_name_count(split[:trafficTypeName]) - decrease_tt_name_count(existing_split[:trafficTypeName]) - end - - @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json) - end - - def remove_split(split) - tt_name = split[:trafficTypeName] - - decrease_tt_name_count(split[:trafficTypeName]) - - @adapter.delete(namespace_key(".split.#{split[:name]}")) - end - - def get_splits(names, symbolize_names = true) - splits = {} - split_names = names.map { |name| namespace_key(".split.#{name}") } - splits.merge!( - @adapter - .multiple_strings(split_names) - .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h - ) - - splits.map do |name, data| - parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil - split_name = symbolize_names ? name.to_sym : name - [split_name, parsed_data] - end.to_h + def update(to_add, to_delete, new_change_number) + to_add.each{ |feature_flag| add_feature_flag(feature_flag) } + to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) } + set_change_number(new_change_number) end def get_split(name) @@ -65,7 +35,7 @@ def get_split(name) JSON.parse(split, symbolize_names: true) if split end - def splits + def splits(split_names) get_splits(split_names, false) end @@ -144,8 +114,92 @@ def splits_count split_names.length end + def get_feature_flags_by_sets(flag_sets) + sets_to_fetch = Array.new + flag_sets.each do |flag_set| + unless @flag_sets.flag_set_exist?(flag_set) + @config.logger.warn("Flag set #{flag_set} is not part of the configured flag set list, ignoring it.") + next + end + sets_to_fetch.push(flag_set) + end + to_return = Array.new + sets_to_fetch.each { |flag_set| to_return.concat(@flag_sets.get_flag_set(flag_set).to_a)} + to_return.uniq + end + + def is_flag_set_exist(flag_set) + @flag_sets.flag_set_exist?(flag_set) + end + + def flag_set_filter + @flag_set_filter + end + private + def add_feature_flag(split) + return unless split[:name] + existing_split = get_split(split[:name]) + + if(!existing_split) + increase_tt_name_count(split[:trafficTypeName]) + elsif(existing_split[:trafficTypeName] != split[:trafficTypeName]) + increase_tt_name_count(split[:trafficTypeName]) + decrease_tt_name_count(existing_split[:trafficTypeName]) + remove_from_flag_sets(existing_split) + end + + if !split[:sets].nil? + for flag_set in split[:sets] + if !@flag_sets.flag_set_exist?(flag_set) + if @flag_set_filter.should_filter? + next + end + @flag_sets.add_flag_set(flag_set) + end + @flag_sets.add_feature_flag_to_flag_set(flag_set, split[:name]) + end + end + + @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json) + end + + def remove_feature_flag(split) + tt_name = split[:trafficTypeName] + + decrease_tt_name_count(split[:trafficTypeName]) + remove_from_flag_sets(split) + @adapter.delete(namespace_key(".split.#{split[:name]}")) + end + + def get_splits(names, symbolize_names = true) + splits = {} + split_names = names.map { |name| namespace_key(".split.#{name}") } + splits.merge!( + @adapter + .multiple_strings(split_names) + .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h + ) + + splits.map do |name, data| + parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil + split_name = symbolize_names ? name.to_sym : name + [split_name, parsed_data] + end.to_h + end + + def remove_from_flag_sets(feature_flag) + if !feature_flag[:sets].nil? + for flag_set in feature_flag[:sets] + @flag_sets.remove_feature_flag_from_flag_set(flag_set, feature_flag[:name]) + if is_flag_set_exist(flag_set) && @flag_sets.get_flag_set(flag_set).length == 0 && !@flag_set_filter.should_filter? + @flag_sets.remove_flag_set(flag_set) + end + end + end + end + def increase_tt_name_count(tt_name) return unless tt_name diff --git a/lib/splitclient-rb/cache/stores/localhost_split_store.rb b/lib/splitclient-rb/cache/stores/localhost_split_store.rb index 222128ce..6dbb11a7 100644 --- a/lib/splitclient-rb/cache/stores/localhost_split_store.rb +++ b/lib/splitclient-rb/cache/stores/localhost_split_store.rb @@ -55,7 +55,7 @@ def store_splits def store_split(split) @config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled - @splits_repository.add_split(split) + @splits_repository.update([split], [], -1) end def load_features diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index bb045235..0f6d7953 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -5,6 +5,10 @@ module SplitIoClient GET_TREATMENTS = 'get_treatments' GET_TREATMENT_WITH_CONFIG = 'get_treatment_with_config' GET_TREATMENTS_WITH_CONFIG = 'get_treatments_with_config' + GET_TREATMENTS_BY_FLAG_SET = 'get_treatments_by_flag_set' + GET_TREATMENTS_BY_FLAG_SETS = 'get_treatments_by_flag_sets' + GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'get_treatments_with_config_by_flag_set' + GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'get_treatments_with_config_by_flag_sets' TRACK = 'track' class SplitClient @@ -25,6 +29,7 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage @config = config @impressions_manager = impressions_manager @telemetry_evaluation_producer = telemetry_evaluation_producer + @split_validator = SplitIoClient::Validators.new(self) end def get_treatment( @@ -65,6 +70,38 @@ def get_treatments_with_config(key, split_names, attributes = {}) treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG) end + def get_treatments_by_flag_set(key, flag_set, attributes = {}) + valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_by_flag_set, [flag_set]) + split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) + treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET) + return treatments if treatments.nil? + keys = treatments.keys + treats = treatments.map { |_,t| t[:treatment] } + Hash[keys.zip(treats)] + end + + def get_treatments_by_flag_sets(key, flag_sets, attributes = {}) + valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_by_flag_sets, flag_sets) + split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) + treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS) + return treatments if treatments.nil? + keys = treatments.keys + treats = treatments.map { |_,t| t[:treatment] } + Hash[keys.zip(treats)] + end + + def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}) + valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_with_config_by_flag_set, [flag_set]) + split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) + treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET) + end + + def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}) + valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_with_config_by_flag_sets, flag_sets) + split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) + treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) + end + def destroy @config.logger.info('Split client shutdown started...') if @config.debug_enabled @@ -206,13 +243,13 @@ def treatments(key, split_names, attributes = {}, calling_method = 'get_treatmen bucketing_key, matching_key = keys_from_key(key) bucketing_key = bucketing_key ? bucketing_key.to_s : nil - matching_key = matching_key ? matching_key.to_s : nil + matching_key = matching_key ? matching_key.to_s : nil evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config, true) start = Time.now impressions = [] treatments_labels_change_numbers = - @splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo| + @splits_repository.splits(sanitized_split_names).each_with_object({}) do |(name, data), memo| memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator, calling_method, impressions)) end @@ -288,7 +325,7 @@ def treatment( end record_latency(calling_method, start) unless multiple - + impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, treatment_data, { attributes: attributes, time: nil }) impressions << impression unless impression.nil? rescue StandardError => e @@ -320,7 +357,7 @@ def parsed_attributes(attributes) def record_latency(method, start) bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0) - + case method when GET_TREATMENT @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT, bucket) @@ -330,9 +367,17 @@ def record_latency(method, start) @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, bucket) when GET_TREATMENTS_WITH_CONFIG @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, bucket) + when GET_TREATMENTS_BY_FLAG_SET + @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, bucket) + when GET_TREATMENTS_BY_FLAG_SETS + @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, bucket) + when GET_TREATMENT_WITH_CONFIG + @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, bucket) + when GET_TREATMENTS_WITH_CONFIG + @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, bucket) when TRACK @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TRACK, bucket) - end + end end def record_exception(method) @@ -345,6 +390,14 @@ def record_exception(method) @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG) when GET_TREATMENTS_WITH_CONFIG @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG) + when GET_TREATMENTS_BY_FLAG_SET + @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET) + when GET_TREATMENTS_BY_FLAG_SETS + @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS) + when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET + @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET) + when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS + @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) when TRACK @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TRACK) end diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index cb8b5d67..f30e30ad 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -66,15 +66,17 @@ def update_feature_flag(notification) return false unless !notification.data['d'].nil? && @feature_flags_repository.get_change_number == notification.data['pcn'] new_split = return_split_from_json(notification) + to_add = [] + to_delete = [] if Engine::Models::Split.archived?(new_split) - @feature_flags_repository.remove_split(new_split) + to_delete.push(new_split) else - @feature_flags_repository.add_split(new_split) + to_add.push(new_split) fetch_segments_if_not_exists(new_split) end - @feature_flags_repository.set_change_number(notification.data['changeNumber']) + @feature_flags_repository.update(to_add, to_delete, notification.data['changeNumber']) @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS) true diff --git a/lib/splitclient-rb/validators.rb b/lib/splitclient-rb/validators.rb index 47eb2125..75441840 100644 --- a/lib/splitclient-rb/validators.rb +++ b/lib/splitclient-rb/validators.rb @@ -56,7 +56,7 @@ def valid_flag_sets(method, flag_sets) @config.logger.warn("#{method}: you passed an invalid flag set, flag set name must be a non-empty String") end end - !valid_flag_sets.empty? ? valid_flag_sets : Set[] + !valid_flag_sets.empty? ? valid_flag_sets.to_a : [] end private diff --git a/spec/cache/repositories/splits_repository_spec.rb b/spec/cache/repositories/splits_repository_spec.rb index 75d4c84f..cebd3f97 100644 --- a/spec/cache/repositories/splits_repository_spec.rb +++ b/spec/cache/repositories/splits_repository_spec.rb @@ -15,9 +15,9 @@ before do # in memory setup - repository.add_split(name: 'foo', trafficTypeName: 'tt_name_1') - repository.add_split(name: 'bar', trafficTypeName: 'tt_name_2') - repository.add_split(name: 'baz', trafficTypeName: 'tt_name_1') + repository.update([{name: 'foo', trafficTypeName: 'tt_name_1'}, + {name: 'bar', trafficTypeName: 'tt_name_2'}, + {name: 'baz', trafficTypeName: 'tt_name_1'}], [], -1) # redis setup repository.instance_variable_get(:@adapter).set_string( @@ -29,13 +29,13 @@ end after do - repository.remove_split(name: 'foo', trafficTypeName: 'tt_name_1') - repository.remove_split(name: 'bar', trafficTypeName: 'tt_name_2') - repository.remove_split(name: 'bar', trafficTypeName: 'tt_name_2') - repository.remove_split(name: 'qux', trafficTypeName: 'tt_name_3') - repository.remove_split(name: 'quux', trafficTypeName: 'tt_name_4') - repository.remove_split(name: 'corge', trafficTypeName: 'tt_name_5') - repository.remove_split(name: 'corge', trafficTypeName: 'tt_name_6') + repository.update([], [{name: 'foo', trafficTypeName: 'tt_name_1'}, + {name: 'bar', trafficTypeName: 'tt_name_2'}, + {name: 'bar', trafficTypeName: 'tt_name_2'}, + {name: 'qux', trafficTypeName: 'tt_name_3'}, + {name: 'quux', trafficTypeName: 'tt_name_4'}, + {name: 'corge', trafficTypeName: 'tt_name_5'}, + {name: 'corge', trafficTypeName: 'tt_name_6'}], -1) end it 'returns splits names' do @@ -52,8 +52,8 @@ split = { name: 'qux', trafficTypeName: 'tt_name_3' } - repository.add_split(split) - repository.remove_split(split) + repository.update([split], [], -1) + repository.update([], [split], -1) expect(repository.traffic_type_exists('tt_name_3')).to be false end @@ -61,9 +61,8 @@ it 'does not increment traffic type count when adding same split twice' do split = { name: 'quux', trafficTypeName: 'tt_name_4' } - repository.add_split(split) - repository.add_split(split) - repository.remove_split(split) + repository.update([split, split], [], -1) + repository.update([], [split], -1) expect(repository.traffic_type_exists('tt_name_4')).to be false end @@ -71,7 +70,7 @@ it 'updates traffic type count accordingly when split changes traffic type' do split = { name: 'corge', trafficTypeName: 'tt_name_5' } - repository.add_split(split) + repository.update([split], [], -1) repository.instance_variable_get(:@adapter).set_string( repository.send(:namespace_key, '.trafficType.tt_name_5'), '1' ) @@ -80,7 +79,7 @@ split = { name: 'corge', trafficTypeName: 'tt_name_6' } - repository.add_split(split) + repository.update([split], [], -1) # mimicing synchronizer internals repository.instance_variable_get(:@adapter).set_string( diff --git a/spec/engine_spec.rb b/spec/engine_spec.rb index 63057aae..3695921f 100644 --- a/spec/engine_spec.rb +++ b/spec/engine_spec.rb @@ -1054,9 +1054,7 @@ def add_splits_to_repository(splits_json) splits_repository = subject.instance_variable_get(:@splits_repository) - splits.each do |split| - splits_repository.add_split(split) - end + splits_repository.update(splits, [], -1) end def add_segments_to_repository(segments_json) diff --git a/spec/integrations/dedupe_impression_spec.rb b/spec/integrations/dedupe_impression_spec.rb index 312b7c6d..94d8bb3d 100644 --- a/spec/integrations/dedupe_impression_spec.rb +++ b/spec/integrations/dedupe_impression_spec.rb @@ -31,19 +31,24 @@ end context 'checking logic impressions' do - it 'get_treament should post 5 impressions - debug mode' 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?since=1506703262916').to_return(status: 200, body: '') + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=-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 - - debug_client.block_until_ready + debug_client.block_until_ready(2) sleep 1 expect(debug_client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' - expect(debug_client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' - expect(debug_client.get_treatment('admin', 'FACUNDO_TEST')).to eq 'off' + treatments = {"FACUNDO_TEST"=>"on"} + expect(debug_client.get_treatments_by_flag_set('nico_test', 'set_3')).to eq treatments + treatments = {"FACUNDO_TEST"=>"off"} + expect(debug_client.get_treatments_by_flag_sets('admin', ['set_3'])).to eq treatments + treatments = {"FACUNDO_TEST"=>{:treatment=>"off", :config=>nil}} + expect(debug_client.get_treatments_with_config_by_flag_set('admin', 'set_3')).to eq treatments + 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' @@ -51,18 +56,17 @@ sleep 0.5 - expect(impressions.size).to eq 5 + expect(impressions.size).to eq 7 end - it 'get_treaments should post 11 impressions - debug mode' do + it 'get_treaments should post 8 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?since=1506703262916').to_return(status: 200, body: '') factory = SplitIoClient::SplitFactory.new('test_api_key_debug-2', streaming_enabled: false, impressions_mode: :debug) debug_client = factory.client - debug_client.block_until_ready + debug_client.block_until_ready(2) - debug_client.get_treatments('nico_test', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) debug_client.get_treatments('admin', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) debug_client.get_treatments('maldo', %w[FACUNDO_TEST Test_Save_1]) debug_client.get_treatments('nico_test', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) @@ -71,7 +75,7 @@ sleep 0.5 - expect(impressions.size).to eq 11 + expect(impressions.size).to eq 8 end it 'get_treament should post 3 impressions - optimized mode' do @@ -81,7 +85,7 @@ factory = SplitIoClient::SplitFactory.new('test_api_key-1', streaming_enabled: false, impressions_mode: :optimized, impressions_refresh_rate: 60) client = factory.client - client.block_until_ready + client.block_until_ready(2) expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' @@ -113,7 +117,7 @@ factory = SplitIoClient::SplitFactory.new('test_api_key-2', streaming_enabled: false, impressions_mode: :optimized) client = factory.client - client.block_until_ready + client.block_until_ready(2) sleep 1 client.get_treatments('nico_test', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) diff --git a/spec/integrations/redis_client_spec.rb b/spec/integrations/redis_client_spec.rb index 8656f3c5..4af00aee 100644 --- a/spec/integrations/redis_client_spec.rb +++ b/spec/integrations/redis_client_spec.rb @@ -634,7 +634,7 @@ def load_splits_redis(splits_json, cli) splits_repository = cli.instance_variable_get(:@splits_repository) splits.each do |split| - splits_repository.add_split(split) + splits_repository.update([split], [], -1) end end diff --git a/spec/telemetry/synchronizer_spec.rb b/spec/telemetry/synchronizer_spec.rb index e40a2439..a87c1a38 100644 --- a/spec/telemetry/synchronizer_spec.rb +++ b/spec/telemetry/synchronizer_spec.rb @@ -63,9 +63,9 @@ end it 'with data' do - splits_repository.add_split(name: 'foo', trafficTypeName: 'tt_name_1') - splits_repository.add_split(name: 'bar', trafficTypeName: 'tt_name_2') - splits_repository.add_split(name: 'baz', trafficTypeName: 'tt_name_1') + splits_repository.update([{name: 'foo', trafficTypeName: 'tt_name_1'}, + {name: 'bar', trafficTypeName: 'tt_name_2'}, + {name: 'baz', trafficTypeName: 'tt_name_1'}], [], -1) segments_repository.add_to_segment(name: 'foo-1', added: [1, 2, 3], removed: []) segments_repository.add_to_segment(name: 'foo-2', added: [1, 2, 3, 4], removed: []) diff --git a/spec/test_data/integrations/splits.json b/spec/test_data/integrations/splits.json index dedc179a..dd94bd8f 100644 --- a/spec/test_data/integrations/splits.json +++ b/spec/test_data/integrations/splits.json @@ -116,7 +116,8 @@ ], "label": "in segment all" } - ] + ], + "sets": ["set_3"] }, { "trafficTypeName": "account", @@ -228,7 +229,8 @@ ], "label": "in split test_definition_as_of treatment [off]" } - ] + ], + "sets": ["set_2"] }, { "trafficTypeName": "account", @@ -280,7 +282,8 @@ ], "label": "in segment all" } - ] + ], + "sets": ["set_1", "set_2"] }, { "trafficTypeName": "account", @@ -544,7 +547,8 @@ ], "label": "eee in list [1, 2, ...]" } - ] + ], + "sets": [] }, { "trafficTypeName": "account", From 94b350c9aa64496ad79f0a81d18b4c932b6a4784 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 3 Nov 2023 11:27:15 -0700 Subject: [PATCH 08/27] added updates from other prs --- lib/splitclient-rb.rb | 3 ++ .../cache/filter/flag_set_filter.rb | 40 +++++++++++++++++++ .../helpers/repository_helper.rb | 24 +++++++++++ 3 files changed, 67 insertions(+) create mode 100644 lib/splitclient-rb/cache/filter/flag_set_filter.rb create mode 100644 lib/splitclient-rb/helpers/repository_helper.rb diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index bba7eb84..f2337816 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -14,6 +14,7 @@ require 'splitclient-rb/cache/fetchers/split_fetcher' require 'splitclient-rb/cache/filter/bloom_filter' require 'splitclient-rb/cache/filter/filter_adapter' +require 'splitclient-rb/cache/filter/flag_set_filter' require 'splitclient-rb/cache/hashers/impression_hasher' require 'splitclient-rb/cache/observers/impression_observer' require 'splitclient-rb/cache/observers/noop_impression_observer' @@ -24,6 +25,7 @@ require 'splitclient-rb/cache/repositories/impressions_repository' require 'splitclient-rb/cache/repositories/events/memory_repository' require 'splitclient-rb/cache/repositories/events/redis_repository' +require 'splitclient-rb/cache/repositories/flag_sets_repository' require 'splitclient-rb/cache/repositories/impressions/memory_repository' require 'splitclient-rb/cache/repositories/impressions/redis_repository' require 'splitclient-rb/cache/senders/impressions_formatter' @@ -43,6 +45,7 @@ require 'splitclient-rb/helpers/thread_helper' require 'splitclient-rb/helpers/decryption_helper' require 'splitclient-rb/helpers/util' +require 'splitclient-rb/helpers/repository_helper' require 'splitclient-rb/split_factory' require 'splitclient-rb/split_factory_builder' require 'splitclient-rb/split_config' diff --git a/lib/splitclient-rb/cache/filter/flag_set_filter.rb b/lib/splitclient-rb/cache/filter/flag_set_filter.rb new file mode 100644 index 00000000..09f25d66 --- /dev/null +++ b/lib/splitclient-rb/cache/filter/flag_set_filter.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'set' + +module SplitIoClient + module Cache + module Filter + class FlagSetsFilter + def initialize(flag_sets = []) + @flag_sets = Set.new(flag_sets) + @should_filter = @flag_sets.any? + end + + def should_filter? + @should_filter + end + + def flag_set_exist?(flag_set) + return true unless @should_filter + + if not flag_set.is_a?(String) or flag_set.empty? + return false + end + + @flag_sets.intersection([flag_set]).any? + end + + def intersect?(flag_sets) + return true unless @should_filter + + if not flag_sets.is_a?(Array) or flag_sets.empty? + return false + end + + @flag_sets.intersection(Set.new(flag_sets)).any? + end + end + end + end +end diff --git a/lib/splitclient-rb/helpers/repository_helper.rb b/lib/splitclient-rb/helpers/repository_helper.rb new file mode 100644 index 00000000..192d18cc --- /dev/null +++ b/lib/splitclient-rb/helpers/repository_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SplitIoClient + module Helpers + class RepositoryHelper + def self.update_feature_flag_repository(feature_flag_repository, feature_flags, change_number, config) + to_add = [] + to_delete = [] + for feature_flag in feature_flags + if feature_flag_repository.flag_set_filter.intersect?(feature_flag[:sets]) && !Engine::Models::Split.archived?(feature_flag) + to_add.push(feature_flag) + config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled + else + if !feature_flag_repository.get_split(feature_flag[:name]).nil? + config.logger.debug("removing feature flag from store(#{feature_flag})") if config.debug_enabled + to_delete.push(feature_flag) + end + end + end + feature_flag_repository.update(to_add, to_delete, change_number) + end + end + end +end From b6f302ffdb64946f664cad0acb5b59192237bc1f Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 6 Nov 2023 11:11:34 -0800 Subject: [PATCH 09/27] polishing --- .../repositories/flag_sets_repository.rb | 2 +- .../cache/repositories/splits_repository.rb | 6 +-- .../helpers/repository_helper.rb | 15 +++--- lib/splitclient-rb/split_factory.rb | 16 ++++-- .../repositories/flag_set_repository_spec.rb | 47 +++++++++++++++++ .../repositories/splits_repository_spec.rb | 6 ++- spec/filter/flag_set_filter_spec.rb | 24 +++++++++ spec/repository_helper.rb | 51 +++++++++++++++++++ 8 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 spec/cache/repositories/flag_set_repository_spec.rb create mode 100644 spec/filter/flag_set_filter_spec.rb create mode 100644 spec/repository_helper.rb diff --git a/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb b/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb index 4baf48d2..4ebea579 100644 --- a/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb +++ b/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb @@ -3,7 +3,7 @@ module SplitIoClient module Cache module Repositories - class FlagSets + class FlagSetsRepository def initialize(flag_sets = []) @sets_feature_flag_map = {} flag_sets.each{ |flag_set| @sets_feature_flag_map[flag_set] = Set[] } diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 378174bc..377b9c15 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -6,7 +6,7 @@ module Repositories class SplitsRepository < Repository attr_reader :adapter - def initialize(config, flag_sets = []) + def initialize(config, flag_sets_repository, flag_set_filter) super(config) @tt_cache = {} @adapter = case @config.cache_adapter.class.to_s @@ -15,8 +15,8 @@ def initialize(config, flag_sets = []) else @config.cache_adapter end - @flag_sets = SplitIoClient::Cache::Repositories::FlagSets.new(flag_sets) - @flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(flag_sets) + @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')) diff --git a/lib/splitclient-rb/helpers/repository_helper.rb b/lib/splitclient-rb/helpers/repository_helper.rb index 192d18cc..d7294c45 100644 --- a/lib/splitclient-rb/helpers/repository_helper.rb +++ b/lib/splitclient-rb/helpers/repository_helper.rb @@ -7,15 +7,14 @@ def self.update_feature_flag_repository(feature_flag_repository, feature_flags, to_add = [] to_delete = [] for feature_flag in feature_flags - if feature_flag_repository.flag_set_filter.intersect?(feature_flag[:sets]) && !Engine::Models::Split.archived?(feature_flag) - to_add.push(feature_flag) - config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled - else - if !feature_flag_repository.get_split(feature_flag[:name]).nil? - config.logger.debug("removing feature flag from store(#{feature_flag})") if config.debug_enabled - to_delete.push(feature_flag) - end + if Engine::Models::Split.archived?(feature_flag) || !feature_flag_repository.flag_set_filter.intersect?(feature_flag[:sets]) + config.logger.debug("removing feature flag from store(#{feature_flag})") if config.debug_enabled + to_delete.push(feature_flag) + next end + + config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled + to_add.push(feature_flag) end feature_flag_repository.update(to_add, to_delete, change_number) end diff --git a/lib/splitclient-rb/split_factory.rb b/lib/splitclient-rb/split_factory.rb index 4a18f4fa..1eeb7e39 100644 --- a/lib/splitclient-rb/split_factory.rb +++ b/lib/splitclient-rb/split_factory.rb @@ -35,6 +35,7 @@ def initialize(api_key, config_hash = {}) register_factory build_telemetry_components + build_flag_sets_filter build_repositories build_telemetry_synchronizer build_impressions_sender_adapter @@ -55,11 +56,11 @@ def start! if @config.consumer? build_synchronizer build_sync_manager - + @sync_manager.start_consumer return end - + build_fetchers build_synchronizer build_streaming_components @@ -135,7 +136,7 @@ def validate_api_key end def repositories - { + { splits: @splits_repository, segments: @segments_repository, impressions: @impressions_repository, @@ -188,7 +189,7 @@ def build_streaming_components 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) - event_parser = SSE::EventSource::EventParser.new(config) + event_parser = SSE::EventSource::EventParser.new(config) sse_client = SSE::EventSource::Client.new(@config, @api_key, @runtime_producer, event_parser, notification_manager_keeper, notification_processor, @push_status_queue) @sse_handler = SSE::SSEHandler.new(@config, splits_worker, segments_worker, sse_client) @push_manager = Engine::PushManager.new(@config, @sse_handler, @api_key, @runtime_producer) @@ -199,7 +200,8 @@ def build_sync_manager end def build_repositories - @splits_repository = SplitsRepository.new(@config) + @flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new(@config.flag_sets_filter) + @splits_repository = SplitsRepository.new(@config, @flag_sets_repository, @flag_sets_filter) @segments_repository = SegmentsRepository.new(@config) @impressions_repository = ImpressionsRepository.new(@config) @events_repository = EventsRepository.new(@config, @api_key, @runtime_producer) @@ -251,5 +253,9 @@ def build_impressions_components @impressions_manager = Engine::Common::ImpressionManager.new(@config, @impressions_repository, @impression_counter, @runtime_producer, @impression_observer, @unique_keys_tracker) end + + def build_flag_sets_filter + @flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(@config.flag_sets_filter) + end end end diff --git a/spec/cache/repositories/flag_set_repository_spec.rb b/spec/cache/repositories/flag_set_repository_spec.rb new file mode 100644 index 00000000..c6a1355f --- /dev/null +++ b/spec/cache/repositories/flag_set_repository_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'set' + +describe SplitIoClient::Cache::Repositories::FlagSetsRepository do + context 'test flag set repository' do + it 'test add/delete' do + flag_set = SplitIoClient::Cache::Repositories::FlagSetsRepository.new [] + expect(flag_set.instance_variable_get(:@sets_feature_flag_map)).to eq({}) + + flag_set.add_flag_set('set_1') + expect(flag_set.flag_set_exist?('set_1')).to eq(true) + expect(flag_set.get_flag_set('set_1')).to eq(Set[]) + + flag_set.add_flag_set('set_2') + expect(flag_set.flag_set_exist?('set_2')).to eq(true) + expect(flag_set.get_flag_set('set_2')).to eq(Set[]) + + flag_set.remove_flag_set('set_1') + expect(flag_set.flag_set_exist?('set_1')).to eq(false) + + flag_set.remove_flag_set('set_2') + expect(flag_set.instance_variable_get(:@sets_feature_flag_map)).to eq({}) + end + + it 'test add/delete feature flags to flag sets' do + flag_set = SplitIoClient::Cache::Repositories::FlagSetsRepository.new [] + expect(flag_set.instance_variable_get(:@sets_feature_flag_map)).to eq({}) + + flag_set.add_flag_set('set_1') + flag_set.add_feature_flag_to_flag_set('set_1', 'feature1') + expect(flag_set.flag_set_exist?('set_1')).to eq(true) + expect(flag_set.get_flag_set('set_1')).to eq(Set['feature1']) + + flag_set.add_feature_flag_to_flag_set('set_1', 'feature2') + expect(flag_set.get_flag_set('set_1')).to eq(Set['feature1', 'feature2']) + + flag_set.remove_feature_flag_from_flag_set('set_1', 'feature1') + expect(flag_set.get_flag_set('set_1')).to eq(Set['feature2']) + + flag_set.remove_feature_flag_from_flag_set('set_1', 'feature2') + expect(flag_set.get_flag_set('set_1')).to eq(Set[]) + + end + end +end diff --git a/spec/cache/repositories/splits_repository_spec.rb b/spec/cache/repositories/splits_repository_spec.rb index cebd3f97..5383fc05 100644 --- a/spec/cache/repositories/splits_repository_spec.rb +++ b/spec/cache/repositories/splits_repository_spec.rb @@ -6,7 +6,9 @@ describe SplitIoClient::Cache::Repositories::SplitsRepository do RSpec.shared_examples 'Splits Repository' do |cache_adapter| let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: cache_adapter) } - let(:repository) { described_class.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} + let(:repository) { described_class.new(config, flag_sets_repository, flag_set_filter) } before :all do redis = Redis.new @@ -94,7 +96,7 @@ end it 'returns splits data' do - expect(repository.splits).to eq( + expect(repository.splits(repository.split_names)).to eq( 'foo' => { name: 'foo', trafficTypeName: 'tt_name_1' }, 'bar' => { name: 'bar', trafficTypeName: 'tt_name_2' }, 'baz' => { name: 'baz', trafficTypeName: 'tt_name_1' } diff --git a/spec/filter/flag_set_filter_spec.rb b/spec/filter/flag_set_filter_spec.rb new file mode 100644 index 00000000..4368efe3 --- /dev/null +++ b/spec/filter/flag_set_filter_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe SplitIoClient::Cache::Filter::FlagSetsFilter do + subject { SplitIoClient::Cache::Filter::FlagSetsFilter } + + it 'validate initialize, contains one or multiple sets' do + fs = subject.new(['set_1', 'set_2']) + + expect(fs.flag_set_exist?('set_1')).to eq(true) + expect(fs.flag_set_exist?('set_3')).to eq(false) + expect(fs.flag_set_exist?(1)).to eq(false) + + expect(fs.intersect?(['set_3', 'set_1'])).to eq(true) + expect(fs.intersect?(['set_2', 'set_1'])).to eq(true) + expect(fs.intersect?(['set_3', 'set_4'])).to eq(false) + expect(fs.intersect?('set_1')).to eq(false) + + fs2 = subject.new() + expect(fs2.flag_set_exist?('set_1')).to eq(true) + expect(fs2.intersect?(['set_2', 'set_1'])).to eq(true) + + end +end diff --git a/spec/repository_helper.rb b/spec/repository_helper.rb new file mode 100644 index 00000000..e6abf0db --- /dev/null +++ b/spec/repository_helper.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +require 'spec_helper' +require 'set' + +describe SplitIoClient::Helpers::RepositoryHelper do + context 'test repository helper' do + it 'with flag set filter' do + config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) + flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(['set_1', 'set_2']) + flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new(['set_1', 'set_2']) + 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', :sets => []}], -1, config) + 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', :sets => ['set_3']}], -1, config) + 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', :sets => ['set_1']}], -1, config) + 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', :sets => ['set_1']}], -1, config) + expect(feature_flag_repository.get_split('split1').nil?).to eq(true) + end + + it 'without flag set filter' do + config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) + flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) + flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.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', :sets => []}], -1, config) + 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', :sets => ['set_3']}], -1, config) + 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', :sets => ['set_1']}], -1, config) + 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', :sets => ['set_1']}], -1, config) + expect(feature_flag_repository.get_split('split1').nil?).to eq(true) + end + end +end From d0a03e2f78ac2fc4b6d9a3072468a3999b67f9c3 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 10 Nov 2023 11:28:23 -0800 Subject: [PATCH 10/27] refactor evaluator with splitclient --- .../cache/repositories/splits_repository.rb | 13 +- lib/splitclient-rb/clients/split_client.rb | 150 +++++++++++------- lib/splitclient-rb/engine/parser/evaluator.rb | 53 +++++-- lib/splitclient-rb/validators.rb | 12 +- spec/engine/parser/evaluator_spec.rb | 12 +- spec/splitclient/split_config_spec.rb | 22 +-- 6 files changed, 164 insertions(+), 98 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 377b9c15..302599ba 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -6,7 +6,7 @@ module Repositories class SplitsRepository < Repository attr_reader :adapter - def initialize(config, flag_sets_repository, flag_set_filter) + def initialize(config, flag_sets_repositry, flag_set_filter) super(config) @tt_cache = {} @adapter = case @config.cache_adapter.class.to_s @@ -15,7 +15,7 @@ def initialize(config, flag_sets_repository, flag_set_filter) else @config.cache_adapter end - @flag_sets = flag_sets_repository + @flag_sets = flag_sets_repositry @flag_set_filter = flag_set_filter unless @config.mode.equal?(:consumer) @adapter.set_string(namespace_key('.splits.till'), '-1') @@ -35,8 +35,13 @@ def get_split(name) JSON.parse(split, symbolize_names: true) if split end - def splits(split_names) - get_splits(split_names, false) + def splits(filtered_names=nil) + symbolize = true + if filtered_names.nil? + filtered_names = split_names + symbolize = false + end + get_splits(filtered_names, symbolize) end def traffic_type_exists(tt_name) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 0f6d7953..45c6ce47 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -29,16 +29,15 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage @config = config @impressions_manager = impressions_manager @telemetry_evaluation_producer = telemetry_evaluation_producer - @split_validator = SplitIoClient::Validators.new(self) + @split_validator = SplitIoClient::Validators.new(@config) + @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config) end def get_treatment( key, split_name, attributes = {}, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - impressions = [] - result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, GET_TREATMENT, impressions) - @impressions_manager.track(impressions) + result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, GET_TREATMENT) if multiple result.tap { |t| t.delete(:config) } @@ -51,15 +50,12 @@ def get_treatment_with_config( key, split_name, attributes = {}, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - impressions = [] - result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, GET_TREATMENT_WITH_CONFIG, impressions) - @impressions_manager.track(impressions) - - result + treatment(key, split_name, attributes, split_data, store_impressions, multiple, GET_TREATMENT_WITH_CONFIG) end def get_treatments(key, split_names, attributes = {}) treatments = treatments(key, split_names, attributes) + return treatments if treatments.nil? keys = treatments.keys treats = treatments.map { |_,t| t[:treatment] } @@ -180,6 +176,9 @@ def parsed_treatment(multiple, treatment_data) end def sanitize_split_names(calling_method, split_names) + if split_names.nil? + return nil + end split_names.compact.uniq.select do |split_name| if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty? true @@ -231,12 +230,15 @@ def valid_client @config.valid_mode end - def treatments(key, split_names, attributes = {}, calling_method = 'get_treatments') - return nil unless @config.split_validator.valid_get_treatments_parameters(calling_method, split_names) + def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments') + sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names) - sanitized_split_names = sanitize_split_names(calling_method, split_names) + if sanitized_feature_flag_names.nil? + @config.logger.error("#{calling_method}: feature_flag_names must be a non-empty Array") + return nil + end - if sanitized_split_names.empty? + if sanitized_feature_flag_names.empty? @config.logger.error("#{calling_method}: feature_flag_names must be a non-empty Array") return {} end @@ -245,25 +247,54 @@ def treatments(key, split_names, attributes = {}, calling_method = 'get_treatmen bucketing_key = bucketing_key ? bucketing_key.to_s : nil matching_key = matching_key ? matching_key.to_s : nil - evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config, true) - start = Time.now - impressions = [] - treatments_labels_change_numbers = - @splits_repository.splits(sanitized_split_names).each_with_object({}) do |(name, data), memo| - memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator, calling_method, impressions)) - end + if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes) + to_return = Hash.new + sanitized_feature_flag_names.each {|name| + to_return[name.to_sym] = control_treatment_with_config + } + return to_return + end - record_latency(calling_method, start) - @impressions_manager.track(impressions) + if !ready? + impressions = [] + to_return = Hash.new + sanitized_feature_flag_names.each {|name| + to_return[name.to_sym] = control_treatment_with_config + impressions << @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ label: Engine::Models::Label::NOT_READY }), { attributes: attributes, time: nil }) + } + @impressions_manager.track(impressions) + return to_return + end - split_names_keys = treatments_labels_change_numbers.keys - treatments = treatments_labels_change_numbers.values.map do |v| + valid_feature_flag_names = [] + sanitized_feature_flag_names.each { |feature_flag_name| + valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil? + } + start = Time.now + impressions_total = [] + + feature_flags = @splits_repository.splits(feature_flag_names) + treatments = Hash.new + invalid_treatments = Hash.new + feature_flags.each do |key, feature_flag| + if feature_flag.nil? + @config.logger.warn("#{calling_method}: you passed #{key} that " \ + 'does not exist in this environment, please double check what feature flags exist in the Split user interface') + invalid_treatments[key] = control_treatment_with_config + next + end + treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method) + impressions_total.concat(impressions) unless impressions.nil? + treatments[key] = { - treatment: v[:treatment], - config: v[:config] + treatment: treatments_labels_change_numbers[:treatment], + config: treatments_labels_change_numbers[:config] } end - Hash[split_names_keys.zip(treatments)] + record_latency(calling_method, start) + @impressions_manager.track(impressions_total) unless impressions_total.empty? + + treatments.merge(invalid_treatments) end # @@ -275,71 +306,76 @@ def treatments(key, split_names, attributes = {}, calling_method = 'get_treatmen # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data # @param store_impressions [Boolean] impressions aren't stored if this flag is false # @param multiple [Hash] internal flag to signal if method is called by get_treatments - # @param evaluator [Evaluator] Evaluator class instance, used to cache treatments - # # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features - def treatment( - key, split_name, attributes = {}, split_data = nil, store_impressions = true, - multiple = false, evaluator = nil, calling_method = 'get_treatment', impressions = [] - ) - control_treatment = { treatment: Engine::Models::Treatment::CONTROL } - + def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true, + multiple = false, calling_method = 'get_treatment') + impressions = [] bucketing_key, matching_key = keys_from_key(key) attributes = parsed_attributes(attributes) - return parsed_treatment(multiple, control_treatment) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes) + return parsed_treatment(multiple, control_treatment) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes) bucketing_key = bucketing_key ? bucketing_key.to_s : nil matching_key = matching_key.to_s - sanitized_split_name = split_name.to_s.strip + sanitized_feature_flag_name = feature_flag_name.to_s.strip - if split_name.to_s != sanitized_split_name - @config.logger.warn("#{calling_method}: feature_flag_name #{split_name} has extra whitespace, trimming") - split_name = sanitized_split_name + if feature_flag_name.to_s != sanitized_feature_flag_name + @config.logger.warn("#{calling_method}: feature_flag_name #{feature_flag_name} has extra whitespace, trimming") + feature_flag_name = sanitized_feature_flag_name end - evaluator ||= Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config) + feature_flag = @splits_repository.get_split(feature_flag_name) + treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method) + + @impressions_manager.track(impressions) unless impressions.nil? + treatments + end + def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method) + impressions = [] begin start = Time.now - - split = multiple ? split_data : @splits_repository.get_split(split_name) - - if split.nil? && ready? - @config.logger.warn("#{calling_method}: you passed #{split_name} that " \ + if feature_flag.nil? && ready? + @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \ 'does not exist in this environment, please double check what feature flags exist in the Split user interface') - return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND })) + return parsed_treatment(false, control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND })), nil end treatment_data = - if !split.nil? && ready? - evaluator.call( - { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes + if !feature_flag.nil? && ready? + @evaluator.evaluate_feature_flag( + { bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes ) else @config.logger.error("#{calling_method}: the SDK is not ready, the operation cannot be executed") - control_treatment.merge({ label: Engine::Models::Label::NOT_READY }) end - record_latency(calling_method, start) unless multiple - - impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, treatment_data, { attributes: attributes, time: nil }) + record_latency(calling_method, start) + impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, { attributes: attributes, time: nil }) impressions << impression unless impression.nil? rescue StandardError => e @config.log_found_exception(__method__.to_s, e) record_exception(calling_method) - impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, control_treatment, { attributes: attributes, time: nil }) + impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil }) impressions << impression unless impression.nil? - return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION })) + return parsed_treatment(false, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION })), impressions end - parsed_treatment(multiple, treatment_data) + return parsed_treatment(false, treatment_data), impressions + end + + def control_treatment + { treatment: Engine::Models::Treatment::CONTROL } + end + + def control_treatment_with_config + {:treatment => Engine::Models::Treatment::CONTROL, :config => nil} end def variable_size(value) diff --git a/lib/splitclient-rb/engine/parser/evaluator.rb b/lib/splitclient-rb/engine/parser/evaluator.rb index cd91503d..0240e860 100644 --- a/lib/splitclient-rb/engine/parser/evaluator.rb +++ b/lib/splitclient-rb/engine/parser/evaluator.rb @@ -2,32 +2,53 @@ module SplitIoClient module Engine module Parser class Evaluator - def initialize(segments_repository, splits_repository, config, multiple = false) + def initialize(segments_repository, splits_repository, config) @splits_repository = splits_repository @segments_repository = segments_repository - @multiple = multiple @config = config @cache = {} end - def call(keys, split, attributes = nil) + def evaluate_feature_flag(keys, feature_flag, attributes = nil) # DependencyMatcher here, split is actually a split_name in this case - cache_result = split.is_a? String - split = @splits_repository.get_split(split) if cache_result - digest = Digest::MD5.hexdigest("#{{matching_key: keys[:matching_key]}}#{split}#{attributes}") + cache_result = feature_flag.is_a? String + evaluate_treatment(keys, feature_flag, cache_result, attributes) + end + + def evaluate_feature_flags(keys, feature_flag_names, attributes = nil, calling_method) + # DependencyMatcher here, split is actually a split_name in this case + feature_flags = @splits_repository.splits(feature_flag_names) + treatments = Hash.new + invalid_treatments = Hash.new + feature_flags.each do |key, feature_flag| + if feature_flag.nil? + @config.logger.warn("#{calling_method}: you passed #{key} that " \ + 'does not exist in this environment, please double check what feature flags exist in the Split user interface') + invalid_treatments[key] = { + treatment: "control", + config: nil, + label: Engine::Models::Label::NOT_FOUND + } + next + end - if Models::Split.archived?(split) - return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, split[:changeNumber]) + treatments[key] = evaluate_treatment(keys, feature_flag, feature_flag[:name], attributes) end + treatments.merge(invalid_treatments) + end - treatment = if Models::Split.matchable?(split) - if @multiple && @cache[digest] - @cache[digest] - else - match(split, keys, attributes) - end + private + + def evaluate_treatment(keys, feature_flag, cache_result, attributes) + digest = Digest::MD5.hexdigest("#{{matching_key: keys[:matching_key]}}#{feature_flag}#{attributes}") + if Models::Split.archived?(feature_flag) + return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, feature_flag[:changeNumber]) + end + + treatment = if Models::Split.matchable?(feature_flag) + match(feature_flag, keys, attributes) else - treatment_hash(Models::Label::KILLED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) + treatment_hash(Models::Label::KILLED, feature_flag[:defaultTreatment], feature_flag[:changeNumber], split_configurations(feature_flag[:defaultTreatment], feature_flag)) end @cache[digest] = treatment if cache_result @@ -35,8 +56,6 @@ def call(keys, split, attributes = nil) treatment end - private - def split_configurations(treatment, split) return nil if split[:configurations].nil? split[:configurations][treatment.to_sym] diff --git a/lib/splitclient-rb/validators.rb b/lib/splitclient-rb/validators.rb index 75441840..2c403ee9 100644 --- a/lib/splitclient-rb/validators.rb +++ b/lib/splitclient-rb/validators.rb @@ -18,8 +18,12 @@ def valid_get_treatment_parameters(method, key, split_name, matching_key, bucket valid_attributes?(method, attributes) end - def valid_get_treatments_parameters(method, split_names) - valid_split_names?(method, split_names) + def valid_get_treatments_parameters(method, key, split_names, matching_key, bucketing_key, attributes) + valid_key?(method, key) && + valid_split_names?(method, split_names) + valid_matching_key?(method, matching_key) && + valid_bucketing_key?(method, key, bucketing_key) && + valid_attributes?(method, attributes) end def valid_track_parameters(key, traffic_type_name, event_type, value, properties) @@ -42,7 +46,7 @@ def valid_matcher_arguments(args) end def valid_flag_sets(method, flag_sets) - return Set[] if flag_sets.nil? || !flag_sets.is_a?(Array) + return [] if flag_sets.nil? || !flag_sets.is_a?(Array) valid_flag_sets = SortedSet[] flag_sets.compact.uniq.select do |flag_set| @@ -50,7 +54,7 @@ def valid_flag_sets(method, flag_sets) log_invalid_type(:flag_set, method) elsif flag_set.is_a?(String) && flag_set.empty? log_nil(:flag_set, method) - elsif !flag_set.empty? && string_match?(flag_set.strip, method) + elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method) valid_flag_sets.add(flag_set.strip.downcase) else @config.logger.warn("#{method}: you passed an invalid flag set, flag set name must be a non-empty String") diff --git a/spec/engine/parser/evaluator_spec.rb b/spec/engine/parser/evaluator_spec.rb index 78b9bb6f..506be411 100644 --- a/spec/engine/parser/evaluator_spec.rb +++ b/spec/engine/parser/evaluator_spec.rb @@ -4,7 +4,9 @@ describe SplitIoClient::Engine::Parser::Evaluator do let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(@default_config) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:killed_split) { { killed: true, defaultTreatment: 'default' } } @@ -16,12 +18,12 @@ end it 'returns killed treatment' do - expect(evaluator.call({ matching_key: 'foo' }, killed_split)) + expect(evaluator.evaluate_feature_flag({ matching_key: 'foo' }, killed_split)) .to eq(label: 'killed', treatment: 'default', change_number: nil, config: nil) end it 'returns archived treatment' do - expect(evaluator.call({ matching_key: 'foo' }, archived_split)) + expect(evaluator.evaluate_feature_flag({ matching_key: 'foo' }, archived_split)) .to eq(label: 'archived', treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, change_number: nil, config: nil) end @@ -32,9 +34,9 @@ .to receive(:get_split).and_return(split_data[:splits][0]) expect(evaluator).to receive(:match).exactly(2).times - evaluator.call({ 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[:splits][0]) - evaluator.call({ 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[:splits][1]) end end end diff --git a/spec/splitclient/split_config_spec.rb b/spec/splitclient/split_config_spec.rb index d1cdc252..1c867aa4 100644 --- a/spec/splitclient/split_config_spec.rb +++ b/spec/splitclient/split_config_spec.rb @@ -34,7 +34,7 @@ expect(configs.machine_ip).to eq SplitIoClient::SplitConfig.machine_ip(default_ip, nil, :redis) expect(configs.on_demand_fetch_retry_delay_seconds).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_retry_delay_seconds expect(configs.on_demand_fetch_max_retries).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_max_retries - expect(configs.flag_sets_filter).to eq nil + expect(configs.flag_sets_filter).to eq [] end it 'stores and retrieves correctly the customized values' do @@ -49,7 +49,7 @@ expect(configs.impressions_refresh_rate).to eq custom_options[:impressions_refresh_rate] expect(configs.impressions_queue_size).to eq custom_options[:impressions_queue_size] expect(configs.debug_enabled).to eq custom_options[:debug_enabled] - expect(configs.flag_sets_filter).to eq Set["set_1"] + expect(configs.flag_sets_filter).to eq ["set_1"] end it 'has the current default values for timeouts and intervals, with impressions_mode in :optimized' do @@ -154,31 +154,31 @@ it 'test flag sets filter validations' do configs = SplitIoClient::SplitConfig.new(flag_sets_filter: 0) - expect(configs.flag_sets_filter).to eq nil + expect(configs.flag_sets_filter).to eq [] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: []) - expect(configs.flag_sets_filter).to eq nil + expect(configs.flag_sets_filter).to eq [] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['set2 ', ' set1', 'set3']) - expect(configs.flag_sets_filter).to eq Set['set1', 'set2', 'set3'] + expect(configs.flag_sets_filter).to eq ['set1', 'set2', 'set3'] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['1set', '_set2']) - expect(configs.flag_sets_filter).to eq Set['1set'] + expect(configs.flag_sets_filter).to eq ['1set'] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['Set1', 'SET2']) - expect(configs.flag_sets_filter).to eq Set['set1', 'set2'] + expect(configs.flag_sets_filter).to eq ['set1', 'set2'] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['se\t1', 's/et2', 's*et3', 's!et4', 'se@t5', 'se#t5', 'se$t5', 'se^t5', 'se%t5', 'se&t5']) - expect(configs.flag_sets_filter).to eq nil + expect(configs.flag_sets_filter).to eq [] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['set4', 'set1', 'set3', 'set1']) - expect(configs.flag_sets_filter).to eq Set['set1', 'set3', 'set4'] + expect(configs.flag_sets_filter).to eq ['set1', 'set3', 'set4'] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: '1set') - expect(configs.flag_sets_filter).to eq nil + expect(configs.flag_sets_filter).to eq [] configs = SplitIoClient::SplitConfig.new(flag_sets_filter: ['1set', 12]) - expect(configs.flag_sets_filter).to eq Set['1set'] + expect(configs.flag_sets_filter).to eq ['1set'] end end end From 554508e88d88370ab99e45886e5e53d8bf80b648 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 10 Nov 2023 11:33:03 -0800 Subject: [PATCH 11/27] fixed typo --- lib/splitclient-rb/cache/repositories/splits_repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 302599ba..989bd675 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -6,7 +6,7 @@ module Repositories class SplitsRepository < Repository attr_reader :adapter - def initialize(config, flag_sets_repositry, flag_set_filter) + def initialize(config, flag_sets_repository, flag_set_filter) super(config) @tt_cache = {} @adapter = case @config.cache_adapter.class.to_s @@ -15,7 +15,7 @@ def initialize(config, flag_sets_repositry, flag_set_filter) else @config.cache_adapter end - @flag_sets = flag_sets_repositry + @flag_sets = flag_sets_repository @flag_set_filter = flag_set_filter unless @config.mode.equal?(:consumer) @adapter.set_string(namespace_key('.splits.till'), '-1') From 1bc870f9ef31d3007dbbb39be943c7056edfb9b3 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 13 Nov 2023 11:53:20 -0800 Subject: [PATCH 12/27] polishing with test --- lib/splitclient-rb/clients/split_client.rb | 48 +++++++------------ lib/splitclient-rb/engine/parser/evaluator.rb | 39 +++------------ spec/splitclient/split_client_spec.rb | 40 ++++++++++++++++ 3 files changed, 62 insertions(+), 65 deletions(-) create mode 100644 spec/splitclient/split_client_spec.rb diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 45c6ce47..b9cc2caa 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -37,20 +37,15 @@ def get_treatment( key, split_name, attributes = {}, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, GET_TREATMENT) - - if multiple - result.tap { |t| t.delete(:config) } - else - result[:treatment] - end + result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT) + result[:treatment] end def get_treatment_with_config( key, split_name, attributes = {}, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - treatment(key, split_name, attributes, split_data, store_impressions, multiple, GET_TREATMENT_WITH_CONFIG) + treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG) end def get_treatments(key, split_names, attributes = {}) @@ -150,6 +145,12 @@ def track(key, traffic_type_name, event_type, value = nil, properties = nil) false end + def block_until_ready(time = nil) + @status_manager.wait_until_ready(time) if @status_manager + end + + private + def keys_from_key(key) case key when Hash @@ -159,20 +160,11 @@ def keys_from_key(key) end end - def parsed_treatment(multiple, treatment_data) - if multiple + def parsed_treatment(treatment_data) { treatment: treatment_data[:treatment], - label: treatment_data[:label], - change_number: treatment_data[:change_number], - config: treatment_data[:config] + config: treatment_data[:config], } - else - { - treatment: treatment_data[:treatment], - config: treatment_data[:config], - } - end end def sanitize_split_names(calling_method, split_names) @@ -192,12 +184,6 @@ def sanitize_split_names(calling_method, split_names) end end - def block_until_ready(time = nil) - @status_manager.wait_until_ready(time) if @status_manager - end - - private - def validate_properties(properties) properties_count = 0 size = 0 @@ -305,16 +291,15 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t # @param attributes [Hash] attributes to pass to the treatment class # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data # @param store_impressions [Boolean] impressions aren't stored if this flag is false - # @param multiple [Hash] internal flag to signal if method is called by get_treatments # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true, - multiple = false, calling_method = 'get_treatment') + calling_method = 'get_treatment') impressions = [] bucketing_key, matching_key = keys_from_key(key) attributes = parsed_attributes(attributes) - return parsed_treatment(multiple, control_treatment) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes) + return parsed_treatment(control_treatment) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes) bucketing_key = bucketing_key ? bucketing_key.to_s : nil matching_key = matching_key.to_s @@ -340,9 +325,8 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_ @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \ 'does not exist in this environment, please double check what feature flags exist in the Split user interface') - return parsed_treatment(false, control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND })), nil + return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND })), nil end - treatment_data = if !feature_flag.nil? && ready? @evaluator.evaluate_feature_flag( @@ -364,10 +348,10 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_ impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil }) impressions << impression unless impression.nil? - return parsed_treatment(false, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION })), impressions + return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION })), impressions end - return parsed_treatment(false, treatment_data), impressions + return parsed_treatment(treatment_data), impressions end def control_treatment diff --git a/lib/splitclient-rb/engine/parser/evaluator.rb b/lib/splitclient-rb/engine/parser/evaluator.rb index 0240e860..8d2e1699 100644 --- a/lib/splitclient-rb/engine/parser/evaluator.rb +++ b/lib/splitclient-rb/engine/parser/evaluator.rb @@ -6,52 +6,25 @@ def initialize(segments_repository, splits_repository, config) @splits_repository = splits_repository @segments_repository = segments_repository @config = config - @cache = {} end def evaluate_feature_flag(keys, feature_flag, attributes = nil) # DependencyMatcher here, split is actually a split_name in this case - cache_result = feature_flag.is_a? String - evaluate_treatment(keys, feature_flag, cache_result, attributes) - end - - def evaluate_feature_flags(keys, feature_flag_names, attributes = nil, calling_method) - # DependencyMatcher here, split is actually a split_name in this case - feature_flags = @splits_repository.splits(feature_flag_names) - treatments = Hash.new - invalid_treatments = Hash.new - feature_flags.each do |key, feature_flag| - if feature_flag.nil? - @config.logger.warn("#{calling_method}: you passed #{key} that " \ - 'does not exist in this environment, please double check what feature flags exist in the Split user interface') - invalid_treatments[key] = { - treatment: "control", - config: nil, - label: Engine::Models::Label::NOT_FOUND - } - next - end - - treatments[key] = evaluate_treatment(keys, feature_flag, feature_flag[:name], attributes) - end - treatments.merge(invalid_treatments) + evaluate_treatment(keys, feature_flag, attributes) end private - def evaluate_treatment(keys, feature_flag, cache_result, attributes) - digest = Digest::MD5.hexdigest("#{{matching_key: keys[:matching_key]}}#{feature_flag}#{attributes}") + def evaluate_treatment(keys, feature_flag, attributes) if Models::Split.archived?(feature_flag) return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, feature_flag[:changeNumber]) end treatment = if Models::Split.matchable?(feature_flag) - match(feature_flag, keys, attributes) - else - treatment_hash(Models::Label::KILLED, feature_flag[:defaultTreatment], feature_flag[:changeNumber], split_configurations(feature_flag[:defaultTreatment], feature_flag)) - end - - @cache[digest] = treatment if cache_result + match(feature_flag, keys, attributes) + else + treatment_hash(Models::Label::KILLED, feature_flag[:defaultTreatment], feature_flag[:changeNumber], split_configurations(feature_flag[:defaultTreatment], feature_flag)) + end treatment end diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb new file mode 100644 index 00000000..ec172332 --- /dev/null +++ b/spec/splitclient/split_client_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::SplitClient do + let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config)} + let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } + let(:events_repository) {SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer)} + let(:evaluator) { described_class.new(segments_repository, splits_repository, true) } + 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(:split_client) {SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, SplitIoClient::Telemetry::EvaluationProducer.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) + end + + it 'check getting treatments' do + expect(split_client.get_treatment('key', 'testing222')).to eq('off') + expect(split_client.get_treatments('key', ['testing222'])).to eq({:testing222 => 'off'}) + expect(split_client.get_treatment_with_config('key', 'testing222')).to eq({:treatment => 'off', :config => nil}) + expect(split_client.get_treatments_with_config('key', ['testing222'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + expect(split_client.get_treatments_by_flag_set('key', 'set_1')).to eq({:testing222 => 'off'}) + expect(split_client.get_treatments_by_flag_sets('key', ['set_2'])).to eq({:testing222 => 'off'}) + expect(split_client.get_treatments_with_config_by_flag_set('key', 'set_1')).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + expect(split_client.get_treatments_with_config_by_flag_sets('key', ['set_2'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + end + + it 'check track' do + expect(split_client.track('key', 'account', 'event', 1)).to eq(true) + end + +end From eb72e1c81b3eb683fc98c855053842c933ce6b6e Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 14 Nov 2023 12:04:13 -0800 Subject: [PATCH 13/27] Fixed rubocop offenses --- lib/splitclient-rb/clients/split_client.rb | 11 +++++----- .../helpers/repository_helper.rb | 2 +- lib/splitclient-rb/split_factory.rb | 20 +++++++++++++++---- .../telemetry/memory/memory_synchronizer.rb | 12 +++++++---- .../telemetry/redis/redis_init_producer.rb | 4 +++- .../telemetry/redis/redis_synchronizer.rb | 4 ++-- .../telemetry/storages/memory.rb | 12 +++++++---- lib/splitclient-rb/telemetry/synchronizer.rb | 8 ++++++-- spec/splitclient/split_client_spec.rb | 16 ++++++++------- 9 files changed, 58 insertions(+), 31 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index b9cc2caa..55d2d8f2 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -18,7 +18,7 @@ class SplitClient # @param sdk_key [String] the SDK key for your split account # # @return [SplitIoClient] split.io client instance - def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer) + def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator) @api_key = sdk_key @splits_repository = repositories[:splits] @segments_repository = repositories[:segments] @@ -29,8 +29,8 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage @config = config @impressions_manager = impressions_manager @telemetry_evaluation_producer = telemetry_evaluation_producer - @split_validator = SplitIoClient::Validators.new(@config) - @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config) + @split_validator = split_validator + @evaluator = evaluator end def get_treatment( @@ -168,9 +168,8 @@ def parsed_treatment(treatment_data) end def sanitize_split_names(calling_method, split_names) - if split_names.nil? - return nil - end + return nil if split_names.nil? + split_names.compact.uniq.select do |split_name| if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty? true diff --git a/lib/splitclient-rb/helpers/repository_helper.rb b/lib/splitclient-rb/helpers/repository_helper.rb index d7294c45..c53e53ae 100644 --- a/lib/splitclient-rb/helpers/repository_helper.rb +++ b/lib/splitclient-rb/helpers/repository_helper.rb @@ -6,7 +6,7 @@ class RepositoryHelper def self.update_feature_flag_repository(feature_flag_repository, feature_flags, change_number, config) to_add = [] to_delete = [] - for feature_flag in feature_flags + feature_flags.each do |feature_flag| if Engine::Models::Split.archived?(feature_flag) || !feature_flag_repository.flag_set_filter.intersect?(feature_flag[:sets]) config.logger.debug("removing feature flag from store(#{feature_flag})") if config.debug_enabled to_delete.push(feature_flag) diff --git a/lib/splitclient-rb/split_factory.rb b/lib/splitclient-rb/split_factory.rb index 1eeb7e39..15449a2b 100644 --- a/lib/splitclient-rb/split_factory.rb +++ b/lib/splitclient-rb/split_factory.rb @@ -26,8 +26,18 @@ def initialize(api_key, config_hash = {}) end @api_key = api_key + + store_flag_sets = config_hash[:flag_set_filter] if config_hash.key?(:flag_set_filter) + flag_sets = 0 + flag_sets_invalid = 0 + @config = SplitConfig.new(config_hash.merge(localhost_mode: @api_key == LOCALHOST_API_KEY )) + if @config.key?(:flag_set_filter) + flag_sets = @config[:flag_set_filter].length() + flag_sets_invalid = store_flag_sets[:flag_set_filter].length() - valid_flag_sets + end + raise 'Invalid SDK mode' unless valid_mode validate_api_key @@ -37,16 +47,18 @@ def initialize(api_key, config_hash = {}) build_telemetry_components build_flag_sets_filter build_repositories - build_telemetry_synchronizer + build_telemetry_synchronizer(flag_sets, flag_sets_invalid) build_impressions_sender_adapter build_unique_keys_tracker build_impressions_components @status_manager = Engine::StatusManager.new(@config) + @split_validator = SplitIoClient::Validators.new(@config) + @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config) start! - @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer) + @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator) @manager = SplitManager.new(@splits_repository, @status_manager, @config) end @@ -207,9 +219,9 @@ def build_repositories @events_repository = EventsRepository.new(@config, @api_key, @runtime_producer) end - def build_telemetry_synchronizer + def build_telemetry_synchronizer(flag_sets, flag_sets_invalid) @telemetry_api = Api::TelemetryApi.new(@config, @api_key, @runtime_producer) - @telemetry_synchronizer = Telemetry::Synchronizer.new(@config, @telemetry_consumers, @init_producer, repositories, @telemetry_api) + @telemetry_synchronizer = Telemetry::Synchronizer.new(@config, @telemetry_consumers, @init_producer, repositories, @telemetry_api, flag_sets, flag_sets_invalid) end def build_unique_keys_tracker diff --git a/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb b/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb index 8129a34e..f976354f 100644 --- a/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +++ b/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb @@ -6,7 +6,9 @@ class MemorySynchronizer def initialize(config, telemtry_consumers, repositories, - telemetry_api) + telemetry_api, + flag_sets, + flag_sets_invalid) @config = config @telemetry_init_consumer = telemtry_consumers[:init] @telemetry_runtime_consumer = telemtry_consumers[:runtime] @@ -14,6 +16,8 @@ def initialize(config, @splits_repository = repositories[:splits] @segments_repository = repositories[:segments] @telemetry_api = telemetry_api + @flag_sets = flag_sets + @flag_sets_invalid = flag_sets_invalid end def synchronize_stats @@ -42,7 +46,7 @@ def synchronize_stats @config.log_found_exception(__method__.to_s, e) end - def synchronize_config(active_factories = nil, redundant_active_factories = nil, time_until_ready = nil, flag_sets = nil, flag_sets_invalid = nil) + def synchronize_config(active_factories = nil, redundant_active_factories = nil, time_until_ready = nil) rates = Rates.new(@config.features_refresh_rate, @config.segments_refresh_rate, @config.impressions_refresh_rate, @@ -64,8 +68,8 @@ def synchronize_config(active_factories = nil, redundant_active_factories = nil, active_factories, redundant_active_factories, @telemetry_runtime_consumer.pop_tags, - flag_sets, - flag_sets_invalid, + @flag_sets, + @flag_sets_invalid, @config.streaming_enabled, rates, url_overrides, diff --git a/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb b/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb index a6f5a676..6e21aa5a 100644 --- a/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +++ b/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb @@ -11,7 +11,9 @@ def initialize(config) def record_config(config_data) return if config_data.nil? - data = { t: { oM: config_data.om, st: config_data.st, aF: config_data.af, rF: config_data.rf, t: config_data.t, fsT: config_data.fsT, fsI: config_data.fsI } } + data = { t: { oM: config_data.om, st: config_data.st, aF: config_data.af, rF: config_data.rf, t: config_data.t } } + data['t']['fsT'] = config_data.fsT + data['t']['fsI'] = config_data.fsI field = "#{@config.language}-#{@config.version}/#{@config.machine_name}/#{@config.machine_ip}" @adapter.add_to_map(config_key, field, data.to_json) diff --git a/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb b/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb index d70e0019..38f879f0 100644 --- a/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +++ b/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb @@ -13,11 +13,11 @@ def synchronize_stats # No-op end - def synchronize_config(active_factories = nil, redundant_active_factories = nil, tags = nil, flag_sets = nil, flag_sets_invalid = nil) + def synchronize_config(active_factories = nil, redundant_active_factories = nil, tags = nil) active_factories ||= SplitIoClient.split_factory_registry.active_factories redundant_active_factories ||= SplitIoClient.split_factory_registry.redundant_active_factories - init_config = ConfigInit.new(@config.mode, 'redis', active_factories, redundant_active_factories, tags, flag_sets, flag_sets_invalid) + init_config = ConfigInit.new(@config.mode, 'redis', active_factories, redundant_active_factories, tags) @telemetry_init_producer.record_config(init_config) rescue StandardError => e diff --git a/lib/splitclient-rb/telemetry/storages/memory.rb b/lib/splitclient-rb/telemetry/storages/memory.rb index b7991258..d9f5d3bc 100644 --- a/lib/splitclient-rb/telemetry/storages/memory.rb +++ b/lib/splitclient-rb/telemetry/storages/memory.rb @@ -38,6 +38,8 @@ def initialize def init_latencies @latencies = Concurrent::Array.new + treatment_with_config_by_flag_set_const = Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET + treatment_with_config_by_flag_sets_const = Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS array_size = BinarySearchLatencyTracker::BUCKETS.length @latencies << { method: Domain::Constants::TREATMENT, latencies: Concurrent::Array.new(array_size, 0) } @@ -46,13 +48,15 @@ def init_latencies @latencies << { method: Domain::Constants::TREATMENTS_WITH_CONFIG, latencies: Concurrent::Array.new(array_size, 0) } @latencies << { method: Domain::Constants::TREATMENTS_BY_FLAG_SET, latencies: Concurrent::Array.new(array_size, 0) } @latencies << { method: Domain::Constants::TREATMENTS_BY_FLAG_SETS, latencies: Concurrent::Array.new(array_size, 0) } - @latencies << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, latencies: Concurrent::Array.new(array_size, 0) } - @latencies << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, latencies: Concurrent::Array.new(array_size, 0) } + @latencies << { method: treatment_with_config_by_flag_set_const, latencies: Concurrent::Array.new(array_size, 0) } + @latencies << { method: treatment_with_config_by_flag_sets_const, latencies: Concurrent::Array.new(array_size, 0) } @latencies << { method: Domain::Constants::TRACK, latencies: Concurrent::Array.new(array_size, 0) } end def init_exceptions @exceptions = Concurrent::Array.new + treatment_with_config_by_flag_set_const = Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET + treatment_with_config_by_flag_sets_const = Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS @exceptions << { method: Domain::Constants::TREATMENT, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TREATMENTS, exceptions: Concurrent::AtomicFixnum.new(0) } @@ -60,8 +64,8 @@ def init_exceptions @exceptions << { method: Domain::Constants::TREATMENTS_WITH_CONFIG, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TREATMENTS_BY_FLAG_SET, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TREATMENTS_BY_FLAG_SETS, exceptions: Concurrent::AtomicFixnum.new(0) } - @exceptions << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, exceptions: Concurrent::AtomicFixnum.new(0) } - @exceptions << { method: Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, exceptions: Concurrent::AtomicFixnum.new(0) } + @exceptions << { method: treatment_with_config_by_flag_set_const, exceptions: Concurrent::AtomicFixnum.new(0) } + @exceptions << { method: treatment_with_config_by_flag_sets_const, exceptions: Concurrent::AtomicFixnum.new(0) } @exceptions << { method: Domain::Constants::TRACK, exceptions: Concurrent::AtomicFixnum.new(0) } end diff --git a/lib/splitclient-rb/telemetry/synchronizer.rb b/lib/splitclient-rb/telemetry/synchronizer.rb index 723e8f8e..c4b19e08 100644 --- a/lib/splitclient-rb/telemetry/synchronizer.rb +++ b/lib/splitclient-rb/telemetry/synchronizer.rb @@ -12,7 +12,9 @@ def initialize(config, telemtry_consumers, telemetry_init_producer, repositories, - telemetry_api) + telemetry_api, + flag_sets, + flag_sets_invalid) @synchronizer = case config.telemetry_adapter.class.to_s when 'SplitIoClient::Cache::Adapters::RedisAdapter' SplitIoClient::Telemetry::RedisSynchronizer.new(config, @@ -21,7 +23,9 @@ def initialize(config, SplitIoClient::Telemetry::MemorySynchronizer.new(config, telemtry_consumers, repositories, - telemetry_api) + telemetry_api, + flag_sets, + flag_sets_invalid) end end end diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index ec172332..5926bca9 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -5,15 +5,17 @@ describe SplitIoClient::SplitClient do let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} - let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config)} + let(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:events_repository) {SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer)} - let(:evaluator) { described_class.new(segments_repository, splits_repository, true) } - 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(:split_client) {SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, SplitIoClient::Telemetry::EvaluationProducer.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(:splits) do File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) end From f87ab3dad5ab36e8f9884157816fdf2f693a7622 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 17 Nov 2023 13:53:14 -0800 Subject: [PATCH 14/27] Updated all specs plus minor fixes --- lib/splitclient-rb/clients/split_client.rb | 40 +- .../engine/matchers/dependency_matcher.rb | 2 +- lib/splitclient-rb/engine/parser/evaluator.rb | 2 + lib/splitclient-rb/split_factory.rb | 15 +- .../telemetry/redis/redis_init_producer.rb | 2 - .../telemetry/redis/redis_synchronizer.rb | 1 - lib/splitclient-rb/version.rb | 2 +- .../clients/split_client_spec.rb | 8 +- spec/cache/fetchers/segment_fetch_spec.rb | 8 +- spec/cache/fetchers/split_fetch_spec.rb | 58 ++- .../repositories/segments_repository_spec.rb | 4 +- .../repositories/splits_repository_spec.rb | 2 +- .../stores/localhost_split_store_spec.rb | 4 +- spec/engine/api/splits_spec.rb | 6 +- spec/engine/matchers/between_matcher_spec.rb | 9 +- .../matchers/depencdency_matcher_spec.rb | 4 +- spec/engine/push_manager_spec.rb | 4 +- spec/engine/sync_manager_spec.rb | 6 +- spec/engine/synchronizer_spec.rb | 4 +- spec/engine_spec.rb | 8 +- spec/integrations/dedupe_impression_spec.rb | 13 +- spec/integrations/in_memory_client_spec.rb | 450 ++++++++++++++++++ spec/integrations/push_client_spec.rb | 4 +- spec/integrations/redis_client_spec.rb | 363 ++++++++++++++ spec/splitclient/split_factory_spec.rb | 89 ++++ spec/sse/event_source/client_spec.rb | 4 +- spec/sse/sse_handler_spec.rb | 4 +- spec/sse/workers/segments_worker_spec.rb | 4 +- spec/sse/workers/splits_worker_spec.rb | 7 +- spec/telemetry/synchronizer_spec.rb | 20 +- spec/telemetry/telemetry_init_spec.rb | 2 - spec/test_data/splits/splits2.json | 6 +- 32 files changed, 1079 insertions(+), 76 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 55d2d8f2..ef1b3e5e 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -37,7 +37,8 @@ def get_treatment( key, split_name, attributes = {}, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT) + result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple) + return result.tap { |t| t.delete(:config) } if multiple result[:treatment] end @@ -45,7 +46,7 @@ def get_treatment_with_config( key, split_name, attributes = {}, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG) + treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple) end def get_treatments(key, split_names, attributes = {}) @@ -160,18 +161,29 @@ def keys_from_key(key) end end - def parsed_treatment(treatment_data) + def parsed_treatment(treatment_data, multiple = false) + if multiple + { + treatment: treatment_data[:treatment], + label: treatment_data[:label], + change_number: treatment_data[:change_number], + config: treatment_data[:config] + } + else { treatment: treatment_data[:treatment], config: treatment_data[:config], } + end end def sanitize_split_names(calling_method, split_names) - return nil if split_names.nil? + return nil if !split_names.is_a?(Array) split_names.compact.uniq.select do |split_name| - if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty? + if split_name.nil? + false + elsif (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty? true elsif split_name.is_a?(String) && split_name.empty? @config.logger.warn("#{calling_method}: you passed an empty feature_flag_name, flag name must be a non-empty String or a Symbol") @@ -216,6 +228,7 @@ def valid_client end def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments') + attributes = {} if attributes.nil? sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names) if sanitized_feature_flag_names.nil? @@ -258,7 +271,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t start = Time.now impressions_total = [] - feature_flags = @splits_repository.splits(feature_flag_names) + feature_flags = @splits_repository.splits(valid_feature_flag_names) treatments = Hash.new invalid_treatments = Hash.new feature_flags.each do |key, feature_flag| @@ -292,13 +305,13 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t # @param store_impressions [Boolean] impressions aren't stored if this flag is false # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true, - calling_method = 'get_treatment') + calling_method = 'get_treatment', multiple = false) impressions = [] bucketing_key, matching_key = keys_from_key(key) attributes = parsed_attributes(attributes) - return parsed_treatment(control_treatment) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes) + return parsed_treatment(control_treatment, multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes) bucketing_key = bucketing_key ? bucketing_key.to_s : nil matching_key = matching_key.to_s @@ -310,21 +323,20 @@ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_i end feature_flag = @splits_repository.get_split(feature_flag_name) - treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method) + treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple) @impressions_manager.track(impressions) unless impressions.nil? treatments end - def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method) + def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false) impressions = [] begin start = Time.now if feature_flag.nil? && ready? @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \ 'does not exist in this environment, please double check what feature flags exist in the Split user interface') - - return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND })), nil + return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND }), multiple), nil end treatment_data = if !feature_flag.nil? && ready? @@ -347,10 +359,10 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_ impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil }) impressions << impression unless impression.nil? - return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION })), impressions + return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }), multiple), impressions end - return parsed_treatment(treatment_data), impressions + return parsed_treatment(treatment_data, multiple), impressions end def control_treatment diff --git a/lib/splitclient-rb/engine/matchers/dependency_matcher.rb b/lib/splitclient-rb/engine/matchers/dependency_matcher.rb index ec8c1467..e04b2a6a 100644 --- a/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +++ b/lib/splitclient-rb/engine/matchers/dependency_matcher.rb @@ -12,7 +12,7 @@ def initialize(feature_flag, treatments, logger) def match?(args) keys = { matching_key: args[:matching_key], bucketing_key: args[:bucketing_key] } - evaluate = args[:evaluator].call(keys, @feature_flag, args[:attributes]) + evaluate = args[:evaluator].evaluate_feature_flag(keys, @feature_flag, args[:attributes]) matches = @treatments.include?(evaluate[:treatment]) @logger.log_if_debug("[dependencyMatcher] Parent feature flag #{@feature_flag} evaluated to #{evaluate[:treatment]} \ with label #{evaluate[:label]}. #{@feature_flag} evaluated treatment is part of [#{@treatments}] ? #{matches}.") diff --git a/lib/splitclient-rb/engine/parser/evaluator.rb b/lib/splitclient-rb/engine/parser/evaluator.rb index 8d2e1699..afefd364 100644 --- a/lib/splitclient-rb/engine/parser/evaluator.rb +++ b/lib/splitclient-rb/engine/parser/evaluator.rb @@ -10,6 +10,8 @@ def initialize(segments_repository, splits_repository, config) def evaluate_feature_flag(keys, feature_flag, attributes = nil) # DependencyMatcher here, split is actually a split_name in this case + cache_result = feature_flag.is_a? String + feature_flag = @splits_repository.get_split(feature_flag) if cache_result evaluate_treatment(keys, feature_flag, attributes) end diff --git a/lib/splitclient-rb/split_factory.rb b/lib/splitclient-rb/split_factory.rb index 15449a2b..af88b192 100644 --- a/lib/splitclient-rb/split_factory.rb +++ b/lib/splitclient-rb/split_factory.rb @@ -27,15 +27,16 @@ def initialize(api_key, config_hash = {}) @api_key = api_key - store_flag_sets = config_hash[:flag_set_filter] if config_hash.key?(:flag_set_filter) - flag_sets = 0 + store_flag_sets = config_hash.key?(:flag_sets_filter) ? config_hash[:flag_sets_filter] : [] + store_flag_sets = [] if !store_flag_sets.is_a?(Array) + flag_sets_count = 0 flag_sets_invalid = 0 @config = SplitConfig.new(config_hash.merge(localhost_mode: @api_key == LOCALHOST_API_KEY )) - if @config.key?(:flag_set_filter) - flag_sets = @config[:flag_set_filter].length() - flag_sets_invalid = store_flag_sets[:flag_set_filter].length() - valid_flag_sets + if config_hash.key?(:flag_sets_filter) + flag_sets_count = store_flag_sets.length() + flag_sets_invalid = flag_sets_count - @config.flag_sets_filter.length() end raise 'Invalid SDK mode' unless valid_mode @@ -47,7 +48,7 @@ def initialize(api_key, config_hash = {}) build_telemetry_components build_flag_sets_filter build_repositories - build_telemetry_synchronizer(flag_sets, flag_sets_invalid) + build_telemetry_synchronizer(flag_sets_count, flag_sets_invalid) build_impressions_sender_adapter build_unique_keys_tracker build_impressions_components @@ -267,7 +268,7 @@ def build_impressions_components end def build_flag_sets_filter - @flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(@config.flag_sets_filter) + @flag_sets_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(@config.flag_sets_filter) end end end diff --git a/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb b/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb index 6e21aa5a..22f26fc3 100644 --- a/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +++ b/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb @@ -12,8 +12,6 @@ def record_config(config_data) return if config_data.nil? data = { t: { oM: config_data.om, st: config_data.st, aF: config_data.af, rF: config_data.rf, t: config_data.t } } - data['t']['fsT'] = config_data.fsT - data['t']['fsI'] = config_data.fsI field = "#{@config.language}-#{@config.version}/#{@config.machine_name}/#{@config.machine_ip}" @adapter.add_to_map(config_key, field, data.to_json) diff --git a/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb b/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb index 38f879f0..0a138f58 100644 --- a/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +++ b/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb @@ -18,7 +18,6 @@ def synchronize_config(active_factories = nil, redundant_active_factories = nil, redundant_active_factories ||= SplitIoClient.split_factory_registry.redundant_active_factories init_config = ConfigInit.new(@config.mode, 'redis', active_factories, redundant_active_factories, tags) - @telemetry_init_producer.record_config(init_config) rescue StandardError => e @config.log_found_exception(__method__.to_s, e) diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 116ff706..65859346 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.2.0' + VERSION = '8.3.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 af3852a0..fd661269 100644 --- a/spec/allocations/splitclient-rb/clients/split_client_spec.rb +++ b/spec/allocations/splitclient-rb/clients/split_client_spec.rb @@ -5,7 +5,9 @@ describe SplitIoClient::SplitClient do let(:config) { SplitIoClient::SplitConfig.new(impressions_queue_size: 10) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:impression_counter) { SplitIoClient::Engine::Common::ImpressionCounter.new } @@ -17,6 +19,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(:sender_adapter) do SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, @@ -37,8 +40,7 @@ unique_keys_tracker) end let(:client) do - repositories = { splits: splits_repository, segments: segments_repository, impressions: impressions_repository, events: nil } - SplitIoClient::SplitClient.new('', repositories, nil, config, impressions_manager, evaluation_producer) + SplitIoClient::SplitClient.new('', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => nil}, nil, config, impressions_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) end context 'control' do diff --git a/spec/cache/fetchers/segment_fetch_spec.rb b/spec/cache/fetchers/segment_fetch_spec.rb index a8016075..448d423f 100644 --- a/spec/cache/fetchers/segment_fetch_spec.rb +++ b/spec/cache/fetchers/segment_fetch_spec.rb @@ -33,7 +33,9 @@ context 'memory adapter' do let(:config) { SplitIoClient::SplitConfig.new } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segment_fetcher) { described_class.new(segments_repository, '', config, telemetry_runtime_producer) } let(:split_fetcher) do @@ -67,7 +69,9 @@ let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :redis) } let(:adapter) { SplitIoClient::Cache::Adapters::RedisAdapter.new(config.redis_url) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segment_fetcher) { described_class.new(segments_repository, '', config, telemetry_runtime_producer) } let(:split_fetcher) do diff --git a/spec/cache/fetchers/split_fetch_spec.rb b/spec/cache/fetchers/split_fetch_spec.rb index d605cfd3..d43286d5 100644 --- a/spec/cache/fetchers/split_fetch_spec.rb +++ b/spec/cache/fetchers/split_fetch_spec.rb @@ -23,7 +23,9 @@ cache_adapter: :memory ) end - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } @@ -63,15 +65,18 @@ end end - context 'redis adapter' do + context 'memory adapter with flag sets' do let(:log) { StringIO.new } let(:config) do SplitIoClient::SplitConfig.new( logger: Logger.new(log), - cache_adapter: :redis + cache_adapter: :memory, + flag_sets_filter: ['set_2'] ) end - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } @@ -84,6 +89,51 @@ 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]) + end + + it 'refreshes splits' do + store.send(:fetch_splits) + 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?since=1473413807667') + .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?since=-1') + .to_return(status: 200, body: active_splits_json) + + store.send(:fetch_splits) + expect(store.splits_repository.get_split('sample_feature')[:name]).to eq('sample_feature') + end + end + + context 'redis adapter' do + let(:log) { StringIO.new } + let(:config) do + SplitIoClient::SplitConfig.new( + logger: Logger.new(log), + cache_adapter: :redis + ) + end + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } + + it 'returns splits since' do + splits = store.send(:splits_since, -1) + expect(splits[:splits].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) end diff --git a/spec/cache/repositories/segments_repository_spec.rb b/spec/cache/repositories/segments_repository_spec.rb index a7f6824d..0fea9953 100644 --- a/spec/cache/repositories/segments_repository_spec.rb +++ b/spec/cache/repositories/segments_repository_spec.rb @@ -5,7 +5,9 @@ describe SplitIoClient::Cache::Repositories::SegmentsRepository do context 'memory adapter' do let(:repository) { described_class.new(@default_config) } - let(:split_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } + let(:split_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config, flag_sets_repository, flag_set_filter) } it 'removes keys' do repository.add_to_segment(name: 'foo', added: [1, 2, 3], removed: []) diff --git a/spec/cache/repositories/splits_repository_spec.rb b/spec/cache/repositories/splits_repository_spec.rb index 5383fc05..28311675 100644 --- a/spec/cache/repositories/splits_repository_spec.rb +++ b/spec/cache/repositories/splits_repository_spec.rb @@ -96,7 +96,7 @@ end it 'returns splits data' do - expect(repository.splits(repository.split_names)).to eq( + expect(repository.splits).to eq( 'foo' => { name: 'foo', trafficTypeName: 'tt_name_1' }, 'bar' => { name: 'bar', trafficTypeName: 'tt_name_2' }, 'baz' => { name: 'baz', trafficTypeName: 'tt_name_1' } diff --git a/spec/cache/stores/localhost_split_store_spec.rb b/spec/cache/stores/localhost_split_store_spec.rb index 520f2667..b76f8fd1 100644 --- a/spec/cache/stores/localhost_split_store_spec.rb +++ b/spec/cache/stores/localhost_split_store_spec.rb @@ -5,7 +5,9 @@ describe SplitIoClient::Cache::Stores::LocalhostSplitStore do let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:split_file) do ['local_feature local_treatment'] diff --git a/spec/engine/api/splits_spec.rb b/spec/engine/api/splits_spec.rb index 9ae5ca6f..adbe91f7 100644 --- a/spec/engine/api/splits_spec.rb +++ b/spec/engine/api/splits_spec.rb @@ -68,7 +68,7 @@ expect(log.string).to include returned_splits.to_s end - it 'raise api exception when status 409' do + it 'raise api exception when status 414' do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1,set_2') .with(headers: { 'Accept' => '*/*', @@ -78,7 +78,7 @@ 'Keep-Alive' => '30', 'Splitsdkversion' => "#{config.language}-#{config.version}" }) - .to_return(status: 409, body: splits) + .to_return(status: 414, body: splits) fetch_options = { cache_control_headers: false, till: nil, sets: ['set_1','set_2'] } captured = 0 @@ -87,7 +87,7 @@ rescue SplitIoClient::ApiException => e captured = e.exception_code end - expect(captured).to eq(409) + expect(captured).to eq(414) end diff --git a/spec/engine/matchers/between_matcher_spec.rb b/spec/engine/matchers/between_matcher_spec.rb index 7f84cd3e..ba8b980c 100644 --- a/spec/engine/matchers/between_matcher_spec.rb +++ b/spec/engine/matchers/between_matcher_spec.rb @@ -4,7 +4,8 @@ describe SplitIoClient::BetweenMatcher do subject do - SplitIoClient::SplitFactory.new('test_api_key', {logger: Logger.new('/dev/null'), streaming_enabled: false, impressions_refresh_rate: 9999, impressions_mode: :none, features_refresh_rate: 9999, telemetry_refresh_rate: 99999}).client +# SplitIoClient::SplitFactory.new('test_api_key', {logger: Logger.new('/dev/null'), streaming_enabled: false, impressions_refresh_rate: 9999, impressions_mode: :none, features_refresh_rate: 9999, telemetry_refresh_rate: 99999}).client + SplitIoClient::SplitFactory.new('test_api_key', {streaming_enabled: false, impressions_refresh_rate: 9999, impressions_mode: :none, features_refresh_rate: 9999, telemetry_refresh_rate: 99999}).client end let(:datetime_matcher_splits) do @@ -38,7 +39,11 @@ let(:non_matching_low_value_attributes) { { income: 99 } } before do - stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since/) +# 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?since=-1') .to_return(status: 200, body: number_matcher_splits) subject.block_until_ready sleep 1 diff --git a/spec/engine/matchers/depencdency_matcher_spec.rb b/spec/engine/matchers/depencdency_matcher_spec.rb index 6cf415c3..5ba74246 100644 --- a/spec/engine/matchers/depencdency_matcher_spec.rb +++ b/spec/engine/matchers/depencdency_matcher_spec.rb @@ -6,7 +6,7 @@ let(:evaluator) { double } it 'matches' do - allow(evaluator).to receive(:call).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'foo', nil) + allow(evaluator).to receive(:evaluate_feature_flag).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'foo', nil) .and_return(treatment: 'yes') expect(described_class.new('foo', %w[on yes true], @split_logger) @@ -14,7 +14,7 @@ end it 'does not match' do - allow(evaluator).to receive(:call).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'foo', nil) + allow(evaluator).to receive(:evaluate_feature_flag).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'foo', nil) .and_return(treatment: 'no') expect(described_class.new('foo', %w[on yes true], @split_logger) diff --git a/spec/engine/push_manager_spec.rb b/spec/engine/push_manager_spec.rb index 215535bf..6089cb54 100644 --- a/spec/engine/push_manager_spec.rb +++ b/spec/engine/push_manager_spec.rb @@ -10,7 +10,9 @@ let(:api_key) { 'PushManager-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.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) } diff --git a/spec/engine/sync_manager_spec.rb b/spec/engine/sync_manager_spec.rb index a8a5ee7a..3bca2a82 100644 --- a/spec/engine/sync_manager_spec.rb +++ b/spec/engine/sync_manager_spec.rb @@ -16,7 +16,9 @@ let(:api_key) { 'SyncManager-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log), streaming_enabled: true) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } @@ -46,7 +48,7 @@ let(:evaluation_consumer) { SplitIoClient::Telemetry::EvaluationConsumer.new(config) } let(:telemetry_consumers) { { init: init_consumer, runtime: runtime_consumer, evaluation: evaluation_consumer } } 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) } + 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(:segments_worker) { SplitIoClient::SSE::Workers::SegmentsWorker.new(synchronizer, config, segments_repository) } diff --git a/spec/engine/synchronizer_spec.rb b/spec/engine/synchronizer_spec.rb index 86ae7882..5add3c52 100644 --- a/spec/engine/synchronizer_spec.rb +++ b/spec/engine/synchronizer_spec.rb @@ -14,7 +14,9 @@ let(:synchronizer) do api_key = 'Synchronizer-key' runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config) + flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) + 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) repositories = { diff --git a/spec/engine_spec.rb b/spec/engine_spec.rb index 3695921f..0fc71469 100644 --- a/spec/engine_spec.rb +++ b/spec/engine_spec.rb @@ -135,8 +135,10 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL end - it 'returns CONTROL and label for random key' do - expect(subject.get_treatment('random_user_id', 'test_feature', nil, nil, false, true)).to eq( + it 'returns CONTROL and label for incorrect feature name' do + treatment = subject.get_treatment('random_user_id', 'test_featur', nil, nil, false, true) + puts treatment + expect(treatment).to eq( treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, label: SplitIoClient::Engine::Models::Label::NOT_FOUND, change_number: nil @@ -210,6 +212,7 @@ expect(log.string).to include "get_treatment: bucketing_key \"#{value}\" is not of type String, converting" end + #TODO We will remove multiple param in the future. it 'returns CONTROL and label on nil key' do expect(subject.get_treatment(nil, 'test_feature', nil, nil, false, true)).to eq( treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, @@ -261,6 +264,7 @@ 'split_name must be a non-empty String or a Symbol' end + #TODO We will remove multiple param in the future. it 'returns CONTROL and label on nil split_name' do expect(subject.get_treatment('random_user_id', nil, nil, nil, false, true)).to eq( treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, diff --git a/spec/integrations/dedupe_impression_spec.rb b/spec/integrations/dedupe_impression_spec.rb index 94d8bb3d..8aa67ef7 100644 --- a/spec/integrations/dedupe_impression_spec.rb +++ b/spec/integrations/dedupe_impression_spec.rb @@ -42,11 +42,11 @@ sleep 1 expect(debug_client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' - treatments = {"FACUNDO_TEST"=>"on"} + treatments = {:FACUNDO_TEST=>"on"} expect(debug_client.get_treatments_by_flag_set('nico_test', 'set_3')).to eq treatments - treatments = {"FACUNDO_TEST"=>"off"} + treatments = {:FACUNDO_TEST=>"off"} expect(debug_client.get_treatments_by_flag_sets('admin', ['set_3'])).to eq treatments - treatments = {"FACUNDO_TEST"=>{:treatment=>"off", :config=>nil}} + treatments = {:FACUNDO_TEST=>{:treatment=>"off", :config=>nil}} expect(debug_client.get_treatments_with_config_by_flag_set('admin', 'set_3')).to eq treatments 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' @@ -59,7 +59,7 @@ expect(impressions.size).to eq 7 end - it 'get_treaments should post 8 impressions - debug mode' do + 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?since=1506703262916').to_return(status: 200, body: '') @@ -70,12 +70,13 @@ debug_client.get_treatments('admin', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) debug_client.get_treatments('maldo', %w[FACUNDO_TEST Test_Save_1]) debug_client.get_treatments('nico_test', %w[FACUNDO_TEST MAURO_TEST Test_Save_1]) + expect(debug_client.get_treatments_by_flag_set('admin', 'set_3')).to eq ({:FACUNDO_TEST=>"off"}) impressions = debug_client.instance_variable_get(:@impressions_repository).batch sleep 0.5 - expect(impressions.size).to eq 8 + expect(impressions.size).to eq 9 end it 'get_treament should post 3 impressions - optimized mode' do @@ -87,7 +88,7 @@ client = factory.client client.block_until_ready(2) - expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' + expect(client.get_treatments_by_flag_sets('nico_test', ['set_3'])).to eq ({:FACUNDO_TEST=>"on"}) expect(client.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' expect(client.get_treatment('admin', 'FACUNDO_TEST')).to eq 'off' expect(client.get_treatment('24', 'Test_Save_1')).to eq 'off' diff --git a/spec/integrations/in_memory_client_spec.rb b/spec/integrations/in_memory_client_spec.rb index 7a45cd74..c625c18d 100644 --- a/spec/integrations/in_memory_client_spec.rb +++ b/spec/integrations/in_memory_client_spec.rb @@ -474,6 +474,388 @@ end end + context '#get_treatments_by_flag_set' do + before do + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1506703262916").to_return(status: 200, body: 'ok') + end + + it 'returns treatments and check impressions' do + client.block_until_ready + result = client.get_treatments_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq 'on' + + result = client.get_treatments_by_flag_set('nico_test', 'set_2') + expect(result[:testing]).to eq 'off' + expect(result[:testing222]).to eq 'off' + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:treatment][:treatment]).to eq('off') + expect(impressions[2][:treatment][:label]).to eq('in segment all') + expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + client.block_until_ready + result1 = client.get_treatments_by_flag_set('nico_test', 'set_3 ') + result2 = client.get_treatments_by_flag_set('', 'set_2') + result3 = client.get_treatments_by_flag_set(nil, 'set_1') + + expect(result1[:FACUNDO_TEST]).to eq 'on' + expect(result2[:testing]).to eq 'control' + expect(result2[:testing222]).to eq 'control' + expect(result2[:testing222]).to eq 'control' + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + client.block_until_ready + result = client.get_treatments_by_flag_set('nico_test', 'invalid_set') + expect(result).to eq({}) + result = client.get_treatments_by_flag_set('nico_test', 'set_3') + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL when server return 500' do + mock_split_changes_error + + result = client.get_treatments_by_flag_set('nico_test', 'set_2') + expect(result).to eq({}) + end + end + + context '#get_treatments_by_flag_sets' do + before do + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1506703262916").to_return(status: 200, body: 'ok') + end + + it 'returns treatments and check impressions' do + client.block_until_ready + result = client.get_treatments_by_flag_sets('nico_test', ['set_3', 'set_2']) + expect(result[:FACUNDO_TEST]).to eq 'on' + expect(result[:testing]).to eq 'off' + expect(result[:testing222]).to eq 'off' + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[2][:treatment][:treatment]).to eq('on') + expect(impressions[2][:treatment][:label]).to eq('whitelisted') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + client.block_until_ready + result1 = client.get_treatments_by_flag_sets('nico_test', ['set_3 ', 'invalid', '']) + result2 = client.get_treatments_by_flag_sets('', ['set_2', 123, 'se@t', '//s']) + result3 = client.get_treatments_by_flag_sets(nil, ['set_1']) + + expect(result1[:FACUNDO_TEST]).to eq 'on' + expect(result2[:testing]).to eq 'control' + expect(result2[:testing222]).to eq 'control' + expect(result2[:testing222]).to eq 'control' + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + client.block_until_ready + result = client.get_treatments_by_flag_sets('nico_test', ['invalid_set']) + expect(result).to eq({}) + result = client.get_treatments_by_flag_sets('nico_test', ['set_3']) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL when server return 500' do + mock_split_changes_error + + result = client.get_treatments_by_flag_sets('nico_test', ['set_2']) + expect(result).to eq({}) + end + end + + context '#get_treatments_with_config_by_flag_set' do + before do + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1506703262916").to_return(status: 200, body: 'ok') + end + + it 'returns treatments and check impressions' do + client.block_until_ready + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_2') + expect(result[:testing]).to eq( + treatment: 'off', + config: nil + ) + expect(result[:testing222]).to eq( + treatment: 'off', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:treatment][:treatment]).to eq('off') + expect(impressions[2][:treatment][:label]).to eq('in segment all') + expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + client.block_until_ready + result1 = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3 ') + result2 = client.get_treatments_with_config_by_flag_set('', 'set_2') + result3 = client.get_treatments_with_config_by_flag_set(nil, 'set_1') + + expect(result1[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result2[:testing]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + client.block_until_ready + result = client.get_treatments_with_config_by_flag_set('nico_test', 'invalid_set') + expect(result).to eq({}) + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL when server return 500' do + mock_split_changes_error + + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_2') + expect(result).to eq({}) + end + end + + context '#get_treatments_with_config_by_flag_sets' do + before do + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1506703262916").to_return(status: 200, body: 'ok') + end + + it 'returns treatments and check impressions' do + client.block_until_ready + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3', 'set_2']) + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result[:testing]).to eq( + treatment: 'off', + config: nil + ) + expect(result[:testing222]).to eq( + treatment: 'off', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[2][:treatment][:treatment]).to eq('on') + expect(impressions[2][:treatment][:label]).to eq('whitelisted') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + client.block_until_ready + result1 = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3 ', 'invalid', '']) + result2 = client.get_treatments_with_config_by_flag_sets('', ['set_2', 123, 'se@t', '//s']) + result3 = client.get_treatments_with_config_by_flag_sets(nil, ['set_1']) + + expect(result1[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result2[:testing]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + client.block_until_ready + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['invalid_set']) + expect(result).to eq({}) + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3']) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL when server return 500' do + mock_split_changes_error + + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_2']) + expect(result).to eq({}) + end + end + context '#get_treatments_with_config' do it 'returns treatments and check impressions' do stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1506703262916").to_return(status: 200, body: 'ok') @@ -748,6 +1130,74 @@ client.destroy() end end + + context 'using flag set filter' do + let(:factory1) do + SplitIoClient::SplitFactory.new('test_api_key', + features_refresh_rate: 9999, + telemetry_refresh_rate: 99999, + impressions_refresh_rate: 99999, + streaming_enabled: false, + flag_sets_filter: ['set_3']) + end + before do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') + .to_return(status: 200, body: splits) + mock_segment_changes('segment1', segment1, '-1') + mock_segment_changes('segment1', segment1, '1470947453877') + mock_segment_changes('segment2', segment2, '-1') + mock_segment_changes('segment2', segment2, '1470947453878') + mock_segment_changes('segment3', segment3, '-1') + + end + + it 'test get_treatments_by_flag_set' do + client1 = factory1.client + client1.block_until_ready + result = client1.get_treatments_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq 'on' + + result = client1.get_treatments_by_flag_set('nico_test', 'set_2') + expect(result).to eq({}) + end + + it 'test get_treatments_by_flag_sets' do + client1 = factory1.client + client1.block_until_ready + result = client1.get_treatments_by_flag_sets('nico_test', ['set_3']) + expect(result[:FACUNDO_TEST]).to eq 'on' + + result = client1.get_treatments_by_flag_sets('nico_test', ['set_2', 'set_1']) + expect(result).to eq({}) + + result = client1.get_treatments_by_flag_sets('nico_test', ['set_2', 'set_3']) + expect(result[:FACUNDO_TEST]).to eq 'on' + end + + it 'test get_treatments_with_config_by_flag_set' do + client1 = factory1.client + client1.block_until_ready + result = client1.get_treatments_with_config_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq({:config=>"{\"color\":\"green\"}", :treatment=>"on"}) + + result = client1.get_treatments_with_config_by_flag_set('nico_test', 'set_2') + expect(result).to eq({}) + end + + it 'test get_treatments_with_config_by_flag_sets' do + client1 = factory1.client + client1.block_until_ready + result = client1.get_treatments_with_config_by_flag_sets('nico_test', ['set_3']) + expect(result[:FACUNDO_TEST]).to eq({:config=>"{\"color\":\"green\"}", :treatment=>"on"}) + + result = client1.get_treatments_with_config_by_flag_sets('nico_test', ['set_2', 'set_1']) + expect(result).to eq({}) + + result = client1.get_treatments_with_config_by_flag_sets('nico_test', ['set_2', 'set_3']) + expect(result[:FACUNDO_TEST]).to eq({:config=>"{\"color\":\"green\"}", :treatment=>"on"}) + end + + end end private diff --git a/spec/integrations/push_client_spec.rb b/spec/integrations/push_client_spec.rb index 7bd17fb3..4c78d00f 100644 --- a/spec/integrations/push_client_spec.rb +++ b/spec/integrations/push_client_spec.rb @@ -150,6 +150,8 @@ 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?since=1585948850111").to_return(status: 200, body: '') + stub_request(:get, "https://sdk.split.io/api/segmentChanges/bilal_segment?since=-1").to_return(status: 200, body: '') mock_server do |server| server.setup_response('/') do |_, res| send_content(res, event_split_iff_update_no_compression) @@ -166,7 +168,7 @@ client = factory.client client.block_until_ready - sleep(2) + sleep(3) expect(client.get_treatment('admin', 'bilal_split')).to eq('off') end end diff --git a/spec/integrations/redis_client_spec.rb b/spec/integrations/redis_client_spec.rb index 4af00aee..6f7da9b5 100644 --- a/spec/integrations/redis_client_spec.rb +++ b/spec/integrations/redis_client_spec.rb @@ -581,6 +581,369 @@ end end + context '#get_treatments_by_flag_set' do + it 'returns treatments and check impressions' do + result = client.get_treatments_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq 'on' + + result = client.get_treatments_by_flag_set('nico_test', 'set_2') + expect(result[:testing]).to eq 'off' + expect(result[:testing222]).to eq 'off' + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:treatment][:treatment]).to eq('off') + expect(impressions[2][:treatment][:label]).to eq('in segment all') + expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + result1 = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3 ') + result2 = client.get_treatments_with_config_by_flag_set('', 'set_2') + result3 = client.get_treatments_with_config_by_flag_set(nil, 'set_1') + + expect(result1[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result2[:testing]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + result = client.get_treatments_with_config_by_flag_set('nico_test', 'invalid_set') + expect(result).to eq({}) + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + end + + context '#get_treatments_by_flag_sets' do + it 'returns treatments and check impressions' do + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3', 'set_2']) + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result[:testing]).to eq( + treatment: 'off', + config: nil + ) + expect(result[:testing222]).to eq( + treatment: 'off', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[2][:treatment][:treatment]).to eq('on') + expect(impressions[2][:treatment][:label]).to eq('whitelisted') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + result1 = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3 ', 'invalid', '']) + result2 = client.get_treatments_with_config_by_flag_sets('', ['set_2', 123, 'se@t', '//s']) + result3 = client.get_treatments_with_config_by_flag_sets(nil, ['set_1']) + + expect(result1[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result2[:testing]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['invalid_set']) + expect(result).to eq({}) + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3']) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + end + + context '#get_treatments_with_config_by_flag_set' do + it 'returns treatments and check impressions' do + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_2') + expect(result[:testing]).to eq( + treatment: 'off', + config: nil + ) + expect(result[:testing222]).to eq( + treatment: 'off', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:treatment][:treatment]).to eq('off') + expect(impressions[2][:treatment][:label]).to eq('in segment all') + expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + result1 = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3 ') + result2 = client.get_treatments_with_config_by_flag_set('', 'set_2') + result3 = client.get_treatments_with_config_by_flag_set(nil, 'set_1') + + expect(result1[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result2[:testing]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + result = client.get_treatments_with_config_by_flag_set('nico_test', 'invalid_set') + expect(result).to eq({}) + result = client.get_treatments_with_config_by_flag_set('nico_test', 'set_3') + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + end + + context '#get_treatments_with_config_by_flag_sets' do + it 'returns treatments and check impressions' do + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3', 'set_2']) + expect(result[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result[:testing]).to eq( + treatment: 'off', + config: nil + ) + expect(result[:testing222]).to eq( + treatment: 'off', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 3 + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[2][:treatment][:treatment]).to eq('on') + expect(impressions[2][:treatment][:label]).to eq('whitelisted') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + end + + it 'returns treatments with input validation' do + result1 = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3 ', 'invalid', '']) + result2 = client.get_treatments_with_config_by_flag_sets('', ['set_2', 123, 'se@t', '//s']) + result3 = client.get_treatments_with_config_by_flag_sets(nil, ['set_1']) + + expect(result1[:FACUNDO_TEST]).to eq( + treatment: 'on', + config: '{"color":"green"}' + ) + expect(result2[:testing]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + expect(result2[:testing222]).to eq( + treatment: 'control', + config: nil + ) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + + it 'returns CONTROL with treatment doesnt exist' do + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['invalid_set']) + expect(result).to eq({}) + result = client.get_treatments_with_config_by_flag_sets('nico_test', ['set_3']) + + sleep 0.5 + impressions = custom_impression_listener.queue + + expect(impressions.size).to eq 1 + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:FACUNDO_TEST) + expect(impressions[0][:treatment][:treatment]).to eq('on') + expect(impressions[0][:treatment][:label]).to eq('whitelisted') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) + end + end + context '#track' do it 'returns true' do properties = { diff --git a/spec/splitclient/split_factory_spec.rb b/spec/splitclient/split_factory_spec.rb index 13805082..14a1b20d 100644 --- a/spec/splitclient/split_factory_spec.rb +++ b/spec/splitclient/split_factory_spec.rb @@ -227,4 +227,93 @@ expect(SplitIoClient.split_factory_registry.redundant_active_factories).to be(6) end end + + context 'when using flagsets' do + let(:cache_adapter) { :memory } + let(:mode) { :standalone } + + it 'no flagsets used' do + factory = described_class.new('apikey', options) + + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage') + .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?since=-1') + .to_return(status: 200, body: []) + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') + .to_return(status: 200, body: '') + + tel_sync = factory.instance_variable_get(:@telemetry_synchronizer) + tel_mem_sync = tel_sync.instance_variable_get(:@synchronizer) + + expect(tel_mem_sync.instance_variable_get(:@flag_sets)).to eq(0) + expect(tel_mem_sync.instance_variable_get(:@flag_sets_invalid)).to eq(0) + + factory.client.destroy + end + + it 'no invalid flagsets used' do + factory = described_class.new('apikey', options.merge({:flag_sets_filter => ['set1', 'set2']})) + + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage') + .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?since=-1') + .to_return(status: 200, body: []) + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') + .to_return(status: 200, body: '') + + tel_sync = factory.instance_variable_get(:@telemetry_synchronizer) + tel_mem_sync = tel_sync.instance_variable_get(:@synchronizer) + + expect(tel_mem_sync.instance_variable_get(:@flag_sets)).to eq(2) + expect(tel_mem_sync.instance_variable_get(:@flag_sets_invalid)).to eq(0) + + factory.client.destroy + end + + it 'have invalid flagsets used' do + factory = described_class.new('apikey', options.merge({:flag_sets_filter => ['set1', 'se$t2', 'set3', 123, nil, '@@']})) + + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage') + .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?since=-1') + .to_return(status: 200, body: []) + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') + .to_return(status: 200, body: '') + + tel_sync = factory.instance_variable_get(:@telemetry_synchronizer) + tel_mem_sync = tel_sync.instance_variable_get(:@synchronizer) + + expect(tel_mem_sync.instance_variable_get(:@flag_sets)).to eq(6) + expect(tel_mem_sync.instance_variable_get(:@flag_sets_invalid)).to eq(4) + + factory.client.destroy + end + + it 'have invalid value for param flagsets used' do + factory = described_class.new('apikey', options.merge({:flag_sets_filter => 'set1'})) + + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage') + .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?since=-1') + .to_return(status: 200, body: []) + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') + .to_return(status: 200, body: '') + + tel_sync = factory.instance_variable_get(:@telemetry_synchronizer) + tel_mem_sync = tel_sync.instance_variable_get(:@synchronizer) + + expect(tel_mem_sync.instance_variable_get(:@flag_sets)).to eq(0) + expect(tel_mem_sync.instance_variable_get(:@flag_sets_invalid)).to eq(0) + + factory.client.destroy + end + end end diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index ffae8516..305a96d9 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -13,9 +13,11 @@ let(:api_token) { 'api-token-test' } let(:api_key) { 'client-spec-key' } let(:event_parser) { SplitIoClient::SSE::EventSource::EventParser.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:repositories) do { - splits: SplitIoClient::Cache::Repositories::SplitsRepository.new(config), + 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) diff --git a/spec/sse/sse_handler_spec.rb b/spec/sse/sse_handler_spec.rb index 28fcb1ed..db45782b 100644 --- a/spec/sse/sse_handler_spec.rb +++ b/spec/sse/sse_handler_spec.rb @@ -9,7 +9,9 @@ let(:api_key) { 'SSEHandler-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:push_status_queue) { Queue.new } diff --git a/spec/sse/workers/segments_worker_spec.rb b/spec/sse/workers/segments_worker_spec.rb index 642ebd1a..ec265993 100644 --- a/spec/sse/workers/segments_worker_spec.rb +++ b/spec/sse/workers/segments_worker_spec.rb @@ -13,7 +13,9 @@ let(:api_key) { 'SegmentsWorker-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } diff --git a/spec/sse/workers/splits_worker_spec.rb b/spec/sse/workers/splits_worker_spec.rb index 9ac38740..d4b88f1f 100644 --- a/spec/sse/workers/splits_worker_spec.rb +++ b/spec/sse/workers/splits_worker_spec.rb @@ -14,7 +14,9 @@ let(:api_key) { 'SplitsWorker-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } @@ -139,12 +141,13 @@ context 'instant ff update split notification' do 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.start splits_repository.set_change_number(1234) worker.add_to_queue(event_split_update_no_compression) - sleep 1 + sleep 2 split = splits_repository.get_split('bilal_split') expect(split[:name] == 'bilal_split') diff --git a/spec/telemetry/synchronizer_spec.rb b/spec/telemetry/synchronizer_spec.rb index a87c1a38..9a64d4c8 100644 --- a/spec/telemetry/synchronizer_spec.rb +++ b/spec/telemetry/synchronizer_spec.rb @@ -9,7 +9,7 @@ let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log), cache_adapter: :redis, mode: :consumer, redis_namespace: 'synch-test') } let(:adapter) { config.telemetry_adapter } let(:init_producer) { SplitIoClient::Telemetry::InitProducer.new(config) } - let(:synchronizer) { SplitIoClient::Telemetry::Synchronizer.new(config, nil, init_producer, nil, nil) } + let(:synchronizer) { SplitIoClient::Telemetry::Synchronizer.new(config, nil, init_producer, nil, nil, nil, nil) } let(:config_key) { 'synch-test.SPLITIO.telemetry.init' } it 'synchronize_config with data' do @@ -34,7 +34,9 @@ let(:evaluation_consumer) { SplitIoClient::Telemetry::EvaluationConsumer.new(config) } let(:init_consumer) { SplitIoClient::Telemetry::InitConsumer.new(config) } let(:runtime_consumer) { SplitIoClient::Telemetry::RuntimeConsumer.new(config) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } let(:api_key) { 'Synchronizer-key' } let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } @@ -59,7 +61,7 @@ telemetry_consumers, init_producer, { splits: splits_repository, segments: segments_repository }, - telemetry_api) + telemetry_api, 0, 0) end it 'with data' do @@ -168,9 +170,9 @@ telemetry_consumers, init_producer, { splits: splits_repository, segments: segments_repository }, - telemetry_api) + telemetry_api, 1, 0) - synchronizer.synchronize_config(1, 1, 100, 1, 0) + synchronizer.synchronize_config(1, 1, 100) expect(a_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .with(body: body_custom_config)).to have_been_made @@ -181,9 +183,9 @@ telemetry_consumers, init_producer, { splits: splits_repository, segments: segments_repository }, - telemetry_api) + telemetry_api, 1, 0) - synchronizer.synchronize_config(1, 1, 500, 1, 0) + synchronizer.synchronize_config(1, 1, 500) expect(a_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .with(body: body_default_config)).to have_been_made @@ -196,9 +198,9 @@ telemetry_consumers, init_producer, { splits: splits_repository, segments: segments_repository }, - telemetry_api) + telemetry_api, 1, 0) - synchronizer.synchronize_config(1, 1, 500, 1, 0) + synchronizer.synchronize_config(1, 1, 500) expect(a_request(:post, 'https://telemetry.split.io/api/v1/metrics/config') .with(body: body_proxy_config)).to have_been_made diff --git a/spec/telemetry/telemetry_init_spec.rb b/spec/telemetry/telemetry_init_spec.rb index 333d7df3..61061d15 100644 --- a/spec/telemetry/telemetry_init_spec.rb +++ b/spec/telemetry/telemetry_init_spec.rb @@ -73,8 +73,6 @@ expect(result[:t][:aF]).to eq(1) expect(result[:t][:rF]).to eq(0) expect(result[:t][:t]).to eq(%w[t1 t2]) - expect(result[:t][:fsT]).to eq(1) - expect(result[:t][:fsI]).to eq(0) adapter.redis.del(telemetry_config_key) end diff --git a/spec/test_data/splits/splits2.json b/spec/test_data/splits/splits2.json index abbc0b96..d591990a 100644 --- a/spec/test_data/splits/splits2.json +++ b/spec/test_data/splits/splits2.json @@ -97,7 +97,8 @@ } ] } - ] + ], + "sets":["set_1"] }, { "trafficTypeName":"user", @@ -254,7 +255,8 @@ } ] } - ] + ], + "sets":["set_1"] } ], "since":-1, From da38227f6624c4c68665f28dac8e34dd092594a7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 27 Nov 2023 15:26:26 -0800 Subject: [PATCH 15/27] Added redis flag set repo and validation cleanup --- lib/splitclient-rb.rb | 3 +- .../memory_repository.rb} | 2 +- .../flag_sets/redis_repository.rb | 40 ++ lib/splitclient-rb/clients/split_client.rb | 8 +- lib/splitclient-rb/split_config.rb | 9 +- lib/splitclient-rb/split_factory.rb | 6 +- lib/splitclient-rb/validators.rb | 37 +- .../clients/split_client_spec.rb | 2 +- spec/cache/fetchers/segment_fetch_spec.rb | 4 +- spec/cache/fetchers/split_fetch_spec.rb | 6 +- .../repositories/flag_set_repository_spec.rb | 32 +- .../repositories/segments_repository_spec.rb | 2 +- .../repositories/splits_repository_spec.rb | 2 +- .../stores/localhost_split_store_spec.rb | 2 +- spec/engine/parser/evaluator_spec.rb | 2 +- spec/engine/push_manager_spec.rb | 2 +- spec/engine/sync_manager_spec.rb | 2 +- spec/engine/synchronizer_spec.rb | 2 +- spec/engine_spec.rb | 359 +++++++++++++++--- spec/integrations/redis_client_spec.rb | 87 +++-- spec/repository_helper.rb | 4 +- spec/splitclient/split_client_spec.rb | 2 +- spec/sse/event_source/client_spec.rb | 2 +- spec/sse/sse_handler_spec.rb | 2 +- spec/sse/workers/segments_worker_spec.rb | 2 +- spec/sse/workers/splits_worker_spec.rb | 2 +- spec/telemetry/synchronizer_spec.rb | 2 +- spec/test_data/integrations/flag_sets.json | 5 + .../splits/engine/all_keys_matcher.json | 3 +- .../splits/engine/configurations.json | 3 +- .../splits/engine/dependency_matcher.json | 3 +- .../splits/engine/equal_to_set_matcher.json | 3 +- spec/test_data/splits/engine/flag_sets.json | 6 + .../splits/engine/impressions_test.json | 3 +- .../splits/equal_to_matcher/date_splits.json | 3 +- .../equal_to_matcher/negative_splits.json | 5 +- .../splits/equal_to_matcher/splits.json | 3 +- .../splits/equal_to_matcher/zero_splits.json | 3 +- spec/test_data/splits/flag_sets.json | 4 + 39 files changed, 526 insertions(+), 143 deletions(-) rename lib/splitclient-rb/cache/repositories/{flag_sets_repository.rb => flag_sets/memory_repository.rb} (96%) create mode 100644 lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb create mode 100644 spec/test_data/integrations/flag_sets.json create mode 100644 spec/test_data/splits/engine/flag_sets.json create mode 100644 spec/test_data/splits/flag_sets.json diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index f2337816..642db7d5 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -25,7 +25,8 @@ require 'splitclient-rb/cache/repositories/impressions_repository' require 'splitclient-rb/cache/repositories/events/memory_repository' require 'splitclient-rb/cache/repositories/events/redis_repository' -require 'splitclient-rb/cache/repositories/flag_sets_repository' +require 'splitclient-rb/cache/repositories/flag_sets/memory_repository' +require 'splitclient-rb/cache/repositories/flag_sets/redis_repository' require 'splitclient-rb/cache/repositories/impressions/memory_repository' require 'splitclient-rb/cache/repositories/impressions/redis_repository' require 'splitclient-rb/cache/senders/impressions_formatter' diff --git a/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb b/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb similarity index 96% rename from lib/splitclient-rb/cache/repositories/flag_sets_repository.rb rename to lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb index 4ebea579..a1a15aa0 100644 --- a/lib/splitclient-rb/cache/repositories/flag_sets_repository.rb +++ b/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb @@ -3,7 +3,7 @@ module SplitIoClient module Cache module Repositories - class FlagSetsRepository + class MemoryFlagSetsRepository def initialize(flag_sets = []) @sets_feature_flag_map = {} flag_sets.each{ |flag_set| @sets_feature_flag_map[flag_set] = Set[] } diff --git a/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb b/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb new file mode 100644 index 00000000..fce50168 --- /dev/null +++ b/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb @@ -0,0 +1,40 @@ +require 'concurrent' + +module SplitIoClient + module Cache + module Repositories + class RedisFlagSetsRepository < Repository + + def initialize(config) + super(config) + @adapter = SplitIoClient::Cache::Adapters::CacheAdapter.new(@config) + end + + def flag_set_exist?(flag_set) + @adapter.exists?(namespace_key(".flagSet.#{flag_set}")) + end + + def get_flag_set(flag_set) + @adapter.get_set(namespace_key(".flagSet.#{flag_set}")) + end + + def add_flag_set(flag_set) + # not implemented + end + + def remove_flag_set(flag_set) + # not implemented + end + + def add_feature_flag_to_flag_set(flag_set, feature_flag) + # not implemented + end + + def remove_feature_flag_from_flag_set(flag_set, feature_flag) + # not implemented + end + + end + end + end +end diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index ef1b3e5e..a419c704 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -63,7 +63,7 @@ def get_treatments_with_config(key, split_names, attributes = {}) end def get_treatments_by_flag_set(key, flag_set, attributes = {}) - valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_by_flag_set, [flag_set]) + valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set]) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET) return treatments if treatments.nil? @@ -73,7 +73,7 @@ def get_treatments_by_flag_set(key, flag_set, attributes = {}) end def get_treatments_by_flag_sets(key, flag_sets, attributes = {}) - valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_by_flag_sets, flag_sets) + valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS) return treatments if treatments.nil? @@ -83,13 +83,13 @@ def get_treatments_by_flag_sets(key, flag_sets, attributes = {}) end def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}) - valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_with_config_by_flag_set, [flag_set]) + valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set]) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET) end def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}) - valid_flag_set = @split_validator.valid_flag_sets(:get_treatments_with_config_by_flag_sets, flag_sets) + valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) end diff --git a/lib/splitclient-rb/split_config.rb b/lib/splitclient-rb/split_config.rb index 786c7cf5..da434c91 100644 --- a/lib/splitclient-rb/split_config.rb +++ b/lib/splitclient-rb/split_config.rb @@ -122,7 +122,7 @@ def initialize(opts = {}) @on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds @on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries - @flag_sets_filter = SplitConfig.sanitize_flag_set_filter(opts[:flag_sets_filter], @split_validator) + @flag_sets_filter = SplitConfig.sanitize_flag_set_filter(opts[:flag_sets_filter], @split_validator, opts[:cache_adapter], @logger) startup_log end @@ -520,7 +520,12 @@ def self.default_offline_refresh_rate 5 end - def self.sanitize_flag_set_filter(flag_sets, validator) + def self.sanitize_flag_set_filter(flag_sets, validator, adapter, logger) + return [] if flag_sets.nil? + if adapter == :redis + logger.warn("config: : flag_sets_filter is not applicable for Consumer modes where the SDK does not keep rollout data in sync. FlagSet filter was discarded") + return [] + end return validator.valid_flag_sets(:config, flag_sets) end diff --git a/lib/splitclient-rb/split_factory.rb b/lib/splitclient-rb/split_factory.rb index af88b192..10b9b46e 100644 --- a/lib/splitclient-rb/split_factory.rb +++ b/lib/splitclient-rb/split_factory.rb @@ -213,7 +213,11 @@ def build_sync_manager end def build_repositories - @flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new(@config.flag_sets_filter) + if @config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter + @flag_sets_repository = SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(@config) + else + @flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(@config.flag_sets_filter) + end @splits_repository = SplitsRepository.new(@config, @flag_sets_repository, @flag_sets_filter) @segments_repository = SegmentsRepository.new(@config) @impressions_repository = ImpressionsRepository.new(@config) diff --git a/lib/splitclient-rb/validators.rb b/lib/splitclient-rb/validators.rb index 2c403ee9..e75c85b4 100644 --- a/lib/splitclient-rb/validators.rb +++ b/lib/splitclient-rb/validators.rb @@ -46,18 +46,33 @@ def valid_matcher_arguments(args) end def valid_flag_sets(method, flag_sets) - return [] if flag_sets.nil? || !flag_sets.is_a?(Array) - + if flag_sets.nil? || !flag_sets.is_a?(Array) + @config.logger.error("#{method}: FlagSets must be a non-empty list.") + return [] + end + if flag_sets.empty? + @config.logger.error("#{method}: FlagSets must be a non-empty list.") + return [] + end + without_nil = Array.new + flag_sets.each { |flag_set| + without_nil.push(flag_set) if !flag_set.nil? + log_nil("flag set", method) if flag_set.nil? + } + if without_nil.length() == 0 + log_invalid_flag_set_type(method) + return [] + end valid_flag_sets = SortedSet[] - flag_sets.compact.uniq.select do |flag_set| - if !flag_set.is_a?(String) - log_invalid_type(:flag_set, method) + without_nil.compact.uniq.select do |flag_set| + if flag_set.nil? || !flag_set.is_a?(String) + log_invalid_flag_set_type(method) elsif flag_set.is_a?(String) && flag_set.empty? - log_nil(:flag_set, method) + log_invalid_flag_set_type(method) elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method) valid_flag_sets.add(flag_set.strip.downcase) else - @config.logger.warn("#{method}: you passed an invalid flag set, flag set name must be a non-empty String") + log_invalid_flag_set_type(method) end end !valid_flag_sets.empty? ? valid_flag_sets.to_a : [] @@ -93,7 +108,9 @@ def log_invalid_match(key, method) end def log_nil(key, method) - @config.logger.error("#{method}: you passed a nil #{key}, #{key} must be a non-empty String or a Symbol") + msg_text = String.new("#{method}: you passed a nil #{key}, #{key} must be a non-empty String") + msg_text << " or a Symbol" if !key.equal?("flag set") + @config.logger.error(msg_text) end def log_empty_string(key, method) @@ -104,6 +121,10 @@ def log_invalid_type(key, method) @config.logger.error("#{method}: you passed an invalid #{key} type, #{key} must be a non-empty String or a Symbol") end + def log_invalid_flag_set_type(method) + @config.logger.warn("#{method}: you passed an invalid flag set type, flag set must be a non-empty String") + end + def log_convert_numeric(key, method, value) @config.logger.warn("#{method}: #{key} \"#{value}\" is not of type String, converting") end diff --git a/spec/allocations/splitclient-rb/clients/split_client_spec.rb b/spec/allocations/splitclient-rb/clients/split_client_spec.rb index fd661269..0dd15231 100644 --- a/spec/allocations/splitclient-rb/clients/split_client_spec.rb +++ b/spec/allocations/splitclient-rb/clients/split_client_spec.rb @@ -5,7 +5,7 @@ describe SplitIoClient::SplitClient do let(:config) { SplitIoClient::SplitConfig.new(impressions_queue_size: 10) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } diff --git a/spec/cache/fetchers/segment_fetch_spec.rb b/spec/cache/fetchers/segment_fetch_spec.rb index 448d423f..e49c788a 100644 --- a/spec/cache/fetchers/segment_fetch_spec.rb +++ b/spec/cache/fetchers/segment_fetch_spec.rb @@ -33,7 +33,7 @@ context 'memory adapter' do let(:config) { SplitIoClient::SplitConfig.new } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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) } @@ -69,7 +69,7 @@ let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :redis) } let(:adapter) { SplitIoClient::Cache::Adapters::RedisAdapter.new(config.redis_url) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } diff --git a/spec/cache/fetchers/split_fetch_spec.rb b/spec/cache/fetchers/split_fetch_spec.rb index d43286d5..977f9e78 100644 --- a/spec/cache/fetchers/split_fetch_spec.rb +++ b/spec/cache/fetchers/split_fetch_spec.rb @@ -23,7 +23,7 @@ cache_adapter: :memory ) end - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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) } @@ -74,7 +74,7 @@ flag_sets_filter: ['set_2'] ) end - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new(['set_2']) } + 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(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } @@ -121,7 +121,7 @@ cache_adapter: :redis ) end - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } diff --git a/spec/cache/repositories/flag_set_repository_spec.rb b/spec/cache/repositories/flag_set_repository_spec.rb index c6a1355f..2848df19 100644 --- a/spec/cache/repositories/flag_set_repository_spec.rb +++ b/spec/cache/repositories/flag_set_repository_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' require 'set' -describe SplitIoClient::Cache::Repositories::FlagSetsRepository do - context 'test flag set repository' do +describe "Flag set repository" do + context 'test memory' do it 'test add/delete' do - flag_set = SplitIoClient::Cache::Repositories::FlagSetsRepository.new [] + flag_set = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new [] expect(flag_set.instance_variable_get(:@sets_feature_flag_map)).to eq({}) flag_set.add_flag_set('set_1') @@ -25,7 +25,7 @@ end it 'test add/delete feature flags to flag sets' do - flag_set = SplitIoClient::Cache::Repositories::FlagSetsRepository.new [] + flag_set = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new [] expect(flag_set.instance_variable_get(:@sets_feature_flag_map)).to eq({}) flag_set.add_flag_set('set_1') @@ -41,7 +41,31 @@ flag_set.remove_feature_flag_from_flag_set('set_1', 'feature2') expect(flag_set.get_flag_set('set_1')).to eq(Set[]) + end + end + + context 'test redis' do + let(:adapter) { SplitIoClient::Cache::Adapters::RedisAdapter.new('redis://127.0.0.1:6379/0') } + + it 'test get and exist' do + Redis.new.flushall + + adapter.add_to_set('SPLITIO.flagSet.set_1', 'feature1') + adapter.add_to_set('SPLITIO.flagSet.set_2', 'feature2') + flag_set = SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(SplitIoClient::SplitConfig.new(cache_adapter: :redis)) + + expect(flag_set.get_flag_set('set_1')).to eq(['feature1']) + + adapter.add_to_set('SPLITIO.flagSet.set_1', 'feature2') + expect(flag_set.get_flag_set('set_1').sort).to eq(['feature1', 'feature2']) + sleep 0.1 + expect(flag_set.get_flag_set('set_2')).to eq(['feature2']) + + adapter.delete_from_set('SPLITIO.flagSet.set_2', 'feature2') + sleep 0.1 + expect(flag_set.get_flag_set('set_2')).to eq([]) + Redis.new.flushall end end end diff --git a/spec/cache/repositories/segments_repository_spec.rb b/spec/cache/repositories/segments_repository_spec.rb index 0fea9953..cd2dd021 100644 --- a/spec/cache/repositories/segments_repository_spec.rb +++ b/spec/cache/repositories/segments_repository_spec.rb @@ -5,7 +5,7 @@ describe SplitIoClient::Cache::Repositories::SegmentsRepository do context 'memory adapter' do let(:repository) { described_class.new(@default_config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) } let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } let(:split_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config, flag_sets_repository, flag_set_filter) } diff --git a/spec/cache/repositories/splits_repository_spec.rb b/spec/cache/repositories/splits_repository_spec.rb index 28311675..351ed30d 100644 --- a/spec/cache/repositories/splits_repository_spec.rb +++ b/spec/cache/repositories/splits_repository_spec.rb @@ -6,7 +6,7 @@ describe SplitIoClient::Cache::Repositories::SplitsRepository do RSpec.shared_examples 'Splits Repository' do |cache_adapter| let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: cache_adapter) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(config)} let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:repository) { described_class.new(config, flag_sets_repository, flag_set_filter) } diff --git a/spec/cache/stores/localhost_split_store_spec.rb b/spec/cache/stores/localhost_split_store_spec.rb index b76f8fd1..73dbb90c 100644 --- a/spec/cache/stores/localhost_split_store_spec.rb +++ b/spec/cache/stores/localhost_split_store_spec.rb @@ -5,7 +5,7 @@ describe SplitIoClient::Cache::Stores::LocalhostSplitStore do let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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) } diff --git a/spec/engine/parser/evaluator_spec.rb b/spec/engine/parser/evaluator_spec.rb index 506be411..b3af7d57 100644 --- a/spec/engine/parser/evaluator_spec.rb +++ b/spec/engine/parser/evaluator_spec.rb @@ -4,7 +4,7 @@ describe SplitIoClient::Engine::Parser::Evaluator do let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(@default_config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + 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) } diff --git a/spec/engine/push_manager_spec.rb b/spec/engine/push_manager_spec.rb index 6089cb54..03f2187b 100644 --- a/spec/engine/push_manager_spec.rb +++ b/spec/engine/push_manager_spec.rb @@ -10,7 +10,7 @@ let(:api_key) { 'PushManager-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } diff --git a/spec/engine/sync_manager_spec.rb b/spec/engine/sync_manager_spec.rb index 3bca2a82..7bedc97b 100644 --- a/spec/engine/sync_manager_spec.rb +++ b/spec/engine/sync_manager_spec.rb @@ -16,7 +16,7 @@ let(:api_key) { 'SyncManager-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log), streaming_enabled: true) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } diff --git a/spec/engine/synchronizer_spec.rb b/spec/engine/synchronizer_spec.rb index 5add3c52..5a406419 100644 --- a/spec/engine/synchronizer_spec.rb +++ b/spec/engine/synchronizer_spec.rb @@ -14,7 +14,7 @@ let(:synchronizer) do api_key = 'Synchronizer-key' runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) - flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) + flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) 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) diff --git a/spec/engine_spec.rb b/spec/engine_spec.rb index 0fc71469..873ec0b4 100644 --- a/spec/engine_spec.rb +++ b/spec/engine_spec.rb @@ -29,44 +29,17 @@ let(:segments_json) do File.read(File.join(SplitIoClient.root, 'spec/test_data/segments/engine_segments.json')) end - let(:segments2_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/segments/engine_segments2.json')) - end +# let(:segments2_json) do +# File.read(File.join(SplitIoClient.root, 'spec/test_data/segments/engine_segments2.json')) +# end let(:all_keys_matcher_json) do File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/all_keys_matcher.json')) end - let(:killed_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/killed.json')) - end - let(:segment_deleted_matcher_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/segment_deleted_matcher.json')) - end - let(:segment_matcher_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/segment_matcher.json')) - end - let(:segment_matcher2_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/segment_matcher2.json')) - end - let(:whitelist_matcher_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/whitelist_matcher.json')) - end - let(:dependency_matcher_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/dependency_matcher.json')) - end - let(:impressions_test_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/impressions_test.json')) - end - let(:traffic_allocation_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/splits_traffic_allocation.json')) - end - let(:traffic_allocation_one_percent_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/splits_traffic_allocation_one_percent.json')) - end let(:configurations_json) do File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/configurations.json')) end - let(:equal_to_set_matcher_json) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/equal_to_set_matcher.json')) + let(:flag_sets_json) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/flag_sets.json')) end before do @@ -83,8 +56,9 @@ context '#equal_to_set_matcher and get_treatment validation attributes' do before do + equal_to_set_matcher_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/equal_to_set_matcher.json')) + load_splits(equal_to_set_matcher_json, flag_sets_json) sleep 1 - load_splits(equal_to_set_matcher_json) subject.block_until_ready end @@ -101,6 +75,7 @@ treatment: 'off', config: nil ) + destroy_factory end it 'get_treatment returns off' do @@ -119,6 +94,33 @@ expect(subject.get_treatments('nicolas', ['mauro_test'], {})).to eq( mauro_test: 'off' ) + destroy_factory + end + + it 'get_treatments_by_flag_set returns off' do + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', nil)).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2')).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {})).to eq( + mauro_test: 'off' + ) + destroy_factory + end + + it 'get_treatments_by_flag_sets returns off' do + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], nil)).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'])).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {})).to eq( + mauro_test: 'off' + ) + destroy_factory end end @@ -126,13 +128,14 @@ before do stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since/).to_return(status: 200, body: '') - load_splits(all_keys_matcher_json) + load_splits(all_keys_matcher_json, flag_sets_json) subject.block_until_ready end it 'returns CONTROL for random id' do expect(subject.get_treatment('random_user_id', 'my_random_feature')) .to eq SplitIoClient::Engine::Models::Treatment::CONTROL + destroy_factory end it 'returns CONTROL and label for incorrect feature name' do @@ -143,17 +146,20 @@ label: SplitIoClient::Engine::Models::Label::NOT_FOUND, change_number: nil ) + destroy_factory end it 'returns CONTROL on nil key' do expect(subject.get_treatment(nil, 'test_feature')).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed a nil key, key must be a non-empty String or a Symbol' + destroy_factory end it 'returns CONTROL on empty key' do expect(subject.get_treatment('', 'test_feature')).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty matching_key, ' \ 'matching_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns CONTROL on nil matching_key' do @@ -161,6 +167,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string) .to include 'get_treatment: you passed a nil matching_key, matching_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns control on empty matching_key' do @@ -168,6 +175,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty matching_key, ' \ 'matching_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns control on longer than specified max characters matching_key' do @@ -175,6 +183,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: matching_key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" + destroy_factory end it 'logs warning when Numeric matching_key' do @@ -189,6 +198,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed a nil bucketing_key, ' \ 'bucketing_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns control on empty bucketing_key' do @@ -196,6 +206,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty bucketing_key, ' \ 'bucketing_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns control on longer than specified max characters bucketing_key' do @@ -203,6 +214,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: bucketing_key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" + destroy_factory end it 'logs warning when Numeric bucketing_key' do @@ -210,6 +222,7 @@ expect(subject.get_treatment({ bucketing_key: value, matching_key: 'random_user_id' }, 'test_feature')) .to eq 'on' expect(log.string).to include "get_treatment: bucketing_key \"#{value}\" is not of type String, converting" + destroy_factory end #TODO We will remove multiple param in the future. @@ -219,6 +232,7 @@ label: nil, change_number: nil ) + destroy_factory end it 'returns control on empty key' do @@ -226,6 +240,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty matching_key, ' \ 'matching_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns control on NaN key' do @@ -233,6 +248,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an invalid matching_key type, ' \ 'matching_key must be a non-empty String or a Symbol' + destroy_factory end it 'returns control on longer than specified max characters key' do @@ -240,28 +256,33 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: matching_key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" + destroy_factory end it 'logs warning when Numeric key' do value = 123 expect(subject.get_treatment(value, 'test_feature')).to eq 'on' expect(log.string).to include "get_treatment: matching_key \"#{value}\" is not of type String, converting" + destroy_factory end it 'returns CONTROL on nil split_name' do expect(subject.get_treatment('random_user_id', nil)).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed a nil split_name, ' \ 'split_name must be a non-empty String or a Symbol' + destroy_factory end it 'returns CONTROL on empty split_name' do expect(subject.get_treatment('random_user_id', '')).to eq SplitIoClient::Engine::Models::Treatment::CONTROL + destroy_factory end it 'returns CONTROL on number split_name' do expect(subject.get_treatment('random_user_id', 123)).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an invalid split_name type, ' \ 'split_name must be a non-empty String or a Symbol' + destroy_factory end #TODO We will remove multiple param in the future. @@ -271,18 +292,21 @@ label: nil, change_number: nil ) + destroy_factory end it 'trims split_name and logs warning when extra whitespaces' do split_name = ' test_feature ' expect(subject.get_treatment('fake_user_id_1', split_name)).to eq 'on' expect(log.string).to include "get_treatment: feature_flag_name #{split_name} has extra whitespace, trimming" + destroy_factory end it 'returns CONTROL when non Hash attributes' do expect(subject.get_treatment('random_user_id', 'test_feature', ["I'm an Array"])) .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: attributes must be of type Hash' + destroy_factory end it 'returns CONTROL and logs warning when ready and split does not exist' do @@ -291,6 +315,7 @@ expect(log.string).to include 'get_treatment: you passed non_existing_feature ' \ 'that does not exist in this environment, please double check what feature flags exist ' \ 'in the Split user interface' + destroy_factory end it 'returns CONTROL with NOT_READY label when not ready' do @@ -299,12 +324,13 @@ expect(subject.get_treatment('random_user_id', 'test_feature')) .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: the SDK is not ready, the operation cannot be executed' + destroy_factory end end context '#get_treatment_with_config' do before do - load_splits(configurations_json) + load_splits(configurations_json, flag_sets_json) subject.block_until_ready sleep 1 end @@ -314,6 +340,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'on' expect(result[:config]).to eq '{"killed":false}' + destroy_factory end it 'returns the default treatment config on killed split' do @@ -321,6 +348,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'off' expect(result[:config]).to eq '{"killed":true}' + destroy_factory end it 'returns nil when no configs' do @@ -328,6 +356,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'on' expect(result[:config]).to eq nil + destroy_factory end it 'returns nil when no configs for feature' do @@ -335,6 +364,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'on' expect(result[:config]).to eq nil + destroy_factory end it 'returns control and logs the correct message on nil key' do @@ -344,6 +374,7 @@ expect(result[:config]).to eq nil expect(log.string) .to include 'get_treatment_with_config: you passed a nil key, key must be a non-empty String or a Symbol' + destroy_factory end it 'returns nil when killed and no configs for default treatment' do @@ -351,50 +382,58 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'off' expect(result[:config]).to eq nil + destroy_factory end end context '#get_treatments' do before do - load_splits(all_keys_matcher_json) + load_splits(all_keys_matcher_json, flag_sets_json) + subject.block_until_ready end it 'returns empty hash on nil split_names' do expect(subject.get_treatments('random_user_id', nil)).to be_nil expect(log.string).to include 'get_treatments: feature_flag_names must be a non-empty Array' + destroy_factory end it 'returns empty hash when no Array split_names' do expect(subject.get_treatments('random_user_id', Object.new)).to be_nil expect(log.string).to include 'get_treatments: feature_flag_names must be a non-empty Array' + destroy_factory end it 'returns empty hash on empty array split_names' do expect(subject.get_treatments('random_user_id', [])).to eq({}) expect(log.string).to include 'get_treatments: feature_flag_names must be a non-empty Array' + destroy_factory end it 'sanitizes split_names removing repeating and nil split_names' do treatments = subject.get_treatments('random_user_id', ['test_feature', nil, nil, 'test_feature']) expect(treatments.size).to eq 1 + destroy_factory end it 'warns when non string split_names' do expect(subject.get_treatments('random_user_id', [Object.new, Object.new])).to eq({}) expect(log.string).to include 'get_treatments: you passed an invalid feature_flag_name, ' \ 'flag name must be a non-empty String or a Symbol' + destroy_factory end it 'warns when empty split_names' do expect(subject.get_treatments('random_user_id', [''])).to eq({}) expect(log.string).to include 'get_treatments: you passed an empty feature_flag_name, ' \ 'flag name must be a non-empty String or a Symbol' + destroy_factory end end context '#get_treatments_with_config' do before do - load_splits(configurations_json) + load_splits(configurations_json, flag_sets_json) subject.block_until_ready end @@ -407,46 +446,168 @@ expect(result[:test_feature]).to eq(treatment: 'on', config: '{"killed":false}') expect(result[:no_configs_feature]).to eq(treatment: 'on', config: nil) expect(result[:killed_feature]).to eq(treatment: 'off', config: '{"killed":true}') + destroy_factory + end + end + + context '#get_treatments_by_flag_set' do + before do + load_splits(all_keys_matcher_json, flag_sets_json) + subject.block_until_ready + end + + it 'returns empty hash on nil split_names' do + expect(subject.get_treatments_by_flag_set('random_user_id', nil)).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + + it 'returns empty hash when no Array split_names' do + expect(subject.get_treatments_by_flag_set('random_user_id', Object.new)).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + + it 'returns empty hash on empty array split_names' do + expect(subject.get_treatments_by_flag_set('random_user_id', [])).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + + it 'sanitizes flagset names removing repeating and nil' do + treatments = subject.get_treatments_by_flag_set('random_user_id', ['set_1', nil, nil, 'set_1']) + expect(treatments.size).to eq 0 + destroy_factory + end + + it 'warns when non string flagset names' do + expect(subject.get_treatments_by_flag_set('random_user_id', [Object.new, Object.new])).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + + it 'warns when empty flagset names' do + expect(subject.get_treatments_by_flag_set('random_user_id', [''])).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + end + + context '#get_treatments_by_flag_sets' do + before do + load_splits(all_keys_matcher_json, flag_sets_json) + sleep 1 + subject.block_until_ready + end + + it 'returns empty hash on nil split_names' do + expect(subject.get_treatments_by_flag_sets('random_user_id', nil)).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_sets: FlagSets must be a non-empty list' + destroy_factory + end + + it 'returns empty hash when no Array split_names' do + expect(subject.get_treatments_by_flag_sets('random_user_id', Object.new)).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_sets: FlagSets must be a non-empty list' + destroy_factory + end + + it 'returns empty hash on empty array split_names' do + expect(subject.get_treatments_by_flag_sets('random_user_id', [])).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_sets: FlagSets must be a non-empty list' + destroy_factory + end + + it 'sanitizes flagset names removing repeating and nil' do + treatments = subject.get_treatments_by_flag_sets('random_user_id', ['set_1', nil, nil, 'set_1']) + expect(log.string).to include 'get_treatments_by_flag_sets: you passed a nil flag set, flag set must be a non-empty String' + expect(treatments.size).to eq 1 + destroy_factory + end + + it 'warns when non string flagset names' do + expect(subject.get_treatments_by_flag_sets('random_user_id', [Object.new, Object.new])).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_sets: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + + it 'warns when empty flagset names' do + expect(subject.get_treatments_by_flag_sets('random_user_id', [''])).to eq({}) + expect(log.string).to include 'get_treatments_by_flag_sets: you passed an invalid flag set type, flag set must be a non-empty String' + destroy_factory + end + end + + context '#get_treatments_with_config_by_flag_set' do + before do + load_splits(configurations_json, flag_sets_json) + subject.block_until_ready + end + + it 'returns the configs' do + result = subject.get_treatments_with_config_by_flag_set('fake_user_id_1', 'set_1') + expect(result.size).to eq 1 + expect(result[:test_feature]).to eq(treatment: 'on', config: '{"killed":false}') + destroy_factory + end + end + + context '#get_treatments_with_config_by_flag_set' do + before do + load_splits(configurations_json, flag_sets_json) + subject.block_until_ready + end + + it 'returns the configs' do + result = subject.get_treatments_with_config_by_flag_sets('fake_user_id_1', ['set_1']) + expect(result.size).to eq 1 + expect(result[:test_feature]).to eq(treatment: 'on', config: '{"killed":false}') + destroy_factory end end context 'all keys matcher' do before do - load_splits(all_keys_matcher_json) + load_splits(all_keys_matcher_json, flag_sets_json) subject.block_until_ready end it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'on' expect(subject.get_treatment('fake_user_id_2', 'test_feature')).to eq 'on' + destroy_factory end xit 'allocates minimum objects' do expect { subject.get_treatment('fake_user_id_1', 'test_feature') }.to allocate_max(283).objects expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'on' + destroy_factory end end context 'in segment matcher' do before do load_segments(segments_json) - - load_splits(segment_matcher_json) + segment_matcher_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/segment_matcher.json')) + load_splits(segment_matcher_json, flag_sets_json) subject.block_until_ready end it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_1', 'new_feature')).to eq 'on' + destroy_factory end it 'validates the feature is on for integer' do expect(subject.get_treatment(222, 'new_feature')).to eq 'on' + destroy_factory end it 'validates the feature is on for all ids multiple keys' do expect(subject.get_treatments('fake_user_id_1', %w[new_feature foo])).to eq( new_feature: 'on', foo: SplitIoClient::Engine::Models::Treatment::CONTROL ) + destroy_factory end it "[#{cache_adapter}] validates the feature is on for all ids multiple keys for integer key" do @@ -456,6 +617,7 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.size).to eq(1) + destroy_factory end it 'validates the feature is on for all ids multiple keys for integer key' do @@ -470,6 +632,7 @@ .new(subject.instance_variable_get(:@impressions_repository)) .call(true, impressions) .select { |im| im[:f] == :new_feature }[0][:i].size).to eq(2) + destroy_factory end it 'validates the feature by bucketing_key' do @@ -479,12 +642,14 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.first[:i][:k]).to eq('fake_user_id_1') + destroy_factory end it 'validates the feature by bucketing_key for nil matching_key' do key = { bucketing_key: 'fake_user_id_1' } expect(subject.get_treatment(key, 'new_feature')).to eq 'control' + destroy_factory end it 'validates the feature by bucketing_key' do @@ -494,14 +659,17 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.first[:i][:k]).to eq('222') + destroy_factory end it 'validates the feature returns default treatment for non matching ids' do expect(subject.get_treatment('fake_user_id_3', 'new_feature')).to eq 'def_test' + destroy_factory end it 'returns default treatment for active splits with a non matching id' do expect(subject.get_treatment('fake_user_id_3', 'new_feature')).to eq 'def_test' + destroy_factory end end @@ -509,7 +677,8 @@ before do load_segments(segments_json) - load_splits(segment_matcher2_json) + segment_matcher2_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/segment_matcher2.json')) + load_splits(segment_matcher2_json, flag_sets_json) subject.block_until_ready end @@ -520,6 +689,7 @@ new_feature3: 'on', new_feature4: SplitIoClient::Engine::Models::Treatment::CONTROL ) + destroy_factory end it 'validates the feature by bucketing_key' do @@ -532,6 +702,7 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.first[:i][:k]).to eq('fake_user_id_1') + destroy_factory end it 'validates the feature by bucketing_key for nil matching_key' do @@ -539,40 +710,48 @@ expect(subject.get_treatments(key, ['new_feature'])) .to eq(new_feature: SplitIoClient::Engine::Models::Treatment::CONTROL) + destroy_factory end it 'validates the feature returns default treatment for non matching ids' do expect(subject.get_treatments('fake_user_id_3', ['new_feature'])).to eq(new_feature: 'def_test') + destroy_factory end it 'returns default treatment for active splits with a non matching id' do expect(subject.get_treatments('fake_user_id_3', ['new_feature'])).to eq(new_feature: 'def_test') + destroy_factory end end context 'whitelist matcher' do before do - load_splits(whitelist_matcher_json) + whitelist_matcher_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/whitelist_matcher.json')) + load_splits(whitelist_matcher_json, flag_sets_json) subject.block_until_ready end it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_1', 'test_whitelist')).to eq 'on' + destroy_factory end it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_2', 'test_whitelist')).to eq 'off' + destroy_factory end end context 'dependency matcher' do before do - load_splits(dependency_matcher_json) + dependency_matcher_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/dependency_matcher.json')) + load_splits(dependency_matcher_json, flag_sets_json) subject.block_until_ready end it 'returns on treatment' do expect(subject.get_treatment('fake_user_id_1', 'test_dependency')).to eq 'on' + destroy_factory end it 'produces only 1 impression' do @@ -580,12 +759,15 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.size).to eq(1) + destroy_factory end end context 'killed feature' do before do - load_splits(killed_json) + killed_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/killed.json')) + load_splits(killed_json, flag_sets_json) + subject.block_until_ready end it 'returns default treatment for killed splits' do @@ -593,6 +775,7 @@ expect(subject.get_treatment('fake_user_id_1', 'test_killed')).to eq 'def_test' expect(subject.get_treatment('fake_user_id_2', 'test_killed')).to eq 'def_test' expect(subject.get_treatment('fake_user_id_3', 'test_killed')).to eq 'def_test' + destroy_factory end end @@ -600,11 +783,14 @@ before do load_segments(segments_json) - load_splits(segment_deleted_matcher_json) + segment_deleted_matcher_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/segment_deleted_matcher.json')) + load_splits(segment_deleted_matcher_json, flag_sets_json) + subject.block_until_ready end it 'returns control for deleted splits' do expect(subject.get_treatment('fake_user_id_3', 'new_feature')).to eq 'control' + destroy_factory end end @@ -638,12 +824,14 @@ expect(range.cover?(treatments[i])).to be true i += 1 end + destroy_factory end end describe 'impressions' do before do - load_splits(impressions_test_json) + impressions_test_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/impressions_test.json')) + load_splits(impressions_test_json, flag_sets_json) stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since/).to_return(status: 200, body: '') end @@ -659,6 +847,7 @@ impressions = customer_impression_listener.queue expect(impressions.size >= 2).to be true + destroy_factory end it 'returns correct impressions for get_treatments' do @@ -676,11 +865,13 @@ expect(impressions.select { |i| i[:split_name] == :sample_feature }.size).to eq(6) expect(impressions.select { |i| i[:split_name] == :beta_feature }.size).to eq(6) + destroy_factory end context 'traffic allocations' do before do - load_splits(traffic_allocation_json) + traffic_allocation_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/splits_traffic_allocation.json')) + load_splits(traffic_allocation_json, flag_sets_json) subject.block_until_ready end @@ -688,43 +879,50 @@ expect(subject.get_treatment('01', 'Traffic_Allocation_UI')).to eq('off') expect(subject.get_treatment('ab', 'Traffic_Allocation_UI')).to eq('off') expect(subject.get_treatment('00b0', 'Traffic_Allocation_UI')).to eq('off') + destroy_factory end it 'returns expected treatment when traffic alllocation < 100' do expect(subject.get_treatment('01', 'Traffic_Allocation_UI3')).to eq('off') expect(subject.get_treatment('ab', 'Traffic_Allocation_UI3')).to eq('off') expect(subject.get_treatment('00b0', 'Traffic_Allocation_UI3')).to eq('off') + destroy_factory end it 'returns expected treatment when traffic alllocation is 0' do expect(subject.get_treatment('01', 'Traffic_Allocation_UI4')).to eq('on') expect(subject.get_treatment('ab', 'Traffic_Allocation_UI4')).to eq('on') expect(subject.get_treatment('00b0', 'Traffic_Allocation_UI4')).to eq('on') + destroy_factory end it 'returns "not in split" label' do subject.get_treatment('test', 'Traffic_Allocation_UI2') impressions_repository = subject.instance_variable_get(:@impressions_repository) expect(impressions_repository.batch[0][:i][:r]).to eq(SplitIoClient::Engine::Models::Label::NOT_IN_SPLIT) + destroy_factory end end end context 'traffic allocation one percent' do before do - load_splits(traffic_allocation_one_percent_json) + traffic_allocation_one_percent_json = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/splits_traffic_allocation_one_percent.json')) + load_splits(traffic_allocation_one_percent_json, flag_sets_json) + subject.block_until_ready end it 'returns expected treatment' do allow_any_instance_of(SplitIoClient::Splitter).to receive(:bucket).and_return(1) subject.block_until_ready expect(subject.get_treatment('test', 'Traffic_Allocation_One_Percent')).to eq('on') + destroy_factory end end describe 'client destroy' do before do - load_splits(all_keys_matcher_json) + load_splits(all_keys_matcher_json, flag_sets_json) end it 'returns control' do @@ -734,7 +932,7 @@ subject.block_until_ready expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'on' sleep 0.5 - subject.destroy + destroy_factory expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'control' end end @@ -748,6 +946,7 @@ it 'returns control' do allow(subject.instance_variable_get(:@impressions_repository)) .to receive(:add).and_raise(Redis::CannotConnectError) + destroy_factory end end @@ -755,6 +954,7 @@ before do stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since/) .to_return(status: 200, body: all_keys_matcher_json) + subject.block_until_ready end it 'fetches and deletes events' do @@ -776,6 +976,7 @@ ) expect(subject.instance_variable_get(:@events_repository).clear).to eq([]) + destroy_factory end end @@ -783,30 +984,35 @@ before do stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since/) .to_return(status: 200, body: all_keys_matcher_json) - end + subject.block_until_ready + end it 'event is not added when nil key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track(nil, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed a nil key, key must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when empty key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track('', 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed an empty key, key must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when nil key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track(nil, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed a nil key, key must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when no Integer, String or Symbol key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track(Object.new, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed an invalid key type, key must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when longer than specified max characters key' do @@ -814,6 +1020,7 @@ expect(subject.track(very_long_key, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" + destroy_factory end it 'event is added and a Warn is logged when Integer key' do @@ -822,6 +1029,7 @@ value = 123 expect(subject.track(value, 'traffic_type', 'event_type', 123)).to be true expect(log.string).to include "track: key \"#{value}\" is not of type String, converting" + destroy_factory end it 'event is not added when nil traffic_type_name' do @@ -829,6 +1037,7 @@ expect(subject.track(1, nil, 'event_type', 123)).to be false expect(log.string).to include 'track: you passed a nil traffic_type_name, ' \ 'traffic_type_name must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when empty string traffic_type_name' do @@ -836,6 +1045,7 @@ expect(subject.track(1, '', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed an empty traffic_type_name, ' \ 'traffic_type_name must be a non-empty String or a Symbol' + destroy_factory end it 'event is added and a Warn is logged when capitalized traffic_type_name' do @@ -845,6 +1055,7 @@ expect(subject.track('key', 'TRAFFIC_TYPE', 'event_type', 123)).to be true expect(log.string).to include 'track: traffic_type_name should be all lowercase - ' \ 'converting string to lowercase' + destroy_factory end it 'event is not added when nil event_type' do @@ -852,6 +1063,7 @@ expect(subject.track('key', 'traffic_type', nil, 123)).to be false expect(log.string).to include 'track: you passed a nil event_type, ' \ 'event_type must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when no String or Symbol event_type' do @@ -859,6 +1071,7 @@ expect(subject.track('key', 'traffic_type', Object.new, 123)).to be false expect(log.string).to include 'track: you passed an invalid event_type type, ' \ 'event_type must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when empty event_type' do @@ -866,6 +1079,7 @@ expect(subject.track('key', 'traffic_type', '', 123)).to be false expect(log.string).to include 'track: you passed an empty event_type, ' \ 'event_type must be a non-empty String or a Symbol' + destroy_factory end it 'event is not added when event_type does not conform with specified format' do @@ -873,29 +1087,34 @@ expect(subject.track('key', 'traffic_type', 'foo@bar', 123)).to be false expect(log.string).to include 'event_type must adhere to the regular expression ' \ '^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$. ' + destroy_factory end it 'event is not added when no Integer value' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 'non-integer')).to be false expect(log.string).to include 'track: value must be Numeric' + destroy_factory end it 'event is added when nil value' do expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', nil)).to be true + destroy_factory end it 'event is not added when non Hash properties' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, 'not a Hash')).to be false expect(log.string).to include 'track: properties must be a Hash' + destroy_factory end it 'event is not added when error calling add' do expect(subject.instance_variable_get(:@events_repository)).to receive(:add).and_throw(StandardError) expect(subject.track('key', 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include '[splitclient-rb] Unexpected exception in track' + destroy_factory end it 'warns users when property count exceeds 300' do @@ -905,6 +1124,7 @@ expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be true expect(log.string).to include 'Event has more than 300 properties. Some of them will be trimmed when processed' + destroy_factory end it 'removes non String key properties' do @@ -916,6 +1136,7 @@ .to eq [properties.select { |key, _| key.is_a?(String) }, 5] expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be true + destroy_factory end it 'changes invalid property values to nil' do @@ -931,6 +1152,7 @@ expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be true expect(log.string).to include 'Property invalid_property_value is of invalid type. Setting value to nil' + destroy_factory end it 'event is not added when properties size exceeds threshold' do @@ -940,7 +1162,9 @@ expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be false expect(log.string).to include 'The maximum size allowed for the properties is 32768. ' \ 'Current is 33078. Event not queued' + destroy_factory end + it 'event is added and a Warn is logged when traffic type does not exist' do expect(subject.instance_variable_get(:@events_repository)).to receive(:add) allow(subject).to receive(:ready?).and_return(true) @@ -951,7 +1175,7 @@ expect(log.string).to include "track: Traffic Type #{traffic_type_name} " \ "does not have any corresponding feature flags in this environment, make sure you're tracking " \ 'your events to a valid traffic type defined in the Split user interface' - subject.destroy + destroy_factory end end end @@ -960,6 +1184,9 @@ let(:all_keys_matcher_json) do File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/all_keys_matcher.json')) end + let(:flag_sets_json) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/engine/flag_sets.json')) + end before do stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since.*/) @@ -968,7 +1195,7 @@ context 'standalone mode' do subject do - SplitIoClient::SplitFactory.new('engine-standalone-key', + SplitIoClient::SplitFactory.new('engine-standalone-key', logger: Logger.new('/dev/null'), cache_adapter: :memory, mode: :standalone, @@ -981,6 +1208,7 @@ it 'fetch splits' do subject.block_until_ready expect(subject.instance_variable_get(:@splits_repository).splits.size).to eq(1) + subject.destroy end end @@ -1012,7 +1240,7 @@ # config.max_cache_size set to 1 forcing cache adapter to fallback to redis it 'retrieves splits from redis adapter in a single mget call' do - load_splits(all_keys_matcher_json) + load_splits(all_keys_matcher_json, flag_sets_json) expect_any_instance_of(Redis) .to receive(:mget).once.and_call_original @@ -1020,6 +1248,7 @@ .not_to receive(:get) subject.get_treatments(222, %w[new_feature foo test_feature]) + subject.destroy end end end @@ -1035,12 +1264,13 @@ private -def load_splits(splits_json) +def load_splits(splits_json, flag_sets_json) if @mode.equal?(:standalone) stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?since.*/) .to_return(status: 200, body: splits_json) else add_splits_to_repository(splits_json) + add_flag_sets_to_redis(flag_sets_json) end end @@ -1066,3 +1296,22 @@ def add_segments_to_repository(segments_json) segments_repository.add_to_segment(JSON.parse(segments_json, symbolize_names: true)) end + +def add_flag_sets_to_redis(flag_sets_json) + repository = subject.instance_variable_get(:@splits_repository) + adapter = repository.instance_variable_get(:@adapter) + JSON.parse(flag_sets_json, symbolize_names: true).each do |key, values| + values.each { |value| + adapter.add_to_set("test.SPLITIO.flagSet." + key.to_s, value) + } + end +end + +def destroy_factory + config = subject.instance_variable_get(:@config) + if config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter + redis = config.cache_adapter.instance_variable_get(:@redis) + redis.close + end + subject.destroy +end diff --git a/spec/integrations/redis_client_spec.rb b/spec/integrations/redis_client_spec.rb index 6f7da9b5..b19733db 100644 --- a/spec/integrations/redis_client_spec.rb +++ b/spec/integrations/redis_client_spec.rb @@ -36,6 +36,10 @@ File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment3.json')) end + let(:flag_sets) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/flag_sets.json')) + end + let(:client) { factory.client } let(:config) { client.instance_variable_get(:@config) } @@ -44,6 +48,7 @@ load_segment_redis(segment1, client) load_segment_redis(segment2, client) load_segment_redis(segment3, client) + load_flag_sets_redis(flag_sets, client) end after do @@ -601,17 +606,17 @@ expect(impressions[0][:treatment][:label]).to eq('whitelisted') expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing) - expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[2][:matching_key]).to eq('nico_test') - expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:split_name]).to eq(:testing) expect(impressions[2][:treatment][:treatment]).to eq('off') - expect(impressions[2][:treatment][:label]).to eq('in segment all') - expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[2][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -695,17 +700,17 @@ expect(impressions[2][:treatment][:label]).to eq('whitelisted') expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[0][:matching_key]).to eq('nico_test') - expect(impressions[0][:split_name]).to eq(:testing) - expect(impressions[0][:treatment][:treatment]).to eq('off') - expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:split_name]).to eq(:testing) expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in segment all') - expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing222) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in segment all') + expect(impressions[0][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -787,17 +792,17 @@ expect(impressions[0][:treatment][:label]).to eq('whitelisted') expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing) - expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[2][:matching_key]).to eq('nico_test') - expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:split_name]).to eq(:testing) expect(impressions[2][:treatment][:treatment]).to eq('off') - expect(impressions[2][:treatment][:label]).to eq('in segment all') - expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[2][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -881,17 +886,17 @@ expect(impressions[2][:treatment][:label]).to eq('whitelisted') expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[0][:matching_key]).to eq('nico_test') - expect(impressions[0][:split_name]).to eq(:testing) - expect(impressions[0][:treatment][:treatment]).to eq('off') - expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:split_name]).to eq(:testing) expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in segment all') - expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing222) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in segment all') + expect(impressions[0][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -1006,3 +1011,13 @@ def load_segment_redis(segment_json, cli) segments_repository.add_to_segment(JSON.parse(segment_json, symbolize_names: true)) end + +def load_flag_sets_redis(flag_sets_json, client) + repository = client.instance_variable_get(:@splits_repository) + adapter = repository.instance_variable_get(:@adapter) + JSON.parse(flag_sets_json, symbolize_names: true).each do |key, values| + values.each { |value| + adapter.add_to_set("test.SPLITIO.flagSet." + key.to_s, value) + } + end +end diff --git a/spec/repository_helper.rb b/spec/repository_helper.rb index e6abf0db..994c9ca9 100644 --- a/spec/repository_helper.rb +++ b/spec/repository_helper.rb @@ -7,7 +7,7 @@ it 'with flag set filter' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(['set_1', 'set_2']) - flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new(['set_1', 'set_2']) + flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(['set_1', 'set_2']) feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, @@ -29,7 +29,7 @@ it 'without flag set filter' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - flag_sets_repository = SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) + flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index 5926bca9..c82d225d 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -5,7 +5,7 @@ describe SplitIoClient::SplitClient do let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) } let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([]) } + 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(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 305a96d9..416b66ad 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -13,7 +13,7 @@ let(:api_token) { 'api-token-test' } let(:api_key) { 'client-spec-key' } let(:event_parser) { SplitIoClient::SSE::EventSource::EventParser.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([])} let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:repositories) do { diff --git a/spec/sse/sse_handler_spec.rb b/spec/sse/sse_handler_spec.rb index db45782b..e6a314dc 100644 --- a/spec/sse/sse_handler_spec.rb +++ b/spec/sse/sse_handler_spec.rb @@ -9,7 +9,7 @@ let(:api_key) { 'SSEHandler-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } diff --git a/spec/sse/workers/segments_worker_spec.rb b/spec/sse/workers/segments_worker_spec.rb index ec265993..37d6e868 100644 --- a/spec/sse/workers/segments_worker_spec.rb +++ b/spec/sse/workers/segments_worker_spec.rb @@ -13,7 +13,7 @@ let(:api_key) { 'SegmentsWorker-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } diff --git a/spec/sse/workers/splits_worker_spec.rb b/spec/sse/workers/splits_worker_spec.rb index d4b88f1f..47f92817 100644 --- a/spec/sse/workers/splits_worker_spec.rb +++ b/spec/sse/workers/splits_worker_spec.rb @@ -14,7 +14,7 @@ let(:api_key) { 'SplitsWorker-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + 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) } diff --git a/spec/telemetry/synchronizer_spec.rb b/spec/telemetry/synchronizer_spec.rb index 9a64d4c8..18e67594 100644 --- a/spec/telemetry/synchronizer_spec.rb +++ b/spec/telemetry/synchronizer_spec.rb @@ -34,7 +34,7 @@ let(:evaluation_consumer) { SplitIoClient::Telemetry::EvaluationConsumer.new(config) } let(:init_consumer) { SplitIoClient::Telemetry::InitConsumer.new(config) } let(:runtime_consumer) { SplitIoClient::Telemetry::RuntimeConsumer.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::FlagSetsRepository.new([])} + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } diff --git a/spec/test_data/integrations/flag_sets.json b/spec/test_data/integrations/flag_sets.json new file mode 100644 index 00000000..f9f210f4 --- /dev/null +++ b/spec/test_data/integrations/flag_sets.json @@ -0,0 +1,5 @@ +{ + "set_1": ["testing222"], + "set_2": ["testing222", "testing"], + "set_3": ["FACUNDO_TEST"] +} diff --git a/spec/test_data/splits/engine/all_keys_matcher.json b/spec/test_data/splits/engine/all_keys_matcher.json index 0b8faf7d..107e5e2d 100644 --- a/spec/test_data/splits/engine/all_keys_matcher.json +++ b/spec/test_data/splits/engine/all_keys_matcher.json @@ -33,7 +33,8 @@ } ] } - ] + ], + "sets": ["set_1"] } ] } diff --git a/spec/test_data/splits/engine/configurations.json b/spec/test_data/splits/engine/configurations.json index f8488258..05ccef7a 100644 --- a/spec/test_data/splits/engine/configurations.json +++ b/spec/test_data/splits/engine/configurations.json @@ -34,7 +34,8 @@ } ] } - ] + ], + "sets": ["set_1"] }, { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", diff --git a/spec/test_data/splits/engine/dependency_matcher.json b/spec/test_data/splits/engine/dependency_matcher.json index a43f1707..5a85fa41 100644 --- a/spec/test_data/splits/engine/dependency_matcher.json +++ b/spec/test_data/splits/engine/dependency_matcher.json @@ -35,7 +35,8 @@ } ] } - ] + ], + "sets": ["set_4"] }, { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", 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 b7dc2515..b7c07f23 100644 --- a/spec/test_data/splits/engine/equal_to_set_matcher.json +++ b/spec/test_data/splits/engine/equal_to_set_matcher.json @@ -116,7 +116,8 @@ ], "label": "default rule" } - ] + ], + "sets": ["set_2"] } ] } \ No newline at end of file diff --git a/spec/test_data/splits/engine/flag_sets.json b/spec/test_data/splits/engine/flag_sets.json new file mode 100644 index 00000000..cc6ccb23 --- /dev/null +++ b/spec/test_data/splits/engine/flag_sets.json @@ -0,0 +1,6 @@ +{ + "set_1": ["test_feature"], + "set_2": ["mauro_test"], + "set_3": ["sample_feature", "beta_feature"], + "set_4": ["test_whitelist"] +} diff --git a/spec/test_data/splits/engine/impressions_test.json b/spec/test_data/splits/engine/impressions_test.json index 6c5ae451..c4743e68 100644 --- a/spec/test_data/splits/engine/impressions_test.json +++ b/spec/test_data/splits/engine/impressions_test.json @@ -40,7 +40,8 @@ ], "label": "default label" } - ] + ], + "sets": ["set_3"] }, { "orgId":"cee838c0-b3eb-11e5-855f-4eacec19f7bf", 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 3e2518d2..fa5fa89b 100644 --- a/spec/test_data/splits/equal_to_matcher/date_splits.json +++ b/spec/test_data/splits/equal_to_matcher/date_splits.json @@ -43,7 +43,8 @@ } ] } - ] + ], + "sets": ["set_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 3808ca5f..84f5fc33 100644 --- a/spec/test_data/splits/equal_to_matcher/negative_splits.json +++ b/spec/test_data/splits/equal_to_matcher/negative_splits.json @@ -43,7 +43,8 @@ } ] } - ] - } + ], + "sets": ["set_1"] + } ] } diff --git a/spec/test_data/splits/equal_to_matcher/splits.json b/spec/test_data/splits/equal_to_matcher/splits.json index fec5f93a..13f2cae6 100644 --- a/spec/test_data/splits/equal_to_matcher/splits.json +++ b/spec/test_data/splits/equal_to_matcher/splits.json @@ -43,7 +43,8 @@ } ] } - ] + ], + "sets": ["set_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 d976b9aa..17c63578 100644 --- a/spec/test_data/splits/equal_to_matcher/zero_splits.json +++ b/spec/test_data/splits/equal_to_matcher/zero_splits.json @@ -43,7 +43,8 @@ } ] } - ] + ], + "sets": ["set_1"] } ] } diff --git a/spec/test_data/splits/flag_sets.json b/spec/test_data/splits/flag_sets.json new file mode 100644 index 00000000..f6a03b28 --- /dev/null +++ b/spec/test_data/splits/flag_sets.json @@ -0,0 +1,4 @@ +{ + "set_1": ["test_1_ruby", "sample_feature"], + "set_2": ["sample_feature"] +} From 7ac3a85e6fb76f224096e20a84a2ae88011dbdbe Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 28 Nov 2023 12:50:06 -0800 Subject: [PATCH 16/27] used redis piplined and updated flag_set repositories to fetch array --- .../flag_sets/memory_repository.rb | 6 ++++-- .../flag_sets/redis_repository.rb | 15 +++++++++++--- .../cache/repositories/splits_repository.rb | 6 ++---- .../repositories/flag_set_repository_spec.rb | 20 +++++++++---------- spec/integrations/in_memory_client_spec.rb | 2 +- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb b/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb index a1a15aa0..bb844e8d 100644 --- a/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +++ b/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb @@ -13,8 +13,10 @@ def flag_set_exist?(flag_set) @sets_feature_flag_map.key?(flag_set) end - def get_flag_set(flag_set) - @sets_feature_flag_map[flag_set] + def get_flag_sets(flag_sets) + to_return = Array.new + flag_sets.each { |flag_set| to_return.concat(@sets_feature_flag_map[flag_set].to_a)} + to_return.uniq end def add_flag_set(flag_set) diff --git a/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb b/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb index fce50168..9483e6a4 100644 --- a/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +++ b/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb @@ -7,15 +7,24 @@ class RedisFlagSetsRepository < Repository def initialize(config) super(config) - @adapter = SplitIoClient::Cache::Adapters::CacheAdapter.new(@config) + @adapter = SplitIoClient::Cache::Adapters::RedisAdapter.new(@config.redis_url) end def flag_set_exist?(flag_set) @adapter.exists?(namespace_key(".flagSet.#{flag_set}")) end - def get_flag_set(flag_set) - @adapter.get_set(namespace_key(".flagSet.#{flag_set}")) + def get_flag_sets(flag_sets) + result = @adapter.redis.pipelined do |pipeline| + flag_sets.each do |flag_set| + pipeline.smembers(namespace_key(".flagSet.#{flag_set}")) + end + end + to_return = Array.new + result.each do |flag_set| + flag_set.each { |feature_flag_name| to_return.push(feature_flag_name.to_s)} + end + to_return.uniq end def add_flag_set(flag_set) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 989bd675..64329c9d 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -128,9 +128,7 @@ def get_feature_flags_by_sets(flag_sets) end sets_to_fetch.push(flag_set) end - to_return = Array.new - sets_to_fetch.each { |flag_set| to_return.concat(@flag_sets.get_flag_set(flag_set).to_a)} - to_return.uniq + @flag_sets.get_flag_sets(flag_sets) end def is_flag_set_exist(flag_set) @@ -198,7 +196,7 @@ def remove_from_flag_sets(feature_flag) if !feature_flag[:sets].nil? for flag_set in feature_flag[:sets] @flag_sets.remove_feature_flag_from_flag_set(flag_set, feature_flag[:name]) - if is_flag_set_exist(flag_set) && @flag_sets.get_flag_set(flag_set).length == 0 && !@flag_set_filter.should_filter? + if is_flag_set_exist(flag_set) && @flag_sets.get_flag_sets([flag_set]).length == 0 && !@flag_set_filter.should_filter? @flag_sets.remove_flag_set(flag_set) end end diff --git a/spec/cache/repositories/flag_set_repository_spec.rb b/spec/cache/repositories/flag_set_repository_spec.rb index 2848df19..82100e3b 100644 --- a/spec/cache/repositories/flag_set_repository_spec.rb +++ b/spec/cache/repositories/flag_set_repository_spec.rb @@ -11,11 +11,11 @@ flag_set.add_flag_set('set_1') expect(flag_set.flag_set_exist?('set_1')).to eq(true) - expect(flag_set.get_flag_set('set_1')).to eq(Set[]) + expect(flag_set.get_flag_sets(['set_1'])).to eq([]) flag_set.add_flag_set('set_2') expect(flag_set.flag_set_exist?('set_2')).to eq(true) - expect(flag_set.get_flag_set('set_2')).to eq(Set[]) + expect(flag_set.get_flag_sets(['set_2'])).to eq([]) flag_set.remove_flag_set('set_1') expect(flag_set.flag_set_exist?('set_1')).to eq(false) @@ -31,16 +31,16 @@ flag_set.add_flag_set('set_1') flag_set.add_feature_flag_to_flag_set('set_1', 'feature1') expect(flag_set.flag_set_exist?('set_1')).to eq(true) - expect(flag_set.get_flag_set('set_1')).to eq(Set['feature1']) + expect(flag_set.get_flag_sets(['set_1'])).to eq(['feature1']) flag_set.add_feature_flag_to_flag_set('set_1', 'feature2') - expect(flag_set.get_flag_set('set_1')).to eq(Set['feature1', 'feature2']) + expect(flag_set.get_flag_sets(['set_1'])).to eq(['feature1', 'feature2']) flag_set.remove_feature_flag_from_flag_set('set_1', 'feature1') - expect(flag_set.get_flag_set('set_1')).to eq(Set['feature2']) + expect(flag_set.get_flag_sets(['set_1'])).to eq(['feature2']) flag_set.remove_feature_flag_from_flag_set('set_1', 'feature2') - expect(flag_set.get_flag_set('set_1')).to eq(Set[]) + expect(flag_set.get_flag_sets(['set_1'])).to eq([]) end end @@ -54,16 +54,16 @@ adapter.add_to_set('SPLITIO.flagSet.set_2', 'feature2') flag_set = SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(SplitIoClient::SplitConfig.new(cache_adapter: :redis)) - expect(flag_set.get_flag_set('set_1')).to eq(['feature1']) + expect(flag_set.get_flag_sets(['set_1'])).to eq(['feature1']) adapter.add_to_set('SPLITIO.flagSet.set_1', 'feature2') - expect(flag_set.get_flag_set('set_1').sort).to eq(['feature1', 'feature2']) + expect(flag_set.get_flag_sets(['set_1']).sort).to eq(['feature1', 'feature2']) sleep 0.1 - expect(flag_set.get_flag_set('set_2')).to eq(['feature2']) + expect(flag_set.get_flag_sets(['set_2'])).to eq(['feature2']) adapter.delete_from_set('SPLITIO.flagSet.set_2', 'feature2') sleep 0.1 - expect(flag_set.get_flag_set('set_2')).to eq([]) + expect(flag_set.get_flag_sets(['set_2'])).to eq([]) Redis.new.flushall end diff --git a/spec/integrations/in_memory_client_spec.rb b/spec/integrations/in_memory_client_spec.rb index c625c18d..3ffabb69 100644 --- a/spec/integrations/in_memory_client_spec.rb +++ b/spec/integrations/in_memory_client_spec.rb @@ -1138,7 +1138,7 @@ telemetry_refresh_rate: 99999, impressions_refresh_rate: 99999, streaming_enabled: false, - flag_sets_filter: ['set_3']) + flag_sets_filter: ['set_3', '@3we']) end before do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') From 6a228af9a0079029adc9ca434bf1e7aa1b52cc6c Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 29 Nov 2023 14:36:31 -0800 Subject: [PATCH 17/27] Added support for SSE and minor fix in updating split repository --- .../cache/repositories/splits_repository.rb | 8 +- lib/splitclient-rb/engine/api/splits.rb | 3 +- .../sse/workers/splits_worker.rb | 13 +- spec/cache/fetchers/split_fetch_spec.rb | 9 +- spec/engine/api/splits_spec.rb | 67 ++++---- spec/integrations/in_memory_client_spec.rb | 2 +- spec/integrations/redis_client_spec.rb | 72 ++++---- spec/sse/workers/splits_worker_spec.rb | 158 +++++++++++++++--- 8 files changed, 220 insertions(+), 112 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 64329c9d..7ec96a8e 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -151,6 +151,8 @@ def add_feature_flag(split) increase_tt_name_count(split[:trafficTypeName]) decrease_tt_name_count(existing_split[:trafficTypeName]) remove_from_flag_sets(existing_split) + elsif(existing_split[:sets] != split[:sets]) + remove_from_flag_sets(existing_split) end if !split[:sets].nil? @@ -193,8 +195,10 @@ def get_splits(names, symbolize_names = true) end def remove_from_flag_sets(feature_flag) - if !feature_flag[:sets].nil? - for flag_set in feature_flag[:sets] + name = feature_flag[:name] + flag_sets = get_split(name)[:sets] if exists?(name) + if !flag_sets.nil? + for flag_set in flag_sets @flag_sets.remove_feature_flag_from_flag_set(flag_set, feature_flag[:name]) if is_flag_set_exist(flag_set) && @flag_sets.get_flag_sets([flag_set]).length == 0 && !@flag_set_filter.should_filter? @flag_sets.remove_flag_set(flag_set) diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index 4d01a1bb..d73601ba 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -8,6 +8,7 @@ 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 end def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil }) @@ -15,7 +16,7 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets params = { since: since } params[:till] = fetch_options[:till] unless fetch_options[:till].nil? - params[:sets] = fetch_options[:sets].join(",") unless fetch_options[:sets].nil? + params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty? response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) if response.status == 414 @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.") diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index f30e30ad..7ee68472 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -66,17 +66,9 @@ def update_feature_flag(notification) return false unless !notification.data['d'].nil? && @feature_flags_repository.get_change_number == notification.data['pcn'] new_split = return_split_from_json(notification) - to_add = [] - to_delete = [] - if Engine::Models::Split.archived?(new_split) - to_delete.push(new_split) - else - to_add.push(new_split) - - fetch_segments_if_not_exists(new_split) - end + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository, [new_split], notification.data['changeNumber'], @config) + fetch_segments_if_not_exists(new_split) - @feature_flags_repository.update(to_add, to_delete, notification.data['changeNumber']) @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS) true @@ -100,7 +92,6 @@ def kill_feature_flag(notification) 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) end diff --git a/spec/cache/fetchers/split_fetch_spec.rb b/spec/cache/fetchers/split_fetch_spec.rb index 977f9e78..2d320734 100644 --- a/spec/cache/fetchers/split_fetch_spec.rb +++ b/spec/cache/fetchers/split_fetch_spec.rb @@ -80,6 +80,11 @@ let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:store) { described_class.new(splits_repository, '', config, telemetry_runtime_producer) } + before do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?sets=set_2&since=-1') + .to_return(status: 200, body: active_splits_json) + end + it 'returns splits since' do splits = store.send(:splits_since, -1) @@ -98,14 +103,14 @@ 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?since=1473413807667') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?sets=set_2&since=1473413807667') .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?since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?sets=set_2&since=-1') .to_return(status: 200, body: active_splits_json) store.send(:fetch_splits) diff --git a/spec/engine/api/splits_spec.rb b/spec/engine/api/splits_spec.rb index adbe91f7..7850e6db 100644 --- a/spec/engine/api/splits_spec.rb +++ b/spec/engine/api/splits_spec.rb @@ -3,20 +3,20 @@ require 'spec_helper' describe SplitIoClient::Api::Splits do - let(:config) do - SplitIoClient::SplitConfig.new( - logger: Logger.new(log), - debug_enabled: true, - transport_debug_enabled: true - ) - end - - let(:log) { StringIO.new } - let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:splits_api) { described_class.new('', config, telemetry_runtime_producer) } let(:splits) { File.read(File.expand_path(File.join(File.dirname(__FILE__), '../../test_data/splits/splits.json'))) } context '#splits_with_segment_names' do + let(:config) do + SplitIoClient::SplitConfig.new( + logger: Logger.new(log), + debug_enabled: true, + transport_debug_enabled: true + ) + 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 'returns splits with segment names' do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') .to_return(status: 200, body: splits) @@ -28,28 +28,20 @@ end context '#sets' do - it 'returns the splits - with sets param' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1') - .with(headers: { - 'Accept' => '*/*', - 'Accept-Encoding' => 'gzip', - 'Authorization' => 'Bearer', - 'Connection' => 'keep-alive', - 'Keep-Alive' => '30', - 'Splitsdkversion' => "#{config.language}-#{config.version}" - }) - .to_return(status: 200, body: splits) - - fetch_options = { cache_control_headers: false, till: nil, sets: ['set_1'] } - returned_splits = splits_api.since(-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 + let(:config) do + SplitIoClient::SplitConfig.new( + logger: Logger.new(log), + debug_enabled: true, + transport_debug_enabled: true, + flag_sets_filter: ['set_1', 'set_2'] + ) 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 'returns the splits - with 2 sets param' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1,set_2') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?sets=set_1,set_2&since=-1') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -69,7 +61,7 @@ end it 'raise api exception when status 414' do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&sets=set_1,set_2') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?sets=set_1,set_2&since=-1') .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip', @@ -89,11 +81,20 @@ end expect(captured).to eq(414) end - - end context '#since' do + let(:config) do + SplitIoClient::SplitConfig.new( + logger: Logger.new(log), + debug_enabled: true, + transport_debug_enabled: true + ) + 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 'returns the splits - checking headers when cache_control_headers is false' do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') .with(headers: { diff --git a/spec/integrations/in_memory_client_spec.rb b/spec/integrations/in_memory_client_spec.rb index 3ffabb69..85956360 100644 --- a/spec/integrations/in_memory_client_spec.rb +++ b/spec/integrations/in_memory_client_spec.rb @@ -1141,7 +1141,7 @@ flag_sets_filter: ['set_3', '@3we']) end before do - stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?sets=set_3&since=-1') .to_return(status: 200, body: splits) mock_segment_changes('segment1', segment1, '-1') mock_segment_changes('segment1', segment1, '1470947453877') diff --git a/spec/integrations/redis_client_spec.rb b/spec/integrations/redis_client_spec.rb index b19733db..af10befa 100644 --- a/spec/integrations/redis_client_spec.rb +++ b/spec/integrations/redis_client_spec.rb @@ -606,17 +606,17 @@ expect(impressions[0][:treatment][:label]).to eq('whitelisted') expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[2][:matching_key]).to eq('nico_test') - expect(impressions[2][:split_name]).to eq(:testing) - expect(impressions[2][:treatment][:treatment]).to eq('off') - expect(impressions[2][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[2][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:split_name]).to eq(:testing) expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in segment all') - expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:treatment][:treatment]).to eq('off') + expect(impressions[2][:treatment][:label]).to eq('in segment all') + expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -700,17 +700,17 @@ expect(impressions[2][:treatment][:label]).to eq('whitelisted') expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing) - expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[0][:matching_key]).to eq('nico_test') - expect(impressions[0][:split_name]).to eq(:testing222) + expect(impressions[0][:split_name]).to eq(:testing) expect(impressions[0][:treatment][:treatment]).to eq('off') - expect(impressions[0][:treatment][:label]).to eq('in segment all') - expect(impressions[0][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -792,17 +792,17 @@ expect(impressions[0][:treatment][:label]).to eq('whitelisted') expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[2][:matching_key]).to eq('nico_test') - expect(impressions[2][:split_name]).to eq(:testing) - expect(impressions[2][:treatment][:treatment]).to eq('off') - expect(impressions[2][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[2][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:split_name]).to eq(:testing) expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in segment all') - expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[2][:matching_key]).to eq('nico_test') + expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:treatment][:treatment]).to eq('off') + expect(impressions[2][:treatment][:label]).to eq('in segment all') + expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -886,17 +886,17 @@ expect(impressions[2][:treatment][:label]).to eq('whitelisted') expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing) - expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[0][:matching_key]).to eq('nico_test') - expect(impressions[0][:split_name]).to eq(:testing222) + expect(impressions[0][:split_name]).to eq(:testing) expect(impressions[0][:treatment][:treatment]).to eq('off') - expect(impressions[0][:treatment][:label]).to eq('in segment all') - expect(impressions[0][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do diff --git a/spec/sse/workers/splits_worker_spec.rb b/spec/sse/workers/splits_worker_spec.rb index 47f92817..5cae7f59 100644 --- a/spec/sse/workers/splits_worker_spec.rb +++ b/spec/sse/workers/splits_worker_spec.rb @@ -14,40 +14,42 @@ let(:api_key) { 'SplitsWorker-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } - 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(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } let(:event_split_update_no_compression) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c": 0,"d":"eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiIzM2VhZmE1MC0xYTY1LTExZWQtOTBkZi1mYTMwZDk2OTA0NDUiLCJuYW1lIjoiYmlsYWxfc3BsaXQiLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOi0xMzY0MTE5MjgyLCJzZWVkIjotNjA1OTM4ODQzLCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib2ZmIiwiY2hhbmdlTnVtYmVyIjoxNjg0MzQwOTA4NDc1LCJhbGdvIjoyLCJjb25maWd1cmF0aW9ucyI6e30sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6ImJpbGFsX3NlZ21lbnQifX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjEwMH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgYmlsYWxfc2VnbWVudCJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoiZGVmYXVsdCBydWxlIn1dfQ=="}'), 'test') } + let(:event_split_update_no_compression_with_set_1) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c": 0,"d":"eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiIzM2VhZmE1MC0xYTY1LTExZWQtOTBkZi1mYTMwZDk2OTA0NDUiLCJuYW1lIjoiYmlsYWxfc3BsaXQiLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOi0xMzY0MTE5MjgyLCJzZWVkIjotNjA1OTM4ODQzLCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib2ZmIiwiY2hhbmdlTnVtYmVyIjoxNjg0MzQwOTA4NDc1LCJhbGdvIjoyLCJjb25maWd1cmF0aW9ucyI6e30sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6ImJpbGFsX3NlZ21lbnQifX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjEwMH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgYmlsYWxfc2VnbWVudCJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoiZGVmYXVsdCBydWxlIn1dLCAic2V0cyI6WyJzZXRfMSJdfQ=="}'), 'test') } let(:event_split_update_gzip_compression) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c": 1,"d":"H4sIAAkVZWQC/8WST0+DQBDFv0qzZ0ig/BF6a2xjGismUk2MaZopzOKmy9Isy0EbvrtDwbY2Xo233Tdv5se85cCMBs5FtvrYYwIlsglratTMYiKns+chcAgc24UwsF0Xczt2cm5z8Jw8DmPH9wPyqr5zKyTITb2XwpA4TJ5KWWVgRKXYxHWcX/QUkVi264W+68bjaGyxupdCJ4i9KPI9UgyYpibI9Ha1eJnT/J2QsnNxkDVaLEcOjTQrjWBKVIasFefky95BFZg05Zb2mrhh5I9vgsiL44BAIIuKTeiQVYqLotHHLyLOoT1quRjub4fztQuLxj89LpePzytClGCyd9R3umr21ErOcitUh2PTZHY29HN2+JGixMxUujNfvMB3+u2pY1AXySad3z3Mk46msACDp8W7jhly4uUpFt3qD33vDAx0gLpXkx+P1GusbdcE24M2F4uaywwVEWvxSa1Oa13Vjvn2RXradm0xCVuUVBJqNCBGV0DrX4OcLpeb+/lreh3jH8Uw/JQj3UhkxPgCCurdEnADAAA="}'), 'test') } let(:event_split_update_zlib_compression) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c": 2,"d":"eJzEUtFq20AQ/JUwz2c4WZZr3ZupTQh1FKjcQinGrKU95cjpZE6nh9To34ssJ3FNX0sfd3Zm53b2TgietDbF9vXIGdUMha5lDwFTQiGOmTQlchLRPJlEEZeTVJZ6oimWZTpP5WyWQMCNyoOxZPft0ZoA8TZ5aW1TUDCNg4qk/AueM5dQkyiez6IonS6mAu0IzWWSxovFLBZoA4WuhcLy8/bh+xoCL8bagaXJtixQsqbOhq1nCjW7AIVGawgUz+Qqzrr6wB4qmi9m00/JIk7TZCpAtmqgpgJF47SpOn9+UQt16s9YaS71z9NHOYQFha9Pm83Tty0EagrFM/t733RHqIFZH4wb7LDMVh+Ecc4Lv+ZsuQiNH8hXF3hLv39XXNCHbJ+v7x/X2eDmuKLA74sPihVr47jMuRpWfxy1Kwo0GLQjmv1xpBFD3+96gSP5cLVouM7QQaA1vxhK9uKmd853bEZS9jsBSwe2UDDu7mJxd2Mo/muQy81m/2X9I7+N8R/FcPmUd76zjH7X/w4AAP//90glTw=="}'), 'test') } 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.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 - } - - params = { - split_fetcher: split_fetcher, - segment_fetcher: segment_fetcher, - imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, - impressions_sender_adapter: SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, impressions_api), - impressions_api: SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) - } - - SplitIoClient::Engine::Synchronizer.new(repositories, config, params) - end 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(: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 + 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 + } + + params = { + split_fetcher: split_fetcher, + segment_fetcher: segment_fetcher, + imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, + impressions_sender_adapter: SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, impressions_api), + impressions_api: SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) + } + + SplitIoClient::Engine::Synchronizer.new(repositories, config, params) + end + it 'add change number - must tigger fetch - with retries' do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1').to_return(status: 200, body: '{"splits": [],"since": -1,"till": 1506703262918}') stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918').to_return(status: 200, body: '{"splits": [],"since": 1506703262918,"till": 1506703262918}') @@ -96,6 +98,32 @@ end context 'kill split notification' 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(: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 + 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 + } + + params = { + split_fetcher: split_fetcher, + segment_fetcher: segment_fetcher, + imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, + impressions_sender_adapter: SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, impressions_api), + impressions_api: SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) + } + + SplitIoClient::Engine::Synchronizer.new(repositories, config, params) + end before do mock_split_changes(splits) mock_segment_changes('segment1', segment1, '-1') @@ -139,7 +167,85 @@ end end + context 'update with flagset filter' do + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(["set_1"])} + 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(: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 + 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 + } + + params = { + split_fetcher: split_fetcher, + segment_fetcher: segment_fetcher, + imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, + impressions_sender_adapter: SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, impressions_api), + impressions_api: SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) + } + + 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?since=1234').to_return(status: 200, body: '{"splits": [],"since": 1234,"till": 1234}') + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1234&till=5564531221").to_return(status: 200, body: '{"splits": [],"since": 1234,"till": 5564531221}') + 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.start + + splits_repository.set_change_number(1234) + worker.add_to_queue(event_split_update_no_compression) + sleep 2 + expect(splits_repository.exists?('bilal_split') == false) + + splits_repository.set_change_number(1234) + worker.add_to_queue(event_split_update_no_compression_with_set_1) + sleep 2 + split = splits_repository.get_split('bilal_split') + expect(split[:name] == 'bilal_split') + + splits_repository.set_change_number(1234) + worker.add_to_queue(event_split_update_no_compression) + sleep 2 + expect(splits_repository.exists?('bilal_split') == false) + end + end + context 'instant ff update split notification' 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(: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 + 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 + } + + params = { + split_fetcher: split_fetcher, + segment_fetcher: segment_fetcher, + imp_counter: SplitIoClient::Engine::Common::ImpressionCounter.new, + impressions_sender_adapter: SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config, telemetry_api, impressions_api), + impressions_api: SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) + } + + 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) From 3f753bb9ec2e8eb26278425342945acce1d37455 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 1 Dec 2023 12:57:38 -0800 Subject: [PATCH 18/27] removed SortedSet and inlcude 'set' --- lib/splitclient-rb/engine/api/splits.rb | 1 + lib/splitclient-rb/validators.rb | 5 +- spec/integrations/redis_client_spec.rb | 72 ++++++++++++------------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index d73601ba..2bcb804c 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -17,6 +17,7 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets params = { since: since } params[:till] = fetch_options[:till] unless fetch_options[:till].nil? params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty? + @config.logger.debug("Fetching from splitChanges with #{params}: ") response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) if response.status == 414 @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.") diff --git a/lib/splitclient-rb/validators.rb b/lib/splitclient-rb/validators.rb index e75c85b4..0061115b 100644 --- a/lib/splitclient-rb/validators.rb +++ b/lib/splitclient-rb/validators.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'set' module SplitIoClient class Validators @@ -63,7 +62,7 @@ def valid_flag_sets(method, flag_sets) log_invalid_flag_set_type(method) return [] end - valid_flag_sets = SortedSet[] + valid_flag_sets = Set[] without_nil.compact.uniq.select do |flag_set| if flag_set.nil? || !flag_set.is_a?(String) log_invalid_flag_set_type(method) @@ -75,7 +74,7 @@ def valid_flag_sets(method, flag_sets) log_invalid_flag_set_type(method) end end - !valid_flag_sets.empty? ? valid_flag_sets.to_a : [] + !valid_flag_sets.empty? ? valid_flag_sets.to_a.sort : [] end private diff --git a/spec/integrations/redis_client_spec.rb b/spec/integrations/redis_client_spec.rb index af10befa..b19733db 100644 --- a/spec/integrations/redis_client_spec.rb +++ b/spec/integrations/redis_client_spec.rb @@ -606,17 +606,17 @@ expect(impressions[0][:treatment][:label]).to eq('whitelisted') expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing) - expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[2][:matching_key]).to eq('nico_test') - expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:split_name]).to eq(:testing) expect(impressions[2][:treatment][:treatment]).to eq('off') - expect(impressions[2][:treatment][:label]).to eq('in segment all') - expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[2][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -700,17 +700,17 @@ expect(impressions[2][:treatment][:label]).to eq('whitelisted') expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[0][:matching_key]).to eq('nico_test') - expect(impressions[0][:split_name]).to eq(:testing) - expect(impressions[0][:treatment][:treatment]).to eq('off') - expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:split_name]).to eq(:testing) expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in segment all') - expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing222) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in segment all') + expect(impressions[0][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -792,17 +792,17 @@ expect(impressions[0][:treatment][:label]).to eq('whitelisted') expect(impressions[0][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing) - expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[2][:matching_key]).to eq('nico_test') - expect(impressions[2][:split_name]).to eq(:testing222) + expect(impressions[2][:split_name]).to eq(:testing) expect(impressions[2][:treatment][:treatment]).to eq('off') - expect(impressions[2][:treatment][:label]).to eq('in segment all') - expect(impressions[2][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[2][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[2][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[1][:matching_key]).to eq('nico_test') + expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:treatment][:treatment]).to eq('off') + expect(impressions[1][:treatment][:label]).to eq('in segment all') + expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do @@ -886,17 +886,17 @@ expect(impressions[2][:treatment][:label]).to eq('whitelisted') expect(impressions[2][:treatment][:change_number]).to eq(1_506_703_262_916) - expect(impressions[0][:matching_key]).to eq('nico_test') - expect(impressions[0][:split_name]).to eq(:testing) - expect(impressions[0][:treatment][:treatment]).to eq('off') - expect(impressions[0][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') - expect(impressions[0][:treatment][:change_number]).to eq(1_506_440_189_077) - expect(impressions[1][:matching_key]).to eq('nico_test') - expect(impressions[1][:split_name]).to eq(:testing222) + expect(impressions[1][:split_name]).to eq(:testing) expect(impressions[1][:treatment][:treatment]).to eq('off') - expect(impressions[1][:treatment][:label]).to eq('in segment all') - expect(impressions[1][:treatment][:change_number]).to eq(1_505_162_627_437) + expect(impressions[1][:treatment][:label]).to eq('in split test_definition_as_of treatment [off]') + expect(impressions[1][:treatment][:change_number]).to eq(1_506_440_189_077) + + expect(impressions[0][:matching_key]).to eq('nico_test') + expect(impressions[0][:split_name]).to eq(:testing222) + expect(impressions[0][:treatment][:treatment]).to eq('off') + expect(impressions[0][:treatment][:label]).to eq('in segment all') + expect(impressions[0][:treatment][:change_number]).to eq(1_505_162_627_437) end it 'returns treatments with input validation' do From dd99e35ca564ae76f7f8a07357225b625a107f20 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 4 Dec 2023 13:31:21 -0800 Subject: [PATCH 19/27] Added sets to manager split view dict --- lib/splitclient-rb/managers/split_manager.rb | 3 ++- spec/splitclient/split_manager_spec.rb | 21 +++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/splitclient-rb/managers/split_manager.rb b/lib/splitclient-rb/managers/split_manager.rb index ca824b9e..81beb5ee 100644 --- a/lib/splitclient-rb/managers/split_manager.rb +++ b/lib/splitclient-rb/managers/split_manager.rb @@ -105,7 +105,8 @@ def build_split_view(name, split) killed: split[:killed], treatments: treatments, change_number: split[:changeNumber], - configs: split[:configurations] || {} + configs: split[:configurations] || {}, + sets: split[:sets] } end diff --git a/spec/splitclient/split_manager_spec.rb b/spec/splitclient/split_manager_spec.rb index 9d18a9c8..97c6108a 100644 --- a/spec/splitclient/split_manager_spec.rb +++ b/spec/splitclient/split_manager_spec.rb @@ -24,7 +24,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?since=1473413807667") .to_return(status: 200, body: "", headers: {}) end @@ -95,22 +95,33 @@ end end + context 'sets' do + it 'return split sets in splitview' do + subject.block_until_ready + splits = subject.splits + expect(splits[0][:sets]).to eq(["set_1"]) + expect(splits[1][:sets]).to eq(["set_1", "set_2"]) + expect(subject.split('test_1_ruby')[:sets]).to eq(['set_1']) + end + end + describe 'configurations' do 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?since=-1') .to_return(status: 200, body: splits3) - + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1473863097220") .to_return(status: 200, body: "", headers: {}) end - it 'returns configurations' do + it 'returns configurations and sets' do subject.block_until_ready split = subject.instance_variable_get(:@splits_repository).get_split('test_1_ruby') - result = subject.send(:build_split_view, 'test_1_ruby', split)[:configs] - expect(result).to eq(on: '{"size":15,"test":20}') + result = subject.send(:build_split_view, 'test_1_ruby', split) + expect(result[:configs]).to eq(on: '{"size":15,"test":20}') + expect(result[:sets]).to eq(nil) end it 'returns empty hash when no configurations' do From 642ae8e313c05b4bad7c0bbaf326d72a65c02219 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 4 Dec 2023 13:55:48 -0800 Subject: [PATCH 20/27] return empty list when nil sets --- lib/splitclient-rb/managers/split_manager.rb | 2 +- spec/splitclient/split_manager_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/splitclient-rb/managers/split_manager.rb b/lib/splitclient-rb/managers/split_manager.rb index 81beb5ee..59f8104e 100644 --- a/lib/splitclient-rb/managers/split_manager.rb +++ b/lib/splitclient-rb/managers/split_manager.rb @@ -106,7 +106,7 @@ def build_split_view(name, split) treatments: treatments, change_number: split[:changeNumber], configs: split[:configurations] || {}, - sets: split[:sets] + sets: split[:sets] || [] } end diff --git a/spec/splitclient/split_manager_spec.rb b/spec/splitclient/split_manager_spec.rb index 97c6108a..bc2d9c6f 100644 --- a/spec/splitclient/split_manager_spec.rb +++ b/spec/splitclient/split_manager_spec.rb @@ -121,7 +121,7 @@ split = subject.instance_variable_get(:@splits_repository).get_split('test_1_ruby') result = subject.send(:build_split_view, 'test_1_ruby', split) expect(result[:configs]).to eq(on: '{"size":15,"test":20}') - expect(result[:sets]).to eq(nil) + expect(result[:sets]).to eq([]) end it 'returns empty hash when no configurations' do From dc9f3c0b47b2f924d8cfd38578ebc9ea445584b7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 7 Dec 2023 11:43:50 -0800 Subject: [PATCH 21/27] merge flagset build --- CHANGES.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6e1339d1..e2eed719 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,12 @@ CHANGES +8.3.0 (Dec 7, 2023) +- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation): + - Added new variations of the get treatment methods to support evaluating flags in given flag set/s. + - get_treatments_by_flag_set and get_treatments_by_flag_sets + - get_treatments_with_config_by_flag_set and get_treatments_with_config_by_flag_sets +- Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload. + - Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init. +- Updated the following SDK manager methods to expose flag sets on flag views. 8.2.0 (Jul 18, 2023) - Improved streaming architecture implementation to apply feature flag updates from the notification received which is now enhanced, improving efficiency and reliability of the whole update system. @@ -60,10 +68,10 @@ CHANGES 7.2.1 (Oct 23, 2020) - Updated redis dependency to >= 4.2.2. -- Updated ably error handling. +- Updated ably error handling. 7.2.0 (Sep 25, 2020) -- Added impressions dedupe logic to avoid sending duplicated impressions: +- Added impressions dedupe logic to avoid sending duplicated impressions: - Added `OPTIMIZED` and `DEBUG` modes in order to enabling/disabling how impressions are going to be sent into Split servers, - `OPTIMIZED`: will send unique impressions in a timeframe in order to reduce how many times impressions are posted to Split. - `DEBUG`: will send every impression generated to Split. From 818d21950c35eb578593174c53ecd0698102597b Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:56:16 -0800 Subject: [PATCH 22/27] Update CHANGES.txt --- CHANGES.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d11a7a96..6c3b328e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,10 +6,7 @@ CHANGES - get_treatments_with_config_by_flag_set and get_treatments_with_config_by_flag_sets - Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload. - Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init. -- Updated the following SDK manager methods to expose flag sets on flag views. - -x.x.x (coming soon) -- Added `default_treatment` property to the `split_view` object returned by the `split` and `splits` methods of the SDK manager. +- Added `default_treatment` and `sets` property to the `split_view` object returned by the `split` and `splits` methods of the SDK manager. 8.2.0 (Jul 18, 2023) - Improved streaming architecture implementation to apply feature flag updates from the notification received which is now enhanced, improving efficiency and reliability of the whole update system. From b5b549d24a7d46bfa59283a6ae02c61b89afb422 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 7 Dec 2023 12:39:04 -0800 Subject: [PATCH 23/27] fix rubocop offense --- lib/splitclient-rb/sse/workers/splits_worker.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index 7ee68472..7971bce8 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -66,7 +66,9 @@ def update_feature_flag(notification) 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) + SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository, + [new_split], + notification.data['changeNumber'], @config) fetch_segments_if_not_exists(new_split) @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS) From 3c0e2e3e1abba677ad48c06a0f2a7c12cd13e3d9 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 7 Dec 2023 12:52:50 -0800 Subject: [PATCH 24/27] cleanup --- lib/splitclient-rb/managers/split_manager.rb | 2 +- spec/splitclient/manager_localhost_spec.rb | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/splitclient-rb/managers/split_manager.rb b/lib/splitclient-rb/managers/split_manager.rb index c2228c77..41e6a7f0 100644 --- a/lib/splitclient-rb/managers/split_manager.rb +++ b/lib/splitclient-rb/managers/split_manager.rb @@ -106,7 +106,7 @@ def build_split_view(name, split) treatments: treatments, change_number: split[:changeNumber], configs: split[:configurations] || {}, - sets: split[:sets] || [] + sets: split[:sets] || [], default_treatment: split[:defaultTreatment] } end diff --git a/spec/splitclient/manager_localhost_spec.rb b/spec/splitclient/manager_localhost_spec.rb index 0ca29c32..08067a4b 100644 --- a/spec/splitclient/manager_localhost_spec.rb +++ b/spec/splitclient/manager_localhost_spec.rb @@ -20,7 +20,8 @@ killed: false, name: 'local_feature', traffic_type_name: nil, - treatments: ['local_treatment'] } + treatments: ['local_treatment'], + sets: [] } end let(:split_views) do @@ -30,14 +31,16 @@ killed: false, name: 'local_feature', traffic_type_name: nil, - treatments: ['local_treatment'] }, + treatments: ['local_treatment'], + sets: [] }, { change_number: nil, configs: { local_treatment2: nil }, default_treatment: "control_treatment", killed: false, name: 'local_feature2', traffic_type_name: nil, - treatments: ['local_treatment2'] }] + treatments: ['local_treatment2'], + sets: [] }] end it 'validates the calling manager.splits returns the offline data' do @@ -66,7 +69,8 @@ killed: false, name: 'multiple_keys_feature', traffic_type_name: nil, - treatments: %w[off on] }, + treatments: %w[off on], + sets: [] }, { change_number: nil, configs: { on: '{"desc":"this applies only to ON and only for john_doe. The rest will receive OFF"}', @@ -76,7 +80,8 @@ killed: false, name: 'single_key_feature', traffic_type_name: nil, - treatments: %w[on off] }, + treatments: %w[on off], + sets: [] }, { change_number: nil, configs: { off: '{"desc":"this applies only to OFF treatment"}' @@ -85,7 +90,8 @@ killed: false, name: 'no_keys_feature', traffic_type_name: nil, - treatments: %w[off] }] + treatments: %w[off], + sets: [] }] end it 'returns split_names' do From c4680694270f230410bf96d3d71817074d9eeee9 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 7 Dec 2023 13:52:01 -0800 Subject: [PATCH 25/27] fixed engine_spec for ruby 3.x --- spec/engine_spec.rb | 226 ++++++++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/spec/engine_spec.rb b/spec/engine_spec.rb index 72a6999e..1919879c 100644 --- a/spec/engine_spec.rb +++ b/spec/engine_spec.rb @@ -75,7 +75,7 @@ treatment: 'off', config: nil ) - destroy_factory + close_redis end it 'get_treatment returns off' do @@ -94,7 +94,7 @@ expect(subject.get_treatments('nicolas', ['mauro_test'], {})).to eq( mauro_test: 'off' ) - destroy_factory + close_redis end it 'get_treatments_by_flag_set returns off' do @@ -107,7 +107,7 @@ expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {})).to eq( mauro_test: 'off' ) - destroy_factory + close_redis end it 'get_treatments_by_flag_sets returns off' do @@ -120,7 +120,7 @@ expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {})).to eq( mauro_test: 'off' ) - destroy_factory + close_redis end end @@ -135,7 +135,7 @@ it 'returns CONTROL for random id' do expect(subject.get_treatment('random_user_id', 'my_random_feature')) .to eq SplitIoClient::Engine::Models::Treatment::CONTROL - destroy_factory + close_redis end it 'returns CONTROL and label for incorrect feature name' do @@ -146,20 +146,20 @@ label: SplitIoClient::Engine::Models::Label::NOT_FOUND, change_number: nil ) - destroy_factory + close_redis end it 'returns CONTROL on nil key' do expect(subject.get_treatment(nil, 'test_feature')).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed a nil key, key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns CONTROL on empty key' do expect(subject.get_treatment('', 'test_feature')).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty matching_key, ' \ 'matching_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns CONTROL on nil matching_key' do @@ -167,7 +167,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string) .to include 'get_treatment: you passed a nil matching_key, matching_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns control on empty matching_key' do @@ -175,7 +175,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty matching_key, ' \ 'matching_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns control on longer than specified max characters matching_key' do @@ -183,7 +183,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: matching_key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" - destroy_factory + close_redis end it 'logs warning when Numeric matching_key' do @@ -198,7 +198,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed a nil bucketing_key, ' \ 'bucketing_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns control on empty bucketing_key' do @@ -206,7 +206,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty bucketing_key, ' \ 'bucketing_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns control on longer than specified max characters bucketing_key' do @@ -214,7 +214,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: bucketing_key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" - destroy_factory + close_redis end it 'logs warning when Numeric bucketing_key' do @@ -222,7 +222,7 @@ expect(subject.get_treatment({ bucketing_key: value, matching_key: 'random_user_id' }, 'test_feature')) .to eq 'on' expect(log.string).to include "get_treatment: bucketing_key \"#{value}\" is not of type String, converting" - destroy_factory + close_redis end #TODO We will remove multiple param in the future. @@ -232,7 +232,7 @@ label: nil, change_number: nil ) - destroy_factory + close_redis end it 'returns control on empty key' do @@ -240,7 +240,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an empty matching_key, ' \ 'matching_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns control on NaN key' do @@ -248,7 +248,7 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an invalid matching_key type, ' \ 'matching_key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns control on longer than specified max characters key' do @@ -256,33 +256,33 @@ .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: matching_key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" - destroy_factory + close_redis end it 'logs warning when Numeric key' do value = 123 expect(subject.get_treatment(value, 'test_feature')).to eq 'on' expect(log.string).to include "get_treatment: matching_key \"#{value}\" is not of type String, converting" - destroy_factory + close_redis end it 'returns CONTROL on nil split_name' do expect(subject.get_treatment('random_user_id', nil)).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed a nil split_name, ' \ 'split_name must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns CONTROL on empty split_name' do expect(subject.get_treatment('random_user_id', '')).to eq SplitIoClient::Engine::Models::Treatment::CONTROL - destroy_factory + close_redis end it 'returns CONTROL on number split_name' do expect(subject.get_treatment('random_user_id', 123)).to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: you passed an invalid split_name type, ' \ 'split_name must be a non-empty String or a Symbol' - destroy_factory + close_redis end #TODO We will remove multiple param in the future. @@ -292,21 +292,21 @@ label: nil, change_number: nil ) - destroy_factory + close_redis end it 'trims split_name and logs warning when extra whitespaces' do split_name = ' test_feature ' expect(subject.get_treatment('fake_user_id_1', split_name)).to eq 'on' expect(log.string).to include "get_treatment: feature_flag_name #{split_name} has extra whitespace, trimming" - destroy_factory + close_redis end it 'returns CONTROL when non Hash attributes' do expect(subject.get_treatment('random_user_id', 'test_feature', ["I'm an Array"])) .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: attributes must be of type Hash' - destroy_factory + close_redis end it 'returns CONTROL and logs warning when ready and split does not exist' do @@ -315,7 +315,7 @@ expect(log.string).to include 'get_treatment: you passed non_existing_feature ' \ 'that does not exist in this environment, please double check what feature flags exist ' \ 'in the Split user interface' - destroy_factory + close_redis end it 'returns CONTROL with NOT_READY label when not ready' do @@ -324,7 +324,7 @@ expect(subject.get_treatment('random_user_id', 'test_feature')) .to eq SplitIoClient::Engine::Models::Treatment::CONTROL expect(log.string).to include 'get_treatment: the SDK is not ready, results may be incorrect for feature flag test_feature. Make sure to wait for SDK readiness before using this method' - destroy_factory + close_redis end end @@ -340,7 +340,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'on' expect(result[:config]).to eq '{"killed":false}' - destroy_factory + close_redis end it 'returns the default treatment config on killed split' do @@ -348,7 +348,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'off' expect(result[:config]).to eq '{"killed":true}' - destroy_factory + close_redis end it 'returns nil when no configs' do @@ -356,7 +356,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'on' expect(result[:config]).to eq nil - destroy_factory + close_redis end it 'returns nil when no configs for feature' do @@ -364,7 +364,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'on' expect(result[:config]).to eq nil - destroy_factory + close_redis end it 'returns control and logs the correct message on nil key' do @@ -374,7 +374,7 @@ expect(result[:config]).to eq nil expect(log.string) .to include 'get_treatment_with_config: you passed a nil key, key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'returns nil when killed and no configs for default treatment' do @@ -382,7 +382,7 @@ result = subject.get_treatment_with_config('fake_user_id_1', split_name) expect(result[:treatment]).to eq 'off' expect(result[:config]).to eq nil - destroy_factory + close_redis end end @@ -395,39 +395,39 @@ it 'returns empty hash on nil split_names' do expect(subject.get_treatments('random_user_id', nil)).to be_nil expect(log.string).to include 'get_treatments: feature_flag_names must be a non-empty Array' - destroy_factory + close_redis end it 'returns empty hash when no Array split_names' do expect(subject.get_treatments('random_user_id', Object.new)).to be_nil expect(log.string).to include 'get_treatments: feature_flag_names must be a non-empty Array' - destroy_factory + close_redis end it 'returns empty hash on empty array split_names' do expect(subject.get_treatments('random_user_id', [])).to eq({}) expect(log.string).to include 'get_treatments: feature_flag_names must be a non-empty Array' - destroy_factory + close_redis end it 'sanitizes split_names removing repeating and nil split_names' do treatments = subject.get_treatments('random_user_id', ['test_feature', nil, nil, 'test_feature']) expect(treatments.size).to eq 1 - destroy_factory + close_redis end it 'warns when non string split_names' do expect(subject.get_treatments('random_user_id', [Object.new, Object.new])).to eq({}) expect(log.string).to include 'get_treatments: you passed an invalid feature_flag_name, ' \ 'flag name must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'warns when empty split_names' do expect(subject.get_treatments('random_user_id', [''])).to eq({}) expect(log.string).to include 'get_treatments: you passed an empty feature_flag_name, ' \ 'flag name must be a non-empty String or a Symbol' - destroy_factory + close_redis end end @@ -446,7 +446,7 @@ expect(result[:test_feature]).to eq(treatment: 'on', config: '{"killed":false}') expect(result[:no_configs_feature]).to eq(treatment: 'on', config: nil) expect(result[:killed_feature]).to eq(treatment: 'off', config: '{"killed":true}') - destroy_factory + close_redis end end @@ -459,37 +459,37 @@ it 'returns empty hash on nil split_names' do expect(subject.get_treatments_by_flag_set('random_user_id', nil)).to eq({}) expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end it 'returns empty hash when no Array split_names' do expect(subject.get_treatments_by_flag_set('random_user_id', Object.new)).to eq({}) expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end it 'returns empty hash on empty array split_names' do expect(subject.get_treatments_by_flag_set('random_user_id', [])).to eq({}) expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end it 'sanitizes flagset names removing repeating and nil' do treatments = subject.get_treatments_by_flag_set('random_user_id', ['set_1', nil, nil, 'set_1']) expect(treatments.size).to eq 0 - destroy_factory + close_redis end it 'warns when non string flagset names' do expect(subject.get_treatments_by_flag_set('random_user_id', [Object.new, Object.new])).to eq({}) expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end it 'warns when empty flagset names' do expect(subject.get_treatments_by_flag_set('random_user_id', [''])).to eq({}) expect(log.string).to include 'get_treatments_by_flag_set: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end end @@ -503,38 +503,38 @@ it 'returns empty hash on nil split_names' do expect(subject.get_treatments_by_flag_sets('random_user_id', nil)).to eq({}) expect(log.string).to include 'get_treatments_by_flag_sets: FlagSets must be a non-empty list' - destroy_factory + close_redis end it 'returns empty hash when no Array split_names' do expect(subject.get_treatments_by_flag_sets('random_user_id', Object.new)).to eq({}) expect(log.string).to include 'get_treatments_by_flag_sets: FlagSets must be a non-empty list' - destroy_factory + close_redis end it 'returns empty hash on empty array split_names' do expect(subject.get_treatments_by_flag_sets('random_user_id', [])).to eq({}) expect(log.string).to include 'get_treatments_by_flag_sets: FlagSets must be a non-empty list' - destroy_factory + close_redis end it 'sanitizes flagset names removing repeating and nil' do treatments = subject.get_treatments_by_flag_sets('random_user_id', ['set_1', nil, nil, 'set_1']) expect(log.string).to include 'get_treatments_by_flag_sets: you passed a nil flag set, flag set must be a non-empty String' expect(treatments.size).to eq 1 - destroy_factory + close_redis end it 'warns when non string flagset names' do expect(subject.get_treatments_by_flag_sets('random_user_id', [Object.new, Object.new])).to eq({}) expect(log.string).to include 'get_treatments_by_flag_sets: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end it 'warns when empty flagset names' do expect(subject.get_treatments_by_flag_sets('random_user_id', [''])).to eq({}) expect(log.string).to include 'get_treatments_by_flag_sets: you passed an invalid flag set type, flag set must be a non-empty String' - destroy_factory + close_redis end end @@ -548,7 +548,7 @@ result = subject.get_treatments_with_config_by_flag_set('fake_user_id_1', 'set_1') expect(result.size).to eq 1 expect(result[:test_feature]).to eq(treatment: 'on', config: '{"killed":false}') - destroy_factory + close_redis end end @@ -562,7 +562,7 @@ result = subject.get_treatments_with_config_by_flag_sets('fake_user_id_1', ['set_1']) expect(result.size).to eq 1 expect(result[:test_feature]).to eq(treatment: 'on', config: '{"killed":false}') - destroy_factory + close_redis end end @@ -575,13 +575,13 @@ it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'on' expect(subject.get_treatment('fake_user_id_2', 'test_feature')).to eq 'on' - destroy_factory + close_redis end xit 'allocates minimum objects' do expect { subject.get_treatment('fake_user_id_1', 'test_feature') }.to allocate_max(283).objects expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'on' - destroy_factory + close_redis end end @@ -595,19 +595,19 @@ it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_1', 'new_feature')).to eq 'on' - destroy_factory + close_redis end it 'validates the feature is on for integer' do expect(subject.get_treatment(222, 'new_feature')).to eq 'on' - destroy_factory + close_redis end it 'validates the feature is on for all ids multiple keys' do expect(subject.get_treatments('fake_user_id_1', %w[new_feature foo])).to eq( new_feature: 'on', foo: SplitIoClient::Engine::Models::Treatment::CONTROL ) - destroy_factory + close_redis end it "[#{cache_adapter}] validates the feature is on for all ids multiple keys for integer key" do @@ -617,7 +617,7 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.size).to eq(1) - destroy_factory + close_redis end it 'validates the feature is on for all ids multiple keys for integer key' do @@ -632,7 +632,7 @@ .new(subject.instance_variable_get(:@impressions_repository)) .call(true, impressions) .select { |im| im[:f] == :new_feature }[0][:i].size).to eq(2) - destroy_factory + close_redis end it 'validates the feature by bucketing_key' do @@ -642,14 +642,14 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.first[:i][:k]).to eq('fake_user_id_1') - destroy_factory + close_redis end it 'validates the feature by bucketing_key for nil matching_key' do key = { bucketing_key: 'fake_user_id_1' } expect(subject.get_treatment(key, 'new_feature')).to eq 'control' - destroy_factory + close_redis end it 'validates the feature by bucketing_key' do @@ -659,17 +659,17 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.first[:i][:k]).to eq('222') - destroy_factory + close_redis end it 'validates the feature returns default treatment for non matching ids' do expect(subject.get_treatment('fake_user_id_3', 'new_feature')).to eq 'def_test' - destroy_factory + close_redis end it 'returns default treatment for active splits with a non matching id' do expect(subject.get_treatment('fake_user_id_3', 'new_feature')).to eq 'def_test' - destroy_factory + close_redis end end @@ -689,7 +689,7 @@ new_feature3: 'on', new_feature4: SplitIoClient::Engine::Models::Treatment::CONTROL ) - destroy_factory + close_redis end it 'validates the feature by bucketing_key' do @@ -702,7 +702,7 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.first[:i][:k]).to eq('fake_user_id_1') - destroy_factory + close_redis end it 'validates the feature by bucketing_key for nil matching_key' do @@ -710,17 +710,17 @@ expect(subject.get_treatments(key, ['new_feature'])) .to eq(new_feature: SplitIoClient::Engine::Models::Treatment::CONTROL) - destroy_factory + close_redis end it 'validates the feature returns default treatment for non matching ids' do expect(subject.get_treatments('fake_user_id_3', ['new_feature'])).to eq(new_feature: 'def_test') - destroy_factory + close_redis end it 'returns default treatment for active splits with a non matching id' do expect(subject.get_treatments('fake_user_id_3', ['new_feature'])).to eq(new_feature: 'def_test') - destroy_factory + close_redis end end @@ -733,12 +733,12 @@ it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_1', 'test_whitelist')).to eq 'on' - destroy_factory + close_redis end it 'validates the feature is on for all ids' do expect(subject.get_treatment('fake_user_id_2', 'test_whitelist')).to eq 'off' - destroy_factory + close_redis end end @@ -751,7 +751,7 @@ it 'returns on treatment' do expect(subject.get_treatment('fake_user_id_1', 'test_dependency')).to eq 'on' - destroy_factory + close_redis end it 'produces only 1 impression' do @@ -759,7 +759,7 @@ impressions = subject.instance_variable_get(:@impressions_repository).batch expect(impressions.size).to eq(1) - destroy_factory + close_redis end end @@ -775,7 +775,7 @@ expect(subject.get_treatment('fake_user_id_1', 'test_killed')).to eq 'def_test' expect(subject.get_treatment('fake_user_id_2', 'test_killed')).to eq 'def_test' expect(subject.get_treatment('fake_user_id_3', 'test_killed')).to eq 'def_test' - destroy_factory + close_redis end end @@ -790,7 +790,7 @@ it 'returns control for deleted splits' do expect(subject.get_treatment('fake_user_id_3', 'new_feature')).to eq 'control' - destroy_factory + close_redis end end @@ -824,7 +824,7 @@ expect(range.cover?(treatments[i])).to be true i += 1 end - destroy_factory + close_redis end end @@ -847,7 +847,7 @@ impressions = customer_impression_listener.queue expect(impressions.size >= 2).to be true - destroy_factory + close_redis end it 'returns correct impressions for get_treatments' do @@ -865,7 +865,7 @@ expect(impressions.select { |i| i[:split_name] == :sample_feature }.size).to eq(6) expect(impressions.select { |i| i[:split_name] == :beta_feature }.size).to eq(6) - destroy_factory + close_redis end context 'traffic allocations' do @@ -879,28 +879,28 @@ expect(subject.get_treatment('01', 'Traffic_Allocation_UI')).to eq('off') expect(subject.get_treatment('ab', 'Traffic_Allocation_UI')).to eq('off') expect(subject.get_treatment('00b0', 'Traffic_Allocation_UI')).to eq('off') - destroy_factory + close_redis end it 'returns expected treatment when traffic alllocation < 100' do expect(subject.get_treatment('01', 'Traffic_Allocation_UI3')).to eq('off') expect(subject.get_treatment('ab', 'Traffic_Allocation_UI3')).to eq('off') expect(subject.get_treatment('00b0', 'Traffic_Allocation_UI3')).to eq('off') - destroy_factory + close_redis end it 'returns expected treatment when traffic alllocation is 0' do expect(subject.get_treatment('01', 'Traffic_Allocation_UI4')).to eq('on') expect(subject.get_treatment('ab', 'Traffic_Allocation_UI4')).to eq('on') expect(subject.get_treatment('00b0', 'Traffic_Allocation_UI4')).to eq('on') - destroy_factory + close_redis end it 'returns "not in split" label' do subject.get_treatment('test', 'Traffic_Allocation_UI2') impressions_repository = subject.instance_variable_get(:@impressions_repository) expect(impressions_repository.batch[0][:i][:r]).to eq(SplitIoClient::Engine::Models::Label::NOT_IN_SPLIT) - destroy_factory + close_redis end end end @@ -916,7 +916,7 @@ allow_any_instance_of(SplitIoClient::Splitter).to receive(:bucket).and_return(1) subject.block_until_ready expect(subject.get_treatment('test', 'Traffic_Allocation_One_Percent')).to eq('on') - destroy_factory + close_redis end end @@ -932,7 +932,8 @@ subject.block_until_ready expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'on' sleep 0.5 - destroy_factory + close_redis + subject.destroy expect(subject.get_treatment('fake_user_id_1', 'test_feature')).to eq 'control' end end @@ -946,7 +947,7 @@ it 'returns control' do allow(subject.instance_variable_get(:@impressions_repository)) .to receive(:add).and_raise(Redis::CannotConnectError) - destroy_factory + close_redis end end @@ -976,7 +977,7 @@ ) expect(subject.instance_variable_get(:@events_repository).clear).to eq([]) - destroy_factory + close_redis end end @@ -991,28 +992,28 @@ expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track(nil, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed a nil key, key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when empty key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track('', 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed an empty key, key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when nil key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track(nil, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed a nil key, key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when no Integer, String or Symbol key' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track(Object.new, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed an invalid key type, key must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when longer than specified max characters key' do @@ -1020,7 +1021,7 @@ expect(subject.track(very_long_key, 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include 'track: key is too long - ' \ "must be #{subject.instance_variable_get(:@config).max_key_size} characters or less" - destroy_factory + close_redis end it 'event is added and a Warn is logged when Integer key' do @@ -1029,7 +1030,7 @@ value = 123 expect(subject.track(value, 'traffic_type', 'event_type', 123)).to be true expect(log.string).to include "track: key \"#{value}\" is not of type String, converting" - destroy_factory + close_redis end it 'event is not added when nil traffic_type_name' do @@ -1037,7 +1038,7 @@ expect(subject.track(1, nil, 'event_type', 123)).to be false expect(log.string).to include 'track: you passed a nil traffic_type_name, ' \ 'traffic_type_name must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when empty string traffic_type_name' do @@ -1045,7 +1046,7 @@ expect(subject.track(1, '', 'event_type', 123)).to be false expect(log.string).to include 'track: you passed an empty traffic_type_name, ' \ 'traffic_type_name must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is added and a Warn is logged when capitalized traffic_type_name' do @@ -1055,7 +1056,7 @@ expect(subject.track('key', 'TRAFFIC_TYPE', 'event_type', 123)).to be true expect(log.string).to include 'track: traffic_type_name should be all lowercase - ' \ 'converting string to lowercase' - destroy_factory + close_redis end it 'event is not added when nil event_type' do @@ -1063,7 +1064,7 @@ expect(subject.track('key', 'traffic_type', nil, 123)).to be false expect(log.string).to include 'track: you passed a nil event_type, ' \ 'event_type must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when no String or Symbol event_type' do @@ -1071,7 +1072,7 @@ expect(subject.track('key', 'traffic_type', Object.new, 123)).to be false expect(log.string).to include 'track: you passed an invalid event_type type, ' \ 'event_type must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when empty event_type' do @@ -1079,7 +1080,7 @@ expect(subject.track('key', 'traffic_type', '', 123)).to be false expect(log.string).to include 'track: you passed an empty event_type, ' \ 'event_type must be a non-empty String or a Symbol' - destroy_factory + close_redis end it 'event is not added when event_type does not conform with specified format' do @@ -1087,34 +1088,34 @@ expect(subject.track('key', 'traffic_type', 'foo@bar', 123)).to be false expect(log.string).to include 'event_type must adhere to the regular expression ' \ '^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$. ' - destroy_factory + close_redis end it 'event is not added when no Integer value' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 'non-integer')).to be false expect(log.string).to include 'track: value must be Numeric' - destroy_factory + close_redis end it 'event is added when nil value' do expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', nil)).to be true - destroy_factory + close_redis end it 'event is not added when non Hash properties' do expect(subject.instance_variable_get(:@events_repository)).not_to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, 'not a Hash')).to be false expect(log.string).to include 'track: properties must be a Hash' - destroy_factory + close_redis end it 'event is not added when error calling add' do expect(subject.instance_variable_get(:@events_repository)).to receive(:add).and_throw(StandardError) expect(subject.track('key', 'traffic_type', 'event_type', 123)).to be false expect(log.string).to include '[splitclient-rb] Unexpected exception in track' - destroy_factory + close_redis end it 'warns users when property count exceeds 300' do @@ -1124,7 +1125,7 @@ expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be true expect(log.string).to include 'Event has more than 300 properties. Some of them will be trimmed when processed' - destroy_factory + close_redis end it 'removes non String key properties' do @@ -1136,7 +1137,7 @@ .to eq [properties.select { |key, _| key.is_a?(String) }, 5] expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be true - destroy_factory + close_redis end it 'changes invalid property values to nil' do @@ -1152,7 +1153,7 @@ expect(subject.instance_variable_get(:@events_repository)).to receive(:add) expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be true expect(log.string).to include 'Property invalid_property_value is of invalid type. Setting value to nil' - destroy_factory + close_redis end it 'event is not added when properties size exceeds threshold' do @@ -1162,7 +1163,7 @@ expect(subject.track('key', 'traffic_type', 'event_type', 123, properties)).to be false expect(log.string).to include 'The maximum size allowed for the properties is 32768. ' \ 'Current is 33078. Event not queued' - destroy_factory + close_redis end it 'event is added and a Warn is logged when traffic type does not exist' do @@ -1175,7 +1176,7 @@ expect(log.string).to include "track: Traffic Type #{traffic_type_name} " \ "does not have any corresponding feature flags in this environment, make sure you're tracking " \ 'your events to a valid traffic type defined in the Split user interface' - destroy_factory + close_redis end end end @@ -1307,11 +1308,10 @@ def add_flag_sets_to_redis(flag_sets_json) end end -def destroy_factory +def close_redis config = subject.instance_variable_get(:@config) if config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter redis = config.cache_adapter.instance_variable_get(:@redis) redis.close end - subject.destroy end From bec1e81bb348d1589af065d0fbabbfc409e51be5 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 7 Dec 2023 14:02:47 -0800 Subject: [PATCH 26/27] fixed typo in telemetry record latency --- lib/splitclient-rb/clients/split_client.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 79bf5dd8..99366fde 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -402,10 +402,10 @@ def record_latency(method, start) @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, bucket) when GET_TREATMENTS_BY_FLAG_SETS @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, bucket) - when GET_TREATMENT_WITH_CONFIG - @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, bucket) - when GET_TREATMENTS_WITH_CONFIG - @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, bucket) + when GET_TREATMENT_WITH_CONFIG_BY_FLAG_SET + @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, bucket) + when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS + @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, bucket) when TRACK @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TRACK, bucket) end From c8e27a973feedfb937a7d9a851ce4e14ccd1f695 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 7 Dec 2023 14:13:06 -0800 Subject: [PATCH 27/27] fixed typo and cleanup --- lib/splitclient-rb/cache/repositories/splits_repository.rb | 2 -- lib/splitclient-rb/clients/split_client.rb | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 7ec96a8e..3f17a0c7 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -171,8 +171,6 @@ def add_feature_flag(split) end def remove_feature_flag(split) - tt_name = split[:trafficTypeName] - decrease_tt_name_count(split[:trafficTypeName]) remove_from_flag_sets(split) @adapter.delete(namespace_key(".split.#{split[:name]}")) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 99366fde..322efd30 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -402,7 +402,7 @@ def record_latency(method, start) @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, bucket) when GET_TREATMENTS_BY_FLAG_SETS @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, bucket) - when GET_TREATMENT_WITH_CONFIG_BY_FLAG_SET + when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, bucket) when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, bucket)