diff --git a/lib/datadog/open_feature/binding/assignment_reason.rb b/lib/datadog/open_feature/binding/assignment_reason.rb new file mode 100644 index 00000000000..df4206d71f5 --- /dev/null +++ b/lib/datadog/open_feature/binding/assignment_reason.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Datadog + module OpenFeature + module Binding + module AssignmentReason + TARGETING_MATCH = 'TARGETING_MATCH' + SPLIT = 'SPLIT' + STATIC = 'STATIC' + DEFAULT = 'DEFAULT' + DISABLED = 'DISABLED' + ERROR = 'ERROR' + end + end + end +end diff --git a/lib/datadog/open_feature/binding/condition_operator.rb b/lib/datadog/open_feature/binding/condition_operator.rb new file mode 100644 index 00000000000..3488605d682 --- /dev/null +++ b/lib/datadog/open_feature/binding/condition_operator.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Datadog + module OpenFeature + module Binding + module ConditionOperator + MATCHES = 'MATCHES' + NOT_MATCHES = 'NOT_MATCHES' + GTE = 'GTE' + GT = 'GT' + LTE = 'LTE' + LT = 'LT' + ONE_OF = 'ONE_OF' + NOT_ONE_OF = 'NOT_ONE_OF' + IS_NULL = 'IS_NULL' + end + end + end +end diff --git a/lib/datadog/open_feature/binding/configuration.rb b/lib/datadog/open_feature/binding/configuration.rb new file mode 100644 index 00000000000..a9fda611392 --- /dev/null +++ b/lib/datadog/open_feature/binding/configuration.rb @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +require_relative 'variation_type' +require_relative 'condition_operator' +require_relative 'assignment_reason' + +module Datadog + module OpenFeature + module Binding + class Flag + attr_reader :key, :enabled, :variation_type, :variations, :allocations + + def initialize(key:, enabled:, variation_type:, variations:, allocations:) + @key = key + @enabled = enabled + @variation_type = variation_type + @variations = variations + @allocations = allocations + end + + def self.from_hash(flag_data, key) + new( + key: key, + enabled: flag_data.fetch('enabled', false), + variation_type: flag_data.fetch('variationType'), + variations: parse_variations(flag_data.fetch('variations', {})), + allocations: parse_allocations(flag_data.fetch('allocations', [])) + ) + end + + def self.parse_variations(variations_data) + variations_data.transform_values do |variation_data| + Variation.from_hash(variation_data) + end + end + + def self.parse_allocations(allocations_data) + allocations_data.map { |allocation_data| Allocation.from_hash(allocation_data) } + end + + private_class_method :parse_variations, :parse_allocations + end + + # Represents a flag variation with a key for logging and a value for the application + class Variation + attr_reader :key, :value + + def initialize(key:, value:) + @key = key + @value = value + end + + def self.from_hash(variation_data) + new( + key: variation_data.fetch('key'), + value: variation_data.fetch('value') + ) + end + end + + # Represents an allocation rule with traffic splits + class Allocation + attr_reader :key, :rules, :start_at, :end_at, :splits, :do_log + + def initialize(key:, splits:, rules: nil, start_at: nil, end_at: nil, do_log: true) + @key = key + @rules = rules + @start_at = start_at + @end_at = end_at + @splits = splits + @do_log = do_log + end + + def self.from_hash(allocation_data) + new( + key: allocation_data.fetch('key'), + rules: parse_rules(allocation_data['rules']), + start_at: parse_timestamp(allocation_data['startAt']), + end_at: parse_timestamp(allocation_data['endAt']), + splits: parse_splits(allocation_data.fetch('splits', [])), + do_log: allocation_data.fetch('doLog', true) + ) + end + + def self.parse_rules(rules_data) + return nil if rules_data.nil? || rules_data.empty? + + rules_data.map { |rule_data| Rule.from_hash(rule_data) } + end + + def self.parse_splits(splits_data) + splits_data.map { |split_data| Split.from_hash(split_data) } + end + + def self.parse_timestamp(timestamp_data) + # Handle both Unix timestamps and ISO8601 strings + case timestamp_data + when Numeric + Time.at(timestamp_data) + when String + Time.parse(timestamp_data) + end + rescue + nil + end + + private_class_method :parse_rules, :parse_splits, :parse_timestamp + end + + # Represents a traffic split within an allocation + class Split + attr_reader :shards, :variation_key, :extra_logging + + def initialize(shards:, variation_key:, extra_logging: nil) + @shards = shards + @variation_key = variation_key + @extra_logging = extra_logging || {} + end + + def self.from_hash(split_data) + new( + shards: parse_shards(split_data.fetch('shards', [])), + variation_key: split_data.fetch('variationKey'), + extra_logging: split_data.fetch('extraLogging', {}) + ) + end + + def self.parse_shards(shards_data) + shards_data.map { |shard_data| Shard.from_hash(shard_data) } + end + + private_class_method :parse_shards + end + + # Represents a shard configuration for traffic splitting + class Shard + attr_reader :salt, :total_shards, :ranges + + def initialize(salt:, total_shards:, ranges:) + @salt = salt + @total_shards = total_shards + @ranges = ranges + end + + def self.from_hash(shard_data) + new( + salt: shard_data.fetch('salt'), + total_shards: shard_data.fetch('totalShards'), + ranges: parse_ranges(shard_data.fetch('ranges', [])) + ) + end + + def self.parse_ranges(ranges_data) + ranges_data.map { |range_data| ShardRange.from_hash(range_data) } + end + + private_class_method :parse_ranges + end + + # Represents a shard range for traffic allocation + class ShardRange + attr_reader :start, :end_value + + def initialize(start:, end_value:) + @start = start + @end_value = end_value + end + + def self.from_hash(range_data) + new( + start: range_data.fetch('start'), + end_value: range_data.fetch('end') + ) + end + + # Alias because "end" is a reserved keyword in Ruby + alias_method :end, :end_value + end + + # Represents a targeting rule + class Rule + attr_reader :conditions + + def initialize(conditions:) + @conditions = conditions + end + + def self.from_hash(rule_data) + new( + conditions: parse_conditions(rule_data.fetch('conditions', [])) + ) + end + + def self.parse_conditions(conditions_data) + conditions_data.map { |condition_data| Condition.from_hash(condition_data) } + end + + private_class_method :parse_conditions + end + + # Represents a single condition within a rule + class Condition + attr_reader :attribute, :operator, :value + + def initialize(attribute:, operator:, value:) + @attribute = attribute + @operator = operator + @value = value + end + + def self.from_hash(condition_data) + new( + attribute: condition_data.fetch('attribute'), + operator: condition_data.fetch('operator'), + value: condition_data.fetch('value') + ) + end + end + + # Main configuration container + class Configuration + attr_reader :flags, :schema_version + + def initialize(flags:, schema_version: nil) + @flags = flags + @schema_version = schema_version + end + + def self.from_hash(config_data) + flags_data = config_data.fetch('flags', {}) + + parsed_flags = flags_data.transform_values do |flag_data| + Flag.from_hash(flag_data, flag_data['key'] || '') + end + + new( + flags: parsed_flags, + schema_version: config_data['schemaVersion'] + ) + end + + def get_flag(flag_key) + @flags.values.find { |flag| flag.key == flag_key } + end + end + end + end +end diff --git a/lib/datadog/open_feature/binding/error_codes.rb b/lib/datadog/open_feature/binding/error_codes.rb new file mode 100644 index 00000000000..c5d6dbc0be8 --- /dev/null +++ b/lib/datadog/open_feature/binding/error_codes.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Datadog + module OpenFeature + module Binding + module ErrorCodes + TYPE_MISMATCH_ERROR = 'TYPE_MISMATCH' + CONFIGURATION_PARSE_ERROR = 'CONFIGURATION_PARSE_ERROR' + CONFIGURATION_MISSING = 'CONFIGURATION_MISSING' + FLAG_UNRECOGNIZED_OR_DISABLED = 'FLAG_UNRECOGNIZED_OR_DISABLED' + FLAG_DISABLED = 'FLAG_DISABLED' + DEFAULT_ALLOCATION_NULL = 'DEFAULT_ALLOCATION_NULL' + INTERNAL_ERROR = 'INTERNAL' + end + end + end +end diff --git a/lib/datadog/open_feature/binding/internal_evaluator.rb b/lib/datadog/open_feature/binding/internal_evaluator.rb new file mode 100644 index 00000000000..eed2acb89d7 --- /dev/null +++ b/lib/datadog/open_feature/binding/internal_evaluator.rb @@ -0,0 +1,520 @@ +# frozen_string_literal: true + +require 'json' +require_relative 'configuration' +require_relative 'error_codes' +require_relative '../ext' +require_relative '../resolution_details' + +module Datadog + module OpenFeature + module Binding + class EvaluationError < StandardError + attr_reader :code, :message + + def initialize(code, message) + @code = code + @message = message + super(message) + end + end + + class InternalEvaluator + def initialize(ufc_json) + @ufc_json = ufc_json + @parsed_config = nil + @parse_error = nil + parse_and_validate_json(ufc_json) + end + + def get_assignment(flag_key, default_value, evaluation_context, expected_type) + if @parse_error + return ResolutionDetails.build_error( + value: default_value, + error_code: @parse_error[:error_code], + error_message: @parse_error[:error_message], + reason: AssignmentReason::ERROR + ) + end + + flag = @parsed_config.get_flag(flag_key) + unless flag + return ResolutionDetails.build_error( + value: default_value, + error_code: ErrorCodes::FLAG_UNRECOGNIZED_OR_DISABLED, + error_message: 'flag is missing in configuration, it is either unrecognized or disabled', + reason: AssignmentReason::ERROR + ) + end + + return ResolutionDetails.build_default(value: default_value, reason: AssignmentReason::DISABLED) unless flag.enabled + + if expected_type && !type_matches?(flag.variation_type, expected_type) + return ResolutionDetails.build_error( + value: default_value, + error_code: ErrorCodes::TYPE_MISMATCH_ERROR, + error_message: "invalid flag type (expected: #{expected_type}, found: #{flag.variation_type})", + reason: AssignmentReason::ERROR + ) + end + + begin + selected_allocation, selected_variation, reason = evaluate_flag_allocations( + flag, + evaluation_context, + Time.now.utc + ) + + if selected_allocation.nil? && selected_variation.nil? + return ResolutionDetails.build_default(value: default_value, reason: AssignmentReason::DEFAULT) + end + + allocation = selected_allocation #: Allocation + variation = selected_variation #: Variation + assignment_reason = reason #: String + + ResolutionDetails.build_success( + value: variation.value, + variant: variation.key, + allocation_key: allocation.key, + do_log: allocation.do_log, + reason: assignment_reason + ) + rescue EvaluationError => e + ResolutionDetails.build_error( + value: default_value, + error_code: e.code, + error_message: e.message, + reason: AssignmentReason::ERROR + ) + end + end + + private + + def parse_and_validate_json(ufc_json) + if ufc_json.nil? || ufc_json.strip.empty? + @parse_error = {error_code: ErrorCodes::CONFIGURATION_MISSING, + error_message: 'flags configuration is missing'} + return + end + + parsed_json = JSON.parse(ufc_json) + + unless parsed_json.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return + end + + unless parsed_json.key?('flags') + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return + end + + flags_data = parsed_json['flags'] + unless flags_data.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return + end + + error_found = flags_data.any? do |_flag_key, flag_data| + validate_flag_structure(flag_data) + end + + return if error_found + + @parsed_config = Configuration.from_hash(parsed_json) + rescue JSON::ParserError + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + rescue + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + end + + def validate_flag_structure(flag_data) + unless flag_data.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + unless flag_data.fetch('variations', {}).is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + unless flag_data.fetch('allocations', []).is_a?(Array) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + flag_data.fetch('allocations', []).any? do |allocation_data| + validate_allocation_structure(allocation_data) + end + end + + def validate_allocation_structure(allocation_data) + unless allocation_data.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + unless allocation_data.fetch('splits', []).is_a?(Array) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + rules = allocation_data['rules'] + if rules && !rules.is_a?(Array) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + splits_error = allocation_data.fetch('splits', []).any? do |split_data| + validate_split_structure(split_data) + end + + return true if splits_error + + if rules + rules.any? do |rule_data| + validate_rule_structure(rule_data) + end + else + false + end + end + + def validate_split_structure(split_data) + unless split_data.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + unless split_data.fetch('shards', []).is_a?(Array) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + split_data.fetch('shards', []).any? do |shard_data| + validate_shard_structure(shard_data) + end + end + + def validate_shard_structure(shard_data) + unless shard_data.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + unless shard_data.fetch('ranges', []).is_a?(Array) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + false + end + + def validate_rule_structure(rule_data) + unless rule_data.is_a?(Hash) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + unless rule_data.fetch('conditions', []).is_a?(Array) + @parse_error = {error_code: ErrorCodes::CONFIGURATION_PARSE_ERROR, + error_message: 'failed to parse configuration'} + return true + end + + false + end + + def type_matches?(flag_variation_type, expected_type) + case expected_type + when 'boolean' then flag_variation_type == VariationType::BOOLEAN + when 'string' then flag_variation_type == VariationType::STRING + when 'integer' then flag_variation_type == VariationType::INTEGER + when 'float' then flag_variation_type == VariationType::NUMERIC + when 'object' then flag_variation_type == VariationType::JSON + else false + end + end + + def evaluate_flag_allocations(flag, evaluation_context, time) + return [nil, nil, AssignmentReason::DEFAULT] if flag.allocations.empty? + + evaluation_time = time.is_a?(Time) ? time : Time.at(time) + + flag.allocations.each do |allocation| + matching_split, reason = find_matching_split_for_allocation(allocation, evaluation_context, evaluation_time) + + next unless matching_split + + variation = flag.variations[matching_split.variation_key] + if variation + return [allocation, variation, reason] + else + raise EvaluationError.new( + ErrorCodes::DEFAULT_ALLOCATION_NULL, + 'allocation references non-existent variation' + ) + end + end + + [nil, nil, AssignmentReason::DEFAULT] + end + + def find_matching_split_for_allocation(allocation, evaluation_context, evaluation_time) + if allocation.start_at && evaluation_time < allocation.start_at + return [nil, nil] # Before start time + end + + if allocation.end_at && evaluation_time > allocation.end_at + return [nil, nil] # After end time + end + + if allocation.rules && !allocation.rules.empty? + rules_pass = allocation.rules.any? { |rule| evaluate_rule(rule, evaluation_context) } + return [nil, nil] unless rules_pass # All rules failed + end + + if allocation.splits.any? + targeting_key = get_targeting_key(evaluation_context) + + matching_split = allocation.splits.find { |split| split_matches?(split, targeting_key) } + + if matching_split + reason = determine_assignment_reason(allocation) + return [matching_split, reason] + else + return [nil, nil] + end + end + + [nil, nil] + end + + def evaluate_rule(rule, evaluation_context) + return true if rule.conditions.empty? + + rule.conditions.all? { |condition| evaluate_condition(condition, evaluation_context) } + end + + def evaluate_condition(condition, evaluation_context) + attribute_value = get_attribute_from_context(condition.attribute, evaluation_context) + + attribute_str = nil + if ['ONE_OF', 'NOT_ONE_OF', 'MATCHES', 'NOT_MATCHES'].include?(condition.operator) + attribute_str = coerce_to_string(attribute_value) + end + + case condition.operator + when 'GTE' + evaluate_comparison(attribute_value, condition.value, :>=) + when 'GT' + evaluate_comparison(attribute_value, condition.value, :>) + when 'LTE' + evaluate_comparison(attribute_value, condition.value, :<=) + when 'LT' + evaluate_comparison(attribute_value, condition.value, :<) + when 'ONE_OF' + membership_matches?(attribute_str, condition.value, true) + when 'NOT_ONE_OF' + membership_matches?(attribute_str, condition.value, false) + when 'MATCHES' + regex_matches?(attribute_str, condition.value, true) + when 'NOT_MATCHES' + regex_matches?(attribute_str, condition.value, false) + when 'IS_NULL' + evaluate_null_check(attribute_value, condition.value) + else + false + end + end + + def get_attribute_from_context(attribute_name, evaluation_context) + return nil if evaluation_context.nil? + + attribute_value = evaluation_context[attribute_name] + + attribute_value = get_targeting_key(evaluation_context) if attribute_value.nil? && attribute_name == 'id' + + attribute_value + end + + def evaluate_comparison(attribute_value, condition_value, operator) + return false if attribute_value.nil? + + begin + attr_num = coerce_to_number(attribute_value) + cond_num = coerce_to_number(condition_value) + return false if attr_num.nil? || cond_num.nil? + + attr_num.send(operator, cond_num) + rescue + false + end + end + + def evaluate_membership(attribute_value, condition_values, expected_membership) + return false if attribute_value.nil? + + return false if !expected_membership && attribute_value.nil? + + attr_str = coerce_to_string(attribute_value) + membership_matches?(attr_str, condition_values, expected_membership) + end + + def membership_matches?(attr_str, condition_values, expected_membership) + return false if attr_str.nil? + + values_array = condition_values.is_a?(Array) ? condition_values : [condition_values] + + is_member = values_array.any? { |v| coerce_to_string(v) == attr_str } + + is_member == expected_membership + end + + def evaluate_regex(attribute_value, pattern, expected_match) + return false if attribute_value.nil? + + attr_str = coerce_to_string(attribute_value) + regex_matches?(attr_str, pattern, expected_match) + end + + def regex_matches?(attr_str, pattern, expected_match) + return false if attr_str.nil? + + begin + regex = Regexp.new(pattern.to_s) + matches = !!(attr_str =~ regex) + matches == expected_match + rescue RegexpError + false + rescue + false + end + end + + def evaluate_null_check(attribute_value, expected_null) + is_null = attribute_value.nil? + expected_null_bool = coerce_to_boolean(expected_null) + return false if expected_null_bool.nil? + + is_null == expected_null_bool + end + + def coerce_to_number(value) + case value + when Integer, Float + value.to_f + when String + begin + Float(value) + rescue + nil + end + when true + 1.0 + when false + 0.0 + end + end + + def coerce_to_string(value) + case value + when String + value + when Numeric + value.to_s + when true + 'true' + when false + 'false' + when nil + nil + else + value.to_s + end + end + + def coerce_to_boolean(value) + case value + when true, false + value + when String + case value.downcase + when 'true' then true + when 'false' then false + end + when Numeric + value != 0 + end + end + + def get_targeting_key(evaluation_context) + return nil if evaluation_context.nil? + + evaluation_context['targeting_key'] + end + + def split_matches?(split, targeting_key) + return true if split.shards.empty? + + return false if targeting_key.nil? + + split.shards.all? { |shard| shard_matches?(shard, targeting_key) } + end + + def shard_matches?(shard, targeting_key) + shard_value = compute_shard_hash(shard.salt, targeting_key, shard.total_shards) + + shard.ranges.any? { |range| shard_value >= range.start && shard_value < range.end_value } + end + + def compute_shard_hash(salt, targeting_key, total_shards) + require 'digest/md5' + + # MD5 is used for feature flag allocation hashing, not cryptographic security + hasher = Digest::MD5.new # nosemgrep: ruby.lang.security.weak-hashes-md5.weak-hashes-md5 + hasher.update(salt.to_s) if salt + hasher.update('-') # Separator used in libdatadog PreSaltedSharder + hasher.update(targeting_key.to_s) + + hash_bytes = hasher.digest + byte_slice = hash_bytes[0..3] #: String + hash_value = byte_slice.unpack1('N') #: Integer # 'N' = big-endian uint32 + hash_value % total_shards + end + + def determine_assignment_reason(allocation) + has_rules = allocation.rules && !allocation.rules.empty? + has_time_bounds = allocation.start_at || allocation.end_at + + if has_rules || has_time_bounds + AssignmentReason::TARGETING_MATCH + elsif allocation.splits.length == 1 && allocation.splits.first.shards.empty? + AssignmentReason::STATIC + else + AssignmentReason::SPLIT + end + end + end + end + end +end diff --git a/lib/datadog/open_feature/binding/variation_type.rb b/lib/datadog/open_feature/binding/variation_type.rb new file mode 100644 index 00000000000..33b5c25710b --- /dev/null +++ b/lib/datadog/open_feature/binding/variation_type.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Datadog + module OpenFeature + module Binding + module VariationType + STRING = 'STRING' + INTEGER = 'INTEGER' + NUMERIC = 'NUMERIC' + BOOLEAN = 'BOOLEAN' + JSON = 'JSON' + end + end + end +end diff --git a/lib/datadog/open_feature/evaluation_engine.rb b/lib/datadog/open_feature/evaluation_engine.rb index 8062f55587b..02f2495f0bc 100644 --- a/lib/datadog/open_feature/evaluation_engine.rb +++ b/lib/datadog/open_feature/evaluation_engine.rb @@ -3,6 +3,7 @@ require_relative 'ext' require_relative 'noop_evaluator' require_relative 'resolution_details' +require_relative 'binding/internal_evaluator' module Datadog module OpenFeature @@ -45,7 +46,7 @@ def fetch_value(flag_key:, default_value:, expected_type:, evaluation_context: n def reconfigure!(configuration) @logger.debug('OpenFeature: Removing configuration') if configuration.nil? - @evaluator = NoopEvaluator.new(configuration) + @evaluator = configuration.nil? ? NoopEvaluator.new(nil) : Binding::InternalEvaluator.new(configuration) rescue => e message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration' diff --git a/lib/datadog/open_feature/resolution_details.rb b/lib/datadog/open_feature/resolution_details.rb index 1ef979d0f0d..e447f000473 100644 --- a/lib/datadog/open_feature/resolution_details.rb +++ b/lib/datadog/open_feature/resolution_details.rb @@ -20,14 +20,51 @@ class ResolutionDetails < Struct.new( :error?, keyword_init: true ) + def self.build_success(value:, variant:, allocation_key:, do_log:, reason:) + new( + value: value, + variant: variant, + error_code: nil, + error_message: nil, + reason: reason, + allocation_key: allocation_key, + log?: do_log, + error?: false, + flag_metadata: { + 'allocationKey' => allocation_key, + 'doLog' => do_log + }, + extra_logging: {} + ).freeze + end + + def self.build_default(value:, reason:) + new( + value: value, + variant: nil, + error_code: nil, + error_message: nil, + reason: reason, + allocation_key: nil, + log?: false, + error?: false, + flag_metadata: {}, + extra_logging: {} + ).freeze + end + def self.build_error(value:, error_code:, error_message:, reason: Ext::ERROR) new( value: value, + variant: nil, error_code: error_code, error_message: error_message, reason: reason, + allocation_key: nil, + log?: false, error?: true, - log?: false + flag_metadata: {}, + extra_logging: {} ).freeze end end diff --git a/sig/datadog/open_feature/binding/assignment_reason.rbs b/sig/datadog/open_feature/binding/assignment_reason.rbs new file mode 100644 index 00000000000..36b2a8c0bdb --- /dev/null +++ b/sig/datadog/open_feature/binding/assignment_reason.rbs @@ -0,0 +1,14 @@ +module Datadog + module OpenFeature + module Binding + module AssignmentReason + TARGETING_MATCH: ::String + SPLIT: ::String + STATIC: ::String + DEFAULT: ::String + DISABLED: ::String + ERROR: ::String + end + end + end +end diff --git a/sig/datadog/open_feature/binding/condition_operator.rbs b/sig/datadog/open_feature/binding/condition_operator.rbs new file mode 100644 index 00000000000..0a9590b8553 --- /dev/null +++ b/sig/datadog/open_feature/binding/condition_operator.rbs @@ -0,0 +1,17 @@ +module Datadog + module OpenFeature + module Binding + module ConditionOperator + MATCHES: ::String + NOT_MATCHES: ::String + GTE: ::String + GT: ::String + LTE: ::String + LT: ::String + ONE_OF: ::String + NOT_ONE_OF: ::String + IS_NULL: ::String + end + end + end +end diff --git a/sig/datadog/open_feature/binding/configuration.rbs b/sig/datadog/open_feature/binding/configuration.rbs new file mode 100644 index 00000000000..fb319ff0f3d --- /dev/null +++ b/sig/datadog/open_feature/binding/configuration.rbs @@ -0,0 +1,147 @@ +module Datadog + module OpenFeature + module Binding + class Flag + attr_reader key: ::String + attr_reader enabled: bool + attr_reader variation_type: ::String + attr_reader variations: ::Hash[::String, Variation] + attr_reader allocations: ::Array[Allocation] + + def initialize: ( + key: ::String, + enabled: bool, + variation_type: ::String, + variations: ::Hash[::String, Variation], + allocations: ::Array[Allocation] + ) -> void + + def self.from_hash: (::Hash[::String, untyped] flag_data, ::String key) -> Flag + + private + + def self.parse_variations: (::Hash[::String, untyped] variations_data) -> ::Hash[::String, Variation] + + def self.parse_allocations: (::Array[untyped] allocations_data) -> ::Array[Allocation] + end + + class Variation + attr_reader key: ::String + attr_reader value: untyped + + def initialize: (key: ::String, value: untyped) -> void + + def self.from_hash: (::Hash[::String, untyped] variation_data) -> Variation + end + + class Allocation + attr_reader key: ::String + attr_reader rules: ::Array[Rule]? + attr_reader start_at: ::Time? + attr_reader end_at: ::Time? + attr_reader splits: ::Array[Split] + attr_reader do_log: bool + + def initialize: ( + key: ::String, + ?rules: ::Array[Rule]?, + ?start_at: ::Time?, + ?end_at: ::Time?, + splits: ::Array[Split], + ?do_log: bool + ) -> void + + def self.from_hash: (::Hash[::String, untyped] allocation_data) -> Allocation + + private + + def self.parse_rules: (::Array[untyped]? rules_data) -> ::Array[Rule]? + + def self.parse_splits: (::Array[untyped] splits_data) -> ::Array[Split] + + def self.parse_timestamp: (untyped timestamp_data) -> ::Time? + end + + class Split + attr_reader shards: ::Array[Shard] + attr_reader variation_key: ::String + attr_reader extra_logging: ::Hash[::String, untyped] + + def initialize: ( + shards: ::Array[Shard], + variation_key: ::String, + ?extra_logging: ::Hash[::String, untyped]? + ) -> void + + def self.from_hash: (::Hash[::String, untyped] split_data) -> Split + + private + + def self.parse_shards: (::Array[untyped] shards_data) -> ::Array[Shard] + end + + class Shard + attr_reader salt: ::String + attr_reader total_shards: ::Integer + attr_reader ranges: ::Array[ShardRange] + + def initialize: ( + salt: ::String, + total_shards: ::Integer, + ranges: ::Array[ShardRange] + ) -> void + + def self.from_hash: (::Hash[::String, untyped] shard_data) -> Shard + + private + + def self.parse_ranges: (::Array[untyped] ranges_data) -> ::Array[ShardRange] + end + + class ShardRange + attr_reader start: ::Integer + attr_reader end_value: ::Integer + + def initialize: (start: ::Integer, end_value: ::Integer) -> void + + def self.from_hash: (::Hash[::String, untyped] range_data) -> ShardRange + + def end: () -> ::Integer + end + + class Rule + attr_reader conditions: ::Array[Condition] + + def initialize: (conditions: ::Array[Condition]) -> void + + def self.from_hash: (::Hash[::String, untyped] rule_data) -> Rule + + private + + def self.parse_conditions: (::Array[untyped] conditions_data) -> ::Array[Condition] + end + + class Condition + attr_reader attribute: ::String + attr_reader operator: ::String + attr_reader value: untyped + + def initialize: (attribute: ::String, operator: ::String, value: untyped) -> void + + def self.from_hash: (::Hash[::String, untyped] condition_data) -> Condition + + end + + class Configuration + attr_reader flags: ::Hash[::String, Flag] + attr_reader schema_version: ::String? + + def initialize: (flags: ::Hash[::String, Flag], ?schema_version: ::String?) -> void + + def self.from_hash: (::Hash[::String, untyped] config_data) -> Configuration + + def get_flag: (::String flag_key) -> Flag? + end + end + end +end diff --git a/sig/datadog/open_feature/binding/error_codes.rbs b/sig/datadog/open_feature/binding/error_codes.rbs new file mode 100644 index 00000000000..9cb640db6b3 --- /dev/null +++ b/sig/datadog/open_feature/binding/error_codes.rbs @@ -0,0 +1,15 @@ +module Datadog + module OpenFeature + module Binding + module ErrorCodes + TYPE_MISMATCH_ERROR: ::String + CONFIGURATION_PARSE_ERROR: ::String + CONFIGURATION_MISSING: ::String + FLAG_UNRECOGNIZED_OR_DISABLED: ::String + FLAG_DISABLED: ::String + DEFAULT_ALLOCATION_NULL: ::String + INTERNAL_ERROR: ::String + end + end + end +end diff --git a/sig/datadog/open_feature/binding/internal_evaluator.rbs b/sig/datadog/open_feature/binding/internal_evaluator.rbs new file mode 100644 index 00000000000..ab4ac7a30ae --- /dev/null +++ b/sig/datadog/open_feature/binding/internal_evaluator.rbs @@ -0,0 +1,87 @@ +module Datadog + module OpenFeature + module Binding + class EvaluationError < ::StandardError + attr_reader code: ::String + attr_reader message: ::String + + def initialize: (::String code, ::String message) -> void + end + + class InternalEvaluator + + def initialize: (::String ufc_json) -> void + + def get_assignment: ( + ::String flag_key, + untyped default_value, + ::Hash[::String, untyped]? evaluation_context, + ::String? expected_type + ) -> ::Datadog::OpenFeature::ResolutionDetails + + private + + def parse_and_validate_json: (::String ufc_json) -> void + + def validate_flag_structure: (::Hash[::String, untyped] flag_data) -> bool + + def validate_allocation_structure: (::Hash[::String, untyped] allocation_data) -> bool + + def validate_split_structure: (::Hash[::String, untyped] split_data) -> bool + + def validate_shard_structure: (::Hash[::String, untyped] shard_data) -> bool + + def validate_rule_structure: (::Hash[::String, untyped] rule_data) -> bool + + def membership_matches?: (::String? attr_str, (::Array[untyped] | untyped) condition_values, bool expected_membership) -> bool + + def regex_matches?: (::String? attr_str, ::String pattern, bool expected_match) -> bool + + def type_matches?: (::String flag_variation_type, ::String expected_type) -> bool + + def evaluate_flag_allocations: ( + Flag flag, + ::Hash[::String, untyped]? evaluation_context, + ::Time time + ) -> [(Allocation | nil), (Variation | nil), (::String | nil)] + + def find_matching_split_for_allocation: ( + Allocation allocation, + ::Hash[::String, untyped]? evaluation_context, + ::Time evaluation_time + ) -> [Split?, ::String?] + + def evaluate_rule: (Rule rule, ::Hash[::String, untyped]? evaluation_context) -> bool + + def evaluate_condition: (Condition condition, ::Hash[::String, untyped]? evaluation_context) -> bool + + def get_attribute_from_context: (::String attribute_name, ::Hash[::String, untyped]? evaluation_context) -> untyped + + def evaluate_comparison: (untyped attribute_value, untyped condition_value, ::Symbol operator) -> bool + + def evaluate_membership: (untyped attribute_value, untyped condition_values, bool expected_membership) -> bool + + def evaluate_regex: (untyped attribute_value, untyped pattern, bool expected_match) -> bool + + def evaluate_null_check: (untyped attribute_value, untyped expected_null) -> bool + + def coerce_to_number: (untyped value) -> ::Float? + + def coerce_to_string: (untyped value) -> ::String? + + def coerce_to_boolean: (untyped value) -> bool? + + def get_targeting_key: (::Hash[::String, untyped]? evaluation_context) -> ::String? + + def split_matches?: (Split split, ::String? targeting_key) -> bool + + def shard_matches?: (Shard shard, ::String targeting_key) -> bool + + def compute_shard_hash: (::String? salt, ::String targeting_key, ::Integer total_shards) -> ::Integer + + def determine_assignment_reason: (Allocation allocation) -> ::String + + end + end + end +end diff --git a/sig/datadog/open_feature/binding/variation_type.rbs b/sig/datadog/open_feature/binding/variation_type.rbs new file mode 100644 index 00000000000..a3ec29d9200 --- /dev/null +++ b/sig/datadog/open_feature/binding/variation_type.rbs @@ -0,0 +1,13 @@ +module Datadog + module OpenFeature + module Binding + module VariationType + STRING: ::String + INTEGER: ::String + NUMERIC: ::String + BOOLEAN: ::String + JSON: ::String + end + end + end +end diff --git a/sig/datadog/open_feature/resolution_details.rbs b/sig/datadog/open_feature/resolution_details.rbs index c4180ee3d13..f6464974e6d 100644 --- a/sig/datadog/open_feature/resolution_details.rbs +++ b/sig/datadog/open_feature/resolution_details.rbs @@ -34,6 +34,19 @@ module Datadog ?error?: bool? ) -> instance + def self.build_success: ( + value: untyped, + variant: ::String, + allocation_key: ::String, + do_log: bool, + reason: ::String + ) -> instance + + def self.build_default: ( + value: untyped, + reason: ::String + ) -> instance + def self.build_error: ( value: untyped, error_code: ::String, diff --git a/spec/datadog/open_feature/binding/allocation_matching_spec.rb b/spec/datadog/open_feature/binding/allocation_matching_spec.rb new file mode 100644 index 00000000000..181abec6ed7 --- /dev/null +++ b/spec/datadog/open_feature/binding/allocation_matching_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'json' +require 'datadog/open_feature/binding/internal_evaluator' + +RSpec.describe 'InternalEvaluator Allocation Matching' do + describe 'time-based allocation filtering' do + let(:flag_config) do + { + "flags" => { + "time_test_flag" => { + "key" => "time_test_flag", + "enabled" => true, + "variationType" => "STRING", + "variations" => { + "control" => {"key" => "control", "value" => "control_value"}, + "treatment" => {"key" => "treatment", "value" => "treatment_value"} + }, + "allocations" => [ + { + "key" => "expired_allocation", + "endAt" => (Time.now - 3600).to_i, # Expired 1 hour ago + "doLog" => true, + "splits" => [ + {"variationKey" => "control", "shards" => []} + ] + }, + { + "key" => "active_allocation", + "doLog" => false, + "splits" => [ + {"variationKey" => "treatment", "shards" => []} + ] + } + ] + } + } + } + end + + let(:evaluator) { Datadog::OpenFeature::Binding::InternalEvaluator.new(flag_config.to_json) } + + it 'skips expired allocations and uses active ones' do + result = evaluator.get_assignment("time_test_flag", 'test_default', {}, 'string') + + expect(result.error_code).to be_nil # nil for successful evaluation + expect(result.value).to eq("treatment_value") # Should use active allocation + expect(result.variant).to eq("treatment") + expect(result.flag_metadata['allocationKey']).to eq("active_allocation") + expect(result.flag_metadata['doLog']).to eq(false) + end + + it 'returns assignment reason based on allocation properties' do + result = evaluator.get_assignment("time_test_flag", 'test_default', {}, 'string') + + expect(result.reason).to eq("STATIC") # Single split with no shards = static + end + end + + describe 'default value integration' do + let(:evaluator) { Datadog::OpenFeature::Binding::InternalEvaluator.new('{"flags": {}}') } + + it 'returns error result on flag lookup errors' do + result = evaluator.get_assignment("missing_flag", 'test_default', {}, 'string') + + expect(result.error_code).to eq('FLAG_UNRECOGNIZED_OR_DISABLED') + expect(result.value).to eq('test_default') # Internal evaluator returns default_value for errors + expect(result.variant).to be_nil + expect(result.flag_metadata).to eq({}) + end + + it 'returns consistent error results for different types' do + string_result = evaluator.get_assignment("missing", 'test_default', {}, 'string') + number_result = evaluator.get_assignment("missing", 'test_default', {}, 'float') + bool_result = evaluator.get_assignment("missing", 'test_default', {}, 'boolean') + + expect(string_result.value).to eq('test_default') # Internal evaluator returns default_value + expect(number_result.value).to eq('test_default') + expect(bool_result.value).to eq('test_default') + expect(string_result.error_code).to eq('FLAG_UNRECOGNIZED_OR_DISABLED') + expect(number_result.error_code).to eq('FLAG_UNRECOGNIZED_OR_DISABLED') + expect(bool_result.error_code).to eq('FLAG_UNRECOGNIZED_OR_DISABLED') + end + end + + describe 'allocation reason classification' do + it 'returns TARGETING_MATCH for allocations with time bounds' do + config_with_time_bounds = { + "flags" => { + "timed_flag" => { + "key" => "timed_flag", + "enabled" => true, + "variationType" => "BOOLEAN", + "variations" => {"var1" => {"key" => "var1", "value" => true}}, + "allocations" => [ + { + "key" => "timed_allocation", + "startAt" => (Time.now - 3600).to_i, # Started 1 hour ago + "endAt" => (Time.now + 3600).to_i, # Ends 1 hour from now + "doLog" => true, + "splits" => [{"variationKey" => "var1", "shards" => []}] + } + ] + } + } + } + + evaluator = Datadog::OpenFeature::Binding::InternalEvaluator.new(config_with_time_bounds.to_json) + result = evaluator.get_assignment("timed_flag", 'test_default', {}, 'boolean') + + expect(result.error_code).to be_nil # nil for successful evaluation + expect(result.reason).to eq("TARGETING_MATCH") # Has time bounds + expect(result.variant).not_to be_nil + expect(result.flag_metadata).not_to be_nil + end + end +end diff --git a/spec/datadog/open_feature/binding/configuration_spec.rb b/spec/datadog/open_feature/binding/configuration_spec.rb new file mode 100644 index 00000000000..73d4101bb52 --- /dev/null +++ b/spec/datadog/open_feature/binding/configuration_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'json' +require_relative '../../../../lib/datadog/open_feature/binding/configuration' + +RSpec.describe Datadog::OpenFeature::Binding::Configuration do + let(:flags_v1_path) { File.join(__dir__, '../../../fixtures/ufc/flags-v1.json') } + let(:flags_v1_json) { JSON.parse(File.read(flags_v1_path)) } + let(:flag_config) { flags_v1_json } + + describe '.from_hash' do + it 'parses the main flags-v1.json without errors' do + expect { described_class.from_hash(flag_config) }.not_to raise_error + end + + it 'extracts correct number of flags' do + config = described_class.from_hash(flag_config) + expect(config.flags.keys.count).to be > 10 + end + + it 'parses specific flags correctly' do + config = described_class.from_hash(flag_config) + + empty_flag = config.flags['empty_flag'] + expect(empty_flag).not_to be_nil + expect(empty_flag.key).to eq('empty_flag') + expect(empty_flag.enabled).to be true + expect(empty_flag.variation_type).to eq('STRING') + + disabled_flag = config.flags['disabled_flag'] + expect(disabled_flag).not_to be_nil + expect(disabled_flag.enabled).to be false + expect(disabled_flag.variation_type).to eq('INTEGER') + end + + it 'parses numeric variations correctly' do + config = described_class.from_hash(flag_config) + + numeric_flag = config.flags['numeric_flag'] + expect(numeric_flag).not_to be_nil + expect(numeric_flag.variation_type).to eq('NUMERIC') + + if numeric_flag.variations['e'] + e_value = numeric_flag.variations['e'].value + expect(e_value).to be_within(0.001).of(2.7182818) + end + end + + it 'handles all variation types present in test data' do + config = described_class.from_hash(flag_config) + + variation_types = config.flags.values.map(&:variation_type).uniq + expect(variation_types).to include('STRING', 'INTEGER', 'NUMERIC') + end + + it 'parses allocations with rules and splits' do + config = described_class.from_hash(flag_config) + + flags_with_allocations = config.flags.values.select { |f| f.allocations.any? } + expect(flags_with_allocations).not_to be_empty + + flag_with_allocation = flags_with_allocations.first + allocation = flag_with_allocation.allocations.first + + expect(allocation.key).to be_a(String) + expect([true, false]).to include(allocation.do_log) + end + end + + describe '#get_flag' do + let(:config) { described_class.from_hash(flag_config) } + + it 'returns existing flags' do + flag = config.get_flag('empty_flag') + expect(flag).not_to be_nil + expect(flag.key).to eq('empty_flag') + end + + it 'returns nil for non-existent flags' do + flag = config.get_flag('does_not_exist') + expect(flag).to be_nil + end + end + + describe 'error handling' do + it 'handles empty configuration' do + config = described_class.from_hash({}) + expect(config.flags).to be_empty + end + end +end diff --git a/spec/datadog/open_feature/binding/internal_evaluator_spec.rb b/spec/datadog/open_feature/binding/internal_evaluator_spec.rb new file mode 100644 index 00000000000..4d93419cf9f --- /dev/null +++ b/spec/datadog/open_feature/binding/internal_evaluator_spec.rb @@ -0,0 +1,325 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'json' +require 'open_feature/sdk' +require 'datadog/open_feature/binding/internal_evaluator' + +RSpec.describe Datadog::OpenFeature::Binding::InternalEvaluator do + # The InternalEvaluator implements a three-case evaluation model: + # Case 1: Successful evaluation with result - has variant and value + # Case 2: No results (disabled/default) - not an error but no allocation matched + # Case 3: Evaluation error - has error_code and error_message + let(:flags_v1_path) { File.join(__dir__, '../../../fixtures/ufc/flags-v1.json') } + let(:flags_v1_json) { JSON.parse(File.read(flags_v1_path)) } + let(:valid_ufc_json) { flags_v1_json.to_json } + + describe '#initialize' do + context 'with valid UFC JSON' do + it 'parses configuration successfully' do + evaluator = described_class.new(valid_ufc_json) + config = evaluator.instance_variable_get(:@parsed_config) + + expect(config).to be_a(Datadog::OpenFeature::Binding::Configuration) + expect(config.flags).not_to be_empty + end + end + + context 'with invalid UFC JSON' do + it 'stores parse error for malformed JSON' do + evaluator = described_class.new('invalid json') + result = evaluator.get_assignment('any_flag', 'test_default', {}, 'string') + + expect(result.error_code).to eq('CONFIGURATION_PARSE_ERROR') + expect(result.error_message).to eq('failed to parse configuration') + expect(result.value).to eq('test_default') + expect(result.variant).to be_nil + end + + it 'stores configuration missing error for empty JSON' do + evaluator = described_class.new('') + result = evaluator.get_assignment('any_flag', 'test_default', {}, 'string') + + expect(result.error_code).to eq('CONFIGURATION_MISSING') + expect(result.error_message).to eq('flags configuration is missing') + expect(result.value).to eq('test_default') + expect(result.variant).to be_nil + end + end + end + + describe '#get_assignment' do + let(:evaluator) { described_class.new(valid_ufc_json) } + + context 'when initialization failed' do + let(:bad_evaluator) { described_class.new('invalid json') } + + it 'returns the initialization error' do + result = bad_evaluator.get_assignment('any_flag', 'test_default', {}, 'string') + + expect(result.error_code).to eq('CONFIGURATION_PARSE_ERROR') + expect(result.error_message).to eq('failed to parse configuration') + expect(result.value).to eq('test_default') + expect(result.variant).to be_nil + expect(result.flag_metadata).to eq({}) + end + end + + context 'with type validation' do + it 'returns TYPE_MISMATCH when types do not match' do + result = evaluator.get_assignment('numeric_flag', 'test_default', {}, 'boolean') + + expect(result.error_code).to eq('TYPE_MISMATCH') + expect(result.error_message).to eq('invalid flag type (expected: boolean, found: NUMERIC)') + expect(result.value).to eq('test_default') + expect(result.variant).to be_nil + expect(result.flag_metadata).to eq({}) + end + + it 'succeeds when types match' do + result = evaluator.get_assignment('numeric_flag', 'test_default', {}, 'float') + + expect(result.error_code).to be_nil # nil for successful allocation match + expect(result.error_message).to be_nil # nil for successful cases + expect(result.value).not_to be_nil + expect(result.variant).not_to be_nil + expect(result.allocation_key).not_to be_nil + expect(result.flag_metadata).to include("allocationKey") + expect([true, false]).to include(result.log?) + end + + it 'succeeds when expected_type is nil (no validation)' do + result = evaluator.get_assignment('numeric_flag', 'test_default', {}, nil) + + expect(result.error_code).to be_nil # nil for successful allocation match + expect(result.error_message).to be_nil # nil for successful cases + expect(result.value).not_to be_nil + expect(result.variant).not_to be_nil + expect(result.allocation_key).not_to be_nil + expect(result.flag_metadata).to include("allocationKey") + expect([true, false]).to include(result.log?) + end + end + + context 'type mapping' do + let(:type_checker) { evaluator } + + it 'maps Ruby types to UFC variation types correctly' do + expect(type_checker.send(:type_matches?, 'BOOLEAN', 'boolean')).to be true + expect(type_checker.send(:type_matches?, 'STRING', 'string')).to be true + expect(type_checker.send(:type_matches?, 'INTEGER', 'integer')).to be true + expect(type_checker.send(:type_matches?, 'NUMERIC', 'float')).to be true + expect(type_checker.send(:type_matches?, 'NUMERIC', 'float')).to be true + expect(type_checker.send(:type_matches?, 'JSON', 'object')).to be true + + expect(type_checker.send(:type_matches?, 'BOOLEAN', 'string')).to be false + expect(type_checker.send(:type_matches?, 'STRING', 'integer')).to be false + end + end + + context 'evaluation context types' do + it 'accepts hash evaluation contexts including from OpenFeature SDK fields' do + hash_context = {'targeting_key' => 'user123', 'attr1' => 'value1'} + hash_result = evaluator.get_assignment('numeric_flag', 'test_default', hash_context, 'float') + + expect(hash_result.error_code).to be_nil + expect(hash_result.value).not_to be_nil + expect(hash_result.variant).not_to be_nil + + sdk_context = OpenFeature::SDK::EvaluationContext.new( + targeting_key: 'user123', + fields: {'attr1' => 'value1'} + ) + + sdk_result = evaluator.get_assignment('numeric_flag', 'test_default', sdk_context.fields, 'float') + + expect(sdk_result.error_code).to be_nil + expect(sdk_result.value).not_to be_nil + expect(sdk_result.variant).not_to be_nil + + expect(hash_result.variant).to eq(sdk_result.variant) + expect(hash_result.value).to eq(sdk_result.value) + end + end + end + + describe 'integration with Configuration classes' do + let(:evaluator) { described_class.new(valid_ufc_json) } + + it 'properly integrates with parsed Configuration object' do + config = evaluator.instance_variable_get(:@parsed_config) + + expect(config).to be_a(Datadog::OpenFeature::Binding::Configuration) + expect(config.flags).to be_a(Hash) + expect(config.flags.values).to all(be_a(Datadog::OpenFeature::Binding::Flag)) + end + + it 'accesses flag properties correctly' do + config = evaluator.instance_variable_get(:@parsed_config) + flag = config.get_flag('empty_flag') + + expect(flag).not_to be_nil + expect(flag.enabled).to be true + expect(flag.variation_type).to eq('STRING') + end + end + + describe 'error message correctness' do + let(:evaluator) { described_class.new(valid_ufc_json) } + + it 'uses correct error codes' do + flag_not_found = evaluator.get_assignment('missing', 'test_default', {}, 'string') + expect(flag_not_found.error_code).to eq('FLAG_UNRECOGNIZED_OR_DISABLED') + expect(flag_not_found.value).to eq('test_default') + expect(flag_not_found.variant).to be_nil + expect(flag_not_found.flag_metadata).to eq({}) + + flag_disabled = evaluator.get_assignment('disabled_flag', 'test_default', {}, 'integer') + expect(flag_disabled.error_code).to be_nil # Disabled flags are successful cases with nil error_code + expect(flag_disabled.error_message).to be_nil # nil for successful disabled cases + expect(flag_disabled.reason).to eq('DISABLED') # Disabled reason + expect(flag_disabled.value).to eq('test_default') + expect(flag_disabled.variant).to be_nil + expect(flag_disabled.flag_metadata).to eq({}) + + type_mismatch = evaluator.get_assignment('numeric_flag', 'test_default', {}, 'boolean') + expect(type_mismatch.error_code).to eq('TYPE_MISMATCH') + expect(type_mismatch.value).to eq('test_default') + expect(type_mismatch.variant).to be_nil + expect(type_mismatch.flag_metadata).to eq({}) + end + + it 'provides descriptive error messages' do + result = evaluator.get_assignment('missing_flag', 'test_default', {}, 'string') + expect(result.error_message).to eq('flag is missing in configuration, it is either unrecognized or disabled') + expect(result.value).to eq('test_default') + expect(result.variant).to be_nil + expect(result.flag_metadata).to eq({}) + + type_result = evaluator.get_assignment('numeric_flag', 'test_default', {}, 'boolean') + expect(type_result.error_message).to match(/invalid flag type \(expected: .*, found: .*\)/) + expect(type_result.value).to eq('test_default') + expect(type_result.variant).to be_nil + expect(type_result.flag_metadata).to eq({}) + end + end + + describe 'UFC test case coverage' do + let(:evaluator) { described_class.new(valid_ufc_json) } + + Dir.glob(File.join(__dir__, '../../../fixtures/ufc/test_cases/*.json')).each do |test_file| + describe "Test cases from #{File.basename(test_file)}" do + let(:test_cases) { JSON.parse(File.read(test_file)) } + + it 'executes all test cases in the file' do + test_cases.each_with_index do |test_case, index| + flag_key = test_case['flag'] + variation_type = test_case['variationType'] + targeting_key = test_case['targetingKey'] + attributes = test_case['attributes'] || {} + expected_result = test_case['result'] + + expected_type = case variation_type + when 'STRING' then 'string' + when 'INTEGER' then 'integer' + when 'NUMERIC' then 'float' + when 'BOOLEAN' then 'boolean' + when 'JSON' then 'object' + end + + evaluation_context = attributes.dup + evaluation_context['targeting_key'] = targeting_key if targeting_key # Convert camelCase to snake_case + + result = evaluator.get_assignment(flag_key, 'test_default', evaluation_context, expected_type) + + aggregate_failures "Test case ##{index + 1}: #{targeting_key} with #{attributes.keys.join(", ")}" do + has_detailed_expectations = expected_result.key?('variant') && expected_result.key?('flagMetadata') + + if has_detailed_expectations + expect(result.value).to eq(expected_result['value']), + "Expected value #{expected_result["value"].inspect}, got #{result.value.inspect}" + expect(result.variant).to eq(expected_result['variant']), + "Expected variant #{expected_result["variant"].inspect}, got #{result.variant.inspect}" + expect(result.error_code).to be_nil, + "Expected nil error code for successful evaluation, got #{result.error_code.inspect}" + expect(result.error_message).to be_nil, + "Expected nil error message for successful evaluation, got #{result.error_message.inspect}" + expect(['STATIC', 'TARGETING_MATCH', 'SPLIT']).to include(result.reason), + "Expected success reason (static/targeting_match/split), got #{result.reason.inspect}" + + expected_flag_metadata = expected_result['flagMetadata'] + expect(result.flag_metadata['allocationKey']).to eq(expected_flag_metadata['allocationKey']), + "Expected allocationKey #{expected_flag_metadata["allocationKey"].inspect}, got #{result.flag_metadata["allocationKey"].inspect}" + expect(result.flag_metadata['doLog']).to eq(expected_flag_metadata['doLog']), + "Expected doLog #{expected_flag_metadata["doLog"].inspect}, got #{result.flag_metadata["doLog"].inspect}" + expect(result.allocation_key).to eq(expected_flag_metadata['allocationKey']), + "Expected allocation_key #{expected_flag_metadata["allocationKey"].inspect}, got #{result.allocation_key.inspect}" + expect(result.log?).to eq(expected_flag_metadata['doLog']), + "Expected log? #{expected_flag_metadata["doLog"].inspect}, got #{result.log?.inspect}" + + elsif result.error_code.nil? && !result.variant.nil? + expect(result.value).to eq(expected_result['value']), + "Expected value #{expected_result["value"].inspect}, got #{result.value.inspect}" + expect(result.variant).not_to be_nil, + "Expected variant for successful evaluation, got #{result.variant.inspect}" + expect(result.error_code).to be_nil, + "Expected nil error code for successful evaluation, got #{result.error_code.inspect}" + expect(result.error_message).to be_nil, + "Expected nil error message for successful evaluation, got #{result.error_message.inspect}" + expect(['STATIC', 'TARGETING_MATCH', 'SPLIT']).to include(result.reason), + "Expected success reason (static/targeting_match/split), got #{result.reason.inspect}" + + expect(result.flag_metadata).not_to be_empty, + "Expected flag metadata for successful evaluation, got #{result.flag_metadata.inspect}" + expect(result.flag_metadata).to have_key('allocationKey'), + "Expected allocationKey in flag metadata, got #{result.flag_metadata.inspect}" + expect(result.flag_metadata).to have_key('doLog'), + "Expected doLog in flag metadata, got #{result.flag_metadata.inspect}" + expect(result.allocation_key).not_to be_nil, + "Expected allocation_key for successful evaluation, got #{result.allocation_key.inspect}" + expect([true, false]).to include(result.log?), + "Expected boolean log? value, got #{result.log?.inspect}" + + elsif result.error_code.nil? && result.variant.nil? + expect(result.value).to eq('test_default'), + "Expected nil value for disabled/default case, got #{result.value.inspect}" + expect(result.variant).to be_nil, + "Expected nil variant for disabled/default case, got #{result.variant.inspect}" + expect(result.error_code).to be_nil, + "Expected nil error code for disabled/default case, got #{result.error_code.inspect}" + expect(result.error_message).to be_nil, + "Expected nil error message for disabled/default case, got #{result.error_message.inspect}" + expect(['DISABLED', 'DEFAULT']).to include(result.reason), + "Expected disabled or default reason, got #{result.reason.inspect}" + expect(result.flag_metadata).to eq({}), + "Expected empty flag metadata for disabled/default case, got #{result.flag_metadata.inspect}" + expect(result.allocation_key).to be_nil, + "Expected nil allocation_key for disabled/default case, got #{result.allocation_key.inspect}" + expect(result.log?).to eq(false), + "Expected false log? for disabled/default case, got #{result.log?.inspect}" + + else + expect(result.value).to eq('test_default'), + "Expected nil value for error case, got #{result.value.inspect}" + expect(result.variant).to be_nil, + "Expected nil variant for error case, got #{result.variant.inspect}" + expect(result.error_code).not_to be_nil, + "Expected error code for error case, got #{result.error_code.inspect}" + expect(result.error_message).not_to be_nil, + "Expected error message for error case, got #{result.error_message.inspect}" + expect(result.reason).to eq('ERROR'), + "Expected ERROR reason for error case, got #{result.reason.inspect}" + expect(result.flag_metadata).to eq({}), + "Expected empty flag metadata for error case, got #{result.flag_metadata.inspect}" + expect(result.allocation_key).to be_nil, + "Expected nil allocation_key for error case, got #{result.allocation_key.inspect}" + expect(result.log?).to eq(false), + "Expected false log? for error case, got #{result.log?.inspect}" + end + end + end + end + end + end + end +end diff --git a/spec/datadog/open_feature/binding/rule_evaluation_spec.rb b/spec/datadog/open_feature/binding/rule_evaluation_spec.rb new file mode 100644 index 00000000000..a1bd719ba63 --- /dev/null +++ b/spec/datadog/open_feature/binding/rule_evaluation_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'json' +require 'datadog/open_feature/binding/internal_evaluator' + +RSpec.describe 'InternalEvaluator Rule Evaluation' do + # Most rule evaluation scenarios are covered by UFC test cases: + # - GTE/comparison operators: test-case-comparator-operator-flag.json + # - ONE_OF membership: test-case-boolean-one-of-matches.json + # - Regex matching: test-case-regex-flag.json + # - Null checks: test-case-null-operator-flag.json + # + # The following tests cover scenarios not fully exercised by UFC test cases: + + describe 'multiple rules in allocation (OR logic)' do + let(:flag_config) do + { + "flags" => { + "multi_rule_flag" => { + "key" => "multi_rule_flag", + "enabled" => true, + "variationType" => "STRING", + "variations" => { + "default" => {"key" => "default", "value" => "default_value"}, + "special" => {"key" => "special", "value" => "special_value"} + }, + "allocations" => [ + { + "key" => "special_users", + "rules" => [ + { + "conditions" => [ + { + "attribute" => "user_type", + "operator" => "ONE_OF", + "value" => ["admin", "moderator"] + } + ] + }, + { + "conditions" => [ + { + "attribute" => "age", + "operator" => "GTE", + "value" => 65 + } + ] + } + ], + "doLog" => true, + "splits" => [{"variationKey" => "special", "shards" => []}] + }, + { + "key" => "default_allocation", + "doLog" => false, + "splits" => [{"variationKey" => "default", "shards" => []}] + } + ] + } + } + } + end + + let(:evaluator) { Datadog::OpenFeature::Binding::InternalEvaluator.new(flag_config.to_json) } + + it 'matches allocation when first rule passes' do + admin_context = {"user_type" => "admin", "age" => 30} + result = evaluator.get_assignment("multi_rule_flag", 'test_default', admin_context, 'string') + + expect(result.error_code).to be_nil + expect(result.value).to eq("special_value") + expect(result.flag_metadata['allocationKey']).to eq("special_users") + end + + it 'matches allocation when second rule passes' do + senior_context = {"user_type" => "regular", "age" => 70} + result = evaluator.get_assignment("multi_rule_flag", 'test_default', senior_context, 'string') + + expect(result.error_code).to be_nil + expect(result.value).to eq("special_value") + expect(result.flag_metadata['allocationKey']).to eq("special_users") + end + + it 'matches allocation when both rules pass' do + admin_senior_context = {"user_type" => "admin", "age" => 70} + result = evaluator.get_assignment("multi_rule_flag", 'test_default', admin_senior_context, 'string') + + expect(result.error_code).to be_nil + expect(result.value).to eq("special_value") + expect(result.flag_metadata['allocationKey']).to eq("special_users") + end + + it 'uses fallback allocation when no rules pass' do + regular_context = {"user_type" => "regular", "age" => 30} + result = evaluator.get_assignment("multi_rule_flag", 'test_default', regular_context, 'string') + + expect(result.error_code).to be_nil + expect(result.value).to eq("default_value") + expect(result.flag_metadata['allocationKey']).to eq("default_allocation") + end + end + + describe 'invalid regex patterns' do + let(:evaluator) { Datadog::OpenFeature::Binding::InternalEvaluator.new('{"flags": {}}') } + + it 'handles invalid regex patterns gracefully' do + expect(evaluator.send(:evaluate_regex, "test", "[invalid", true)).to eq(false) + expect(evaluator.send(:evaluate_regex, "test", "*invalid", false)).to eq(false) + end + end + + describe 'NOT_ONE_OF edge cases' do + let(:evaluator) { Datadog::OpenFeature::Binding::InternalEvaluator.new('{"flags": {}}') } + + it 'fails when attribute is missing (NOT_ONE_OF fails for missing attributes)' do + expect(evaluator.send(:evaluate_membership, nil, ["value"], false)).to eq(false) + end + end + + describe 'type coercion' do + let(:evaluator) { Datadog::OpenFeature::Binding::InternalEvaluator.new('{"flags": {}}') } + + it 'coerces numeric strings for comparison' do + expect(evaluator.send(:coerce_to_number, "25")).to eq(25.0) + expect(evaluator.send(:coerce_to_number, "3.14")).to eq(3.14) + expect(evaluator.send(:coerce_to_number, "invalid")).to be_nil + end + + it 'coerces values to strings for membership tests' do + expect(evaluator.send(:coerce_to_string, 42)).to eq("42") + expect(evaluator.send(:coerce_to_string, true)).to eq("true") + expect(evaluator.send(:coerce_to_string, false)).to eq("false") + end + + it 'handles boolean coercion for null checks' do + expect(evaluator.send(:coerce_to_boolean, true)).to eq(true) + expect(evaluator.send(:coerce_to_boolean, "true")).to eq(true) + expect(evaluator.send(:coerce_to_boolean, "false")).to eq(false) + expect(evaluator.send(:coerce_to_boolean, 1)).to eq(true) + expect(evaluator.send(:coerce_to_boolean, 0)).to eq(false) + end + end +end diff --git a/spec/datadog/open_feature/evaluation_engine_spec.rb b/spec/datadog/open_feature/evaluation_engine_spec.rb index 130e3eb3ded..0dee3f815f5 100644 --- a/spec/datadog/open_feature/evaluation_engine_spec.rb +++ b/spec/datadog/open_feature/evaluation_engine_spec.rb @@ -58,7 +58,7 @@ context 'when binding evaluator returns error' do before do engine.reconfigure!(configuration) - allow_any_instance_of(Datadog::OpenFeature::NoopEvaluator).to receive(:get_assignment) + allow_any_instance_of(Datadog::OpenFeature::Binding::InternalEvaluator).to receive(:get_assignment) .and_return(error) end @@ -89,7 +89,7 @@ before do engine.reconfigure!(configuration) allow(telemetry).to receive(:report) - allow_any_instance_of(Datadog::OpenFeature::NoopEvaluator).to receive(:get_assignment) + allow_any_instance_of(Datadog::OpenFeature::Binding::InternalEvaluator).to receive(:get_assignment) .and_raise(error) end @@ -152,7 +152,7 @@ before do engine.reconfigure!(configuration) - allow(Datadog::OpenFeature::NoopEvaluator).to receive(:new).and_raise(error) + allow(Datadog::OpenFeature::Binding::InternalEvaluator).to receive(:new).and_raise(error) end let(:error) { StandardError.new('Ooops') } diff --git a/spec/datadog/open_feature/resolution_details_spec.rb b/spec/datadog/open_feature/resolution_details_spec.rb index 8508e6d5b1a..8f5ffb4dca7 100644 --- a/spec/datadog/open_feature/resolution_details_spec.rb +++ b/spec/datadog/open_feature/resolution_details_spec.rb @@ -4,6 +4,55 @@ require 'datadog/open_feature/resolution_details' RSpec.describe Datadog::OpenFeature::ResolutionDetails do + describe '.build_success' do + subject(:details) do + described_class.build_success( + value: 'test_value', + variant: 'variant_key', + allocation_key: 'alloc_key', + do_log: true, + reason: 'STATIC' + ) + end + + it 'returns frozen success details with success fields populated' do + expect(details).to be_frozen + expect(details.value).to eq('test_value') + expect(details.variant).to eq('variant_key') + expect(details.allocation_key).to eq('alloc_key') + expect(details.reason).to eq('STATIC') + expect(details.error_code).to be_nil + expect(details.error_message).to be_nil + expect(details.error?).to be(false) + expect(details.log?).to be(true) + expect(details.flag_metadata).to eq({ + 'allocationKey' => 'alloc_key', + 'doLog' => true + }) + expect(details.extra_logging).to eq({}) + end + end + + describe '.build_default' do + subject(:details) do + described_class.build_default(value: 'default_value', reason: 'DISABLED') + end + + it 'returns frozen default details with minimal fields populated' do + expect(details).to be_frozen + expect(details.value).to eq('default_value') + expect(details.reason).to eq('DISABLED') + expect(details.variant).to be_nil + expect(details.error_code).to be_nil + expect(details.error_message).to be_nil + expect(details.allocation_key).to be_nil + expect(details.error?).to be(false) + expect(details.log?).to be(false) + expect(details.flag_metadata).to eq({}) + expect(details.extra_logging).to eq({}) + end + end + describe '.build_error' do context 'when reason is not provided' do subject(:details) { described_class.build_error(value: 'fallback', error_code: 'CODE', error_message: 'Oops') } @@ -16,6 +65,10 @@ expect(details.reason).to eq('ERROR') expect(details.error?).to be(true) expect(details.log?).to be(false) + expect(details.variant).to be_nil + expect(details.allocation_key).to be_nil + expect(details.flag_metadata).to eq({}) + expect(details.extra_logging).to eq({}) end end diff --git a/spec/fixtures/ufc/README.md b/spec/fixtures/ufc/README.md new file mode 100644 index 00000000000..4972c431093 --- /dev/null +++ b/spec/fixtures/ufc/README.md @@ -0,0 +1,33 @@ +# Feature Flag Configuration Test Fixtures + +This directory contains test fixtures for Datadog Feature Flags. + +## Source + +These test fixtures originate from the [system-tests project](https://github.com/DataDog/system-tests/blob/main/tests/parametric/test_feature_flag_exposure), where they are used for cross-SDK compatibility testing. In production, this configuration is delivered via a remote config protocol wrapped as: + +```json +{ + "path": "datadog/2/FFE_FLAGS/{config_id}/config", + "msg": +} +``` + +## Directory Contents + +- `flags-v1.json` - Main feature flag configuration fixture used across multiple test suites +- `test_cases/` - Individual test cases demonstrating various flag scenarios including: + - Boolean, integer, numeric, and string flag variations + - Targeting rules with different operators (ONE_OF, MATCHES, GTE, etc.) + - Traffic splitting with shard ranges + - Edge cases like disabled flags and empty configurations + +## Configuration Format + +The configuration format includes: +- **flags**: Feature flag definitions with variations and targeting rules +- **allocations**: Traffic splitting rules with shard-based distribution +- **variations**: Different values a flag can return +- **rules**: Targeting conditions based on user attributes + +This format ensures consistent feature flag behavior across all Datadog SDKs and is validated through system-tests. diff --git a/spec/fixtures/ufc/flags-v1.json b/spec/fixtures/ufc/flags-v1.json new file mode 100644 index 00000000000..3abcf5fd752 --- /dev/null +++ b/spec/fixtures/ufc/flags-v1.json @@ -0,0 +1,3271 @@ +{ + "id": "1-1", + "createdAt": "2024-04-17T19:40:53.716Z", + "format": "SERVER", + "environment": { + "name": "Test" + }, + "flags": { + "empty_flag": { + "key": "empty_flag", + "enabled": true, + "variationType": "STRING", + "variations": {}, + "allocations": [] + }, + "disabled_flag": { + "key": "disabled_flag", + "enabled": false, + "variationType": "INTEGER", + "variations": {}, + "allocations": [] + }, + "no_allocations_flag": { + "key": "no_allocations_flag", + "enabled": true, + "variationType": "JSON", + "variations": { + "control": { + "key": "control", + "value": { + "variant": "control" + } + }, + "treatment": { + "key": "treatment", + "value": { + "variant": "treatment" + } + } + }, + "allocations": [] + }, + "numeric_flag": { + "key": "numeric_flag", + "enabled": true, + "variationType": "NUMERIC", + "variations": { + "e": { + "key": "e", + "value": 2.7182818 + }, + "pi": { + "key": "pi", + "value": 3.1415926 + } + }, + "allocations": [ + { + "key": "rollout", + "splits": [ + { + "variationKey": "pi", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "regex-flag": { + "key": "regex-flag", + "enabled": true, + "variationType": "STRING", + "variations": { + "partial-example": { + "key": "partial-example", + "value": "partial-example" + }, + "test": { + "key": "test", + "value": "test" + } + }, + "allocations": [ + { + "key": "partial-example", + "rules": [ + { + "conditions": [ + { + "attribute": "email", + "operator": "MATCHES", + "value": "@example\\.com" + } + ] + } + ], + "splits": [ + { + "variationKey": "partial-example", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "test", + "rules": [ + { + "conditions": [ + { + "attribute": "email", + "operator": "MATCHES", + "value": ".*@test\\.com" + } + ] + } + ], + "splits": [ + { + "variationKey": "test", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "numeric-one-of": { + "key": "numeric-one-of", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "1": { + "key": "1", + "value": 1 + }, + "2": { + "key": "2", + "value": 2 + }, + "3": { + "key": "3", + "value": 3 + } + }, + "allocations": [ + { + "key": "1-for-1", + "rules": [ + { + "conditions": [ + { + "attribute": "number", + "operator": "ONE_OF", + "value": [ + "1" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "1", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "2-for-123456789", + "rules": [ + { + "conditions": [ + { + "attribute": "number", + "operator": "ONE_OF", + "value": [ + "123456789" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "2", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "3-for-not-2", + "rules": [ + { + "conditions": [ + { + "attribute": "number", + "operator": "NOT_ONE_OF", + "value": [ + "2" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "3", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "boolean-one-of-matches": { + "key": "boolean-one-of-matches", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "1": { + "key": "1", + "value": 1 + }, + "2": { + "key": "2", + "value": 2 + }, + "3": { + "key": "3", + "value": 3 + }, + "4": { + "key": "4", + "value": 4 + }, + "5": { + "key": "5", + "value": 5 + } + }, + "allocations": [ + { + "key": "1-for-one-of", + "rules": [ + { + "conditions": [ + { + "attribute": "one_of_flag", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "1", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "2-for-matches", + "rules": [ + { + "conditions": [ + { + "attribute": "matches_flag", + "operator": "MATCHES", + "value": "true" + } + ] + } + ], + "splits": [ + { + "variationKey": "2", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "3-for-not-one-of", + "rules": [ + { + "conditions": [ + { + "attribute": "not_one_of_flag", + "operator": "NOT_ONE_OF", + "value": [ + "false" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "3", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "4-for-not-matches", + "rules": [ + { + "conditions": [ + { + "attribute": "not_matches_flag", + "operator": "NOT_MATCHES", + "value": "false" + } + ] + } + ], + "splits": [ + { + "variationKey": "4", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "5-for-matches-null", + "rules": [ + { + "conditions": [ + { + "attribute": "null_flag", + "operator": "ONE_OF", + "value": [ + "null" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "5", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "empty_string_flag": { + "key": "empty_string_flag", + "enabled": true, + "comment": "Testing the empty string as a variation value", + "variationType": "STRING", + "variations": { + "empty_string": { + "key": "empty_string", + "value": "" + }, + "non_empty": { + "key": "non_empty", + "value": "non_empty" + } + }, + "allocations": [ + { + "key": "allocation-empty", + "rules": [ + { + "conditions": [ + { + "attribute": "country", + "operator": "MATCHES", + "value": "US" + } + ] + } + ], + "splits": [ + { + "variationKey": "empty_string", + "shards": [ + { + "salt": "allocation-empty-shards", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "allocation-test", + "rules": [], + "splits": [ + { + "variationKey": "non_empty", + "shards": [ + { + "salt": "allocation-empty-shards", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + } + ] + }, + "kill-switch": { + "key": "kill-switch", + "enabled": true, + "variationType": "BOOLEAN", + "variations": { + "on": { + "key": "on", + "value": true + }, + "off": { + "key": "off", + "value": false + } + }, + "allocations": [ + { + "key": "on-for-NA", + "rules": [ + { + "conditions": [ + { + "attribute": "country", + "operator": "ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "on", + "shards": [ + { + "salt": "some-salt", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "on-for-age-50+", + "rules": [ + { + "conditions": [ + { + "attribute": "age", + "operator": "GTE", + "value": 50 + } + ] + } + ], + "splits": [ + { + "variationKey": "on", + "shards": [ + { + "salt": "some-salt", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "off-for-all", + "rules": [], + "splits": [ + { + "variationKey": "off", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "comparator-operator-test": { + "key": "comparator-operator-test", + "enabled": true, + "variationType": "STRING", + "variations": { + "small": { + "key": "small", + "value": "small" + }, + "medium": { + "key": "medium", + "value": "medium" + }, + "large": { + "key": "large", + "value": "large" + } + }, + "allocations": [ + { + "key": "small-size", + "rules": [ + { + "conditions": [ + { + "attribute": "size", + "operator": "LT", + "value": 10 + } + ] + } + ], + "splits": [ + { + "variationKey": "small", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "medum-size", + "rules": [ + { + "conditions": [ + { + "attribute": "size", + "operator": "GTE", + "value": 10 + }, + { + "attribute": "size", + "operator": "LTE", + "value": 20 + } + ] + } + ], + "splits": [ + { + "variationKey": "medium", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "large-size", + "rules": [ + { + "conditions": [ + { + "attribute": "size", + "operator": "GT", + "value": 25 + } + ] + } + ], + "splits": [ + { + "variationKey": "large", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "start-and-end-date-test": { + "key": "start-and-end-date-test", + "enabled": true, + "variationType": "STRING", + "variations": { + "old": { + "key": "old", + "value": "old" + }, + "current": { + "key": "current", + "value": "current" + }, + "new": { + "key": "new", + "value": "new" + } + }, + "allocations": [ + { + "key": "old-versions", + "splits": [ + { + "variationKey": "old", + "shards": [] + } + ], + "endAt": "2002-10-31T09:00:00.594Z", + "doLog": true + }, + { + "key": "future-versions", + "splits": [ + { + "variationKey": "new", + "shards": [] + } + ], + "startAt": "2052-10-31T09:00:00.594Z", + "doLog": true + }, + { + "key": "current-versions", + "splits": [ + { + "variationKey": "current", + "shards": [] + } + ], + "startAt": "2022-10-31T09:00:00.594Z", + "endAt": "2050-10-31T09:00:00.594Z", + "doLog": true + } + ] + }, + "null-operator-test": { + "key": "null-operator-test", + "enabled": true, + "variationType": "STRING", + "variations": { + "old": { + "key": "old", + "value": "old" + }, + "new": { + "key": "new", + "value": "new" + } + }, + "allocations": [ + { + "key": "null-operator", + "rules": [ + { + "conditions": [ + { + "attribute": "size", + "operator": "IS_NULL", + "value": true + } + ] + }, + { + "conditions": [ + { + "attribute": "size", + "operator": "LT", + "value": 10 + } + ] + } + ], + "splits": [ + { + "variationKey": "old", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "not-null-operator", + "rules": [ + { + "conditions": [ + { + "attribute": "size", + "operator": "IS_NULL", + "value": false + } + ] + } + ], + "splits": [ + { + "variationKey": "new", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "new-user-onboarding": { + "key": "new-user-onboarding", + "enabled": true, + "variationType": "STRING", + "variations": { + "control": { + "key": "control", + "value": "control" + }, + "red": { + "key": "red", + "value": "red" + }, + "blue": { + "key": "blue", + "value": "blue" + }, + "green": { + "key": "green", + "value": "green" + }, + "yellow": { + "key": "yellow", + "value": "yellow" + }, + "purple": { + "key": "purple", + "value": "purple" + } + }, + "allocations": [ + { + "key": "id rule", + "rules": [ + { + "conditions": [ + { + "attribute": "id", + "operator": "MATCHES", + "value": "zach" + } + ] + } + ], + "splits": [ + { + "variationKey": "purple", + "shards": [] + } + ], + "doLog": false + }, + { + "key": "internal users", + "rules": [ + { + "conditions": [ + { + "attribute": "email", + "operator": "MATCHES", + "value": "@mycompany.com" + } + ] + } + ], + "splits": [ + { + "variationKey": "green", + "shards": [] + } + ], + "doLog": false + }, + { + "key": "experiment", + "rules": [ + { + "conditions": [ + { + "attribute": "country", + "operator": "NOT_ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "control", + "shards": [ + { + "salt": "traffic-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 6000 + } + ] + }, + { + "salt": "split-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 5000 + } + ] + } + ] + }, + { + "variationKey": "red", + "shards": [ + { + "salt": "traffic-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 6000 + } + ] + }, + { + "salt": "split-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ + { + "start": 5000, + "end": 8000 + } + ] + } + ] + }, + { + "variationKey": "yellow", + "shards": [ + { + "salt": "traffic-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 6000 + } + ] + }, + { + "salt": "split-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ + { + "start": 8000, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "rollout", + "rules": [ + { + "conditions": [ + { + "attribute": "country", + "operator": "ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "blue", + "shards": [ + { + "salt": "split-new-user-onboarding-rollout", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 8000 + } + ] + } + ], + "extraLogging": { + "allocationvalue_type": "rollout", + "owner": "hippo" + } + } + ], + "doLog": true + } + ] + }, + "integer-flag": { + "key": "integer-flag", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "one": { + "key": "one", + "value": 1 + }, + "two": { + "key": "two", + "value": 2 + }, + "three": { + "key": "three", + "value": 3 + } + }, + "allocations": [ + { + "key": "targeted allocation", + "rules": [ + { + "conditions": [ + { + "attribute": "country", + "operator": "ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] + } + ] + }, + { + "conditions": [ + { + "attribute": "email", + "operator": "MATCHES", + "value": ".*@example.com" + } + ] + } + ], + "splits": [ + { + "variationKey": "three", + "shards": [ + { + "salt": "full-range-salt", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "50/50 split", + "rules": [], + "splits": [ + { + "variationKey": "one", + "shards": [ + { + "salt": "split-numeric-flag-some-allocation", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 5000 + } + ] + } + ] + }, + { + "variationKey": "two", + "shards": [ + { + "salt": "split-numeric-flag-some-allocation", + "totalShards": 10000, + "ranges": [ + { + "start": 5000, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + } + ] + }, + "json-config-flag": { + "key": "json-config-flag", + "enabled": true, + "variationType": "JSON", + "variations": { + "one": { + "key": "one", + "value": { + "integer": 1, + "string": "one", + "float": 1.0 + } + }, + "two": { + "key": "two", + "value": { + "integer": 2, + "string": "two", + "float": 2.0 + } + }, + "empty": { + "key": "empty", + "value": {} + } + }, + "allocations": [ + { + "key": "Optionally Force Empty", + "rules": [ + { + "conditions": [ + { + "attribute": "Force Empty", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "empty", + "shards": [ + { + "salt": "full-range-salt", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "50/50 split", + "rules": [], + "splits": [ + { + "variationKey": "one", + "shards": [ + { + "salt": "traffic-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + }, + { + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 5000 + } + ] + } + ] + }, + { + "variationKey": "two", + "shards": [ + { + "salt": "traffic-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] + }, + { + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 5000, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + } + ] + }, + "special-characters": { + "key": "special-characters", + "enabled": true, + "variationType": "JSON", + "variations": { + "de": { + "key": "de", + "value": { + "a": "kümmert", + "b": "schön" + } + }, + "ua": { + "key": "ua", + "value": { + "a": "піклуватися", + "b": "любов" + } + }, + "zh": { + "key": "zh", + "value": { + "a": "照顾", + "b": "漂亮" + } + }, + "emoji": { + "key": "emoji", + "value": { + "a": "🤗", + "b": "🌸" + } + } + }, + "allocations": [ + { + "key": "allocation-test", + "splits": [ + { + "variationKey": "de", + "shards": [ + { + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 2500 + } + ] + } + ] + }, + { + "variationKey": "ua", + "shards": [ + { + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 2500, + "end": 5000 + } + ] + } + ] + }, + { + "variationKey": "zh", + "shards": [ + { + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 5000, + "end": 7500 + } + ] + } + ] + }, + { + "variationKey": "emoji", + "shards": [ + { + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 7500, + "end": 10000 + } + ] + } + ] + } + ], + "doLog": true + }, + { + "key": "allocation-default", + "splits": [ + { + "variationKey": "de", + "shards": [] + } + ], + "doLog": false + } + ] + }, + "string_flag_with_special_characters": { + "key": "string_flag_with_special_characters", + "enabled": true, + "comment": "Testing the string with special characters and spaces", + "variationType": "STRING", + "variations": { + "string_with_spaces": { + "key": "string_with_spaces", + "value": " a b c d e f " + }, + "string_with_only_one_space": { + "key": "string_with_only_one_space", + "value": " " + }, + "string_with_only_multiple_spaces": { + "key": "string_with_only_multiple_spaces", + "value": " " + }, + "string_with_dots": { + "key": "string_with_dots", + "value": ".a.b.c.d.e.f." + }, + "string_with_only_one_dot": { + "key": "string_with_only_one_dot", + "value": "." + }, + "string_with_only_multiple_dots": { + "key": "string_with_only_multiple_dots", + "value": "......." + }, + "string_with_comas": { + "key": "string_with_comas", + "value": ",a,b,c,d,e,f," + }, + "string_with_only_one_coma": { + "key": "string_with_only_one_coma", + "value": "," + }, + "string_with_only_multiple_comas": { + "key": "string_with_only_multiple_comas", + "value": ",,,,,,," + }, + "string_with_colons": { + "key": "string_with_colons", + "value": ":a:b:c:d:e:f:" + }, + "string_with_only_one_colon": { + "key": "string_with_only_one_colon", + "value": ":" + }, + "string_with_only_multiple_colons": { + "key": "string_with_only_multiple_colons", + "value": ":::::::" + }, + "string_with_semicolons": { + "key": "string_with_semicolons", + "value": ";a;b;c;d;e;f;" + }, + "string_with_only_one_semicolon": { + "key": "string_with_only_one_semicolon", + "value": ";" + }, + "string_with_only_multiple_semicolons": { + "key": "string_with_only_multiple_semicolons", + "value": ";;;;;;;" + }, + "string_with_slashes": { + "key": "string_with_slashes", + "value": "/a/b/c/d/e/f/" + }, + "string_with_only_one_slash": { + "key": "string_with_only_one_slash", + "value": "/" + }, + "string_with_only_multiple_slashes": { + "key": "string_with_only_multiple_slashes", + "value": "///////" + }, + "string_with_dashes": { + "key": "string_with_dashes", + "value": "-a-b-c-d-e-f-" + }, + "string_with_only_one_dash": { + "key": "string_with_only_one_dash", + "value": "-" + }, + "string_with_only_multiple_dashes": { + "key": "string_with_only_multiple_dashes", + "value": "-------" + }, + "string_with_underscores": { + "key": "string_with_underscores", + "value": "_a_b_c_d_e_f_" + }, + "string_with_only_one_underscore": { + "key": "string_with_only_one_underscore", + "value": "_" + }, + "string_with_only_multiple_underscores": { + "key": "string_with_only_multiple_underscores", + "value": "_______" + }, + "string_with_plus_signs": { + "key": "string_with_plus_signs", + "value": "+a+b+c+d+e+f+" + }, + "string_with_only_one_plus_sign": { + "key": "string_with_only_one_plus_sign", + "value": "+" + }, + "string_with_only_multiple_plus_signs": { + "key": "string_with_only_multiple_plus_signs", + "value": "+++++++" + }, + "string_with_equal_signs": { + "key": "string_with_equal_signs", + "value": "=a=b=c=d=e=f=" + }, + "string_with_only_one_equal_sign": { + "key": "string_with_only_one_equal_sign", + "value": "=" + }, + "string_with_only_multiple_equal_signs": { + "key": "string_with_only_multiple_equal_signs", + "value": "=======" + }, + "string_with_dollar_signs": { + "key": "string_with_dollar_signs", + "value": "$a$b$c$d$e$f$" + }, + "string_with_only_one_dollar_sign": { + "key": "string_with_only_one_dollar_sign", + "value": "$" + }, + "string_with_only_multiple_dollar_signs": { + "key": "string_with_only_multiple_dollar_signs", + "value": "$$$$$$$" + }, + "string_with_at_signs": { + "key": "string_with_at_signs", + "value": "@a@b@c@d@e@f@" + }, + "string_with_only_one_at_sign": { + "key": "string_with_only_one_at_sign", + "value": "@" + }, + "string_with_only_multiple_at_signs": { + "key": "string_with_only_multiple_at_signs", + "value": "@@@@@@@" + }, + "string_with_amp_signs": { + "key": "string_with_amp_signs", + "value": "&a&b&c&d&e&f&" + }, + "string_with_only_one_amp_sign": { + "key": "string_with_only_one_amp_sign", + "value": "&" + }, + "string_with_only_multiple_amp_signs": { + "key": "string_with_only_multiple_amp_signs", + "value": "&&&&&&&" + }, + "string_with_hash_signs": { + "key": "string_with_hash_signs", + "value": "#a#b#c#d#e#f#" + }, + "string_with_only_one_hash_sign": { + "key": "string_with_only_one_hash_sign", + "value": "#" + }, + "string_with_only_multiple_hash_signs": { + "key": "string_with_only_multiple_hash_signs", + "value": "#######" + }, + "string_with_percentage_signs": { + "key": "string_with_percentage_signs", + "value": "%a%b%c%d%e%f%" + }, + "string_with_only_one_percentage_sign": { + "key": "string_with_only_one_percentage_sign", + "value": "%" + }, + "string_with_only_multiple_percentage_signs": { + "key": "string_with_only_multiple_percentage_signs", + "value": "%%%%%%%" + }, + "string_with_tilde_signs": { + "key": "string_with_tilde_signs", + "value": "~a~b~c~d~e~f~" + }, + "string_with_only_one_tilde_sign": { + "key": "string_with_only_one_tilde_sign", + "value": "~" + }, + "string_with_only_multiple_tilde_signs": { + "key": "string_with_only_multiple_tilde_signs", + "value": "~~~~~~~" + }, + "string_with_asterix_signs": { + "key": "string_with_asterix_signs", + "value": "*a*b*c*d*e*f*" + }, + "string_with_only_one_asterix_sign": { + "key": "string_with_only_one_asterix_sign", + "value": "*" + }, + "string_with_only_multiple_asterix_signs": { + "key": "string_with_only_multiple_asterix_signs", + "value": "*******" + }, + "string_with_single_quotes": { + "key": "string_with_single_quotes", + "value": "'a'b'c'd'e'f'" + }, + "string_with_only_one_single_quote": { + "key": "string_with_only_one_single_quote", + "value": "'" + }, + "string_with_only_multiple_single_quotes": { + "key": "string_with_only_multiple_single_quotes", + "value": "'''''''" + }, + "string_with_question_marks": { + "key": "string_with_question_marks", + "value": "?a?b?c?d?e?f?" + }, + "string_with_only_one_question_mark": { + "key": "string_with_only_one_question_mark", + "value": "?" + }, + "string_with_only_multiple_question_marks": { + "key": "string_with_only_multiple_question_marks", + "value": "???????" + }, + "string_with_exclamation_marks": { + "key": "string_with_exclamation_marks", + "value": "!a!b!c!d!e!f!" + }, + "string_with_only_one_exclamation_mark": { + "key": "string_with_only_one_exclamation_mark", + "value": "!" + }, + "string_with_only_multiple_exclamation_marks": { + "key": "string_with_only_multiple_exclamation_marks", + "value": "!!!!!!!" + }, + "string_with_opening_parentheses": { + "key": "string_with_opening_parentheses", + "value": "(a(b(c(d(e(f(" + }, + "string_with_only_one_opening_parenthese": { + "key": "string_with_only_one_opening_parenthese", + "value": "(" + }, + "string_with_only_multiple_opening_parentheses": { + "key": "string_with_only_multiple_opening_parentheses", + "value": "(((((((" + }, + "string_with_closing_parentheses": { + "key": "string_with_closing_parentheses", + "value": ")a)b)c)d)e)f)" + }, + "string_with_only_one_closing_parenthese": { + "key": "string_with_only_one_closing_parenthese", + "value": ")" + }, + "string_with_only_multiple_closing_parentheses": { + "key": "string_with_only_multiple_closing_parentheses", + "value": ")))))))" + } + }, + "allocations": [ + { + "key": "allocation-test-string_with_spaces", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_spaces", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_spaces", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_space", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_space", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_space", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_spaces", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_spaces", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_spaces", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_dots", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_dots", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_dots", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_dot", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_dot", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_dot", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_dots", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_dots", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_dots", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_comas", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_comas", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_comas", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_coma", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_coma", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_coma", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_comas", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_comas", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_comas", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_colons", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_colons", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_colons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_colon", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_colon", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_colon", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_colons", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_colons", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_colons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_semicolons", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_semicolons", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_semicolons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_semicolon", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_semicolon", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_semicolon", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_semicolons", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_semicolons", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_semicolons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_slashes", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_slashes", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_slashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_slash", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_slash", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_slash", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_slashes", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_slashes", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_slashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_dashes", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_dashes", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_dashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_dash", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_dash", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_dash", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_dashes", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_dashes", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_dashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_underscores", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_underscores", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_underscores", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_underscore", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_underscore", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_underscore", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_underscores", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_underscores", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_underscores", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_plus_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_plus_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_plus_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_plus_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_plus_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_plus_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_plus_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_plus_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_plus_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_equal_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_equal_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_equal_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_equal_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_equal_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_equal_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_equal_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_equal_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_equal_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_dollar_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_dollar_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_dollar_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_dollar_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_dollar_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_dollar_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_dollar_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_dollar_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_dollar_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_at_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_at_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_at_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_at_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_at_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_at_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_at_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_at_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_at_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_amp_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_amp_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_amp_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_amp_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_amp_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_amp_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_amp_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_amp_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_amp_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_hash_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_hash_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_hash_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_hash_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_hash_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_hash_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_hash_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_hash_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_hash_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_percentage_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_percentage_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_percentage_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_percentage_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_percentage_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_percentage_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_percentage_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_percentage_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_percentage_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_tilde_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_tilde_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_tilde_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_tilde_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_tilde_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_tilde_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_tilde_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_tilde_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_tilde_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_asterix_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_asterix_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_asterix_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_asterix_sign", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_asterix_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_asterix_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_asterix_signs", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_asterix_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_asterix_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_single_quotes", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_single_quotes", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_single_quotes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_single_quote", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_single_quote", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_single_quote", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_single_quotes", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_single_quotes", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_single_quotes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_question_marks", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_question_marks", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_question_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_question_mark", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_question_mark", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_question_mark", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_question_marks", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_question_marks", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_question_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_exclamation_marks", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_exclamation_marks", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_exclamation_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_exclamation_mark", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_exclamation_mark", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_exclamation_mark", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_exclamation_marks", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_exclamation_marks", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_exclamation_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_opening_parentheses", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_opening_parentheses", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_opening_parentheses", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_opening_parenthese", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_opening_parenthese", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_opening_parenthese", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_opening_parentheses", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_opening_parentheses", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_opening_parentheses", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_closing_parentheses", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_closing_parentheses", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_closing_parentheses", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_closing_parenthese", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_one_closing_parenthese", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_closing_parenthese", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_closing_parentheses", + "rules": [ + { + "conditions": [ + { + "attribute": "string_with_only_multiple_closing_parentheses", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_closing_parentheses", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "boolean-false-assignment": { + "key": "boolean-false-assignment", + "enabled": true, + "variationType": "BOOLEAN", + "variations": { + "false-variation": { + "key": "false-variation", + "value": false + }, + "true-variation": { + "key": "true-variation", + "value": true + } + }, + "allocations": [ + { + "key": "disable-feature", + "rules": [ + { + "conditions": [ + { + "attribute": "should_disable_feature", + "operator": "ONE_OF", + "value": [ + "true" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "false-variation", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "enable-feature", + "rules": [ + { + "conditions": [ + { + "attribute": "should_disable_feature", + "operator": "ONE_OF", + "value": [ + "false" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "true-variation", + "shards": [] + } + ], + "doLog": true + } + ], + "totalShards": 10000 + }, + "empty-string-variation": { + "key": "empty-string-variation", + "enabled": true, + "variationType": "STRING", + "variations": { + "empty-content": { + "key": "empty-content", + "value": "" + }, + "detailed-content": { + "key": "detailed-content", + "value": "detailed_content" + } + }, + "allocations": [ + { + "key": "minimal-content", + "rules": [ + { + "conditions": [ + { + "attribute": "content_type", + "operator": "ONE_OF", + "value": [ + "minimal" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "empty-content", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "full-content", + "rules": [ + { + "conditions": [ + { + "attribute": "content_type", + "operator": "ONE_OF", + "value": [ + "full" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "detailed-content", + "shards": [] + } + ], + "doLog": true + } + ], + "totalShards": 10000 + }, + "falsy-value-assignments": { + "key": "falsy-value-assignments", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "zero-limit": { + "key": "zero-limit", + "value": 0 + }, + "premium-limit": { + "key": "premium-limit", + "value": 100 + } + }, + "allocations": [ + { + "key": "free-tier-limit", + "rules": [ + { + "conditions": [ + { + "attribute": "plan_tier", + "operator": "ONE_OF", + "value": [ + "free" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "zero-limit", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "premium-tier-limit", + "rules": [ + { + "conditions": [ + { + "attribute": "plan_tier", + "operator": "ONE_OF", + "value": [ + "premium" + ] + } + ] + } + ], + "splits": [ + { + "variationKey": "premium-limit", + "shards": [] + } + ], + "doLog": true + } + ], + "totalShards": 10000 + } + } +} \ No newline at end of file diff --git a/spec/fixtures/ufc/test_cases/test-case-boolean-false-assignment.json b/spec/fixtures/ufc/test_cases/test-case-boolean-false-assignment.json new file mode 100644 index 00000000000..03553f4b2bd --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-boolean-false-assignment.json @@ -0,0 +1,50 @@ +[ + { + "flag": "boolean-false-assignment", + "variationType": "BOOLEAN", + "defaultValue": true, + "targetingKey": "alice", + "attributes": { + "should_disable_feature": true + }, + "result": { + "value": false, + "variant": "false-variation", + "flagMetadata": { + "allocationKey": "disable-feature", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "boolean-false-assignment", + "variationType": "BOOLEAN", + "defaultValue": true, + "targetingKey": "bob", + "attributes": { + "should_disable_feature": false + }, + "result": { + "value": true, + "variant": "true-variation", + "flagMetadata": { + "allocationKey": "enable-feature", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "boolean-false-assignment", + "variationType": "BOOLEAN", + "defaultValue": true, + "targetingKey": "charlie", + "attributes": { + "unknown_attribute": "value" + }, + "result": { + "value": true + } + } +] \ No newline at end of file diff --git a/spec/fixtures/ufc/test_cases/test-case-boolean-one-of-matches.json b/spec/fixtures/ufc/test_cases/test-case-boolean-one-of-matches.json new file mode 100644 index 00000000000..6bfbf0effad --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-boolean-one-of-matches.json @@ -0,0 +1,240 @@ +[ + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "alice", + "attributes": { + "one_of_flag": true + }, + "result": { + "value": 1, + "variant": "1", + "flagMetadata": { + "allocationKey": "1-for-one-of", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "bob", + "attributes": { + "one_of_flag": false + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "charlie", + "attributes": { + "one_of_flag": "True" + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "derek", + "attributes": { + "matches_flag": true + }, + "result": { + "value": 2, + "variant": "2", + "flagMetadata": { + "allocationKey": "2-for-matches", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "erica", + "attributes": { + "matches_flag": false + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "frank", + "attributes": { + "not_matches_flag": false + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "george", + "attributes": { + "not_matches_flag": true + }, + "result": { + "value": 4, + "variant": "4", + "flagMetadata": { + "allocationKey": "4-for-not-matches", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "haley", + "attributes": { + "not_matches_flag": "False" + }, + "result": { + "value": 4, + "variant": "4", + "flagMetadata": { + "allocationKey": "4-for-not-matches", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "ivy", + "attributes": { + "not_one_of_flag": true + }, + "result": { + "value": 3, + "variant": "3", + "flagMetadata": { + "allocationKey": "3-for-not-one-of", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "julia", + "attributes": { + "not_one_of_flag": false + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "kim", + "attributes": { + "not_one_of_flag": "False" + }, + "result": { + "value": 3, + "variant": "3", + "flagMetadata": { + "allocationKey": "3-for-not-one-of", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "lucas", + "attributes": { + "not_one_of_flag": "true" + }, + "result": { + "value": 3, + "variant": "3", + "flagMetadata": { + "allocationKey": "3-for-not-one-of", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "mike", + "attributes": { + "not_one_of_flag": "false" + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "nicole", + "attributes": { + "null_flag": "null" + }, + "result": { + "value": 5, + "variant": "5", + "flagMetadata": { + "allocationKey": "5-for-matches-null", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "owen", + "attributes": { + "null_flag": null + }, + "result": { + "value": 0 + } + }, + { + "flag": "boolean-one-of-matches", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "pete", + "attributes": {}, + "result": { + "value": 0 + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-comparator-operator-flag.json b/spec/fixtures/ufc/test_cases/test-case-comparator-operator-flag.json new file mode 100644 index 00000000000..a5c8ef07cd4 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-comparator-operator-flag.json @@ -0,0 +1,82 @@ +[ + { + "flag": "comparator-operator-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "alice", + "attributes": { + "size": 5, + "country": "US" + }, + "result": { + "value": "small", + "variant": "small", + "flagMetadata": { + "allocationKey": "small-size", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "comparator-operator-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "bob", + "attributes": { + "size": 10, + "country": "Canada" + }, + "result": { + "value": "medium", + "variant": "medium", + "flagMetadata": { + "allocationKey": "medum-size", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "comparator-operator-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "charlie", + "attributes": { + "size": 25 + }, + "result": { + "value": "unknown" + } + }, + { + "flag": "comparator-operator-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "david", + "attributes": { + "size": 26 + }, + "result": { + "value": "large", + "variant": "large", + "flagMetadata": { + "allocationKey": "large-size", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "comparator-operator-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "elize", + "attributes": { + "country": "UK" + }, + "result": { + "value": "unknown" + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-disabled-flag.json b/spec/fixtures/ufc/test_cases/test-case-disabled-flag.json new file mode 100644 index 00000000000..0da79189ade --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-disabled-flag.json @@ -0,0 +1,40 @@ +[ + { + "flag": "disabled_flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": 0 + } + }, + { + "flag": "disabled_flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": 0 + } + }, + { + "flag": "disabled_flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": 0 + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-empty-flag.json b/spec/fixtures/ufc/test_cases/test-case-empty-flag.json new file mode 100644 index 00000000000..52100b1fe47 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-empty-flag.json @@ -0,0 +1,40 @@ +[ + { + "flag": "empty_flag", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": "default_value" + } + }, + { + "flag": "empty_flag", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": "default_value" + } + }, + { + "flag": "empty_flag", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": "default_value" + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-empty-string-variation.json b/spec/fixtures/ufc/test_cases/test-case-empty-string-variation.json new file mode 100644 index 00000000000..2b2480d0512 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-empty-string-variation.json @@ -0,0 +1,50 @@ +[ + { + "flag": "empty-string-variation", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "empty_user", + "attributes": { + "content_type": "minimal" + }, + "result": { + "value": "", + "variant": "empty-content", + "flagMetadata": { + "allocationKey": "minimal-content", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "empty-string-variation", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "full_user", + "attributes": { + "content_type": "full" + }, + "result": { + "value": "detailed_content", + "variant": "detailed-content", + "flagMetadata": { + "allocationKey": "full-content", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "empty-string-variation", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "default_user", + "attributes": { + "content_type": "unknown" + }, + "result": { + "value": "default_value" + } + } +] \ No newline at end of file diff --git a/spec/fixtures/ufc/test_cases/test-case-falsy-value-assignments.json b/spec/fixtures/ufc/test_cases/test-case-falsy-value-assignments.json new file mode 100644 index 00000000000..47662a0625d --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-falsy-value-assignments.json @@ -0,0 +1,50 @@ +[ + { + "flag": "falsy-value-assignments", + "variationType": "INTEGER", + "defaultValue": 999, + "targetingKey": "zero_user", + "attributes": { + "plan_tier": "free" + }, + "result": { + "value": 0, + "variant": "zero-limit", + "flagMetadata": { + "allocationKey": "free-tier-limit", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "falsy-value-assignments", + "variationType": "INTEGER", + "defaultValue": 999, + "targetingKey": "premium_user", + "attributes": { + "plan_tier": "premium" + }, + "result": { + "value": 100, + "variant": "premium-limit", + "flagMetadata": { + "allocationKey": "premium-tier-limit", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "falsy-value-assignments", + "variationType": "INTEGER", + "defaultValue": 999, + "targetingKey": "unknown_user", + "attributes": { + "plan_tier": "trial" + }, + "result": { + "value": 999 + } + } +] \ No newline at end of file diff --git a/spec/fixtures/ufc/test_cases/test-case-flag-with-empty-string.json b/spec/fixtures/ufc/test_cases/test-case-flag-with-empty-string.json new file mode 100644 index 00000000000..32a1c1febb7 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-flag-with-empty-string.json @@ -0,0 +1,36 @@ +[ + { + "flag": "empty_string_flag", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "alice", + "attributes": { + "country": "US" + }, + "result": { + "value": "", + "variant": "empty_string", + "flagMetadata": { + "allocationKey": "allocation-empty", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "empty_string_flag", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "bob", + "attributes": {}, + "result": { + "value": "non_empty", + "variant": "non_empty", + "flagMetadata": { + "allocationKey": "allocation-test", + "variationType": "string", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-integer-flag.json b/spec/fixtures/ufc/test_cases/test-case-integer-flag.json new file mode 100644 index 00000000000..4a41d042f62 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-integer-flag.json @@ -0,0 +1,382 @@ +[ + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": 3, + "variant": "three", + "flagMetadata": { + "allocationKey": "targeted allocation", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": 3, + "variant": "three", + "flagMetadata": { + "allocationKey": "targeted allocation", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "debra", + "attributes": { + "email": "test@test.com", + "country": "Mexico", + "age": 25 + }, + "result": { + "value": 3, + "variant": "three", + "flagMetadata": { + "allocationKey": "targeted allocation", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "1", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "2", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "3", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "4", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "5", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "6", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "7", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "8", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "9", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "10", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "11", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "12", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "13", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "14", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "15", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "16", + "attributes": {}, + "result": { + "value": 2, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "17", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "18", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "integer-flag", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "19", + "attributes": {}, + "result": { + "value": 1, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "number", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-kill-switch-flag.json b/spec/fixtures/ufc/test_cases/test-case-kill-switch-flag.json new file mode 100644 index 00000000000..29e65ba5bb4 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-kill-switch-flag.json @@ -0,0 +1,434 @@ +[ + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "barbara", + "attributes": { + "email": "barbara@example.com", + "country": "canada" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "charlie", + "attributes": { + "age": 40 + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "debra", + "attributes": { + "email": "test@test.com", + "country": "Mexico", + "age": 25 + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "1", + "attributes": {}, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "2", + "attributes": { + "country": "Mexico" + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "3", + "attributes": { + "country": "UK", + "age": 50 + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-age-50+", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "4", + "attributes": { + "country": "Germany" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "5", + "attributes": { + "country": "Germany" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "6", + "attributes": { + "country": "Germany" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "7", + "attributes": { + "country": "US", + "age": 12 + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "8", + "attributes": { + "country": "Italy", + "age": 60 + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-age-50+", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "9", + "attributes": { + "email": "email@email.com" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "10", + "attributes": {}, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "11", + "attributes": {}, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "12", + "attributes": { + "country": "US" + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "13", + "attributes": { + "country": "Canada" + }, + "result": { + "value": true, + "variant": "on", + "flagMetadata": { + "allocationKey": "on-for-NA", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "14", + "attributes": {}, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "15", + "attributes": { + "country": "Denmark" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "16", + "attributes": { + "country": "Norway" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "17", + "attributes": { + "country": "UK" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "18", + "attributes": { + "country": "UK" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + }, + { + "flag": "kill-switch", + "variationType": "BOOLEAN", + "defaultValue": false, + "targetingKey": "19", + "attributes": { + "country": "UK" + }, + "result": { + "value": false, + "variant": "off", + "flagMetadata": { + "allocationKey": "off-for-all", + "variationType": "boolean", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-new-user-onboarding-flag.json b/spec/fixtures/ufc/test_cases/test-case-new-user-onboarding-flag.json new file mode 100644 index 00000000000..3845597270e --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-new-user-onboarding-flag.json @@ -0,0 +1,420 @@ +[ + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": "green", + "variant": "green", + "flagMetadata": { + "allocationKey": "internal users", + "variationType": "string", + "doLog": false + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "debra", + "attributes": { + "email": "test@test.com", + "country": "Mexico", + "age": 25 + }, + "result": { + "value": "blue", + "variant": "blue", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "zach", + "attributes": { + "email": "test@test.com", + "country": "Mexico", + "age": 25 + }, + "result": { + "value": "purple", + "variant": "purple", + "flagMetadata": { + "allocationKey": "id rule", + "variationType": "string", + "doLog": false + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "zach", + "attributes": { + "id": "override-id", + "email": "test@test.com", + "country": "Mexico", + "age": 25 + }, + "result": { + "value": "blue", + "variant": "blue", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "Zach", + "attributes": { + "email": "test@test.com", + "country": "Mexico", + "age": 25 + }, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "1", + "attributes": {}, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "2", + "attributes": { + "country": "Mexico" + }, + "result": { + "value": "blue", + "variant": "blue", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "3", + "attributes": { + "country": "UK", + "age": 33 + }, + "result": { + "value": "control", + "variant": "control", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "4", + "attributes": { + "country": "Germany" + }, + "result": { + "value": "red", + "variant": "red", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "5", + "attributes": { + "country": "Germany" + }, + "result": { + "value": "yellow", + "variant": "yellow", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "6", + "attributes": { + "country": "Germany" + }, + "result": { + "value": "yellow", + "variant": "yellow", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "7", + "attributes": { + "country": "US" + }, + "result": { + "value": "blue", + "variant": "blue", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "8", + "attributes": { + "country": "Italy" + }, + "result": { + "value": "red", + "variant": "red", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "9", + "attributes": { + "email": "email@email.com" + }, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "10", + "attributes": {}, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "11", + "attributes": {}, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "12", + "attributes": { + "country": "US" + }, + "result": { + "value": "blue", + "variant": "blue", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "13", + "attributes": { + "country": "Canada" + }, + "result": { + "value": "blue", + "variant": "blue", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "14", + "attributes": {}, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "15", + "attributes": { + "country": "Denmark" + }, + "result": { + "value": "yellow", + "variant": "yellow", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "16", + "attributes": { + "country": "Norway" + }, + "result": { + "value": "control", + "variant": "control", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "17", + "attributes": { + "country": "UK" + }, + "result": { + "value": "control", + "variant": "control", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "18", + "attributes": { + "country": "UK" + }, + "result": { + "value": "default" + } + }, + { + "flag": "new-user-onboarding", + "variationType": "STRING", + "defaultValue": "default", + "targetingKey": "19", + "attributes": { + "country": "UK" + }, + "result": { + "value": "red", + "variant": "red", + "flagMetadata": { + "allocationKey": "experiment", + "variationType": "string", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-no-allocations-flag.json b/spec/fixtures/ufc/test_cases/test-case-no-allocations-flag.json new file mode 100644 index 00000000000..132c39db32a --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-no-allocations-flag.json @@ -0,0 +1,52 @@ +[ + { + "flag": "no_allocations_flag", + "variationType": "JSON", + "defaultValue": { + "hello": "world" + }, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": { + "hello": "world" + } + } + }, + { + "flag": "no_allocations_flag", + "variationType": "JSON", + "defaultValue": { + "hello": "world" + }, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": { + "hello": "world" + } + } + }, + { + "flag": "no_allocations_flag", + "variationType": "JSON", + "defaultValue": { + "hello": "world" + }, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": { + "hello": "world" + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-null-operator-flag.json b/spec/fixtures/ufc/test_cases/test-case-null-operator-flag.json new file mode 100644 index 00000000000..dd5c687b893 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-null-operator-flag.json @@ -0,0 +1,94 @@ +[ + { + "flag": "null-operator-test", + "variationType": "STRING", + "defaultValue": "default-null", + "targetingKey": "alice", + "attributes": { + "size": 5, + "country": "US" + }, + "result": { + "value": "old", + "variant": "old", + "flagMetadata": { + "allocationKey": "null-operator", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "null-operator-test", + "variationType": "STRING", + "defaultValue": "default-null", + "targetingKey": "bob", + "attributes": { + "size": 10, + "country": "Canada" + }, + "result": { + "value": "new", + "variant": "new", + "flagMetadata": { + "allocationKey": "not-null-operator", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "null-operator-test", + "variationType": "STRING", + "defaultValue": "default-null", + "targetingKey": "charlie", + "attributes": { + "size": null + }, + "result": { + "value": "old", + "variant": "old", + "flagMetadata": { + "allocationKey": "null-operator", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "null-operator-test", + "variationType": "STRING", + "defaultValue": "default-null", + "targetingKey": "david", + "attributes": { + "size": 26 + }, + "result": { + "value": "new", + "variant": "new", + "flagMetadata": { + "allocationKey": "not-null-operator", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "null-operator-test", + "variationType": "STRING", + "defaultValue": "default-null", + "targetingKey": "elize", + "attributes": { + "country": "UK" + }, + "result": { + "value": "old", + "variant": "old", + "flagMetadata": { + "allocationKey": "null-operator", + "variationType": "string", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-numeric-flag.json b/spec/fixtures/ufc/test_cases/test-case-numeric-flag.json new file mode 100644 index 00000000000..0e6ed8b1b3a --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-numeric-flag.json @@ -0,0 +1,58 @@ +[ + { + "flag": "numeric_flag", + "variationType": "NUMERIC", + "defaultValue": 0.0, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": 3.1415926, + "variant": "pi", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric_flag", + "variationType": "NUMERIC", + "defaultValue": 0.0, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": 3.1415926, + "variant": "pi", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric_flag", + "variationType": "NUMERIC", + "defaultValue": 0.0, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": 3.1415926, + "variant": "pi", + "flagMetadata": { + "allocationKey": "rollout", + "variationType": "number", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-numeric-one-of.json b/spec/fixtures/ufc/test_cases/test-case-numeric-one-of.json new file mode 100644 index 00000000000..5ab68af5196 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-numeric-one-of.json @@ -0,0 +1,122 @@ +[ + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "alice", + "attributes": { + "number": 1 + }, + "result": { + "value": 1, + "variant": "1", + "flagMetadata": { + "allocationKey": "1-for-1", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "bob", + "attributes": { + "number": 2 + }, + "result": { + "value": 0 + } + }, + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "charlie", + "attributes": { + "number": 3 + }, + "result": { + "value": 3, + "variant": "3", + "flagMetadata": { + "allocationKey": "3-for-not-2", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "derek", + "attributes": { + "number": 4 + }, + "result": { + "value": 3, + "variant": "3", + "flagMetadata": { + "allocationKey": "3-for-not-2", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "erica", + "attributes": { + "number": "1" + }, + "result": { + "value": 1, + "variant": "1", + "flagMetadata": { + "allocationKey": "1-for-1", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "frank", + "attributes": { + "number": 1 + }, + "result": { + "value": 1, + "variant": "1", + "flagMetadata": { + "allocationKey": "1-for-1", + "variationType": "number", + "doLog": true + } + } + }, + { + "flag": "numeric-one-of", + "variationType": "INTEGER", + "defaultValue": 0, + "targetingKey": "george", + "attributes": { + "number": 123456789 + }, + "result": { + "value": 2, + "variant": "2", + "flagMetadata": { + "allocationKey": "2-for-123456789", + "variationType": "number", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-regex-flag.json b/spec/fixtures/ufc/test_cases/test-case-regex-flag.json new file mode 100644 index 00000000000..952a37aabcf --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-regex-flag.json @@ -0,0 +1,65 @@ +[ + { + "flag": "regex-flag", + "variationType": "STRING", + "defaultValue": "none", + "targetingKey": "alice", + "attributes": { + "version": "1.15.0", + "email": "alice@example.com" + }, + "result": { + "value": "partial-example", + "variant": "partial-example", + "flagMetadata": { + "allocationKey": "partial-example", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "regex-flag", + "variationType": "STRING", + "defaultValue": "none", + "targetingKey": "bob", + "attributes": { + "version": "0.20.1", + "email": "bob@test.com" + }, + "result": { + "value": "test", + "variant": "test", + "flagMetadata": { + "allocationKey": "test", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "regex-flag", + "variationType": "STRING", + "defaultValue": "none", + "targetingKey": "charlie", + "attributes": { + "version": "2.1.13" + }, + "result": { + "value": "none" + } + }, + { + "flag": "regex-flag", + "variationType": "STRING", + "defaultValue": "none", + "targetingKey": "derek", + "attributes": { + "version": "2.1.13", + "email": "derek@gmail.com" + }, + "result": { + "value": "none" + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-case-start-and-end-date-flag.json b/spec/fixtures/ufc/test_cases/test-case-start-and-end-date-flag.json new file mode 100644 index 00000000000..caf805d1432 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-case-start-and-end-date-flag.json @@ -0,0 +1,58 @@ +[ + { + "flag": "start-and-end-date-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "alice", + "attributes": { + "version": "1.15.0", + "country": "US" + }, + "result": { + "value": "current", + "variant": "current", + "flagMetadata": { + "allocationKey": "current-versions", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "start-and-end-date-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "bob", + "attributes": { + "version": "0.20.1", + "country": "Canada" + }, + "result": { + "value": "current", + "variant": "current", + "flagMetadata": { + "allocationKey": "current-versions", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "start-and-end-date-test", + "variationType": "STRING", + "defaultValue": "unknown", + "targetingKey": "charlie", + "attributes": { + "version": "2.1.13" + }, + "result": { + "value": "current", + "variant": "current", + "flagMetadata": { + "allocationKey": "current-versions", + "variationType": "string", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-flag-that-does-not-exist.json b/spec/fixtures/ufc/test_cases/test-flag-that-does-not-exist.json new file mode 100644 index 00000000000..7499bba1c50 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-flag-that-does-not-exist.json @@ -0,0 +1,40 @@ +[ + { + "flag": "flag-that-does-not-exist", + "variationType": "NUMERIC", + "defaultValue": 0.0, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": 0.0 + } + }, + { + "flag": "flag-that-does-not-exist", + "variationType": "NUMERIC", + "defaultValue": 0.0, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": 0.0 + } + }, + { + "flag": "flag-that-does-not-exist", + "variationType": "NUMERIC", + "defaultValue": 0.0, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": 0.0 + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-json-config-flag.json b/spec/fixtures/ufc/test_cases/test-json-config-flag.json new file mode 100644 index 00000000000..3d4478ff711 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-json-config-flag.json @@ -0,0 +1,96 @@ +[ + { + "flag": "json-config-flag", + "variationType": "JSON", + "defaultValue": { + "foo": "bar" + }, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": { + "integer": 1, + "string": "one", + "float": 1.0 + }, + "variant": "one", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "object", + "doLog": true + } + } + }, + { + "flag": "json-config-flag", + "variationType": "JSON", + "defaultValue": { + "foo": "bar" + }, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": { + "integer": 2, + "string": "two", + "float": 2.0 + }, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "object", + "doLog": true + } + } + }, + { + "flag": "json-config-flag", + "variationType": "JSON", + "defaultValue": { + "foo": "bar" + }, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": { + "integer": 2, + "string": "two", + "float": 2.0 + }, + "variant": "two", + "flagMetadata": { + "allocationKey": "50/50 split", + "variationType": "object", + "doLog": true + } + } + }, + { + "flag": "json-config-flag", + "variationType": "JSON", + "defaultValue": { + "foo": "bar" + }, + "targetingKey": "diana", + "attributes": { + "Force Empty": true + }, + "result": { + "value": {}, + "variant": "empty", + "flagMetadata": { + "allocationKey": "Optionally Force Empty", + "variationType": "object", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-no-allocations-flag.json b/spec/fixtures/ufc/test_cases/test-no-allocations-flag.json new file mode 100644 index 00000000000..45867e5897c --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-no-allocations-flag.json @@ -0,0 +1,52 @@ +[ + { + "flag": "no_allocations_flag", + "variationType": "JSON", + "defaultValue": { + "message": "Hello, world!" + }, + "targetingKey": "alice", + "attributes": { + "email": "alice@mycompany.com", + "country": "US" + }, + "result": { + "value": { + "message": "Hello, world!" + } + } + }, + { + "flag": "no_allocations_flag", + "variationType": "JSON", + "defaultValue": { + "message": "Hello, world!" + }, + "targetingKey": "bob", + "attributes": { + "email": "bob@example.com", + "country": "Canada" + }, + "result": { + "value": { + "message": "Hello, world!" + } + } + }, + { + "flag": "no_allocations_flag", + "variationType": "JSON", + "defaultValue": { + "message": "Hello, world!" + }, + "targetingKey": "charlie", + "attributes": { + "age": 50 + }, + "result": { + "value": { + "message": "Hello, world!" + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-special-characters.json b/spec/fixtures/ufc/test_cases/test-special-characters.json new file mode 100644 index 00000000000..59ef5cbe87d --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-special-characters.json @@ -0,0 +1,78 @@ +[ + { + "flag": "special-characters", + "variationType": "JSON", + "defaultValue": {}, + "targetingKey": "ash", + "attributes": {}, + "result": { + "value": { + "a": "kümmert", + "b": "schön" + }, + "variant": "de", + "flagMetadata": { + "allocationKey": "allocation-test", + "variationType": "object", + "doLog": true + } + } + }, + { + "flag": "special-characters", + "variationType": "JSON", + "defaultValue": {}, + "targetingKey": "ben", + "attributes": {}, + "result": { + "value": { + "a": "піклуватися", + "b": "любов" + }, + "variant": "ua", + "flagMetadata": { + "allocationKey": "allocation-test", + "variationType": "object", + "doLog": true + } + } + }, + { + "flag": "special-characters", + "variationType": "JSON", + "defaultValue": {}, + "targetingKey": "cameron", + "attributes": {}, + "result": { + "value": { + "a": "照顾", + "b": "漂亮" + }, + "variant": "zh", + "flagMetadata": { + "allocationKey": "allocation-test", + "variationType": "object", + "doLog": true + } + } + }, + { + "flag": "special-characters", + "variationType": "JSON", + "defaultValue": {}, + "targetingKey": "darryl", + "attributes": {}, + "result": { + "value": { + "a": "🤗", + "b": "🌸" + }, + "variant": "emoji", + "flagMetadata": { + "allocationKey": "allocation-test", + "variationType": "object", + "doLog": true + } + } + } +] diff --git a/spec/fixtures/ufc/test_cases/test-string-with-special-characters.json b/spec/fixtures/ufc/test_cases/test-string-with-special-characters.json new file mode 100644 index 00000000000..27d063122c0 --- /dev/null +++ b/spec/fixtures/ufc/test_cases/test-string-with-special-characters.json @@ -0,0 +1,1190 @@ +[ + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_spaces", + "attributes": { + "string_with_spaces": true + }, + "result": { + "value": " a b c d e f ", + "variant": "string_with_spaces", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_spaces", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_space", + "attributes": { + "string_with_only_one_space": true + }, + "result": { + "value": " ", + "variant": "string_with_only_one_space", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_space", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_spaces", + "attributes": { + "string_with_only_multiple_spaces": true + }, + "result": { + "value": " ", + "variant": "string_with_only_multiple_spaces", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_spaces", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_dots", + "attributes": { + "string_with_dots": true + }, + "result": { + "value": ".a.b.c.d.e.f.", + "variant": "string_with_dots", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_dots", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_dot", + "attributes": { + "string_with_only_one_dot": true + }, + "result": { + "value": ".", + "variant": "string_with_only_one_dot", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_dot", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_dots", + "attributes": { + "string_with_only_multiple_dots": true + }, + "result": { + "value": ".......", + "variant": "string_with_only_multiple_dots", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_dots", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_comas", + "attributes": { + "string_with_comas": true + }, + "result": { + "value": ",a,b,c,d,e,f,", + "variant": "string_with_comas", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_comas", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_coma", + "attributes": { + "string_with_only_one_coma": true + }, + "result": { + "value": ",", + "variant": "string_with_only_one_coma", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_coma", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_comas", + "attributes": { + "string_with_only_multiple_comas": true + }, + "result": { + "value": ",,,,,,,", + "variant": "string_with_only_multiple_comas", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_comas", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_colons", + "attributes": { + "string_with_colons": true + }, + "result": { + "value": ":a:b:c:d:e:f:", + "variant": "string_with_colons", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_colons", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_colon", + "attributes": { + "string_with_only_one_colon": true + }, + "result": { + "value": ":", + "variant": "string_with_only_one_colon", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_colon", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_colons", + "attributes": { + "string_with_only_multiple_colons": true + }, + "result": { + "value": ":::::::", + "variant": "string_with_only_multiple_colons", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_colons", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_semicolons", + "attributes": { + "string_with_semicolons": true + }, + "result": { + "value": ";a;b;c;d;e;f;", + "variant": "string_with_semicolons", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_semicolons", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_semicolon", + "attributes": { + "string_with_only_one_semicolon": true + }, + "result": { + "value": ";", + "variant": "string_with_only_one_semicolon", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_semicolon", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_semicolons", + "attributes": { + "string_with_only_multiple_semicolons": true + }, + "result": { + "value": ";;;;;;;", + "variant": "string_with_only_multiple_semicolons", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_semicolons", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_slashes", + "attributes": { + "string_with_slashes": true + }, + "result": { + "value": "/a/b/c/d/e/f/", + "variant": "string_with_slashes", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_slashes", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_slash", + "attributes": { + "string_with_only_one_slash": true + }, + "result": { + "value": "/", + "variant": "string_with_only_one_slash", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_slash", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_slashes", + "attributes": { + "string_with_only_multiple_slashes": true + }, + "result": { + "value": "///////", + "variant": "string_with_only_multiple_slashes", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_slashes", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_dashes", + "attributes": { + "string_with_dashes": true + }, + "result": { + "value": "-a-b-c-d-e-f-", + "variant": "string_with_dashes", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_dashes", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_dash", + "attributes": { + "string_with_only_one_dash": true + }, + "result": { + "value": "-", + "variant": "string_with_only_one_dash", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_dash", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_dashes", + "attributes": { + "string_with_only_multiple_dashes": true + }, + "result": { + "value": "-------", + "variant": "string_with_only_multiple_dashes", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_dashes", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_underscores", + "attributes": { + "string_with_underscores": true + }, + "result": { + "value": "_a_b_c_d_e_f_", + "variant": "string_with_underscores", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_underscores", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_underscore", + "attributes": { + "string_with_only_one_underscore": true + }, + "result": { + "value": "_", + "variant": "string_with_only_one_underscore", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_underscore", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_underscores", + "attributes": { + "string_with_only_multiple_underscores": true + }, + "result": { + "value": "_______", + "variant": "string_with_only_multiple_underscores", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_underscores", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_plus_signs", + "attributes": { + "string_with_plus_signs": true + }, + "result": { + "value": "+a+b+c+d+e+f+", + "variant": "string_with_plus_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_plus_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_plus_sign", + "attributes": { + "string_with_only_one_plus_sign": true + }, + "result": { + "value": "+", + "variant": "string_with_only_one_plus_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_plus_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_plus_signs", + "attributes": { + "string_with_only_multiple_plus_signs": true + }, + "result": { + "value": "+++++++", + "variant": "string_with_only_multiple_plus_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_plus_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_equal_signs", + "attributes": { + "string_with_equal_signs": true + }, + "result": { + "value": "=a=b=c=d=e=f=", + "variant": "string_with_equal_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_equal_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_equal_sign", + "attributes": { + "string_with_only_one_equal_sign": true + }, + "result": { + "value": "=", + "variant": "string_with_only_one_equal_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_equal_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_equal_signs", + "attributes": { + "string_with_only_multiple_equal_signs": true + }, + "result": { + "value": "=======", + "variant": "string_with_only_multiple_equal_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_equal_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_dollar_signs", + "attributes": { + "string_with_dollar_signs": true + }, + "result": { + "value": "$a$b$c$d$e$f$", + "variant": "string_with_dollar_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_dollar_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_dollar_sign", + "attributes": { + "string_with_only_one_dollar_sign": true + }, + "result": { + "value": "$", + "variant": "string_with_only_one_dollar_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_dollar_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_dollar_signs", + "attributes": { + "string_with_only_multiple_dollar_signs": true + }, + "result": { + "value": "$$$$$$$", + "variant": "string_with_only_multiple_dollar_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_dollar_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_at_signs", + "attributes": { + "string_with_at_signs": true + }, + "result": { + "value": "@a@b@c@d@e@f@", + "variant": "string_with_at_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_at_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_at_sign", + "attributes": { + "string_with_only_one_at_sign": true + }, + "result": { + "value": "@", + "variant": "string_with_only_one_at_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_at_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_at_signs", + "attributes": { + "string_with_only_multiple_at_signs": true + }, + "result": { + "value": "@@@@@@@", + "variant": "string_with_only_multiple_at_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_at_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_amp_signs", + "attributes": { + "string_with_amp_signs": true + }, + "result": { + "value": "&a&b&c&d&e&f&", + "variant": "string_with_amp_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_amp_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_amp_sign", + "attributes": { + "string_with_only_one_amp_sign": true + }, + "result": { + "value": "&", + "variant": "string_with_only_one_amp_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_amp_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_amp_signs", + "attributes": { + "string_with_only_multiple_amp_signs": true + }, + "result": { + "value": "&&&&&&&", + "variant": "string_with_only_multiple_amp_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_amp_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_hash_signs", + "attributes": { + "string_with_hash_signs": true + }, + "result": { + "value": "#a#b#c#d#e#f#", + "variant": "string_with_hash_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_hash_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_hash_sign", + "attributes": { + "string_with_only_one_hash_sign": true + }, + "result": { + "value": "#", + "variant": "string_with_only_one_hash_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_hash_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_hash_signs", + "attributes": { + "string_with_only_multiple_hash_signs": true + }, + "result": { + "value": "#######", + "variant": "string_with_only_multiple_hash_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_hash_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_percentage_signs", + "attributes": { + "string_with_percentage_signs": true + }, + "result": { + "value": "%a%b%c%d%e%f%", + "variant": "string_with_percentage_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_percentage_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_percentage_sign", + "attributes": { + "string_with_only_one_percentage_sign": true + }, + "result": { + "value": "%", + "variant": "string_with_only_one_percentage_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_percentage_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_percentage_signs", + "attributes": { + "string_with_only_multiple_percentage_signs": true + }, + "result": { + "value": "%%%%%%%", + "variant": "string_with_only_multiple_percentage_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_percentage_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_tilde_signs", + "attributes": { + "string_with_tilde_signs": true + }, + "result": { + "value": "~a~b~c~d~e~f~", + "variant": "string_with_tilde_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_tilde_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_tilde_sign", + "attributes": { + "string_with_only_one_tilde_sign": true + }, + "result": { + "value": "~", + "variant": "string_with_only_one_tilde_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_tilde_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_tilde_signs", + "attributes": { + "string_with_only_multiple_tilde_signs": true + }, + "result": { + "value": "~~~~~~~", + "variant": "string_with_only_multiple_tilde_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_tilde_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_asterix_signs", + "attributes": { + "string_with_asterix_signs": true + }, + "result": { + "value": "*a*b*c*d*e*f*", + "variant": "string_with_asterix_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_asterix_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_asterix_sign", + "attributes": { + "string_with_only_one_asterix_sign": true + }, + "result": { + "value": "*", + "variant": "string_with_only_one_asterix_sign", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_asterix_sign", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_asterix_signs", + "attributes": { + "string_with_only_multiple_asterix_signs": true + }, + "result": { + "value": "*******", + "variant": "string_with_only_multiple_asterix_signs", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_asterix_signs", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_single_quotes", + "attributes": { + "string_with_single_quotes": true + }, + "result": { + "value": "'a'b'c'd'e'f'", + "variant": "string_with_single_quotes", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_single_quotes", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_single_quote", + "attributes": { + "string_with_only_one_single_quote": true + }, + "result": { + "value": "'", + "variant": "string_with_only_one_single_quote", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_single_quote", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_single_quotes", + "attributes": { + "string_with_only_multiple_single_quotes": true + }, + "result": { + "value": "'''''''", + "variant": "string_with_only_multiple_single_quotes", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_single_quotes", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_question_marks", + "attributes": { + "string_with_question_marks": true + }, + "result": { + "value": "?a?b?c?d?e?f?", + "variant": "string_with_question_marks", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_question_marks", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_question_mark", + "attributes": { + "string_with_only_one_question_mark": true + }, + "result": { + "value": "?", + "variant": "string_with_only_one_question_mark", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_question_mark", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_question_marks", + "attributes": { + "string_with_only_multiple_question_marks": true + }, + "result": { + "value": "???????", + "variant": "string_with_only_multiple_question_marks", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_question_marks", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_exclamation_marks", + "attributes": { + "string_with_exclamation_marks": true + }, + "result": { + "value": "!a!b!c!d!e!f!", + "variant": "string_with_exclamation_marks", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_exclamation_marks", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_exclamation_mark", + "attributes": { + "string_with_only_one_exclamation_mark": true + }, + "result": { + "value": "!", + "variant": "string_with_only_one_exclamation_mark", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_exclamation_mark", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_exclamation_marks", + "attributes": { + "string_with_only_multiple_exclamation_marks": true + }, + "result": { + "value": "!!!!!!!", + "variant": "string_with_only_multiple_exclamation_marks", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_exclamation_marks", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_opening_parentheses", + "attributes": { + "string_with_opening_parentheses": true + }, + "result": { + "value": "(a(b(c(d(e(f(", + "variant": "string_with_opening_parentheses", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_opening_parentheses", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_opening_parenthese", + "attributes": { + "string_with_only_one_opening_parenthese": true + }, + "result": { + "value": "(", + "variant": "string_with_only_one_opening_parenthese", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_opening_parenthese", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_opening_parentheses", + "attributes": { + "string_with_only_multiple_opening_parentheses": true + }, + "result": { + "value": "(((((((", + "variant": "string_with_only_multiple_opening_parentheses", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_opening_parentheses", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_closing_parentheses", + "attributes": { + "string_with_closing_parentheses": true + }, + "result": { + "value": ")a)b)c)d)e)f)", + "variant": "string_with_closing_parentheses", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_closing_parentheses", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_one_closing_parenthese", + "attributes": { + "string_with_only_one_closing_parenthese": true + }, + "result": { + "value": ")", + "variant": "string_with_only_one_closing_parenthese", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_one_closing_parenthese", + "variationType": "string", + "doLog": true + } + } + }, + { + "flag": "string_flag_with_special_characters", + "variationType": "STRING", + "defaultValue": "default_value", + "targetingKey": "string_with_only_multiple_closing_parentheses", + "attributes": { + "string_with_only_multiple_closing_parentheses": true + }, + "result": { + "value": ")))))))", + "variant": "string_with_only_multiple_closing_parentheses", + "flagMetadata": { + "allocationKey": "allocation-test-string_with_only_multiple_closing_parentheses", + "variationType": "string", + "doLog": true + } + } + } +]