diff --git a/lib/configcat.rb b/lib/configcat.rb index 89b05e8..a9a6667 100644 --- a/lib/configcat.rb +++ b/lib/configcat.rb @@ -33,6 +33,9 @@ def ConfigCat.create_client_with_auto_poll(sdk_key, proxy_port: nil, proxy_user: nil, proxy_pass: nil, + open_timeout: 10, + read_timeout: 30, + flag_overrides: nil, data_governance: DataGovernance::GLOBAL) # # Create an instance of ConfigCatClient and setup Auto Poll mode with custom options @@ -48,6 +51,9 @@ def ConfigCat.create_client_with_auto_poll(sdk_key, # :param proxy_port: Proxy port # :param proxy_user: username for proxy authentication # :param proxy_pass: password for proxy authentication + # :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds. + # :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds. + # :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings. # :param data_governance: # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard: # https://app.configcat.com/organization/data-governance @@ -73,6 +79,9 @@ def ConfigCat.create_client_with_auto_poll(sdk_key, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass, + open_timeout: open_timeout, + read_timeout: read_timeout, + flag_overrides: flag_overrides, data_governance: data_governance) end @@ -84,6 +93,9 @@ def ConfigCat.create_client_with_lazy_load(sdk_key, proxy_port: nil, proxy_user: nil, proxy_pass: nil, + open_timeout: 10, + read_timeout: 30, + flag_overrides: nil, data_governance: DataGovernance::GLOBAL) # # Create an instance of ConfigCatClient and setup Lazy Load mode with custom options @@ -97,6 +109,9 @@ def ConfigCat.create_client_with_lazy_load(sdk_key, # :param proxy_port: Proxy port # :param proxy_user: username for proxy authentication # :param proxy_pass: password for proxy authentication + # :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds. + # :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds. + # :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings. # :param data_governance: # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard: # https://app.configcat.com/organization/data-governance @@ -119,16 +134,22 @@ def ConfigCat.create_client_with_lazy_load(sdk_key, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass, + open_timeout: open_timeout, + read_timeout: read_timeout, + flag_overrides: flag_overrides, data_governance: data_governance) end def ConfigCat.create_client_with_manual_poll(sdk_key, config_cache_class: nil, base_url: nil, - proxy_address:nil, - proxy_port:nil, - proxy_user:nil, - proxy_pass:nil, + proxy_address: nil, + proxy_port: nil, + proxy_user: nil, + proxy_pass: nil, + open_timeout: 10, + read_timeout: 30, + flag_overrides: nil, data_governance: DataGovernance::GLOBAL) # # Create an instance of ConfigCatClient and setup Manual Poll mode with custom options @@ -141,6 +162,9 @@ def ConfigCat.create_client_with_manual_poll(sdk_key, # :param proxy_port: Proxy port # :param proxy_user: username for proxy authentication # :param proxy_pass: password for proxy authentication + # :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds. + # :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds. + # :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings. # :param data_governance: # Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard: # https://app.configcat.com/organization/data-governance @@ -160,6 +184,9 @@ def ConfigCat.create_client_with_manual_poll(sdk_key, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass, + open_timeout: open_timeout, + read_timeout: read_timeout, + flag_overrides: flag_overrides, data_governance: data_governance) end diff --git a/lib/configcat/autopollingcachepolicy.rb b/lib/configcat/autopollingcachepolicy.rb index 2d1b24a..3e12a35 100644 --- a/lib/configcat/autopollingcachepolicy.rb +++ b/lib/configcat/autopollingcachepolicy.rb @@ -82,6 +82,9 @@ def force_refresh() if !@_initialized && !old_configuration.equal?(nil) @_initialized = true end + rescue Timeout::Error => e + ConfigCat.logger.error("Request timed out. Timeout values: [open: %ss, read: %ss]" % + [@_config_fetcher.get_open_timeout(), @_config_fetcher.get_read_timeout()]) rescue Exception => e ConfigCat.logger.error("Double-check your SDK Key at https://app.configcat.com/sdkkey.") ConfigCat.logger.error "threw exception #{e.class}:'#{e}'" diff --git a/lib/configcat/configcatclient.rb b/lib/configcat/configcatclient.rb index 014acda..2fcc87b 100644 --- a/lib/configcat/configcatclient.rb +++ b/lib/configcat/configcatclient.rb @@ -7,25 +7,41 @@ require 'configcat/rolloutevaluator' require 'configcat/datagovernance' + module ConfigCat KeyValue = Struct.new(:key, :value) class ConfigCatClient + @@sdk_keys = [] + def initialize(sdk_key, - poll_interval_seconds:60, - max_init_wait_time_seconds:5, - on_configuration_changed_callback:nil, - cache_time_to_live_seconds:60, - config_cache_class:nil, - base_url:nil, - proxy_address:nil, - proxy_port:nil, - proxy_user:nil, - proxy_pass:nil, + poll_interval_seconds: 60, + max_init_wait_time_seconds: 5, + on_configuration_changed_callback: nil, + cache_time_to_live_seconds: 60, + config_cache_class: nil, + base_url: nil, + proxy_address: nil, + proxy_port: nil, + proxy_user: nil, + proxy_pass: nil, + open_timeout: 10, + read_timeout: 30, + flag_overrides: nil, data_governance: DataGovernance::GLOBAL) if sdk_key === nil raise ConfigCatClientException, "SDK Key is required." end + + if @@sdk_keys.include?(sdk_key) + ConfigCat.logger.warn("A ConfigCat Client is already initialized with sdk_key %s. "\ + "We strongly recommend you to use the ConfigCat Client as "\ + "a Singleton object in your application." % sdk_key) + else + @@sdk_keys.push(sdk_key) + end + @_sdk_key = sdk_key + @_override_data_source = flag_overrides if config_cache_class @_config_cache = config_cache_class.new() @@ -33,23 +49,35 @@ def initialize(sdk_key, @_config_cache = InMemoryConfigCache.new() end - if poll_interval_seconds > 0 - @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url, proxy_address, proxy_port, proxy_user, proxy_pass, data_governance) + if !@_override_data_source.equal?(nil) && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY + @_config_fetcher = nil + @_cache_policy = nil + elsif poll_interval_seconds > 0 + @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url: base_url, + proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass, + open_timeout: open_timeout, read_timeout: read_timeout, + data_governance: data_governance) @_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback) + elsif cache_time_to_live_seconds > 0 + @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url: base_url, + proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass, + open_timeout: open_timeout, read_timeout: read_timeout, + data_governance: data_governance) + @_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds) else - if cache_time_to_live_seconds > 0 - @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url, proxy_address, proxy_port, proxy_user, proxy_pass, data_governance) - @_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds) - else - @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url, proxy_address, proxy_port, proxy_user, proxy_pass, data_governance) - @_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key()) - end + @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url: base_url, + proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass, + open_timeout: open_timeout, read_timeout: read_timeout, + data_governance: data_governance) + @_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key()) end end def get_value(key, default_value, user=nil) - config = @_cache_policy.get() + config = _get_settings() if config === nil + ConfigCat.logger.warn("Evaluating get_value('%s') failed. Cache is empty. "\ + "Returning default_value in your get_value call: [%s]." % [key, default_value.to_s]) return default_value end value, variation_id = RolloutEvaluator.evaluate(key, user, default_value, nil, config) @@ -57,7 +85,7 @@ def get_value(key, default_value, user=nil) end def get_all_keys() - config = @_cache_policy.get() + config = _get_settings() if config === nil return [] end @@ -69,7 +97,7 @@ def get_all_keys() end def get_variation_id(key, default_variation_id, user=nil) - config = @_cache_policy.get() + config = _get_settings() if config === nil ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. "\ "Returning default_variation_id in your get_variation_id call: [%s]." % @@ -93,7 +121,7 @@ def get_all_variation_ids(user: nil) end def get_key_and_value(variation_id) - config = @_cache_policy.get() + config = _get_settings() if config === nil ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. Returning nil." % variation_id) return nil @@ -126,17 +154,56 @@ def get_key_and_value(variation_id) end end + def get_all_values(user: nil) + keys = get_all_keys() + all_values = {} + for key in keys + value = get_value(key, nil, user) + if !value.equal?(nil) + all_values[key] = value + end + end + return all_values + end + def force_refresh() @_cache_policy.force_refresh() end def stop() - @_cache_policy.stop() - @_config_fetcher.close() + @_cache_policy.stop() if @_cache_policy + @_config_fetcher.close() if @_config_fetcher + @@sdk_keys.delete(@_sdk_key) end private + def _get_settings() + if !@_override_data_source.nil? + behaviour = @_override_data_source.get_behaviour() + if behaviour == OverrideBehaviour::LOCAL_ONLY + return @_override_data_source.get_overrides() + elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL + remote_settings = @_cache_policy.get() + local_settings = @_override_data_source.get_overrides() + result = local_settings.clone() + if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS) + result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(remote_settings[FEATURE_FLAGS]) + end + return result + elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE + remote_settings = @_cache_policy.get() + local_settings = @_override_data_source.get_overrides() + result = remote_settings.clone() + if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS) + result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(local_settings[FEATURE_FLAGS]) + end + return result + end + end + return @_cache_policy.get() + end + def _get_cache_key() return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key) end diff --git a/lib/configcat/configfetcher.rb b/lib/configcat/configfetcher.rb index 0f5d92e..565167d 100644 --- a/lib/configcat/configfetcher.rb +++ b/lib/configcat/configfetcher.rb @@ -41,13 +41,16 @@ def is_not_modified() end class CacheControlConfigFetcher < ConfigFetcher - def initialize(sdk_key, mode, base_url=nil, proxy_address=nil, proxy_port=nil, proxy_user=nil, proxy_pass=nil, - data_governance=DataGovernance::GLOBAL) + def initialize(sdk_key, mode, base_url:nil, proxy_address:nil, proxy_port:nil, proxy_user:nil, proxy_pass:nil, + open_timeout:10, read_timeout:30, + data_governance:DataGovernance::GLOBAL) @_sdk_key = sdk_key @_proxy_address = proxy_address @_proxy_port = proxy_port @_proxy_user = proxy_user @_proxy_pass = proxy_pass + @_open_timeout = open_timeout + @_read_timeout = read_timeout @_etag = "" @_headers = {"User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json"} if !base_url.equal?(nil) @@ -63,6 +66,14 @@ def initialize(sdk_key, mode, base_url=nil, proxy_address=nil, proxy_port=nil, p end end + def get_open_timeout() + return @_open_timeout + end + + def get_read_timeout() + return @_read_timeout + end + # Returns the FetchResponse object contains configuration json Dictionary def get_configuration_json(retries=0) ConfigCat.logger.debug "Fetching configuration from ConfigCat" @@ -141,8 +152,8 @@ def _create_http() close() @_http = Net::HTTP.new(uri.host, uri.port, @_proxy_address, @_proxy_port, @_proxy_user, @_proxy_pass) @_http.use_ssl = use_ssl - @_http.open_timeout = 10 # in seconds - @_http.read_timeout = 30 # in seconds + @_http.open_timeout = @_open_timeout + @_http.read_timeout = @_read_timeout end end end diff --git a/lib/configcat/localdictionarydatasource.rb b/lib/configcat/localdictionarydatasource.rb new file mode 100644 index 0000000..a5de760 --- /dev/null +++ b/lib/configcat/localdictionarydatasource.rb @@ -0,0 +1,20 @@ +require 'configcat/overridedatasource' +require 'configcat/constants' + + +module ConfigCat + class LocalDictionaryDataSource < OverrideDataSource + def initialize(source, override_behaviour) + super(override_behaviour) + dictionary = {} + source.each do |key, value| + dictionary[key] = {VALUE => value} + end + @_settings = {FEATURE_FLAGS => dictionary} + end + + def get_overrides() + return @_settings + end + end +end diff --git a/lib/configcat/localfiledatasource.rb b/lib/configcat/localfiledatasource.rb new file mode 100644 index 0000000..2c97ab3 --- /dev/null +++ b/lib/configcat/localfiledatasource.rb @@ -0,0 +1,49 @@ +require 'configcat/overridedatasource' +require 'configcat/constants' + + +module ConfigCat + class LocalFileDataSource < OverrideDataSource + def initialize(file_path, override_behaviour) + super(override_behaviour) + if File.exists?(file_path) + ConfigCat.logger.error("The file '%s' does not exists." % file_path) + end + @_file_path = file_path + @_settings = nil + @_cached_file_stamp = 0 + end + + def get_overrides() + reload_file_content() + return @_settings + end + + private + + def reload_file_content() + begin + stamp = File.mtime(@_file_path) + if stamp != @_cached_file_stamp + @_cached_file_stamp = stamp + file = File.read(@_file_path) + data = JSON.parse(file) + if data.key?("flags") + dictionary = {} + source = data["flags"] + source.each do |key, value| + dictionary[key] = {VALUE => value} + end + @_settings = {FEATURE_FLAGS => dictionary} + else + @_settings = data + end + end + rescue JSON::ParserError => e + ConfigCat.logger.error("Could not decode json from file %s. %s" % [@_file_path, e.to_s]) + rescue Exception => e + ConfigCat.logger.error("Could not read the content of the file %s. %s" % [@_file_path, e.to_s]) + end + end + end +end diff --git a/lib/configcat/overridedatasource.rb b/lib/configcat/overridedatasource.rb new file mode 100644 index 0000000..8fc57c9 --- /dev/null +++ b/lib/configcat/overridedatasource.rb @@ -0,0 +1,32 @@ +module ConfigCat + class OverrideBehaviour + # When evaluating values, the SDK will not use feature flags & settings from the ConfigCat CDN, but it will use + # all feature flags & settings that are loaded from local-override sources. + LOCAL_ONLY = 0 + + # When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN, + # plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is + # defined both in the fetched and the local-override source then the local-override version will take precedence. + LOCAL_OVER_REMOTE = 1 + + # When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN, + # plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is + # defined both in the fetched and the local-override source then the fetched version will take precedence. + REMOTE_OVER_LOCAL = 2 + end + + class OverrideDataSource + def initialize(override_behaviour) + @_override_behaviour = override_behaviour + end + + def get_behaviour() + return @_override_behaviour + end + + def get_overrides() + # :returns the override dictionary + return {} + end + end +end diff --git a/lib/configcat/rolloutevaluator.rb b/lib/configcat/rolloutevaluator.rb index fe97938..f60efb9 100644 --- a/lib/configcat/rolloutevaluator.rb +++ b/lib/configcat/rolloutevaluator.rb @@ -39,132 +39,142 @@ def self.evaluate(key, user, default_value, default_variation_id, config) return return_value, return_variation_id end - ConfigCat.logger.info("User object:\n%s" % user.to_s) - - # Evaluate targeting rules - for rollout_rule in rollout_rules - comparison_attribute = rollout_rule.fetch(COMPARISON_ATTRIBUTE) - comparison_value = rollout_rule.fetch(COMPARISON_VALUE, nil) - comparator = rollout_rule.fetch(COMPARATOR, nil) - - user_value = user.get_attribute(comparison_attribute) - if user_value === nil || !user_value - ConfigCat.logger.info(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)) - next - end + log_entries = ["Evaluating get_value('%s')." % key, "User object:\n%s" % user.to_s] + + begin + # Evaluate targeting rules + for rollout_rule in rollout_rules + comparison_attribute = rollout_rule.fetch(COMPARISON_ATTRIBUTE) + comparison_value = rollout_rule.fetch(COMPARISON_VALUE, nil) + comparator = rollout_rule.fetch(COMPARATOR, nil) + + user_value = user.get_attribute(comparison_attribute) + if user_value === nil || !user_value + log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)) + next + end - value = rollout_rule.fetch(VALUE, nil) - variation_id = rollout_rule.fetch(VARIATION_ID, default_variation_id) + value = rollout_rule.fetch(VALUE, nil) + variation_id = rollout_rule.fetch(VARIATION_ID, default_variation_id) - # IS ONE OF - if comparator == 0 - if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) - return value, variation_id - end - # IS NOT ONE OF - elsif comparator == 1 - if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) - return value, variation_id - end - # CONTAINS - elsif comparator == 2 - if user_value.to_s.include?(comparison_value.to_s) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) - return value, variation_id - end - # DOES NOT CONTAIN - elsif comparator == 3 - if !user_value.to_s.include?(comparison_value.to_s) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) - return value, variation_id - end - # IS ONE OF, IS NOT ONE OF (Semantic version) - elsif (4 <= comparator) && (comparator <= 5) - begin - match = false - user_value_version = Semantic::Version.new(user_value.to_s.strip()) - ((comparison_value.to_s.split(",").map { |x| x.strip() }).reject { |c| c.empty? }).each { |x| - version = Semantic::Version.new(x) - match = (user_value_version == version) || match - } - if match && comparator == 4 || !match && comparator == 5 - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + # IS ONE OF + if comparator == 0 + if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) return value, variation_id end - rescue ArgumentError => e - ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)) - next - end - # LESS THAN, LESS THAN OR EQUALS TO, GREATER THAN, GREATER THAN OR EQUALS TO (Semantic version) - elsif (6 <= comparator) && (comparator <= 9) - begin - user_value_version = Semantic::Version.new(user_value.to_s.strip()) - comparison_value_version = Semantic::Version.new(comparison_value.to_s.strip()) - if (comparator == 6 && user_value_version < comparison_value_version) || - (comparator == 7 && user_value_version <= comparison_value_version) || - (comparator == 8 && user_value_version > comparison_value_version) || - (comparator == 9 && user_value_version >= comparison_value_version) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + # IS NOT ONE OF + elsif comparator == 1 + if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) return value, variation_id end - rescue ArgumentError => e - ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)) - next - end - elsif (10 <= comparator) && (comparator <= 15) - begin - user_value_float = Float(user_value.to_s.gsub(",", ".")) - comparison_value_float = Float(comparison_value.to_s.gsub(",", ".")) - if (comparator == 10 && user_value_float == comparison_value_float) || - (comparator == 11 && user_value_float != comparison_value_float) || - (comparator == 12 && user_value_float < comparison_value_float) || - (comparator == 13 && user_value_float <= comparison_value_float) || - (comparator == 14 && user_value_float > comparison_value_float) || - (comparator == 15 && user_value_float >= comparison_value_float) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + # CONTAINS + elsif comparator == 2 + if user_value.to_s.include?(comparison_value.to_s) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + return value, variation_id + end + # DOES NOT CONTAIN + elsif comparator == 3 + if !user_value.to_s.include?(comparison_value.to_s) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + return value, variation_id + end + # IS ONE OF, IS NOT ONE OF (Semantic version) + elsif (4 <= comparator) && (comparator <= 5) + begin + match = false + user_value_version = Semantic::Version.new(user_value.to_s.strip()) + ((comparison_value.to_s.split(",").map { |x| x.strip() }).reject { |c| c.empty? }).each { |x| + version = Semantic::Version.new(x) + match = (user_value_version == version) || match + } + if match && comparator == 4 || !match && comparator == 5 + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + return value, variation_id + end + rescue ArgumentError => e + message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s) + ConfigCat.logger.warn(message) + log_entries.push(message) + next + end + # LESS THAN, LESS THAN OR EQUALS TO, GREATER THAN, GREATER THAN OR EQUALS TO (Semantic version) + elsif (6 <= comparator) && (comparator <= 9) + begin + user_value_version = Semantic::Version.new(user_value.to_s.strip()) + comparison_value_version = Semantic::Version.new(comparison_value.to_s.strip()) + if (comparator == 6 && user_value_version < comparison_value_version) || + (comparator == 7 && user_value_version <= comparison_value_version) || + (comparator == 8 && user_value_version > comparison_value_version) || + (comparator == 9 && user_value_version >= comparison_value_version) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + return value, variation_id + end + rescue ArgumentError => e + message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s) + ConfigCat.logger.warn(message) + log_entries.push(message) + next + end + elsif (10 <= comparator) && (comparator <= 15) + begin + user_value_float = Float(user_value.to_s.gsub(",", ".")) + comparison_value_float = Float(comparison_value.to_s.gsub(",", ".")) + if (comparator == 10 && user_value_float == comparison_value_float) || + (comparator == 11 && user_value_float != comparison_value_float) || + (comparator == 12 && user_value_float < comparison_value_float) || + (comparator == 13 && user_value_float <= comparison_value_float) || + (comparator == 14 && user_value_float > comparison_value_float) || + (comparator == 15 && user_value_float >= comparison_value_float) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + return value, variation_id + end + rescue Exception => e + message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s) + ConfigCat.logger.warn(message) + log_entries.push(message) + next + end + # IS ONE OF (Sensitive) + elsif comparator == 16 + if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) + return value, variation_id + end + # IS NOT ONE OF (Sensitive) + elsif comparator == 17 + if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s) + log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) return value, variation_id end - rescue Exception => e - ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)) - next - end - # IS ONE OF (Sensitive) - elsif comparator == 16 - if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) - return value, variation_id - end - # IS NOT ONE OF (Sensitive) - elsif comparator == 17 - if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s) - ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value)) - return value, variation_id end + log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)) end - ConfigCat.logger.info(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value)) - end - if rollout_percentage_items.size > 0 - user_key = user.get_identifier() - hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8") - hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100 - bucket = 0 - for rollout_percentage_item in rollout_percentage_items || [] - bucket += rollout_percentage_item.fetch(PERCENTAGE, 0) - if hash_val < bucket - percentage_value = rollout_percentage_item.fetch(VALUE, nil) - variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id) - ConfigCat.logger.info("Evaluating %% options. Returning %s" % percentage_value) - return percentage_value, variation_id + if rollout_percentage_items.size > 0 + user_key = user.get_identifier() + hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8") + hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100 + bucket = 0 + for rollout_percentage_item in rollout_percentage_items || [] + bucket += rollout_percentage_item.fetch(PERCENTAGE, 0) + if hash_val < bucket + percentage_value = rollout_percentage_item.fetch(VALUE, nil) + variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id) + log_entries.push("Evaluating %% options. Returning %s" % percentage_value) + return percentage_value, variation_id + end end end + return_value = setting_descriptor.fetch(VALUE, default_value) + return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id) + log_entries.push("Returning %s" % return_value) + return return_value, return_variation_id + ensure + ConfigCat.logger.info(log_entries.join("\n")) end - return_value = setting_descriptor.fetch(VALUE, default_value) - return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id) - ConfigCat.logger.info("Returning %s" % return_value) - return return_value, return_variation_id end private diff --git a/lib/configcat/user.rb b/lib/configcat/user.rb index 62a5a98..732e1e6 100644 --- a/lib/configcat/user.rb +++ b/lib/configcat/user.rb @@ -32,6 +32,25 @@ def get_attribute(attribute) end return nil end + + def to_s() + r = %Q({\n "Identifier": "#{@__identifier}") + if !@__data["Email"].equal?(nil) + r += %Q(,\n "Email": "#{@__data["Email"]}") + end + if !@__data["Country"].equal?(nil) + r += %Q(,\n "Country": "#{@__data["Country"]}") + end + if !@__custom.equal?(nil) + r += %Q(,\n "Custom": {) + for customField in @__custom + r += %Q(\n "#{customField}": "#{@__custom[customField]}",) + end + r += "\n }" + end + r += "\n}" + return r + end end end diff --git a/lib/configcat/version.rb b/lib/configcat/version.rb index b18e4e7..44774e4 100644 --- a/lib/configcat/version.rb +++ b/lib/configcat/version.rb @@ -1,3 +1,3 @@ module ConfigCat - VERSION = "4.0.0" + VERSION = "5.0.0" end diff --git a/samples/consolesample.rb b/samples/consolesample.rb index b54dd46..3330304 100644 --- a/samples/consolesample.rb +++ b/samples/consolesample.rb @@ -1,5 +1,9 @@ require 'configcat' +# Info level logging helps to inspect the feature flag evaluation process. +# Use the default warning level to avoid too detailed logging in your application. +ConfigCat.logger.level = Logger::INFO + # Initialize the ConfigCatClient with an SDK Key. client = ConfigCat.create_client("PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A") diff --git a/samples/consolesample2.rb b/samples/consolesample2.rb index 7ca692e..f8789bf 100644 --- a/samples/consolesample2.rb +++ b/samples/consolesample2.rb @@ -1,11 +1,12 @@ require 'configcat' +# Info level logging helps to inspect the feature flag evaluation process. +# Use the default warning level to avoid too detailed logging in your application. +ConfigCat.logger.level = Logger::INFO + # Initializing the ConfigCatClient with an SDK Key. client = ConfigCat.create_client("PKDVCLf-Hq-h-kCzMp-L7Q/HhOWfwVtZ0mb30i9wi17GQ") -# Setting the log level to Info to show detailed feature flag evaluation. -ConfigCat.logger.level = Logger::INFO - # Creating a user object to identify your user (optional). userObject = ConfigCat::User.new("Some UserID", email: "configcat@example.com", custom: { 'version': '1.0.0' @@ -15,4 +16,6 @@ puts("'isPOCFeatureEnabled' value from ConfigCat: " + value.to_s) value = client.get_value("isAwesomeFeatureEnabled", "default value") -puts("'isAwesomeFeatureEnabled' value from ConfigCat: " + value.to_s) \ No newline at end of file +puts("'isAwesomeFeatureEnabled' value from ConfigCat: " + value.to_s) + +client.stop() diff --git a/spec/configcat/autopollingcachepolicy_spec.rb b/spec/configcat/autopollingcachepolicy_spec.rb index 3f0cc98..3678740 100644 --- a/spec/configcat/autopollingcachepolicy_spec.rb +++ b/spec/configcat/autopollingcachepolicy_spec.rb @@ -15,6 +15,7 @@ expect(config).to eq TEST_JSON cache_policy.stop() end + it "test_init_wait_time_ok" do config_fetcher = ConfigFetcherWaitMock.new(0) config_cache = InMemoryConfigCache.new() @@ -23,6 +24,7 @@ expect(config).to eq TEST_JSON cache_policy.stop() end + it "test_init_wait_time_timeout" do config_fetcher = ConfigFetcherWaitMock.new(5) config_cache = InMemoryConfigCache.new() @@ -36,6 +38,7 @@ expect(elapsed_time).to be < 2 cache_policy.stop() end + it "test_fetch_call_count" do config_fetcher = ConfigFetcherMock.new() config_cache = InMemoryConfigCache.new() @@ -46,6 +49,7 @@ expect(config).to eq TEST_JSON cache_policy.stop() end + it "test_updated_values" do config_fetcher = ConfigFetcherCountMock.new() config_cache = InMemoryConfigCache.new() @@ -57,6 +61,7 @@ expect(config).to eq 20 cache_policy.stop() end + it "test_http_error" do config_fetcher = ConfigFetcherWithErrorMock.new(StandardError.new("error")) config_cache = InMemoryConfigCache.new() @@ -65,6 +70,7 @@ expect(value).to be nil cache_policy.stop() end + it "test_stop" do config_fetcher = ConfigFetcherCountMock.new() config_cache = InMemoryConfigCache.new() @@ -77,6 +83,7 @@ expect(config).to eq 10 cache_policy.stop() end + it "test_rerun" do config_fetcher = ConfigFetcherMock.new() config_cache = InMemoryConfigCache.new() @@ -85,6 +92,7 @@ expect(config_fetcher.get_call_count).to eq 2 cache_policy.stop() end + it "test_callback" do call_counter = CallCounter.new() config_fetcher = ConfigFetcherMock.new() diff --git a/spec/configcat/configcatclient_spec.rb b/spec/configcat/configcatclient_spec.rb index 5148245..d9b7fa9 100644 --- a/spec/configcat/configcatclient_spec.rb +++ b/spec/configcat/configcatclient_spec.rb @@ -8,6 +8,7 @@ ConfigCatClient.new(nil) }.to raise_error(ConfigCatClientException) end + it "test_bool" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -18,6 +19,7 @@ expect(client.get_value("testBoolKey", false)).to eq true client.stop() end + it "test_string" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -28,6 +30,7 @@ expect(client.get_value("testStringKey", "default")).to eq "testValue" client.stop() end + it "test_int" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -38,6 +41,7 @@ expect(client.get_value("testIntKey", 0)).to eq 1 client.stop() end + it "test_double" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -48,6 +52,7 @@ expect(client.get_value("testDoubleKey", 0.0)).to eq 1.1 client.stop() end + it "test_unknown" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -58,6 +63,7 @@ expect(client.get_value("testUnknownKey", "default")).to eq "default" client.stop() end + it "test_invalidation" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -68,6 +74,7 @@ expect(client.get_value("testBoolKey", false)).to eq true client.stop() end + it "test_get_all_keys" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -78,6 +85,25 @@ expect(Set.new(client.get_all_keys())).to eq Set.new(["testBoolKey", "testStringKey", "testIntKey", "testDoubleKey", "key1", "key2"]) client.stop() end + + it "test_get_all_values" do + client = ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + on_configuration_changed_callback: nil, + cache_time_to_live_seconds: 0, + config_cache_class: ConfigCacheMock) + all_values = client.get_all_values() + expect(all_values.size).to eq 6 + expect(all_values["testBoolKey"]).to eq true + expect(all_values["testStringKey"]).to eq "testValue" + expect(all_values["testIntKey"]).to eq 1 + expect(all_values["testDoubleKey"]).to eq 1.1 + expect(all_values["key1"]).to eq true + expect(all_values["key2"]).to eq false + client.stop() + end + it "test_get_variation_id" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -89,6 +115,7 @@ expect(client.get_variation_id("key2", nil)).to eq "fakeId2" client.stop() end + it "test_get_variation_id_not_found" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -99,6 +126,7 @@ expect(client.get_variation_id("nonexisting", "default_variation_id")).to eq "default_variation_id" client.stop() end + it "test_get_variation_id_empty_config" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -109,6 +137,7 @@ expect(client.get_variation_id("nonexisting", "default_variation_id")).to eq "default_variation_id" client.stop() end + it "test_get_all_variation_ids" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, @@ -122,6 +151,7 @@ expect(result.include?("fakeId2")).to eq true client.stop() end + it "test_get_key_and_value" do client = ConfigCatClient.new("test", poll_interval_seconds: 0, diff --git a/spec/datagovernance_spec.rb b/spec/datagovernance_spec.rb index 8aae3f5..7e4fcc8 100644 --- a/spec/datagovernance_spec.rb +++ b/spec/datagovernance_spec.rb @@ -4,9 +4,10 @@ RSpec.describe 'Data governance tests', type: :feature do BASE_URL_FORCED = "https://forced.configcat.com" + BASE_URL_CUSTOM = "https://custom.configcat.com" URI_GLOBAL = ConfigCat::BASE_URL_GLOBAL + "/" + ConfigCat::BASE_PATH + ConfigCat::BASE_EXTENSION URI_EU_ONLY = ConfigCat::BASE_URL_EU_ONLY + "/" + ConfigCat::BASE_PATH + ConfigCat::BASE_EXTENSION - URI_CUSTOM = "https://custom.configcat.com/" + ConfigCat::BASE_PATH + ConfigCat::BASE_EXTENSION + URI_CUSTOM = BASE_URL_CUSTOM + "/" + ConfigCat::BASE_PATH + ConfigCat::BASE_EXTENSION URI_FORCED = BASE_URL_FORCED + "/" + ConfigCat::BASE_PATH + ConfigCat::BASE_EXTENSION TEST_JSON = '{"test": "json"}' @@ -34,8 +35,7 @@ def stub_request(request_uri, response_uri, redirect) eu_only_stub = stub_request(URI_EU_ONLY, ConfigCat::BASE_URL_EU_ONLY, 0) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::GLOBAL) + data_governance: ConfigCat::DataGovernance::GLOBAL) # First fetch fetch_response = fetcher.get_configuration_json() @@ -62,8 +62,7 @@ def stub_request(request_uri, response_uri, redirect) eu_only_stub = stub_request(URI_EU_ONLY, ConfigCat::BASE_URL_EU_ONLY, 0) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::EU_ONLY) + data_governance: ConfigCat::DataGovernance::EU_ONLY) # First fetch fetch_response = fetcher.get_configuration_json() @@ -90,8 +89,7 @@ def stub_request(request_uri, response_uri, redirect) eu_only_stub = stub_request(URI_EU_ONLY, ConfigCat::BASE_URL_EU_ONLY, 0) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::GLOBAL) + data_governance: ConfigCat::DataGovernance::GLOBAL) # First fetch fetch_response = fetcher.get_configuration_json() expect(fetch_response.is_fetched()).to be true @@ -117,8 +115,7 @@ def stub_request(request_uri, response_uri, redirect) eu_only_stub = stub_request(URI_EU_ONLY, ConfigCat::BASE_URL_EU_ONLY, 0) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::EU_ONLY) + data_governance: ConfigCat::DataGovernance::EU_ONLY) # First fetch fetch_response = fetcher.get_configuration_json() @@ -146,8 +143,8 @@ def stub_request(request_uri, response_uri, redirect) custom_stub = stub_request(URI_CUSTOM, ConfigCat::BASE_URL_GLOBAL, 0) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - "https://custom.configcat.com", nil, nil, nil, nil, - ConfigCat::DataGovernance::GLOBAL) + base_url: BASE_URL_CUSTOM, + data_governance: ConfigCat::DataGovernance::GLOBAL) # First fetch fetch_response = fetcher.get_configuration_json() @@ -177,8 +174,8 @@ def stub_request(request_uri, response_uri, redirect) custom_stub = stub_request(URI_CUSTOM, ConfigCat::BASE_URL_GLOBAL, 0) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - "https://custom.configcat.com", nil, nil, nil, nil, - ConfigCat::DataGovernance::EU_ONLY) + base_url: BASE_URL_CUSTOM, + data_governance: ConfigCat::DataGovernance::EU_ONLY) # First fetch fetch_response = fetcher.get_configuration_json() @@ -208,8 +205,7 @@ def stub_request(request_uri, response_uri, redirect) forced_to_forced_stub = stub_request(URI_FORCED, BASE_URL_FORCED, 2) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::GLOBAL) + data_governance: ConfigCat::DataGovernance::GLOBAL) # First fetch fetch_response = fetcher.get_configuration_json() @@ -239,8 +235,7 @@ def stub_request(request_uri, response_uri, redirect) forced_to_forced_stub = stub_request(URI_FORCED, BASE_URL_FORCED, 2) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::EU_ONLY) + data_governance: ConfigCat::DataGovernance::EU_ONLY) # First fetch fetch_response = fetcher.get_configuration_json() @@ -271,8 +266,8 @@ def stub_request(request_uri, response_uri, redirect) custom_to_forced_stub = stub_request(URI_CUSTOM, BASE_URL_FORCED, 2) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - "https://custom.configcat.com", nil, nil, nil, nil, - ConfigCat::DataGovernance::GLOBAL) + base_url: BASE_URL_CUSTOM, + data_governance: ConfigCat::DataGovernance::GLOBAL) # First fetch fetch_response = fetcher.get_configuration_json() @@ -306,8 +301,7 @@ def stub_request(request_uri, response_uri, redirect) eu_only_to_global_stub = stub_request(URI_EU_ONLY, ConfigCat::BASE_URL_GLOBAL, 1) fetcher = ConfigCat::CacheControlConfigFetcher.new("", "m", - nil, nil, nil, nil, nil, - ConfigCat::DataGovernance::GLOBAL) + data_governance: ConfigCat::DataGovernance::GLOBAL) # First fetch fetch_response = fetcher.get_configuration_json() diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index 3f745a3..e62b327 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -7,17 +7,20 @@ ConfigCat.create_client(nil) }.to raise_error(ConfigCat::ConfigCatClientException) end + it "test_client_works" do client = ConfigCat.create_client(_SDK_KEY) expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_get_all_keys" do client = ConfigCat.create_client(_SDK_KEY) keys = client.get_all_keys() expect(keys.size).to eq 5 expect(keys).to include "keySampleText" end + it "test_force_refresh" do client = ConfigCat.create_client(_SDK_KEY) expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" @@ -33,26 +36,31 @@ ConfigCat::create_client_with_auto_poll(nil) }.to raise_error(ConfigCat::ConfigCatClientException) end + it "test_client_works" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY) expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_valid_base_url" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY, base_url: "https://cdn.configcat.com") expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_valid_base_url_trailing_slash" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY, base_url: "https://cdn.configcat.com/") expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_invalid_base_url" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY, base_url: "https://invalidcdn.configcat.com") expect(client.get_value("keySampleText", "default value")).to eq "default value" client.stop() end + it "test_client_works_invalid_proxy" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY, proxy_address: "0.0.0.0", @@ -62,6 +70,16 @@ expect(client.get_value("keySampleText", "default value")).to eq "default value" client.stop() end + + it "test_client_works_request_timeout" do + uri = ConfigCat::BASE_URL_GLOBAL + "/" + ConfigCat::BASE_PATH + _SDK_KEY + ConfigCat::BASE_EXTENSION + WebMock.stub_request(:get, uri).to_timeout() + + client = ConfigCat::create_client_with_auto_poll(_SDK_KEY) + expect(client.get_value("keySampleText", "default value")).to eq "default value" + client.stop() + end + it "test_force_refresh" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY) expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" @@ -69,6 +87,7 @@ expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_wrong_param" do client = ConfigCat::create_client_with_auto_poll(_SDK_KEY, poll_interval_seconds: 0, max_init_wait_time_seconds: -1) sleep(2) @@ -83,21 +102,25 @@ ConfigCat::create_client_with_lazy_load(nil) }.to raise_error(ConfigCat::ConfigCatClientException) end + it "test_client_works" do client = ConfigCat::create_client_with_lazy_load(_SDK_KEY) expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_valid_base_url" do client = ConfigCat::create_client_with_lazy_load(_SDK_KEY, base_url: "https://cdn.configcat.com") expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_invalid_base_url" do client = ConfigCat::create_client_with_lazy_load(_SDK_KEY, base_url: "https://invalidcdn.configcat.com") expect(client.get_value("keySampleText", "default value")).to eq "default value" client.stop() end + it "test_wrong_param" do client = ConfigCat::create_client_with_lazy_load(_SDK_KEY, cache_time_to_live_seconds: 0) expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" @@ -111,6 +134,7 @@ ConfigCat::create_client_with_manual_poll(nil) }.to raise_error(ConfigCat::ConfigCatClientException) end + it "test_client_works" do client = ConfigCat::create_client_with_manual_poll(_SDK_KEY) expect(client.get_value("keySampleText", "default value")).to eq "default value" @@ -118,12 +142,14 @@ expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_valid_base_url" do client = ConfigCat::create_client_with_manual_poll(_SDK_KEY, base_url: "https://cdn.configcat.com") client.force_refresh() expect(client.get_value("keySampleText", "default value")).to eq "This text came from ConfigCat" client.stop() end + it "test_client_works_invalid_base_url" do client = ConfigCat::create_client_with_manual_poll(_SDK_KEY, base_url: "https://invalidcdn.configcat.com") client.force_refresh() diff --git a/spec/local_spec.rb b/spec/local_spec.rb new file mode 100644 index 0000000..dc63e0b --- /dev/null +++ b/spec/local_spec.rb @@ -0,0 +1,165 @@ +require 'spec_helper' +require 'configcat/localdictionarydatasource' +require 'configcat/localfiledatasource' +require 'tempfile' +require 'json' + +RSpec.describe 'Local test', type: :feature do + script_dir = File.dirname(__FILE__) + + def stub_request() + uri_template = Addressable::Template.new "https://{base_url}/{base_path}/{api_key}/{base_ext}" + json = '{"f": {"fakeKey": {"v": false} } }' + WebMock.stub_request(:get, uri_template) + .with( + body: "", + headers: { + 'Accept' => '*/*', + 'Content-Type' => 'application/json', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3' + } + ) + .to_return(status: 200, body: json, headers: {}) + end + + it "test file" do + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalFileDataSource.new(File.join(script_dir, 'test.json'), + ConfigCat::OverrideBehaviour::LOCAL_ONLY)) + expect(client.get_value("enabledFeature", false)).to eq true + expect(client.get_value("disabledFeature", true)).to eq false + expect(client.get_value("intSetting", 0)).to eq 5 + expect(client.get_value("doubleSetting", 0.0)).to eq 3.14 + expect(client.get_value("stringSetting", "")).to eq "test" + client.stop() + end + + it "test simple file" do + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalFileDataSource.new(File.join(script_dir, 'test-simple.json'), + ConfigCat::OverrideBehaviour::LOCAL_ONLY)) + expect(client.get_value("enabledFeature", false)).to eq true + expect(client.get_value("disabledFeature", true)).to eq false + expect(client.get_value("intSetting", 0)).to eq 5 + expect(client.get_value("doubleSetting", 0.0)).to eq 3.14 + expect(client.get_value("stringSetting", "")).to eq "test" + client.stop() + end + + it "test non existent file" do + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalFileDataSource.new('non_existent.json', + ConfigCat::OverrideBehaviour::LOCAL_ONLY)) + expect(client.get_value("enabledFeature", false)).to eq false + client.stop() + end + + it "test reload file" do + temp = Tempfile.new("test-simple") + dictionary = {"flags" => {"enabledFeature" => false}} + begin + temp.write(dictionary.to_json()) + temp.flush() + + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalFileDataSource.new(temp.path(), + ConfigCat::OverrideBehaviour::LOCAL_ONLY)) + expect(client.get_value("enabledFeature", true)).to eq false + + sleep(0.5) + + # clear the content of the temp file + temp.seek(0) + temp.truncate(0) + + # change the temporary file + dictionary["flags"]["enabledFeature"] = true + temp.write(dictionary.to_json()) + temp.flush() + + expect(client.get_value("enabledFeature", false)).to eq true + + client.stop() + ensure + temp.unlink() + end + end + + it "test invalid file" do + temp = Tempfile.new("invalid") + begin + temp.write('{"flags": {"enabledFeature": true}') + temp.close() + + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalFileDataSource.new(temp.path(), + ConfigCat::OverrideBehaviour::LOCAL_ONLY)) + expect(client.get_value("enabledFeature", false)).to eq false + client.stop() + ensure + temp.unlink() + end + end + + it "test dictionary" do + dictionary = { + "enabledFeature" => true, + "disabledFeature" => false, + "intSetting" => 5, + "doubleSetting" => 3.14, + "stringSetting" => "test" + } + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalDictionaryDataSource.new(dictionary, ConfigCat::OverrideBehaviour::LOCAL_ONLY)) + expect(client.get_value("enabledFeature", false)).to eq true + expect(client.get_value("disabledFeature", true)).to eq false + expect(client.get_value("intSetting", 0)).to eq 5 + expect(client.get_value("doubleSetting", 0.0)).to eq 3.14 + expect(client.get_value("stringSetting", "")).to eq "test" + client.stop() + end + + it "test local over remote" do + stub_request() + dictionary = { + "fakeKey" => true, + "nonexisting" => true + } + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalDictionaryDataSource.new(dictionary, ConfigCat::OverrideBehaviour::LOCAL_OVER_REMOTE)) + expect(client.get_value("fakeKey", false)).to eq true + expect(client.get_value("nonexisting", false)).to eq true + client.force_refresh() + client.stop() + end + + it "test remote over local" do + stub_request() + dictionary = { + "fakeKey" => true, + "nonexisting" => true + } + client = ConfigCat::ConfigCatClient.new("test", + poll_interval_seconds: 0, + max_init_wait_time_seconds: 0, + flag_overrides: ConfigCat::LocalDictionaryDataSource.new(dictionary, ConfigCat::OverrideBehaviour::REMOTE_OVER_LOCAL)) + expect(client.get_value("fakeKey", true)).to eq false + expect(client.get_value("nonexisting", false)).to eq true + client.force_refresh() + client.stop() + end +end diff --git a/spec/test-simple.json b/spec/test-simple.json new file mode 100644 index 0000000..35dd21b --- /dev/null +++ b/spec/test-simple.json @@ -0,0 +1,9 @@ +{ + "flags": { + "disabledFeature": false, + "enabledFeature": true, + "intSetting": 5, + "doubleSetting": 3.14, + "stringSetting": "test" + } +} \ No newline at end of file diff --git a/spec/test.json b/spec/test.json new file mode 100644 index 0000000..d547507 --- /dev/null +++ b/spec/test.json @@ -0,0 +1,19 @@ +{ + "f": { + "disabledFeature": { + "v": false + }, + "enabledFeature": { + "v": true + }, + "intSetting": { + "v": 5 + }, + "doubleSetting": { + "v": 3.14 + }, + "stringSetting": { + "v": "test" + } + } +} \ No newline at end of file