diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index 83bca12d..94386cf8 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -90,6 +90,12 @@ require 'splitclient-rb/engine/matchers/equal_to_boolean_matcher' require 'splitclient-rb/engine/matchers/equal_to_matcher' require 'splitclient-rb/engine/matchers/matches_string_matcher' +require 'splitclient-rb/engine/matchers/semver' +require 'splitclient-rb/engine/matchers/equal_to_semver_matcher' +require 'splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher' +require 'splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher' +require 'splitclient-rb/engine/matchers/between_semver_matcher' +require 'splitclient-rb/engine/matchers/in_list_semver_matcher' require 'splitclient-rb/engine/evaluator/splitter' require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker' require 'splitclient-rb/engine/impressions/unique_keys_tracker' diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index eab01183..e98e0d84 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -29,8 +29,9 @@ class SplitsRepository < Repository size: 100 } ], - label: "unsupported matcher type" - }] + label: "targeting rule type unsupported by sdk" + }] + def initialize(config, flag_sets_repository, flag_set_filter) super(config) @tt_cache = {} diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index b4d24a5e..b4d17bda 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -16,8 +16,8 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets start = Time.now params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since } - params[:till] = fetch_options[:till] unless fetch_options[:till].nil? params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty? + params[:till] = fetch_options[:till] unless fetch_options[:till].nil? @config.logger.debug("Fetching from splitChanges with #{params}: ") response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) if response.status == 414 diff --git a/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb b/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb new file mode 100644 index 00000000..71bd072b --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module SplitIoClient + class BetweenSemverMatcher < Matcher + MATCHER_TYPE = 'BETWEEN_SEMVER' + + attr_reader :attribute + + def initialize(attribute, start_value, end_value, logger, validator) + super(logger) + @validator = validator + @attribute = attribute + @semver_start = SplitIoClient::Semver.build(start_value, logger) + @semver_end = SplitIoClient::Semver.build(end_value, logger) + @logger = logger + end + + def match?(args) + return false unless verify_semver_arg?(args, 'BetweenSemverMatcher') + + value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger) + if value_to_match.nil? || @semver_start.nil? || @semver_end.nil? + @logger.error('betweenStringMatcherData is required for BETWEEN_SEMVER matcher type') + return false + + end + matches = ([0, -1].include?(@semver_start.compare(value_to_match)) && + [0, 1].include?(@semver_end.compare(value_to_match))) + @logger.debug("[BetweenMatcher] #{value_to_match} matches -> #{matches}") + matches + end + end +end diff --git a/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb b/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb new file mode 100644 index 00000000..476467b3 --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SplitIoClient + class EqualToSemverMatcher < Matcher + MATCHER_TYPE = 'EQUAL_TO_SEMVER' + + attr_reader :attribute + + def initialize(attribute, string_value, logger, validator) + super(logger) + @validator = validator + @attribute = attribute + @semver = SplitIoClient::Semver.build(string_value, logger) + @logger = logger + end + + def match?(args) + return false unless verify_semver_arg?(args, 'EqualsToSemverMatcher') + + value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger) + return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE) + + matches = (@semver.version == value_to_match.version) + @logger.debug("[EqualsToSemverMatcher] #{value_to_match} matches -> #{matches}") + matches + end + end +end diff --git a/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb b/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb new file mode 100644 index 00000000..7f11f8df --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SplitIoClient + class GreaterThanOrEqualToSemverMatcher < Matcher + MATCHER_TYPE = 'GREATER_THAN_OR_EQUAL_TO_SEMVER' + + attr_reader :attribute + + def initialize(attribute, string_value, logger, validator) + super(logger) + @validator = validator + @attribute = attribute + @semver = SplitIoClient::Semver.build(string_value, logger) + @logger = logger + end + + def match?(args) + return false unless verify_semver_arg?(args, 'GreaterThanOrEqualsToSemverMatcher') + + value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger) + return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE) + + matches = [0, 1].include?(value_to_match.compare(@semver)) + @logger.debug("[GreaterThanOrEqualsToSemverMatcher] #{value_to_match} matches -> #{matches}") + matches + end + end +end diff --git a/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb b/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb new file mode 100644 index 00000000..7b411ae7 --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module SplitIoClient + class InListSemverMatcher < Matcher + MATCHER_TYPE = 'IN_LIST_SEMVER' + + attr_reader :attribute + + def initialize(attribute, list_value, logger, validator) + super(logger) + @validator = validator + @attribute = attribute + @semver_list = [] + + list_value.map do |item| + version = SplitIoClient::Semver.build(item, logger) + @semver_list << version unless version.nil? + end + @logger = logger + end + + def match?(args) + return false if @semver_list.empty? || !verify_semver_arg?(args, 'InListSemverMatcher') + + value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger) + if value_to_match.nil? + @logger.error('whitelistMatcherData is required for IN_LIST_SEMVER matcher type') + return false + + end + matches = (@semver_list.map { |item| item.version == value_to_match.version }).any? { |item| item == true } + @logger.debug("[InListSemverMatcher] #{value_to_match} matches -> #{matches}") + matches + end + end +end diff --git a/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb b/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb new file mode 100644 index 00000000..5ca527c5 --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SplitIoClient + class LessThanOrEqualToSemverMatcher < Matcher + MATCHER_TYPE = 'LESS_THAN_OR_EQUAL_TO_SEMVER' + + attr_reader :attribute + + def initialize(attribute, string_value, logger, validator) + super(logger) + @validator = validator + @attribute = attribute + @semver = SplitIoClient::Semver.build(string_value, logger) + @logger = logger + end + + def match?(args) + return false unless verify_semver_arg?(args, 'LessThanOrEqualsToSemverMatcher') + + value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger) + return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE) + + matches = [0, -1].include?(value_to_match.compare(@semver)) + @logger.debug("[LessThanOrEqualsToSemverMatcher] #{value_to_match} matches -> #{matches}") + matches + end + end +end diff --git a/lib/splitclient-rb/engine/matchers/matcher.rb b/lib/splitclient-rb/engine/matchers/matcher.rb index 9f7a43d6..2a54bf00 100644 --- a/lib/splitclient-rb/engine/matchers/matcher.rb +++ b/lib/splitclient-rb/engine/matchers/matcher.rb @@ -30,5 +30,23 @@ def equals?(obj) def string_type? false end + + private + + def verify_semver_arg?(args, matcher_name) + @logger.debug("[#{matcher_name}] evaluating value and attributes.") + return false unless @validator.valid_matcher_arguments(args) + + true + end + + def check_semver_value_to_match(value_to_match, matcher_spec_name) + if value_to_match.nil? || @semver.nil? + @logger.error("stringMatcherData is required for #{matcher_spec_name} matcher type") + return false + + end + true + end end end diff --git a/lib/splitclient-rb/engine/matchers/semver.rb b/lib/splitclient-rb/engine/matchers/semver.rb new file mode 100644 index 00000000..ddc7c4ac --- /dev/null +++ b/lib/splitclient-rb/engine/matchers/semver.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +module SplitIoClient + class Semver + METADATA_DELIMITER = '+' + PRE_RELEASE_DELIMITER = '-' + VALUE_DELIMITER = '.' + + attr_reader :major, :minor, :patch, :pre_release, :is_stable, :version + + def initialize(version) + @major = 0 + @minor = 0 + @patch = 0 + @pre_release = [] + @is_stable = false + @version = '' + @metadata = '' + parse(version) + end + + # + # Class builder + # + # @param version [String] raw version as read from splitChanges response. + # + # @return [type] Semver instance + def self.build(version, logger) + new(version) + rescue NoMethodError => e + logger.error("Failed to parse Semver data, incorrect data type: #{e}") + nil + rescue StandardError => e + logger.error("Failed to parse Semver data: #{e}") + nil + end + + # + # Check if there is any metadata characters in version. + # + # @return [type] String semver without the metadata + # + def remove_metadata_if_exists(old_version) + index = old_version.index(METADATA_DELIMITER) + return old_version if index.nil? + + @metadata = old_version[index + 1, old_version.length] + old_version[0, index] + end + + # Compare the current Semver object to a given Semver object, return: + # 0: if self == passed + # 1: if self > passed + # -1: if self < passed + # + # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object + # + # @returns [Integer] based on comparison + def compare(to_compare) + return 0 if @version == to_compare.version + + # Compare major, minor, and patch versions numerically + result = compare_attributes(to_compare) + return result if result != 0 + + # Compare pre-release versions lexically + compare_pre_release(to_compare) + end + + private + + def integer?(value) + !!value.match(/^(\d)+$/) + end + + # + # Parse the string in version to update the other internal variables + # + def parse(old_version) + without_metadata = remove_metadata_if_exists(old_version) + + index = without_metadata.index(PRE_RELEASE_DELIMITER) + if index.nil? + @is_stable = true + else + pre_release_data = without_metadata[index + 1..-1] + without_metadata = without_metadata[0, index] + @pre_release = pre_release_data.split(VALUE_DELIMITER) + end + assign_major_minor_and_patch(without_metadata) + end + + # + # Set the major, minor and patch internal variables based on string passed. + # + # @param version [String] raw version containing major.minor.patch numbers. + def assign_major_minor_and_patch(version) + parts = version.split(VALUE_DELIMITER) + if parts.length != 3 || + !(integer?(parts[0]) && + integer?(parts[1]) && + integer?(parts[2])) + raise "Unable to convert to Semver, incorrect format: #{version}" + end + + @major = parts[0].to_i + @minor = parts[1].to_i + @patch = parts[2].to_i + @version = "#{@major}#{VALUE_DELIMITER}#{@minor}#{VALUE_DELIMITER}#{@patch}" + @version += parse_pre_release + @version += "#{METADATA_DELIMITER}#{@metadata}" unless @metadata.empty? + end + + def parse_pre_release + return '' if @pre_release.empty? + + pre_parsed = [] + @pre_release.each do |pre_digit| + pre_digit = pre_digit.to_i if integer?(pre_digit) + pre_parsed << pre_digit + end + "#{PRE_RELEASE_DELIMITER}#{pre_parsed.join('.')}" + end + + # + # Compare 2 variables and return int as follows: + # 0: if var1 == var2 + # 1: if var1 > var2 + # -1: if var1 < var2 + # + # @param var1 [type] String/Integer object that accept ==, < or > operators + # @param var2 [type] String/Integer object that accept ==, < or > operators + # + # @returns [Integer] based on comparison + def compare_vars(var1, var2) + return 0 if var1 == var2 + + return 1 if var1 > var2 + + -1 + end + + # Compare the current Semver object's major, minor, patch and is_stable attributes to a given Semver object, return: + # 0: if self == passed + # 1: if self > passed + # -1: if self < passed + # + # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object + # + # @returns [Integer] based on comparison + def compare_attributes(to_compare) + result = compare_vars(@major, to_compare.major) + return result if result != 0 + + result = compare_vars(@minor, to_compare.minor) + return result if result != 0 + + result = compare_vars(@patch, to_compare.patch) + return result if result != 0 + + return -1 if !@is_stable && to_compare.is_stable + + return 1 if @is_stable && !to_compare.is_stable + + 0 + end + + # Compare the current Semver object's pre_release attribute to a given Semver object, return: + # 0: if self == passed + # 1: if self > passed + # -1: if self < passed + # + # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object + # + # @returns [Integer] based on comparison + def compare_pre_release(to_compare) + min_length = get_pre_min_length(to_compare) + 0.upto(min_length - 1) do |i| + next if @pre_release[i] == to_compare.pre_release[i] + + if integer?(@pre_release[i]) && integer?(to_compare.pre_release[i]) + return compare_vars(@pre_release[i].to_i, to_compare.pre_release[i].to_i) + + end + + return compare_vars(@pre_release[i], to_compare.pre_release[i]) + end + # Compare lengths of pre-release versions + compare_vars(@pre_release.length, to_compare.pre_release.length) + end + + # Get minimum of current Semver object's pre_release attributes length to a given Semver object + # + # @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object + # + # @returns [Integer] + def get_pre_min_length(to_compare) + [@pre_release.length, to_compare.pre_release.length].min + end + end +end diff --git a/lib/splitclient-rb/engine/parser/condition.rb b/lib/splitclient-rb/engine/parser/condition.rb index f80fbffb..b5d7567b 100644 --- a/lib/splitclient-rb/engine/parser/condition.rb +++ b/lib/splitclient-rb/engine/parser/condition.rb @@ -189,6 +189,47 @@ def matcher_matches_string(params) ) end + def matcher_equal_to_semver(params) + EqualToSemverMatcher.new( + params[:matcher][:keySelector][:attribute], + params[:matcher][:stringMatcherData], + @config.split_logger, @config.split_validator + ) + end + + def matcher_greater_than_or_equal_to_semver(params) + GreaterThanOrEqualToSemverMatcher.new( + params[:matcher][:keySelector][:attribute], + params[:matcher][:stringMatcherData], + @config.split_logger, @config.split_validator + ) + end + + def matcher_less_than_or_equal_to_semver(params) + LessThanOrEqualToSemverMatcher.new( + params[:matcher][:keySelector][:attribute], + params[:matcher][:stringMatcherData], + @config.split_logger, @config.split_validator + ) + end + + def matcher_between_semver(params) + BetweenSemverMatcher.new( + params[:matcher][:keySelector][:attribute], + params[:matcher][:betweenStringMatcherData][:start], + params[:matcher][:betweenStringMatcherData][:end], + @config.split_logger, @config.split_validator + ) + end + + def matcher_in_list_semver(params) + InListSemverMatcher.new( + params[:matcher][:keySelector][:attribute], + params[:matcher][:whitelistMatcherData][:whitelist], + @config.split_logger, @config.split_validator + ) + end + # # @return [object] the negate value for this condition def negate diff --git a/lib/splitclient-rb/split_logger.rb b/lib/splitclient-rb/split_logger.rb index 5a82b0ce..fa901744 100644 --- a/lib/splitclient-rb/split_logger.rb +++ b/lib/splitclient-rb/split_logger.rb @@ -11,5 +11,13 @@ def log_if_debug(message) def log_if_transport(message) @config.logger.debug(message) if @config.transport_debug_enabled end + + def error(message) + @config.logger.error(message) + end + + def debug(message) + @config.logger.debug(message) if @config.debug_enabled + end end end diff --git a/spec/cache/repositories/splits_repository_spec.rb b/spec/cache/repositories/splits_repository_spec.rb index 34c720e8..b55bfd11 100644 --- a/spec/cache/repositories/splits_repository_spec.rb +++ b/spec/cache/repositories/splits_repository_spec.rb @@ -126,6 +126,7 @@ } }] } + repository.update([split], [], -1) expect(repository.get_split('corge')[:conditions]).to eq SplitIoClient::Cache::Repositories::SplitsRepository::DEFAULT_CONDITIONS_TEMPLATE diff --git a/spec/engine/matchers/matches_between_semver_matcher_spec.rb b/spec/engine/matchers/matches_between_semver_matcher_spec.rb new file mode 100644 index 00000000..b4431fe4 --- /dev/null +++ b/spec/engine/matchers/matches_between_semver_matcher_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::BetweenSemverMatcher do + let(:raw) { { + 'negate': false, + 'matcherType': 'BETWEEN_SEMVER', + 'betweenStringMatcherData': {"start": "2.1.8", "end": "2.1.11"} +} } + let(:config) { SplitIoClient::SplitConfig.new } + + it 'initilized params' do + matcher = described_class.new("version", raw[:betweenStringMatcherData][:start], raw[:betweenStringMatcherData][:end], config.logger, config.split_validator) + expect(matcher.attribute).to eq("version") + semver_start = matcher.instance_variable_get(:@semver_start) + expect(semver_start.instance_variable_get(:@version)).to eq("2.1.8") + semver_end = matcher.instance_variable_get(:@semver_end) + expect(semver_end.instance_variable_get(:@version)).to eq("2.1.11") + end + + it 'matches' do + matcher = described_class.new("version", raw[:betweenStringMatcherData][:start], raw[:betweenStringMatcherData][:end], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8+rc"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.9"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.11-rc12"})).to eq(true) + end + + it 'does not match' do + matcher = described_class.new("version", raw[:betweenStringMatcherData][:start], raw[:betweenStringMatcherData][:end], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.5"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.1.12-rc1"})).to eq(false) + end + + it 'invalid attribute' do + matcher = described_class.new("version", raw[:betweenStringMatcherData][:start], raw[:betweenStringMatcherData][:end], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": 2.1})).to eq(false) + expect(matcher.match?(:attributes=>{"version": nil})).to eq(false) + end + +end diff --git a/spec/engine/matchers/matches_equal_to_semver_matcher_spec.rb b/spec/engine/matchers/matches_equal_to_semver_matcher_spec.rb new file mode 100644 index 00000000..6d382bb3 --- /dev/null +++ b/spec/engine/matchers/matches_equal_to_semver_matcher_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::EqualToSemverMatcher do + let(:raw) { { + 'negate': false, + 'matcherType': 'EQUAL_TO_SEMVER', + 'stringMatcherData': "2.1.8" + } } + let(:config) { SplitIoClient::SplitConfig.new } + + it 'initilized params' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.attribute).to eq("version") + semver = matcher.instance_variable_get(:@semver) + expect(semver.instance_variable_get(:@version)).to eq("2.1.8") + end + + it 'matches' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8"})).to eq(true) + end + + it 'does not match' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8+rc"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.1.5"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.1.5-rc1"})).to eq(false) + end + + it 'invalid attribute' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": 2.1})).to eq(false) + expect(matcher.match?(:attributes=>{"version": nil})).to eq(false) + end +end diff --git a/spec/engine/matchers/matches_greater_than_or_equal_to_semver_matcher_spec.rb b/spec/engine/matchers/matches_greater_than_or_equal_to_semver_matcher_spec.rb new file mode 100644 index 00000000..91c81dec --- /dev/null +++ b/spec/engine/matchers/matches_greater_than_or_equal_to_semver_matcher_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::GreaterThanOrEqualToSemverMatcher do + let(:raw) { { + 'negate': false, + 'matcherType': 'GREATER_THAN_OR_EQUAL_TO_SEMVER', + 'stringMatcherData': "2.1.8" + } } + let(:config) { SplitIoClient::SplitConfig.new } + + it 'initilized params' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.attribute).to eq("version") + semver = matcher.instance_variable_get(:@semver) + expect(semver.instance_variable_get(:@version)).to eq("2.1.8") + end + + it 'matches' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8+rc"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.8"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.11"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.2.0"})).to eq(true) + end + + it 'does not match' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.7"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.0.22"})).to eq(false) + end + + it 'invalid attribute' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": 2.1})).to eq(false) + expect(matcher.match?(:attributes=>{"version": nil})).to eq(false) + end + +end diff --git a/spec/engine/matchers/matches_in_list_semver_matcher_spec.rb b/spec/engine/matchers/matches_in_list_semver_matcher_spec.rb new file mode 100644 index 00000000..76461a65 --- /dev/null +++ b/spec/engine/matchers/matches_in_list_semver_matcher_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::InListSemverMatcher do + let(:raw) { { + 'negate': false, + 'matcherType': 'INLIST_SEMVER', + 'whitelistMatcherData': {"whitelist": ["2.1.8", "2.1.11"]} +} } + let(:config) { SplitIoClient::SplitConfig.new } + + it 'initilized params' do + matcher = described_class.new("version", raw[:whitelistMatcherData][:whitelist], config.logger, config.split_validator) + expect(matcher.attribute).to eq("version") + semver_list = matcher.instance_variable_get(:@semver_list) + expect(semver_list[0].instance_variable_get(:@version)).to eq("2.1.8") + expect(semver_list[1].instance_variable_get(:@version)).to eq("2.1.11") + end + + it 'matches' do + matcher = described_class.new("version", raw[:whitelistMatcherData][:whitelist], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.11"})).to eq(true) + end + + it 'does not match' do + matcher = described_class.new("version", raw[:whitelistMatcherData][:whitelist], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8+rc"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.1.7"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.1.11-rc12"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.1.8-rc1"})).to eq(false) + end + + it 'invalid attribute' do + matcher = described_class.new("version", raw[:whitelistMatcherData][:whitelist], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": 2.1})).to eq(false) + expect(matcher.match?(:attributes=>{"version": nil})).to eq(false) + end + +end diff --git a/spec/engine/matchers/matches_less_than_or_equal_to_semver_matcher_spec.rb b/spec/engine/matchers/matches_less_than_or_equal_to_semver_matcher_spec.rb new file mode 100644 index 00000000..a551e657 --- /dev/null +++ b/spec/engine/matchers/matches_less_than_or_equal_to_semver_matcher_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::LessThanOrEqualToSemverMatcher do + let(:raw) { { + 'negate': false, + 'matcherType': 'LESS_THAN_OR_EQUAL_TO_SEMVER', + 'stringMatcherData': "2.1.8" + } } + let(:config) { SplitIoClient::SplitConfig.new({:logger => Logger.new('/dev/null')}) } + + it 'initilized params' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.attribute).to eq("version") + semver = matcher.instance_variable_get(:@semver) + expect(semver.instance_variable_get(:@version)).to eq("2.1.8") + end + + it 'matches' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.8+rc"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.8"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.5"})).to eq(true) + expect(matcher.match?(:attributes=>{"version": "2.1.5-rc1"})).to eq(true) + end + + it 'does not match' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": "2.1.10"})).to eq(false) + expect(matcher.match?(:attributes=>{"version": "2.2.0-rc1"})).to eq(false) + end + + it 'invalid attribute' do + matcher = described_class.new("version", raw[:stringMatcherData], config.logger, config.split_validator) + expect(matcher.match?(:attributes=>{"version": 2.1})).to eq(false) + expect(matcher.match?(:attributes=>{"version": nil})).to eq(false) + end + +end diff --git a/spec/engine/matchers/semver_matchers_integration_spec.rb b/spec/engine/matchers/semver_matchers_integration_spec.rb new file mode 100644 index 00000000..86909af5 --- /dev/null +++ b/spec/engine/matchers/semver_matchers_integration_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Semver matchers integration' do + subject do + SplitIoClient::SplitFactory.new('test_api_key', { + logger: Logger.new(log), + streaming_enabled: false, + impressions_refresh_rate: 9999, + impressions_mode: :none, + features_refresh_rate: 9999, + telemetry_refresh_rate: 99999}).client + end + + let(:log) { StringIO.new } + + let(:semver_between_matcher_splits) do + File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver_matchers/semver_between.json'))) + end + + let(:semver_equalto_matcher_splits) do + File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver_matchers/semver_equalto.json'))) + end + + let(:semver_greater_or_equalto_matcher_splits) do + File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver_matchers/semver_greater_or_equalto.json'))) + end + + let(:semver_less_or_equalto_matcher_splits) do + File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver_matchers/semver_less_or_equalto.json'))) + end + + let(:semver_inlist_matcher_splits) do + File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver_matchers/semver_inlist.json'))) + end + + let(:user) { 'fake_user_id_1' } + + before do + stub_request(:any, /https:\/\/telemetry\.*/).to_return(status: 200, body: 'ok') + stub_request(:any, /https:\/\/events\.*/).to_return(status: 200, body: "", headers: {}) + stub_request(:any, /https:\/\/metrics\.*/).to_return(status: 200, body: "", headers: {}) + stub_request(:post, "https://telemetry.split.io/api/v1/metrics/config").to_return(status: 200, body: "", headers: {}) + end + + context 'equal to matcher' do + before do + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since\.*/) + .to_return(status: 200, body: semver_equalto_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: semver_equalto_matcher_splits) + stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.1&since=1675259356568") + .to_return(status: 200, body: semver_equalto_matcher_splits) + sleep 1 + subject.block_until_ready + end + + it 'validates the treatment is ON for correct attribute value' do + expect(subject.get_treatment(user, 'semver_equalto', {:version => "1.22.9"})).to eq 'on' + end + + it 'validates the treatment is the default treatment for incorrect attributes hash and nil' do + expect(subject.get_treatment(user, 'semver_equalto')).to eq 'off' + expect(subject.get_treatment(user, 'semver_equalto', {:version => "1.22.10"})).to eq 'off' + sleep 0.2 + subject.destroy() + end + end + + context 'greater than or equal to matcher' do + before do + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + .to_return(status: 200, body: semver_greater_or_equalto_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: semver_greater_or_equalto_matcher_splits) + sleep 1 + subject.block_until_ready + end + + it 'validates the treatment is ON for correct attribute value' do + expect(subject.get_treatment(user, 'semver_greater_or_equalto', {:version => "1.22.9"})).to eq 'on' + expect(subject.get_treatment(user, 'semver_greater_or_equalto', {:version => "1.22.10"})).to eq 'on' + end + + it 'validates the treatment is the default treatment for incorrect attributes hash and nil' do + expect(subject.get_treatment(user, 'semver_greater_or_equalto')).to eq 'off' + expect(subject.get_treatment(user, 'semver_greater_or_equalto', {:version => "1.22.8"})).to eq 'off' + sleep 0.2 + subject.destroy() + end + end + + context 'less than or equal to matcher' do + before do + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + .to_return(status: 200, body: semver_less_or_equalto_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: semver_less_or_equalto_matcher_splits) + sleep 1 + subject.block_until_ready + end + + it 'validates the treatment is ON for correct attribute value' do + expect(subject.get_treatment(user, 'semver_less_or_equalto', {:version => "1.22.9"})).to eq 'on' + expect(subject.get_treatment(user, 'semver_less_or_equalto', {:version => "1.22.8"})).to eq 'on' + end + + it 'validates the treatment is the default treatment for incorrect attributes hash and nil' do + expect(subject.get_treatment(user, 'semver_less_or_equalto')).to eq 'off' + expect(subject.get_treatment(user, 'semver_less_or_equalto', {:version => "1.22.10"})).to eq 'off' + sleep 0.2 + subject.destroy() + end + end + + context 'in list matcher' do + before do + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + .to_return(status: 200, body: semver_inlist_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: semver_inlist_matcher_splits) + sleep 1 + subject.block_until_ready + end + + it 'validates the treatment is ON for correct attribute value' do + expect(subject.get_treatment(user, 'semver_inlist', {:version => "1.22.9"})).to eq 'on' + expect(subject.get_treatment(user, 'semver_inlist', {:version => "2.1.0"})).to eq 'on' + end + + it 'validates the treatment is the default treatment for incorrect attributes hash and nil' do + expect(subject.get_treatment(user, 'semver_inlist')).to eq 'off' + expect(subject.get_treatment(user, 'semver_inlist', {:version => "1.22.10"})).to eq 'off' + sleep 0.2 + subject.destroy() + end + end + + context 'between matcher' do + before do + stub_request(:get, /https:\/\/sdk\.split\.io\/api\/splitChanges\?s=1\.1&since/) + .to_return(status: 200, body: semver_between_matcher_splits) + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.1&since=-1') + .to_return(status: 200, body: semver_between_matcher_splits) + sleep 1 + subject.block_until_ready + end + + it 'validates the treatment is ON for correct attribute value' do + expect(subject.get_treatment(user, 'semver_between', {:version => "1.22.9"})).to eq 'on' + expect(subject.get_treatment(user, 'semver_between', {:version => "2.0.10"})).to eq 'on' + end + + it 'validates the treatment is the default treatment for incorrect attributes hash and nil' do + expect(subject.get_treatment(user, 'semver_between')).to eq 'off' + expect(subject.get_treatment(user, 'semver_between', {:version => "1.22.9-rc1"})).to eq 'off' + expect(subject.get_treatment(user, 'semver_between', {:version => "2.1.1"})).to eq 'off' + sleep 0.2 + subject.destroy() + end + end +end diff --git a/spec/engine/matchers/semver_spec.rb b/spec/engine/matchers/semver_spec.rb new file mode 100644 index 00000000..2e1ca0f1 --- /dev/null +++ b/spec/engine/matchers/semver_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' +require 'csv' + +describe SplitIoClient::Semver do + let(:valid_versions) do + CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver/valid-semantic-versions.csv'))), headers: true) + end + let(:invalid_versions) do + CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver/invalid-semantic-versions.csv'))), headers: true) + end + let(:equal_to_versions) do + CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver/equal-to-semver.csv'))), headers: true) + end + + let(:between_versions) do + CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__), + '../../test_data/splits/semver/between-semver.csv'))), headers: true) + end + + let(:logger) { Logger.new('/dev/null') } + + context 'check versions' do + it 'accept valid versions' do + for i in (0..valid_versions.length-1) + expect(described_class.build(valid_versions[i][0], logger)).not_to be_nil + end + end + it 'reject invalid versions' do + for version in invalid_versions + expect(described_class.build(version[0], logger)).to eq(nil) + end + end + + it 'verify leading-zero integers are converted' do + semver = described_class.build('1.01.2', logger) + expect(semver.version).to eq('1.1.2') + expect(described_class.build('1.01.2-rc.04', logger).version).to eq('1.1.2-rc.4') + end + + end + + context 'compare versions' do + it 'equal and not equal' do + for i in (1..valid_versions.length-1) + expect(described_class.build(valid_versions[i][0], logger).compare(described_class.build(valid_versions[i][1], logger))).to eq(1) + expect(described_class.build(valid_versions[i][1], logger).compare(described_class.build(valid_versions[i][0], logger))).to eq(-1) + expect(described_class.build(valid_versions[i][0], logger).compare(described_class.build(valid_versions[i][0], logger))).to eq(0) + expect(described_class.build(valid_versions[i][1], logger).compare(described_class.build(valid_versions[i][1], logger))).to eq(0) + end + for i in (1..equal_to_versions.length-1) + if equal_to_versions[i][2]=='true' + expect(described_class.build(equal_to_versions[i][0], logger).version == described_class.build(equal_to_versions[i][1], logger).version).to eq(true) + else + expect(described_class.build(equal_to_versions[i][0], logger) == described_class.build(equal_to_versions[i][1], logger)).to eq(false) + end + end + for i in (1..between_versions.length-1) + sem1 = described_class.build(between_versions[i][0], logger) + sem2 = described_class.build(between_versions[i][2], logger) + to_check = described_class.build(between_versions[i][1], logger) + if between_versions[i][3]=='true' + expect(sem1.compare(to_check)).to eq(-1) + expect(sem2.compare(to_check)).to eq(1) + else + compare1 = sem1.compare(to_check) + compare2 = sem2.compare(to_check) + expect(compare1 == -1 && compare2 == 1).to eq(false) + end + end + + end + end +end diff --git a/spec/test_data/splits/semver/between-semver.csv b/spec/test_data/splits/semver/between-semver.csv new file mode 100644 index 00000000..4225e710 --- /dev/null +++ b/spec/test_data/splits/semver/between-semver.csv @@ -0,0 +1,18 @@ +# version1, version2, version3, expected +1.1.1,2.2.2,3.3.3,true +1.1.1-rc.1,1.1.1-rc.2,1.1.1-rc.3,true +1.0.0-alpha,1.0.0-alpha.1,1.0.0-alpha.beta,true +1.0.0-alpha.1,1.0.0-alpha.beta,1.0.0-beta,true +1.0.0-alpha.beta,1.0.0-beta,1.0.0-beta.2,true +1.0.0-beta,1.0.0-beta.2,1.0.0-beta.11,true +1.0.0-beta.2,1.0.0-beta.11,1.0.0-rc.1,true +1.0.0-beta.11,1.0.0-rc.1,1.0.0,true +1.1.2,1.1.3,1.1.4,true +1.2.1,1.3.1,1.4.1,true +2.0.0,3.0.0,4.0.0,true +2.2.2,2.2.3-rc1,2.2.3,true +2.2.2,2.3.2-rc100,2.3.3,true +1.0.0-rc.1+build.1,1.2.3-beta,1.2.3-rc.1+build.123,true +3.3.3,3.3.3-alpha,3.3.4,false +2.2.2-rc.1,2.2.2+metadata,2.2.2-rc.10,false +1.1.1-rc.1,1.1.1-rc.3,1.1.1-rc.2,false \ No newline at end of file diff --git a/spec/test_data/splits/semver/equal-to-semver.csv b/spec/test_data/splits/semver/equal-to-semver.csv new file mode 100644 index 00000000..4ac0b7c6 --- /dev/null +++ b/spec/test_data/splits/semver/equal-to-semver.csv @@ -0,0 +1,7 @@ +# version1, version2, equals +1.1.1,1.1.1,true +1.1.1,1.1.1+metadata,false +1.1.1,1.1.1-rc.1,false +88.88.88,88.88.88,true +1.2.3----RC-SNAPSHOT.12.9.1--.12,1.2.3----RC-SNAPSHOT.12.9.1--.12,true +10.2.3-DEV-SNAPSHOT,10.2.3-SNAPSHOT-123,false \ No newline at end of file diff --git a/spec/test_data/splits/semver/invalid-semantic-versions.csv b/spec/test_data/splits/semver/invalid-semantic-versions.csv new file mode 100644 index 00000000..b9eb3e50 --- /dev/null +++ b/spec/test_data/splits/semver/invalid-semantic-versions.csv @@ -0,0 +1,26 @@ +# invalid +1 +1.2 +1.alpha.2 ++invalid +-invalid +-invalid+invalid +-invalid.01 +alpha +alpha.beta +alpha.beta.1 +alpha.1 +alpha+beta +alpha_beta +alpha. +alpha.. +beta +-alpha. +1.2 +1.2.3.DEV +1.2-SNAPSHOT +1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 +1.2-RC-SNAPSHOT +-1.0.3-gamma+b7718 ++justmeta +#99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12 \ No newline at end of file diff --git a/spec/test_data/splits/semver/valid-semantic-versions.csv b/spec/test_data/splits/semver/valid-semantic-versions.csv new file mode 100644 index 00000000..da85709c --- /dev/null +++ b/spec/test_data/splits/semver/valid-semantic-versions.csv @@ -0,0 +1,25 @@ +# higher, lower +1.1.2,1.1.1 +1.0.0,1.0.0-rc.1 +1.1.0-rc.1,1.0.0-beta.11 +1.0.0-beta.11,1.0.0-beta.2 +1.0.0-beta.2,1.0.0-beta +1.0.0-beta,1.0.0-alpha.beta +1.0.0-alpha.beta,1.0.0-alpha.1 +1.0.0-alpha.1,1.0.0-alpha +2.2.2-rc.2+metadata-lalala,2.2.2-rc.1.2 +1.2.3,0.0.4 +1.1.2+meta,1.1.2-prerelease+meta +1.0.0-beta,1.0.0-alpha +1.0.0-alpha0.valid,1.0.0-alpha.0valid +1.0.0-rc.1+build.1,1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay +10.2.3-DEV-SNAPSHOT,1.2.3-SNAPSHOT-123 +1.1.1-rc2,1.0.0-0A.is.legal +1.2.3----RC-SNAPSHOT.12.9.1--.12+788,1.2.3----R-S.12.9.1--.12+meta +1.2.3----RC-SNAPSHOT.12.9.1--.12.88,1.2.3----RC-SNAPSHOT.12.9.1--.12 +9223372036854775807.9223372036854775807.9223372036854775807,9223372036854775807.9223372036854775807.9223372036854775806 +1.1.1-alpha.beta.rc.build.java.pr.support.10,1.1.1-alpha.beta.rc.build.java.pr.support +1.1.2,1.1.1 +1.2.1,1.1.1 +2.1.1,1.1.1 +1.1.1-rc.1,1.1.1-rc.0 \ No newline at end of file diff --git a/spec/test_data/splits/semver_matchers/semver_between.json b/spec/test_data/splits/semver_matchers/semver_between.json new file mode 100644 index 00000000..44edc2b6 --- /dev/null +++ b/spec/test_data/splits/semver_matchers/semver_between.json @@ -0,0 +1,86 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "semver_between", + "trafficAllocation": 100, + "trafficAllocationSeed": 1068038034, + "seed": -1053389887, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1675259356568, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": + [ + {"keySelector": {"trafficType": "user", "attribute": "version"}, + "matcherType": "BETWEEN_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "dependencyMatcherData": null, + "booleanMatcherData": null, + "stringMatcherData": null, + "betweenStringMatcherData": {"start": "1.22.9", "end": "2.1.0"}} + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "between semver" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ] + } + ], + "since": -1, + "till": 1675259356568 +} diff --git a/spec/test_data/splits/semver_matchers/semver_equalto.json b/spec/test_data/splits/semver_matchers/semver_equalto.json new file mode 100644 index 00000000..c3daa9ea --- /dev/null +++ b/spec/test_data/splits/semver_matchers/semver_equalto.json @@ -0,0 +1,85 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "semver_equalto", + "trafficAllocation": 100, + "trafficAllocationSeed": 1068038034, + "seed": -1053389887, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1675259356568, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": + [ + {"keySelector": {"trafficType": "user", "attribute": "version"}, + "matcherType": "EQUAL_TO_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "dependencyMatcherData": null, + "booleanMatcherData": null, + "stringMatcherData": "1.22.9"} + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "equal to semver" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ] + } + ], + "since": -1, + "till": 1675259356568 +} diff --git a/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json b/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json new file mode 100644 index 00000000..40f0f036 --- /dev/null +++ b/spec/test_data/splits/semver_matchers/semver_greater_or_equalto.json @@ -0,0 +1,85 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "semver_greater_or_equalto", + "trafficAllocation": 100, + "trafficAllocationSeed": 1068038034, + "seed": -1053389887, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1675259356568, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": + [ + {"keySelector": {"trafficType": "user", "attribute": "version"}, + "matcherType": "GREATER_THAN_OR_EQUAL_TO_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "dependencyMatcherData": null, + "booleanMatcherData": null, + "stringMatcherData": "1.22.9"} + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "greater than or equal to semver" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ] + } + ], + "since": -1, + "till": 1675259356568 +} diff --git a/spec/test_data/splits/semver_matchers/semver_inlist.json b/spec/test_data/splits/semver_matchers/semver_inlist.json new file mode 100644 index 00000000..9f1e6246 --- /dev/null +++ b/spec/test_data/splits/semver_matchers/semver_inlist.json @@ -0,0 +1,86 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "semver_inlist", + "trafficAllocation": 100, + "trafficAllocationSeed": 1068038034, + "seed": -1053389887, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1675259356568, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": + [ + {"keySelector": {"trafficType": "user", "attribute": "version"}, + "matcherType": "IN_LIST_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": {"whitelist": ["1.22.9", "2.1.0"]}, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "dependencyMatcherData": null, + "booleanMatcherData": null, + "stringMatcherData": null, + "betweenStringMatcherData": null} + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "between semver" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ] + } + ], + "since": -1, + "till": 1675259356568 +} diff --git a/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json b/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json new file mode 100644 index 00000000..9a46807f --- /dev/null +++ b/spec/test_data/splits/semver_matchers/semver_less_or_equalto.json @@ -0,0 +1,85 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "semver_less_or_equalto", + "trafficAllocation": 100, + "trafficAllocationSeed": 1068038034, + "seed": -1053389887, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1675259356568, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": + [ + {"keySelector": {"trafficType": "user", "attribute": "version"}, + "matcherType": "LESS_THAN_OR_EQUAL_TO_SEMVER", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "dependencyMatcherData": null, + "booleanMatcherData": null, + "stringMatcherData": "1.22.9"} + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ], + "label": "less than or equal to semver" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ] + } + ], + "since": -1, + "till": 1675259356568 +}