From 00cd7e88e35e4a0b94184efb82cd485a6ac58d88 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 14 Feb 2023 08:28:17 -0500 Subject: [PATCH 1/7] Initial .editorconfig update --- .editorconfig | 290 ++++++++++++++++---------------------------------- 1 file changed, 90 insertions(+), 200 deletions(-) diff --git a/.editorconfig b/.editorconfig index d6dfc0902..f7323810c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,239 +1,129 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true -# C# files -[*.cs] - -#### Core EditorConfig Options #### +############################### +# Core EditorConfig Options # +############################### +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options -# Indentation and spacing +# All files +[*] indent_style = space -# New line preferences -insert_final_newline = true +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 -#### .NET Coding Conventions #### +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] # Organize usings -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false -file_header_template = unset - -# this. and Me. preferences -dotnet_style_qualification_for_event = false -dotnet_style_qualification_for_field = false -dotnet_style_qualification_for_method = false -dotnet_style_qualification_for_property = false - +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent # Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true -dotnet_style_predefined_type_for_member_access = true - +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent # Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_operators = never_if_unnecessary -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity - +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent # Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members - +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion # Expression-level preferences -dotnet_style_coalesce_expression = true -dotnet_style_collection_initializer = true -dotnet_style_explicit_tuple_names = true -dotnet_style_namespace_match_folder = true -dotnet_style_null_propagation = true -dotnet_style_object_initializer = true -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true -dotnet_style_prefer_compound_assignment = true -dotnet_style_prefer_conditional_expression_over_assignment = true -dotnet_style_prefer_conditional_expression_over_return = true -dotnet_style_prefer_inferred_anonymous_type_member_names = true -dotnet_style_prefer_inferred_tuple_names = true -dotnet_style_prefer_is_null_check_over_reference_equality_method = true -dotnet_style_prefer_simplified_boolean_expressions = true -dotnet_style_prefer_simplified_interpolation = true - -# Field preferences -dotnet_style_readonly_field = true - -# Parameter preferences -dotnet_code_quality_unused_parameters = all - -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = none - -# New line preferences -dotnet_style_allow_multiple_blank_lines_experimental = true -dotnet_style_allow_statement_immediately_after_block_experimental = true - -#### C# Coding Conventions #### - +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +############################### +# Naming Conventions # +############################### +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +############################### +# C# Coding Conventions # +############################### +[*.cs] # var preferences -csharp_style_var_elsewhere = false -csharp_style_var_for_built_in_types = false -csharp_style_var_when_type_is_apparent = false - +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent # Expression-bodied members -csharp_style_expression_bodied_accessors = true:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = false:silent csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent - +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent # Pattern matching preferences -csharp_style_pattern_matching_over_as_with_null_check = true -csharp_style_pattern_matching_over_is_with_cast_check = true -csharp_style_prefer_extended_property_pattern = true -csharp_style_prefer_not_pattern = true -csharp_style_prefer_pattern_matching = true -csharp_style_prefer_switch_expression = true - +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion # Null-checking preferences -csharp_style_conditional_delegate_call = true - +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences -csharp_prefer_static_local_function = true -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async - -# Code-block preferences -csharp_prefer_braces = true:silent -csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent - +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion # Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion csharp_prefer_simple_default_expression = true:suggestion -csharp_style_deconstructed_variable_declaration = true -csharp_style_implicit_object_creation_when_type_is_apparent = true -csharp_style_inlined_variable_declaration = true -csharp_style_prefer_index_operator = true csharp_style_prefer_local_over_anonymous_function = true:suggestion -csharp_style_prefer_null_check_over_type_check = true:suggestion -csharp_style_prefer_range_operator = true -csharp_style_prefer_tuple_swap = true -csharp_style_throw_expression = true:suggestion -csharp_style_unused_value_assignment_preference = discard_variable -csharp_style_unused_value_expression_statement_preference = discard_variable - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:silent - +csharp_style_inlined_variable_declaration = true:suggestion +############################### +# C# Formatting Rules # +############################### # New line preferences -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true -csharp_style_allow_embedded_statements_on_same_line_experimental = true - -#### C# Formatting Rules #### - -# New line preferences -csharp_new_line_before_catch = true +csharp_new_line_before_open_brace = all csharp_new_line_before_else = true +csharp_new_line_before_catch = true csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true - # Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true - +csharp_indent_labels = flush_left # Space preferences csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false # Wrapping preferences -csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true - -#### Naming styles #### - -# Naming rules - -dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion -dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface -dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i - -dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.types_should_be_pascal_case.symbols = types -dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case - -# Symbol specifications - -dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = - -dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = - -# Naming styles - -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = -dotnet_naming_style.pascal_case.capitalization = pascal_case - -dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = -dotnet_naming_style.begins_with_i.capitalization = pascal_case - -[*.{cs,vb}] -dotnet_style_operator_placement_when_wrapping = beginning_of_line -tab_width = 4 -indent_size = 4 -end_of_line = lf -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_auto_properties = true:silent -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_conditional_expression_over_return = true:silent -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_compound_assignment = true:suggestion -dotnet_style_prefer_simplified_interpolation = true:suggestion -dotnet_style_namespace_match_folder = true:suggestion +csharp_preserve_single_line_blocks = true From e4407ec53ff48126bd5ad07f0c9aebca634f05a2 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 14 Feb 2023 08:38:37 -0500 Subject: [PATCH 2/7] Format full OptimizelySDK project Unit tests pass --- .../AudienceConditions/AndCondition.cs | 10 +- .../AudienceConditions/AudienceIdCondition.cs | 12 +- .../AudienceConditions/BaseCondition.cs | 143 ++++-- .../AudienceConditions/EmptyCondition.cs | 4 +- .../AudienceConditions/NotCondition.cs | 4 +- .../AudienceConditions/OrCondition.cs | 10 +- .../AudienceConditions/SemanticVersion.cs | 58 ++- OptimizelySDK/Bucketing/Bucketer.cs | 52 ++- OptimizelySDK/Bucketing/Decision.cs | 5 +- OptimizelySDK/Bucketing/DecisionService.cs | 360 ++++++++++----- OptimizelySDK/Bucketing/UserProfile.cs | 10 +- OptimizelySDK/Bucketing/UserProfileService.cs | 3 +- OptimizelySDK/Bucketing/UserProfileUtil.cs | 18 +- OptimizelySDK/ClientConfigHandler.cs | 74 +-- OptimizelySDK/Config/DatafileProjectConfig.cs | 181 ++++---- .../Config/FallbackProjectConfigManager.cs | 16 +- .../Config/HttpProjectConfigManager.cs | 38 +- .../Config/PollingProjectConfigManager.cs | 32 +- OptimizelySDK/Entity/Attribute.cs | 5 +- OptimizelySDK/Entity/Audience.cs | 8 + OptimizelySDK/Entity/Entity.cs | 3 +- OptimizelySDK/Entity/Event.cs | 1 + OptimizelySDK/Entity/EventTags.cs | 20 +- OptimizelySDK/Entity/Experiment.cs | 115 +++-- OptimizelySDK/Entity/FeatureDecision.cs | 2 +- OptimizelySDK/Entity/FeatureFlag.cs | 16 +- OptimizelySDK/Entity/FeatureVariable.cs | 24 +- OptimizelySDK/Entity/FeatureVariableUsage.cs | 1 - OptimizelySDK/Entity/ForcedVariation.cs | 5 +- OptimizelySDK/Entity/Group.cs | 5 +- OptimizelySDK/Entity/IdKeyEntity.cs | 3 + OptimizelySDK/Entity/Integration.cs | 6 +- OptimizelySDK/Entity/Result.cs | 5 +- OptimizelySDK/Entity/TrafficAllocation.cs | 1 + OptimizelySDK/Entity/UserAttributes.cs | 9 +- OptimizelySDK/Entity/Variation.cs | 28 +- .../ErrorHandler/DefaultErrorHandler.cs | 6 + OptimizelySDK/ErrorHandler/IErrorHandler.cs | 1 + .../ErrorHandler/NoOpErrorHandler.cs | 5 +- OptimizelySDK/Event/BatchEventProcessor.cs | 120 +++-- OptimizelySDK/Event/Builder/EventBuilder.cs | 136 +++--- OptimizelySDK/Event/Builder/Params.cs | 37 +- .../Dispatcher/DefaultEventDispatcher.cs | 1 + .../Dispatcher/HttpClientEventDispatcher45.cs | 12 +- .../Event/Dispatcher/IEventDispatcher.cs | 1 + .../Dispatcher/WebRequestEventDispatcher35.cs | 19 +- OptimizelySDK/Event/Entity/ConversionEvent.cs | 4 +- OptimizelySDK/Event/Entity/Decision.cs | 11 +- .../Event/Entity/DecisionMetadata.cs | 8 +- OptimizelySDK/Event/Entity/EventBatch.cs | 3 +- OptimizelySDK/Event/Entity/EventContext.cs | 4 +- OptimizelySDK/Event/Entity/ImpressionEvent.cs | 2 +- OptimizelySDK/Event/Entity/Snapshot.cs | 3 +- OptimizelySDK/Event/Entity/SnapshotEvent.cs | 9 +- OptimizelySDK/Event/Entity/UserEvent.cs | 1 + OptimizelySDK/Event/Entity/Visitor.cs | 1 + .../Event/Entity/VisitorAttribute.cs | 5 +- OptimizelySDK/Event/EventFactory.cs | 162 ++++--- .../Event/ForwardingEventProcessor.cs | 13 +- OptimizelySDK/Event/LogEvent.cs | 5 +- OptimizelySDK/Event/UserEventFactory.cs | 114 ++--- .../Exceptions/OptimizelyException.cs | 61 +-- OptimizelySDK/ForcedDecisionsStore.cs | 19 +- OptimizelySDK/Logger/DefaultLogger.cs | 3 +- OptimizelySDK/Logger/ILogger.cs | 1 + OptimizelySDK/Logger/NoOpLogger.cs | 5 +- .../Notifications/NotificationCenter.cs | 122 +++-- .../NotificationCenterRegistry.cs | 2 +- OptimizelySDK/Odp/Entity/Error.cs | 6 +- OptimizelySDK/Odp/Entity/Location.cs | 2 +- OptimizelySDK/Odp/Entity/Node.cs | 2 +- OptimizelySDK/Odp/Entity/Response.cs | 8 +- OptimizelySDK/Odp/ICache.cs | 2 +- OptimizelySDK/Odp/LruCache.cs | 4 +- OptimizelySDK/Odp/OdpEventManager.cs | 19 +- OptimizelySDK/Odp/OdpSegmentApiManager.cs | 15 +- OptimizelySDK/Odp/OdpSegmentManager.cs | 2 +- OptimizelySDK/Optimizely.cs | 169 ++++--- OptimizelySDK/OptimizelyDecisionContext.cs | 15 +- .../OptimizelyDecisions/DecisionMessage.cs | 5 +- .../OptimizelyDecisions/DecisionReasons.cs | 16 +- .../OptimizelyDecideOption.cs | 2 +- .../OptimizelyDecisions/OptimizelyDecision.cs | 26 +- OptimizelySDK/OptimizelyFactory.cs | 155 ++++--- OptimizelySDK/OptimizelyForcedDecision.cs | 2 +- OptimizelySDK/OptimizelyJSON.cs | 27 +- OptimizelySDK/OptimizelySDK.csproj | 422 +++++++++--------- OptimizelySDK/OptimizelyUserContext.cs | 9 +- .../OptlyConfig/OptimizelyAttribute.cs | 4 +- OptimizelySDK/OptlyConfig/OptimizelyConfig.cs | 11 +- .../OptlyConfig/OptimizelyConfigService.cs | 155 ++++--- .../OptlyConfig/OptimizelyExperiment.cs | 5 +- .../OptlyConfig/OptimizelyFeature.cs | 9 +- .../OptlyConfig/OptimizelyVariable.cs | 8 +- .../OptlyConfig/OptimizelyVariation.cs | 5 +- OptimizelySDK/Utils/CollectionExtensions.cs | 2 +- OptimizelySDK/Utils/ConditionParser.cs | 38 +- OptimizelySDK/Utils/ConfigParser.cs | 7 +- OptimizelySDK/Utils/ControlAttributes.cs | 4 +- OptimizelySDK/Utils/DateTimeUtils.cs | 9 +- OptimizelySDK/Utils/EventTagUtils.cs | 47 +- OptimizelySDK/Utils/ExceptionExtensions.cs | 6 +- OptimizelySDK/Utils/ExperimentUtils.cs | 18 +- OptimizelySDK/Utils/Schema.cs | 11 +- OptimizelySDK/Utils/Validator.cs | 38 +- OptimizelySDK/packages.config | 8 +- 106 files changed, 2089 insertions(+), 1387 deletions(-) diff --git a/OptimizelySDK/AudienceConditions/AndCondition.cs b/OptimizelySDK/AudienceConditions/AndCondition.cs index 3835fc8c8..fd4e4a393 100644 --- a/OptimizelySDK/AudienceConditions/AndCondition.cs +++ b/OptimizelySDK/AudienceConditions/AndCondition.cs @@ -26,7 +26,9 @@ public class AndCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, + ILogger logger + ) { // According to the matrix: // false and true is false @@ -40,13 +42,19 @@ public class AndCondition : ICondition { var result = condition.Evaluate(config, userContext, logger); if (result == null) + { foundNull = true; + } else if (result == false) + { return false; + } } if (foundNull) + { return null; + } return true; } diff --git a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs index 06d34f9f1..3faeffb6b 100644 --- a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs +++ b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs @@ -24,14 +24,20 @@ namespace OptimizelySDK.AudienceConditions public class AudienceIdCondition : ICondition { public string AudienceId { get; set; } - - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) + + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, + ILogger logger + ) { var audience = config?.GetAudience(AudienceId); if (audience == null || string.IsNullOrEmpty(audience.Id)) + { return null; + } - logger.Log(LogLevel.DEBUG, $@"Starting to evaluate audience ""{AudienceId}"" with conditions: {audience.ConditionsString}"); + logger.Log(LogLevel.DEBUG, + $@"Starting to evaluate audience ""{AudienceId}"" with conditions: { + audience.ConditionsString}"); var result = audience.ConditionList.Evaluate(config, userContext, logger); var resultText = result?.ToString().ToUpper() ?? "UNKNOWN"; logger.Log(LogLevel.DEBUG, $@"Audience ""{AudienceId}"" evaluated to {resultText}"); diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index 8e71127a0..03282c5b5 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.cs @@ -31,7 +31,7 @@ public class BaseCondition : ICondition /// String constant representing custom attribute condition type. /// public const string CUSTOM_ATTRIBUTE = "custom_attribute"; - + /// /// String constant representing a third-party condition type. /// @@ -45,7 +45,8 @@ public class BaseCondition : ICondition /// /// Valid types allowed for validation /// - public static readonly string[] ValidTypes = { + public static readonly string[] ValidTypes = + { CUSTOM_ATTRIBUTE, THIRD_PARTY_DIMENSION, }; @@ -61,11 +62,15 @@ public class BaseCondition : ICondition [JsonProperty("value")] public object Value { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, + ILogger logger + ) { if (!ValidTypes.Contains(Type)) { - logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."); + logger.Log(LogLevel.WARN, + $@"Audience condition ""{this + }"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } @@ -75,29 +80,36 @@ public class BaseCondition : ICondition { return userContext.IsQualifiedFor(Value.ToString()); } - - logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" has a qualified match but invalid value."); + + logger.Log(LogLevel.WARN, + $@"Audience condition ""{this}"" has a qualified match but invalid value."); return null; } - + var userAttributes = userContext.GetAttributes(); object attributeValue = null; - if (userAttributes.TryGetValue(Name, out attributeValue) == false && Match != AttributeMatchTypes.EXIST) + if (userAttributes.TryGetValue(Name, out attributeValue) == false && + Match != AttributeMatchTypes.EXIST) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because no value was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.DEBUG, + $@"Audience condition {this + } evaluated to UNKNOWN because no value was passed for user attribute ""{Name + }""."); return null; } var evaluator = GetEvaluator(); if (evaluator == null) { - logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK."); + logger.Log(LogLevel.WARN, + $@"Audience condition ""{this + }"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } return evaluator(attributeValue, logger); } - + public Func GetEvaluator() { switch (Match) @@ -135,32 +147,49 @@ public class BaseCondition : ICondition public bool? ExactEvaluator(object attributeValue, ILogger logger) { - if (!IsValueTypeValidForExactConditions(Value) || (Validator.IsNumericType(Value) && !Validator.IsValidNumericValue(Value))) + if (!IsValueTypeValidForExactConditions(Value) || (Validator.IsNumericType(Value) && + !Validator. + IsValidNumericValue(Value))) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this + } has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.DEBUG, + $@"Audience condition {this + } evaluated to UNKNOWN because a null value was passed for user attribute ""{ + Name}""."); return null; } - if (!IsValueTypeValidForExactConditions(attributeValue) || !AreValuesSameType(Value, attributeValue)) + if (!IsValueTypeValidForExactConditions(attributeValue) || + !AreValuesSameType(Value, attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{ + attributeValue.GetType().Name}"" was passed for user attribute ""{Name + }""."); return null; } - if (Validator.IsNumericType(attributeValue) && !Validator.IsValidNumericValue(attributeValue)) + if (Validator.IsNumericType(attributeValue) && + !Validator.IsValidNumericValue(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this + } evaluated to UNKNOWN because the number value for user attribute ""{Name + }"" is not in the range [-2^53, +2^53]."); return null; } if (Validator.IsNumericType(Value) && Validator.IsNumericType(attributeValue)) + { return Convert.ToDouble(Value).Equals(Convert.ToDouble(attributeValue)); + } return Value.Equals(attributeValue); } @@ -172,25 +201,25 @@ public class BaseCondition : ICondition public bool? GreaterThanEvaluator(object attributeValue, ILogger logger) { - int? result = NumberEvaluator(attributeValue, logger); + var result = NumberEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result > 0); } public bool? GreaterOrEqualThanEvaluator(object attributeValue, ILogger logger) { - int? result = NumberEvaluator(attributeValue, logger); + var result = NumberEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result >= 0); } public bool? LessThanEvaluator(object attributeValue, ILogger logger) { - int? result = NumberEvaluator(attributeValue, logger); + var result = NumberEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result < 0); } public bool? LessOrEqualThanEvaluator(object attributeValue, ILogger logger) { - int? result = NumberEvaluator(attributeValue, logger); + var result = NumberEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result <= 0); } @@ -198,26 +227,34 @@ public class BaseCondition : ICondition { if (!(Value is string)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this + } has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.DEBUG, + $@"Audience condition {this + } evaluated to UNKNOWN because a null value was passed for user attribute ""{ + Name}""."); return null; } if (!(attributeValue is string)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{ + attributeValue.GetType().Name}"" was passed for user attribute ""{Name + }""."); return null; } var attrValue = (string)attributeValue; return attrValue != null && attrValue.Contains((string)Value); } - + /// /// Validates the value for exact conditions. /// @@ -230,31 +267,31 @@ public bool IsValueTypeValidForExactConditions(object value) public bool? SemanticVersionEqualEvaluator(object attributeValue, ILogger logger) { - int? result = SemanticVersionEvaluator(attributeValue, logger); + var result = SemanticVersionEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result == 0); } public bool? SemanticVersionGreaterEvaluator(object attributeValue, ILogger logger) { - int? result = SemanticVersionEvaluator(attributeValue, logger); + var result = SemanticVersionEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result > 0); } public bool? SemanticVersionGreaterOrEqualEvaluator(object attributeValue, ILogger logger) { - int? result = SemanticVersionEvaluator(attributeValue, logger); + var result = SemanticVersionEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result >= 0); } public bool? SemanticVersionLessEvaluator(object attributeValue, ILogger logger) { - int? result = SemanticVersionEvaluator(attributeValue, logger); + var result = SemanticVersionEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result < 0); } public bool? SemanticVersionLessOrEqualEvaluator(object attributeValue, ILogger logger) { - int? result = SemanticVersionEvaluator(attributeValue, logger); + var result = SemanticVersionEvaluator(attributeValue, logger); return result == null ? null : (bool?)(result <= 0); } @@ -262,19 +299,27 @@ public bool IsValueTypeValidForExactConditions(object value) { if (!(Value is string)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this + } has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.DEBUG, + $@"Audience condition {this + } evaluated to UNKNOWN because a null value was passed for user attribute ""{ + Name}""."); return null; } if (!(attributeValue is string)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{ + attributeValue.GetType().Name}"" was passed for user attribute ""{Name + }""."); return null; } @@ -295,29 +340,41 @@ public bool IsValueTypeValidForExactConditions(object value) { if (!Validator.IsValidNumericValue(Value)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this + } has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.DEBUG, + $@"Audience condition {this + } evaluated to UNKNOWN because a null value was passed for user attribute ""{ + Name}""."); return null; } if (!Validator.IsNumericType(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{ + attributeValue.GetType().Name}"" was passed for user attribute ""{Name + }""."); return null; } if (!Validator.IsValidNumericValue(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]."); + logger.Log(LogLevel.WARN, + $@"Audience condition {this + } evaluated to UNKNOWN because the number value for user attribute ""{Name + }"" is not in the range [-2^53, +2^53]."); return null; } - double userValue = Convert.ToDouble(attributeValue); - double conditionalValue = Convert.ToDouble(Value); + + var userValue = Convert.ToDouble(attributeValue); + var conditionalValue = Convert.ToDouble(Value); return userValue.CompareTo(conditionalValue); } @@ -331,13 +388,19 @@ public bool IsValueTypeValidForExactConditions(object value) public bool AreValuesSameType(object firstValue, object secondValue) { if (firstValue is string && secondValue is string) + { return true; + } if (firstValue is bool && secondValue is bool) + { return true; + } if (Validator.IsNumericType(firstValue) && Validator.IsNumericType(secondValue)) + { return true; + } return false; } diff --git a/OptimizelySDK/AudienceConditions/EmptyCondition.cs b/OptimizelySDK/AudienceConditions/EmptyCondition.cs index d2ee9d815..70aedf01f 100644 --- a/OptimizelySDK/AudienceConditions/EmptyCondition.cs +++ b/OptimizelySDK/AudienceConditions/EmptyCondition.cs @@ -24,7 +24,9 @@ namespace OptimizelySDK.AudienceConditions /// public class EmptyCondition : ICondition { - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, + ILogger logger + ) { return true; } diff --git a/OptimizelySDK/AudienceConditions/NotCondition.cs b/OptimizelySDK/AudienceConditions/NotCondition.cs index 028996de6..a136ce092 100644 --- a/OptimizelySDK/AudienceConditions/NotCondition.cs +++ b/OptimizelySDK/AudienceConditions/NotCondition.cs @@ -26,7 +26,9 @@ public class NotCondition : ICondition { public ICondition Condition { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, + ILogger logger + ) { var result = Condition?.Evaluate(config, userContext, logger); return result == null ? null : !result; diff --git a/OptimizelySDK/AudienceConditions/OrCondition.cs b/OptimizelySDK/AudienceConditions/OrCondition.cs index ad04787c8..cffd82b16 100644 --- a/OptimizelySDK/AudienceConditions/OrCondition.cs +++ b/OptimizelySDK/AudienceConditions/OrCondition.cs @@ -26,7 +26,9 @@ public class OrCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, + ILogger logger + ) { // According to the matrix: // true returns true @@ -38,13 +40,19 @@ public class OrCondition : ICondition { var result = condition.Evaluate(config, userContext, logger); if (result == null) + { foundNull = true; + } else if (result == true) + { return true; + } } if (foundNull) + { return null; + } return false; } diff --git a/OptimizelySDK/AudienceConditions/SemanticVersion.cs b/OptimizelySDK/AudienceConditions/SemanticVersion.cs index e191dc516..8b0394862 100644 --- a/OptimizelySDK/AudienceConditions/SemanticVersion.cs +++ b/OptimizelySDK/AudienceConditions/SemanticVersion.cs @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; using System.Collections.Generic; using System.Linq; + namespace OptimizelySDK.AudienceConditions { /// @@ -26,7 +28,10 @@ public static class SemanticVersionExtension public const char BuildSeparator = '+'; public const char PreReleaseSeparator = '-'; public const string NumberExpression = "[0-9]+"; - public static readonly System.Text.RegularExpressions.Regex NumberRegex = new System.Text.RegularExpressions.Regex(NumberExpression); + + public static readonly System.Text.RegularExpressions.Regex NumberRegex = + new System.Text.RegularExpressions.Regex(NumberExpression); + /// /// Helper method to check if semantic version contains white spaces. /// @@ -54,6 +59,7 @@ public static bool IsPreRelease(this string semanticVersion) { return false; } + return preReleaseIndex < buildIndex; } @@ -74,6 +80,7 @@ public static bool IsBuild(this string semanticVersion) { return false; } + return buildIndex < preReleaseIndex; } @@ -94,38 +101,48 @@ public static bool IsNumber(this string semanticVersion) /// string array conatining major.minor.patch and prerelease or beta version as last element. public static string[] SplitSemanticVersion(this string version) { - List versionParts = new List(); + var versionParts = new List(); // pre-release or build. - string versionSuffix = string.Empty; - string versionPrefix = version; + var versionSuffix = string.Empty; + var versionPrefix = version; string[] preVersionParts; if (version.ContainsWhiteSpace()) { // log and throw error - throw new Exception("Semantic version contains white spaces. Invalid Semantic Version."); + throw new Exception( + "Semantic version contains white spaces. Invalid Semantic Version."); } if (version.IsBuild() || version.IsPreRelease()) { - var partialVersionParts = version.Split(new char [] { version.IsPreRelease() ? - PreReleaseSeparator : BuildSeparator}, 2, StringSplitOptions.RemoveEmptyEntries); + var partialVersionParts = version.Split(new char[] + { + version.IsPreRelease() ? + PreReleaseSeparator : + BuildSeparator, + }, 2, StringSplitOptions.RemoveEmptyEntries); if (partialVersionParts.Length <= 1) { // throw error throw new Exception("Invalid Semantic Version."); } + // major.minor.patch versionPrefix = partialVersionParts[0]; versionSuffix = partialVersionParts[1]; - preVersionParts = versionPrefix.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + preVersionParts = versionPrefix.Split(new char[] { '.' }, + StringSplitOptions.RemoveEmptyEntries); } else { - preVersionParts = version.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + preVersionParts = version.Split(new char[] { '.' }, + StringSplitOptions.RemoveEmptyEntries); } - int dotCount = versionPrefix.Count(c => c == '.'); - if (preVersionParts.Length != dotCount + 1 || dotCount > 2 || (preVersionParts.Any(part => !part.IsNumber()))) + + var dotCount = versionPrefix.Count(c => c == '.'); + if (preVersionParts.Length != dotCount + 1 || dotCount > 2 || + preVersionParts.Any(part => !part.IsNumber())) { // Throw error as pre version should only contain major.minor.patch version throw new Exception("Invalid Semantic Version."); @@ -180,27 +197,34 @@ public int CompareTo(SemanticVersion targetedVersion) for (var index = 0; index < targetedVersionParts.Length; index++) { - if (userVersionParts.Length <= index) { return targetedVersion.Version.IsPreRelease() ? 1 : -1; } else { - if (!int.TryParse(userVersionParts[index], out int userVersionPartInt)) + if (!int.TryParse(userVersionParts[index], out var userVersionPartInt)) { // Compare strings - var result = string.Compare(userVersionParts[index], targetedVersionParts[index]); + var result = string.Compare(userVersionParts[index], + targetedVersionParts[index]); if (result < 0) { - return targetedVersion.Version.IsPreRelease() && !Version.IsPreRelease() ? 1 : -1; + return targetedVersion.Version.IsPreRelease() && + !Version.IsPreRelease() ? + 1 : + -1; } else if (result > 0) { - return !targetedVersion.Version.IsPreRelease() && Version.IsPreRelease() ? -1 : 1; + return !targetedVersion.Version.IsPreRelease() && + Version.IsPreRelease() ? + -1 : + 1; } } - else if (int.TryParse(targetedVersionParts[index], out int targetVersionPartInt)) + else if (int.TryParse(targetedVersionParts[index], + out var targetVersionPartInt)) { if (userVersionPartInt != targetVersionPartInt) { diff --git a/OptimizelySDK/Bucketing/Bucketer.cs b/OptimizelySDK/Bucketing/Bucketer.cs index 0b0eaa254..c53aa0c95 100644 --- a/OptimizelySDK/Bucketing/Bucketer.cs +++ b/OptimizelySDK/Bucketing/Bucketer.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Entity; using OptimizelySDK.Logger; using OptimizelySDK.OptimizelyDecisions; @@ -54,9 +55,9 @@ public Bucketer(ILogger logger) /// integer Unsigned value denoting the hash value for the user private uint GenerateHashCode(string bucketingKey) { - var murmer32 = Murmur.MurmurHash.Create32(seed: HASH_SEED, managed: true); - byte[] data = Encoding.UTF8.GetBytes(bucketingKey); - byte[] hash = murmer32.ComputeHash(data); + var murmer32 = Murmur.MurmurHash.Create32(HASH_SEED, true); + var data = Encoding.UTF8.GetBytes(bucketingKey); + var hash = murmer32.ComputeHash(data); return BitConverter.ToUInt32(hash, 0); } @@ -68,8 +69,8 @@ private uint GenerateHashCode(string bucketingKey) /// integer Value in the closed range [0, 9999] denoting the bucket the user belongs to public virtual int GenerateBucketValue(string bucketingKey) { - uint hashCode = GenerateHashCode(bucketingKey); - double ratio = hashCode / (double)MAX_HASH_VALUE; + var hashCode = GenerateHashCode(bucketingKey); + var ratio = hashCode / (double)MAX_HASH_VALUE; return (int)(ratio * MAX_TRAFFIC_VALUE); } @@ -81,17 +82,24 @@ public virtual int GenerateBucketValue(string bucketingKey) /// mixed ID representing Experiment or Group /// array Traffic allocations for variation or experiment /// string ID representing experiment or variation, returns null if not found - private string FindBucket(string bucketingId, string userId, string parentId, IEnumerable trafficAllocations) + private string FindBucket(string bucketingId, string userId, string parentId, + IEnumerable trafficAllocations + ) { // Generate the bucketing key based on combination of user ID and experiment ID or group ID. - string bucketingKey = bucketingId + parentId; - int bucketingNumber = GenerateBucketValue(bucketingKey); + var bucketingKey = bucketingId + parentId; + var bucketingNumber = GenerateBucketValue(bucketingKey); - Logger.Log(LogLevel.DEBUG, $"Assigned bucket [{bucketingNumber}] to user [{userId}] with bucketing ID [{bucketingId}]."); + Logger.Log(LogLevel.DEBUG, + $"Assigned bucket [{bucketingNumber}] to user [{userId}] with bucketing ID [{bucketingId}]."); foreach (var ta in trafficAllocations) + { if (bucketingNumber < ta.EndOfRange) + { return ta.EntityId; + } + } return null; } @@ -104,7 +112,9 @@ private string FindBucket(string bucketingId, string userId, string parentId, IE /// A customer-assigned value used to create the key for the murmur hash. /// User identifier /// Variation which will be shown to the user - public virtual Result Bucket(ProjectConfig config, Experiment experiment, string bucketingId, string userId) + public virtual Result Bucket(ProjectConfig config, Experiment experiment, + string bucketingId, string userId + ) { string message; Variation variation; @@ -112,16 +122,21 @@ public virtual Result Bucket(ProjectConfig config, Experiment experim var reasons = new DecisionReasons(); if (string.IsNullOrEmpty(experiment.Key)) + { return Result.NewResult(new Variation(), reasons); + } // Determine if experiment is in a mutually exclusive group. if (experiment.IsInMutexGroup) { - Group group = config.GetGroup(experiment.GroupId); + var group = config.GetGroup(experiment.GroupId); if (string.IsNullOrEmpty(group.Id)) + { return Result.NewResult(new Variation(), reasons); + } - string userExperimentId = FindBucket(bucketingId, userId, group.Id, group.TrafficAllocation); + var userExperimentId = + FindBucket(bucketingId, userId, group.Id, group.TrafficAllocation); if (string.IsNullOrEmpty(userExperimentId)) { message = $"User [{userId}] is in no experiment."; @@ -131,27 +146,30 @@ public virtual Result Bucket(ProjectConfig config, Experiment experim if (userExperimentId != experiment.Id) { - message = $"User [{userId}] is not in experiment [{experiment.Key}] of group [{experiment.GroupId}]."; + message = + $"User [{userId}] is not in experiment [{experiment.Key}] of group [{experiment.GroupId}]."; Logger.Log(LogLevel.INFO, reasons.AddInfo(message)); return Result.NewResult(new Variation(), reasons); } - message = $"User [{userId}] is in experiment [{experiment.Key}] of group [{experiment.GroupId}]."; + message = + $"User [{userId}] is in experiment [{experiment.Key}] of group [{experiment.GroupId}]."; Logger.Log(LogLevel.INFO, reasons.AddInfo(message)); } // Bucket user if not in whitelist and in group (if any). - string variationId = FindBucket(bucketingId, userId, experiment.Id, experiment.TrafficAllocation); + var variationId = FindBucket(bucketingId, userId, experiment.Id, + experiment.TrafficAllocation); if (string.IsNullOrEmpty(variationId)) { Logger.Log(LogLevel.INFO, reasons.AddInfo($"User [{userId}] is in no variation.")); return Result.NewResult(new Variation(), reasons); - } // success! variation = config.GetVariationFromIdByExperimentId(experiment.Id, variationId); - message = $"User [{userId}] is in variation [{variation.Key}] of experiment [{experiment.Key}]."; + message = + $"User [{userId}] is in variation [{variation.Key}] of experiment [{experiment.Key}]."; Logger.Log(LogLevel.INFO, reasons.AddInfo(message)); return Result.NewResult(variation, reasons); } diff --git a/OptimizelySDK/Bucketing/Decision.cs b/OptimizelySDK/Bucketing/Decision.cs index 30985663c..703adce8b 100644 --- a/OptimizelySDK/Bucketing/Decision.cs +++ b/OptimizelySDK/Bucketing/Decision.cs @@ -20,7 +20,6 @@ namespace OptimizelySDK.Bucketing { public class Decision { - /// /// The ID of the Variation into which the user was bucketed. /// @@ -39,8 +38,8 @@ public Dictionary ToMap() { return new Dictionary { - { UserProfile.VARIATION_ID_KEY, VariationId } + { UserProfile.VARIATION_ID_KEY, VariationId }, }; } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs index 97fd958d9..e6088d7e0 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -53,7 +53,8 @@ public class DecisionService #if NET35 private Dictionary> ForcedVariationMap; #else - private System.Collections.Concurrent.ConcurrentDictionary> ForcedVariationMap; + private System.Collections.Concurrent.ConcurrentDictionary> ForcedVariationMap; #endif /// @@ -63,7 +64,9 @@ public class DecisionService /// The error handler of the Optimizely client. /// /// < param name= "logger" > UserProfileService implementation for storing user info. - public DecisionService(Bucketer bucketer, IErrorHandler errorHandler, UserProfileService userProfileService, ILogger logger) + public DecisionService(Bucketer bucketer, IErrorHandler errorHandler, + UserProfileService userProfileService, ILogger logger + ) { Bucketer = bucketer; ErrorHandler = errorHandler; @@ -72,7 +75,9 @@ public DecisionService(Bucketer bucketer, IErrorHandler errorHandler, UserProfil #if NET35 ForcedVariationMap = new Dictionary>(); #else - ForcedVariationMap = new System.Collections.Concurrent.ConcurrentDictionary>(); + ForcedVariationMap = + new System.Collections.Concurrent.ConcurrentDictionary>(); #endif } @@ -85,7 +90,8 @@ public DecisionService(Bucketer bucketer, IErrorHandler errorHandler, UserProfil /// The Variation the user is allocated into. public virtual Result GetVariation(Experiment experiment, OptimizelyUserContext user, - ProjectConfig config) + ProjectConfig config + ) { return GetVariation(experiment, user, config, new OptimizelyDecideOption[] { }); } @@ -101,12 +107,15 @@ public virtual Result GetVariation(Experiment experiment, public virtual Result GetVariation(Experiment experiment, OptimizelyUserContext user, ProjectConfig config, - OptimizelyDecideOption[] options) + OptimizelyDecideOption[] options + ) { var reasons = new DecisionReasons(); var userId = user.GetUserId(); if (!ExperimentUtils.IsExperimentActive(experiment, Logger)) + { return Result.NullResult(reasons); + } // check if a forced variation is set var decisionVariationResult = GetForcedVariation(experiment.Key, userId, config); @@ -128,38 +137,51 @@ public virtual Result GetVariation(Experiment experiment, } // fetch the user profile map from the user profile service - var ignoreUPS = Array.Exists(options, option => option == OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE); + var ignoreUPS = Array.Exists(options, + option => option == OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE); UserProfile userProfile = null; if (!ignoreUPS && UserProfileService != null) { try { - Dictionary userProfileMap = UserProfileService.Lookup(user.GetUserId()); - if (userProfileMap != null && UserProfileUtil.IsValidUserProfileMap(userProfileMap)) + var userProfileMap = UserProfileService.Lookup(user.GetUserId()); + if (userProfileMap != null && + UserProfileUtil.IsValidUserProfileMap(userProfileMap)) { userProfile = UserProfileUtil.ConvertMapToUserProfile(userProfileMap); - decisionVariationResult = GetStoredVariation(experiment, userProfile, config); + decisionVariationResult = + GetStoredVariation(experiment, userProfile, config); reasons += decisionVariationResult.DecisionReasons; - if (decisionVariationResult.ResultObject != null) return decisionVariationResult.SetReasons(reasons); + if (decisionVariationResult.ResultObject != null) + { + return decisionVariationResult.SetReasons(reasons); + } } else if (userProfileMap == null) { - Logger.Log(LogLevel.INFO, reasons.AddInfo("We were unable to get a user profile map from the UserProfileService.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + "We were unable to get a user profile map from the UserProfileService.")); } else { - Logger.Log(LogLevel.ERROR, reasons.AddInfo("The UserProfileService returned an invalid map.")); + Logger.Log(LogLevel.ERROR, + reasons.AddInfo("The UserProfileService returned an invalid map.")); } } catch (Exception exception) { Logger.Log(LogLevel.ERROR, reasons.AddInfo(exception.Message)); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + ErrorHandler.HandleError( + new Exceptions.OptimizelyRuntimeException(exception.Message)); } } + var filteredAttributes = user.GetAttributes(); - var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, user, LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger); + var doesUserMeetAudienceConditionsResult = + ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, user, + LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger); reasons += doesUserMeetAudienceConditionsResult.DecisionReasons; if (doesUserMeetAudienceConditionsResult.ResultObject) { @@ -167,23 +189,33 @@ public virtual Result GetVariation(Experiment experiment, var bucketingIdResult = GetBucketingId(userId, filteredAttributes); reasons += bucketingIdResult.DecisionReasons; - decisionVariationResult = Bucketer.Bucket(config, experiment, bucketingIdResult.ResultObject, userId); + decisionVariationResult = Bucketer.Bucket(config, experiment, + bucketingIdResult.ResultObject, userId); reasons += decisionVariationResult.DecisionReasons; if (decisionVariationResult.ResultObject?.Key != null) { if (UserProfileService != null && !ignoreUPS) { - var bucketerUserProfile = userProfile ?? new UserProfile(userId, new Dictionary()); - SaveVariation(experiment, decisionVariationResult.ResultObject, bucketerUserProfile); + var bucketerUserProfile = userProfile ?? + new UserProfile(userId, + new Dictionary()); + SaveVariation(experiment, decisionVariationResult.ResultObject, + bucketerUserProfile); } else - Logger.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."); + { + Logger.Log(LogLevel.INFO, + "This decision will not be saved since the UserProfileService is null."); + } } return decisionVariationResult.SetReasons(reasons); } - Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{user.GetUserId()}\" does not meet conditions to be in experiment \"{experiment.Key}\".")); + + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"User \"{user.GetUserId()}\" does not meet conditions to be in experiment \"{experiment.Key}\".")); return Result.NullResult(reasons); } @@ -195,46 +227,60 @@ public virtual Result GetVariation(Experiment experiment, /// The user ID /// Project Config /// Variation entity which the given user and experiment should be forced into. - public Result GetForcedVariation(string experimentKey, string userId, ProjectConfig config) + public Result GetForcedVariation(string experimentKey, string userId, + ProjectConfig config + ) { var reasons = new DecisionReasons(); if (ForcedVariationMap.ContainsKey(userId) == false) { - Logger.Log(LogLevel.DEBUG, $@"User ""{userId}"" is not in the forced variation map."); + Logger.Log(LogLevel.DEBUG, + $@"User ""{userId}"" is not in the forced variation map."); return Result.NullResult(reasons); } - Dictionary experimentToVariationMap = ForcedVariationMap[userId]; + var experimentToVariationMap = ForcedVariationMap[userId]; - string experimentId = config.GetExperimentFromKey(experimentKey).Id; + var experimentId = config.GetExperimentFromKey(experimentKey).Id; // this case is logged in getExperimentFromKey if (string.IsNullOrEmpty(experimentId)) + { return Result.NullResult(reasons); + } if (experimentToVariationMap.ContainsKey(experimentId) == false) { - Logger.Log(LogLevel.DEBUG, $@"No experiment ""{experimentKey}"" mapped to user ""{userId}"" in the forced variation map."); + Logger.Log(LogLevel.DEBUG, + $@"No experiment ""{experimentKey}"" mapped to user ""{userId + }"" in the forced variation map."); return Result.NullResult(reasons); } - string variationId = experimentToVariationMap[experimentId]; + var variationId = experimentToVariationMap[experimentId]; if (string.IsNullOrEmpty(variationId)) { - Logger.Log(LogLevel.DEBUG, $@"No variation mapped to experiment ""{experimentKey}"" in the forced variation map."); + Logger.Log(LogLevel.DEBUG, + $@"No variation mapped to experiment ""{experimentKey + }"" in the forced variation map."); return Result.NullResult(reasons); } - string variationKey = config.GetVariationFromId(experimentKey, variationId).Key; + var variationKey = config.GetVariationFromId(experimentKey, variationId).Key; // this case is logged in getVariationFromKey if (string.IsNullOrEmpty(variationKey)) + { return Result.NullResult(reasons); - Logger.Log(LogLevel.DEBUG, reasons.AddInfo($@"Variation ""{variationKey}"" is mapped to experiment ""{experimentKey}"" and user ""{userId}"" in the forced variation map")); + } - Variation variation = config.GetVariationFromKey(experimentKey, variationKey); + Logger.Log(LogLevel.DEBUG, + reasons.AddInfo($@"Variation ""{variationKey}"" is mapped to experiment ""{ + experimentKey}"" and user ""{userId}"" in the forced variation map")); + + var variation = config.GetVariationFromKey(experimentKey, variationKey); return Result.NewResult(variation, reasons); } @@ -247,7 +293,9 @@ public Result GetForcedVariation(string experimentKey, string userId, /// The variation key /// Project Config /// A boolean value that indicates if the set completed successfully. - public bool SetForcedVariation(string experimentKey, string userId, string variationKey, ProjectConfig config) + public bool SetForcedVariation(string experimentKey, string userId, string variationKey, + ProjectConfig config + ) { // Empty variation key is considered as invalid. if (variationKey != null && variationKey.Length == 0) @@ -260,32 +308,45 @@ public bool SetForcedVariation(string experimentKey, string userId, string varia // this case is logged in getExperimentFromKey if (string.IsNullOrEmpty(experimentId)) + { return false; + } // clear the forced variation if the variation key is null if (variationKey == null) { - if (ForcedVariationMap.ContainsKey(userId) && ForcedVariationMap[userId].ContainsKey(experimentId)) + if (ForcedVariationMap.ContainsKey(userId) && + ForcedVariationMap[userId].ContainsKey(experimentId)) + { ForcedVariationMap[userId].Remove(experimentId); + } - Logger.Log(LogLevel.DEBUG, $@"Variation mapped to experiment ""{experimentKey}"" has been removed for user ""{userId}""."); + Logger.Log(LogLevel.DEBUG, + $@"Variation mapped to experiment ""{experimentKey + }"" has been removed for user ""{userId}""."); return true; } - string variationId = config.GetVariationFromKey(experimentKey, variationKey).Id; + var variationId = config.GetVariationFromKey(experimentKey, variationKey).Id; // this case is logged in getVariationFromKey if (string.IsNullOrEmpty(variationId)) + { return false; + } // Add User if not exist. if (ForcedVariationMap.ContainsKey(userId) == false) + { ForcedVariationMap[userId] = new Dictionary(); + } // Add/Replace Experiment to Variation ID map. ForcedVariationMap[userId][experimentId] = variationId; - Logger.Log(LogLevel.DEBUG, $@"Set variation ""{variationId}"" for experiment ""{experimentId}"" and user ""{userId}"" in the forced variation map."); + Logger.Log(LogLevel.DEBUG, + $@"Set variation ""{variationId}"" for experiment ""{experimentId}"" and user ""{ + userId}"" in the forced variation map."); return true; } @@ -302,20 +363,31 @@ public Result GetWhitelistedVariation(Experiment experiment, string u var reasons = new DecisionReasons(); //if a user has a forced variation mapping, return the respective variation - Dictionary userIdToVariationKeyMap = experiment.UserIdToKeyVariations; + var userIdToVariationKeyMap = experiment.UserIdToKeyVariations; if (!userIdToVariationKeyMap.ContainsKey(userId)) + { return Result.NullResult(reasons); + } - string forcedVariationKey = userIdToVariationKeyMap[userId]; - Variation forcedVariation = experiment.VariationKeyToVariationMap.ContainsKey(forcedVariationKey) - ? experiment.VariationKeyToVariationMap[forcedVariationKey] - : null; + var forcedVariationKey = userIdToVariationKeyMap[userId]; + var forcedVariation = + experiment.VariationKeyToVariationMap.ContainsKey(forcedVariationKey) ? + experiment.VariationKeyToVariationMap[forcedVariationKey] : + null; if (forcedVariation != null) - Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" is forced in variation \"{forcedVariationKey}\".")); + { + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"User \"{userId}\" is forced in variation \"{forcedVariationKey}\".")); + } else - Logger.Log(LogLevel.ERROR, reasons.AddInfo($"Variation \"{forcedVariationKey}\" is not in the datafile. Not activating user \"{userId}\".")); + { + Logger.Log(LogLevel.ERROR, + reasons.AddInfo( + $"Variation \"{forcedVariationKey}\" is not in the datafile. Not activating user \"{userId}\".")); + } return Result.NewResult(forcedVariation, reasons); } @@ -326,39 +398,51 @@ public Result GetWhitelistedVariation(Experiment experiment, string u /// which the user was bucketed /// User profile of the user /// The user was previously bucketed into. - public Result GetStoredVariation(Experiment experiment, UserProfile userProfile, ProjectConfig config) + public Result GetStoredVariation(Experiment experiment, UserProfile userProfile, + ProjectConfig config + ) { // ---------- Check User Profile for Sticky Bucketing ---------- // If a user profile instance is present then check it for a saved variation - string experimentId = experiment.Id; - string experimentKey = experiment.Key; + var experimentId = experiment.Id; + var experimentKey = experiment.Key; var reasons = new DecisionReasons(); - Decision decision = userProfile.ExperimentBucketMap.ContainsKey(experimentId) ? - userProfile.ExperimentBucketMap[experimentId] : null; + var decision = userProfile.ExperimentBucketMap.ContainsKey(experimentId) ? + userProfile.ExperimentBucketMap[experimentId] : + null; if (decision == null) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"No previously activated variation of experiment \"{experimentKey}\" for user \"{userProfile.UserId}\" found in user profile.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"No previously activated variation of experiment \"{experimentKey}\" for user \"{userProfile.UserId}\" found in user profile.")); return Result.NullResult(reasons); } try { - string variationId = decision.VariationId; + var variationId = decision.VariationId; - Variation savedVariation = config.ExperimentIdMap[experimentId].VariationIdToVariationMap.ContainsKey(variationId) - ? config.ExperimentIdMap[experimentId].VariationIdToVariationMap[variationId] - : null; + var savedVariation = + config.ExperimentIdMap[experimentId]. + VariationIdToVariationMap.ContainsKey(variationId) ? + config.ExperimentIdMap[experimentId]. + VariationIdToVariationMap[variationId] : + null; if (savedVariation == null) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userProfile.UserId}\" was previously bucketed into variation with ID \"{variationId}\" for experiment \"{experimentId}\", but no matching variation was found for that user. We will re-bucket the user.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"User \"{userProfile.UserId}\" was previously bucketed into variation with ID \"{variationId}\" for experiment \"{experimentId}\", but no matching variation was found for that user. We will re-bucket the user.")); return Result.NullResult(reasons); } - Logger.Log(LogLevel.INFO, reasons.AddInfo($"Returning previously activated variation \"{savedVariation.Key}\" of experiment \"{experimentKey}\" for user \"{userProfile.UserId}\" from user profile.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"Returning previously activated variation \"{savedVariation.Key}\" of experiment \"{experimentKey}\" for user \"{userProfile.UserId}\" from user profile.")); return Result.NewResult(savedVariation, reasons); } catch (Exception) @@ -373,11 +457,15 @@ public Result GetStoredVariation(Experiment experiment, UserProfile u /// The experiment the user was buck /// The Variation to save. /// instance of the user information. - public void SaveVariation(Experiment experiment, Variation variation, UserProfile userProfile) + public void SaveVariation(Experiment experiment, Variation variation, + UserProfile userProfile + ) { //only save if the user has implemented a user profile service if (UserProfileService == null) + { return; + } Decision decision; if (userProfile.ExperimentBucketMap.ContainsKey(experiment.Id)) @@ -395,12 +483,15 @@ public void SaveVariation(Experiment experiment, Variation variation, UserProfil try { UserProfileService.Save(userProfile.ToMap()); - Logger.Log(LogLevel.INFO, $"Saved variation \"{variation.Id}\" of experiment \"{experiment.Id}\" for user \"{userProfile.UserId}\"."); + Logger.Log(LogLevel.INFO, + $"Saved variation \"{variation.Id}\" of experiment \"{experiment.Id}\" for user \"{userProfile.UserId}\"."); } catch (Exception exception) { - Logger.Log(LogLevel.ERROR, $"Failed to save variation \"{variation.Id}\" of experiment \"{experiment.Id}\" for user \"{userProfile.UserId}\"."); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + Logger.Log(LogLevel.ERROR, + $"Failed to save variation \"{variation.Id}\" of experiment \"{experiment.Id}\" for user \"{userProfile.UserId}\"."); + ErrorHandler.HandleError( + new Exceptions.OptimizelyRuntimeException(exception.Message)); } } @@ -413,9 +504,11 @@ public void SaveVariation(Experiment experiment, Variation variation, UserProfil /// The user context. /// null if the user is not bucketed into the rollout or if the feature flag was not attached to a rollout. /// otherwise the FeatureDecision entity - public virtual Result GetVariationForFeatureRollout(FeatureFlag featureFlag, + public virtual Result GetVariationForFeatureRollout( + FeatureFlag featureFlag, OptimizelyUserContext user, - ProjectConfig config) + ProjectConfig config + ) { var reasons = new DecisionReasons(); @@ -427,19 +520,24 @@ public virtual Result GetVariationForFeatureRollout(FeatureFlag if (string.IsNullOrEmpty(featureFlag.RolloutId)) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"The feature flag \"{featureFlag.Key}\" is not used in a rollout.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"The feature flag \"{featureFlag.Key}\" is not used in a rollout.")); return Result.NullResult(reasons); } - Rollout rollout = config.GetRolloutFromId(featureFlag.RolloutId); + var rollout = config.GetRolloutFromId(featureFlag.RolloutId); if (string.IsNullOrEmpty(rollout.Id)) { - Logger.Log(LogLevel.ERROR, reasons.AddInfo($"The rollout with id \"{featureFlag.RolloutId}\" is not found in the datafile for feature flag \"{featureFlag.Key}\"")); + Logger.Log(LogLevel.ERROR, + reasons.AddInfo( + $"The rollout with id \"{featureFlag.RolloutId}\" is not found in the datafile for feature flag \"{featureFlag.Key}\"")); return Result.NullResult(reasons); } - if (rollout.Experiments == null || rollout.Experiments.Count == 0) { + if (rollout.Experiments == null || rollout.Experiments.Count == 0) + { return Result.NullResult(reasons); } @@ -463,7 +561,9 @@ public virtual Result GetVariationForFeatureRollout(FeatureFlag reasons += forcedDecisionResponse.DecisionReasons; if (forcedDecisionResponse.ResultObject != null) { - return Result.NewResult(new FeatureDecision(rule, forcedDecisionResponse.ResultObject, null), reasons); + return Result.NewResult( + new FeatureDecision(rule, forcedDecisionResponse.ResultObject, null), + reasons); } // Regular decision @@ -477,35 +577,48 @@ public virtual Result GetVariationForFeatureRollout(FeatureFlag var loggingKey = everyoneElse ? "Everyone Else" : string.Format("{0}", index + 1); // Evaluate if user meets the audience condition of this rollout rule - var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, rule, user, LOGGING_KEY_TYPE_RULE, rule.Key, Logger); + var doesUserMeetAudienceConditionsResult = + ExperimentUtils.DoesUserMeetAudienceConditions(config, rule, user, + LOGGING_KEY_TYPE_RULE, rule.Key, Logger); reasons += doesUserMeetAudienceConditionsResult.DecisionReasons; if (doesUserMeetAudienceConditionsResult.ResultObject) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" meets condition for targeting rule \"{loggingKey}\".")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"User \"{userId}\" meets condition for targeting rule \"{loggingKey}\".")); - var bucketedVariation = Bucketer.Bucket(config, rule, bucketingIdResult.ResultObject, userId); + var bucketedVariation = Bucketer.Bucket(config, rule, + bucketingIdResult.ResultObject, userId); reasons += bucketedVariation?.DecisionReasons; if (bucketedVariation?.ResultObject?.Key != null) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" is in the traffic group of targeting rule \"{loggingKey}\".")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"User \"{userId}\" is in the traffic group of targeting rule \"{loggingKey}\".")); - return Result.NewResult(new FeatureDecision(rule, bucketedVariation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons); + return Result.NewResult( + new FeatureDecision(rule, bucketedVariation.ResultObject, + FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons); } else if (!everyoneElse) { //skip this logging for everyoneElse rule since this has a message not for everyoneElse - Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{userId}\" is not in the traffic group for targeting rule \"{loggingKey}\". Checking EveryoneElse rule now.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"User \"{userId}\" is not in the traffic group for targeting rule \"{loggingKey}\". Checking EveryoneElse rule now.")); skipToEveryoneElse = true; } } else { - Logger.Log(LogLevel.DEBUG, reasons.AddInfo($"User \"{userId}\" does not meet the conditions for targeting rule \"{loggingKey}\".")); + Logger.Log(LogLevel.DEBUG, + reasons.AddInfo( + $"User \"{userId}\" does not meet the conditions for targeting rule \"{loggingKey}\".")); } // the last rule is special for "Everyone Else" - index = skipToEveryoneElse ? (rolloutRulesLength - 1) : (index + 1); + index = skipToEveryoneElse ? rolloutRulesLength - 1 : index + 1; } return Result.NullResult(reasons); @@ -520,11 +633,13 @@ public virtual Result GetVariationForFeatureRollout(FeatureFlag /// The user's attributes. This should be filtered to just attributes in the Datafile. /// null if the user is not bucketed into the rollout or if the feature flag was not attached to a rollout. /// Otherwise the FeatureDecision entity - public virtual Result GetVariationForFeatureExperiment(FeatureFlag featureFlag, + public virtual Result GetVariationForFeatureExperiment( + FeatureFlag featureFlag, OptimizelyUserContext user, UserAttributes filteredAttributes, ProjectConfig config, - OptimizelyDecideOption[] options) + OptimizelyDecideOption[] options + ) { var reasons = new DecisionReasons(); var userId = user.GetUserId(); @@ -536,7 +651,9 @@ public virtual Result GetVariationForFeatureExperiment(FeatureF if (featureFlag.ExperimentIds == null || featureFlag.ExperimentIds.Count == 0) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"The feature flag \"{featureFlag.Key}\" is not used in any experiments.")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"The feature flag \"{featureFlag.Key}\" is not used in any experiments.")); return Result.NullResult(reasons); } @@ -546,34 +663,43 @@ public virtual Result GetVariationForFeatureExperiment(FeatureF Variation decisionVariation = null; if (string.IsNullOrEmpty(experiment.Key)) + { continue; - var decisionContext = new OptimizelyDecisionContext(featureFlag.Key, experiment?.Key); + } + + var decisionContext = + new OptimizelyDecisionContext(featureFlag.Key, experiment?.Key); var forcedDecisionResponse = ValidatedForcedDecision(decisionContext, config, user); reasons += forcedDecisionResponse.DecisionReasons; - - if (forcedDecisionResponse?.ResultObject != null) + + if (forcedDecisionResponse?.ResultObject != null) { decisionVariation = forcedDecisionResponse.ResultObject; - } - else + } + else { var decisionResponse = GetVariation(experiment, user, config, options); - + reasons += decisionResponse?.DecisionReasons; decisionVariation = decisionResponse.ResultObject; } if (!string.IsNullOrEmpty(decisionVariation?.Id)) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"The user \"{userId}\" is bucketed into experiment \"{experiment.Key}\" of feature \"{featureFlag.Key}\".")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"The user \"{userId}\" is bucketed into experiment \"{experiment.Key}\" of feature \"{featureFlag.Key}\".")); - var featureDecision = new FeatureDecision(experiment, decisionVariation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST); + var featureDecision = new FeatureDecision(experiment, decisionVariation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST); return Result.NewResult(featureDecision, reasons); } } - Logger.Log(LogLevel.INFO, reasons.AddInfo($"The user \"{userId}\" is not bucketed into any of the experiments on the feature \"{featureFlag.Key}\".")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"The user \"{userId}\" is not bucketed into any of the experiments on the feature \"{featureFlag.Key}\".")); return Result.NullResult(reasons); } @@ -585,9 +711,12 @@ public virtual Result GetVariationForFeatureExperiment(FeatureF /// The user's attributes. This should be filtered to just attributes in the Datafile. /// null if the user is not bucketed into any variation or the FeatureDecision entity if the user is /// successfully bucketed. - public virtual Result GetVariationForFeature(FeatureFlag featureFlag, OptimizelyUserContext user, ProjectConfig config) + public virtual Result GetVariationForFeature(FeatureFlag featureFlag, + OptimizelyUserContext user, ProjectConfig config + ) { - return GetVariationForFeature(featureFlag, user, config, user.GetAttributes(), new OptimizelyDecideOption[] { }); + return GetVariationForFeature(featureFlag, user, config, user.GetAttributes(), + new OptimizelyDecideOption[] { }); } /// @@ -604,12 +733,14 @@ public virtual Result GetVariationForFeature(FeatureFlag featur OptimizelyUserContext user, ProjectConfig config, UserAttributes filteredAttributes, - OptimizelyDecideOption[] options) + OptimizelyDecideOption[] options + ) { var reasons = new DecisionReasons(); var userId = user.GetUserId(); // Check if the feature flag has an experiment and the user is bucketed into that experiment. - var decisionResult = GetVariationForFeatureExperiment(featureFlag, user, filteredAttributes, config, options); + var decisionResult = GetVariationForFeatureExperiment(featureFlag, user, + filteredAttributes, config, options); reasons += decisionResult.DecisionReasons; if (decisionResult.ResultObject != null) @@ -623,12 +754,18 @@ public virtual Result GetVariationForFeature(FeatureFlag featur if (decisionResult.ResultObject != null) { - Logger.Log(LogLevel.INFO, reasons.AddInfo($"The user \"{userId}\" is bucketed into a rollout for feature flag \"{featureFlag.Key}\".")); + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"The user \"{userId}\" is bucketed into a rollout for feature flag \"{featureFlag.Key}\".")); return Result.NewResult(decisionResult.ResultObject, reasons); } - Logger.Log(LogLevel.INFO, reasons.AddInfo($"The user \"{userId}\" is not bucketed into a rollout for feature flag \"{featureFlag.Key}\".")); - return Result.NewResult(new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons); ; + Logger.Log(LogLevel.INFO, + reasons.AddInfo( + $"The user \"{userId}\" is not bucketed into a rollout for feature flag \"{featureFlag.Key}\".")); + return Result.NewResult( + new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), reasons); + ; } /// @@ -640,19 +777,23 @@ public virtual Result GetVariationForFeature(FeatureFlag featur private Result GetBucketingId(string userId, UserAttributes filteredAttributes) { var reasons = new DecisionReasons(); - string bucketingId = userId; + var bucketingId = userId; // If the bucketing ID key is defined in attributes, then use that in place of the userID for the murmur hash key - if (filteredAttributes != null && filteredAttributes.ContainsKey(ControlAttributes.BUCKETING_ID_ATTRIBUTE)) + if (filteredAttributes != null && + filteredAttributes.ContainsKey(ControlAttributes.BUCKETING_ID_ATTRIBUTE)) { if (filteredAttributes[ControlAttributes.BUCKETING_ID_ATTRIBUTE] is string) { - bucketingId = (string)filteredAttributes[ControlAttributes.BUCKETING_ID_ATTRIBUTE]; + bucketingId = + (string)filteredAttributes[ControlAttributes.BUCKETING_ID_ATTRIBUTE]; Logger.Log(LogLevel.DEBUG, $"BucketingId is valid: \"{bucketingId}\""); } else { - Logger.Log(LogLevel.WARN, reasons.AddInfo("BucketingID attribute is not a string. Defaulted to userId")); + Logger.Log(LogLevel.WARN, + reasons.AddInfo( + "BucketingID attribute is not a string. Defaulted to userId")); } } @@ -666,23 +807,36 @@ private Result GetBucketingId(string userId, UserAttributes filteredAttr /// The Project config. /// Optimizely user context. /// A result with the variation - public Result ValidatedForcedDecision(OptimizelyDecisionContext context, ProjectConfig config, OptimizelyUserContext user) + public Result ValidatedForcedDecision(OptimizelyDecisionContext context, + ProjectConfig config, OptimizelyUserContext user + ) { - DecisionReasons reasons = new DecisionReasons(); + var reasons = new DecisionReasons(); var userId = user.GetUserId(); var forcedDecision = user.GetForcedDecision(context); - if (config != null && forcedDecision != null) { - var loggingKey = context.RuleKey != null ? "flag (" + context.FlagKey + "), rule (" + context.RuleKey + ")" : "flag (" + context.FlagKey + ")"; + if (config != null && forcedDecision != null) + { + var loggingKey = context.RuleKey != null ? + "flag (" + context.FlagKey + "), rule (" + context.RuleKey + ")" : + "flag (" + context.FlagKey + ")"; var variationKey = forcedDecision.VariationKey; var variation = config.GetFlagVariationByKey(context.FlagKey, variationKey); - if (variation != null) { + if (variation != null) + { reasons.AddInfo("Decided by forced decision."); - reasons.AddInfo("Variation ({0}) is mapped to {1} and user ({2}) in the forced decision map.", variationKey, loggingKey, userId); + reasons.AddInfo( + "Variation ({0}) is mapped to {1} and user ({2}) in the forced decision map.", + variationKey, loggingKey, userId); return Result.NewResult(variation, reasons); - } else { - reasons.AddInfo("Invalid variation is mapped to {0} and user ({1}) in the forced decision map.", loggingKey, userId); + } + else + { + reasons.AddInfo( + "Invalid variation is mapped to {0} and user ({1}) in the forced decision map.", + loggingKey, userId); } } + return Result.NullResult(reasons); } } diff --git a/OptimizelySDK/Bucketing/UserProfile.cs b/OptimizelySDK/Bucketing/UserProfile.cs index 8cebb594d..3f7512ded 100644 --- a/OptimizelySDK/Bucketing/UserProfile.cs +++ b/OptimizelySDK/Bucketing/UserProfile.cs @@ -55,7 +55,7 @@ public UserProfile(string userId, Dictionary experimentBucketM UserId = userId; ExperimentBucketMap = experimentBucketMap; } - + /// /// Convert a User Profile instance to a Map. /// @@ -64,9 +64,11 @@ public Dictionary ToMap() { return new Dictionary { - { UserProfile.USER_ID_KEY, UserId }, - { UserProfile.EXPERIMENT_BUCKET_MAP_KEY, - ExperimentBucketMap.ToDictionary(row => row.Key, row => row.Value.ToMap()) } + { USER_ID_KEY, UserId }, + { + EXPERIMENT_BUCKET_MAP_KEY, + ExperimentBucketMap.ToDictionary(row => row.Key, row => row.Value.ToMap()) + }, }; } } diff --git a/OptimizelySDK/Bucketing/UserProfileService.cs b/OptimizelySDK/Bucketing/UserProfileService.cs index 409437d9c..f5e816235 100644 --- a/OptimizelySDK/Bucketing/UserProfileService.cs +++ b/OptimizelySDK/Bucketing/UserProfileService.cs @@ -19,14 +19,13 @@ namespace OptimizelySDK.Bucketing { - /// /// Class encapsulating user profile service functionality. /// Override with your own implementation for storing and retrieving the user profile. /// public interface UserProfileService { - Dictionary Lookup(String userId); + Dictionary Lookup(String userId); void Save(Dictionary userProfile); } diff --git a/OptimizelySDK/Bucketing/UserProfileUtil.cs b/OptimizelySDK/Bucketing/UserProfileUtil.cs index 0916b7503..582d57a66 100644 --- a/OptimizelySDK/Bucketing/UserProfileUtil.cs +++ b/OptimizelySDK/Bucketing/UserProfileUtil.cs @@ -31,7 +31,9 @@ public static bool IsValidUserProfileMap(Dictionary map) // The Map must contain a value for the user ID and experiment bucket map if (!map.ContainsKey(UserProfile.USER_ID_KEY) || !map.ContainsKey(UserProfile.EXPERIMENT_BUCKET_MAP_KEY)) + { return false; + } // the map is good enough for us to use return true; @@ -44,14 +46,16 @@ public static bool IsValidUserProfileMap(Dictionary map) /// A UserProfile instance. public static UserProfile ConvertMapToUserProfile(Dictionary map) { - var experimentBucketMap = (Dictionary>)map[UserProfile.EXPERIMENT_BUCKET_MAP_KEY]; - Dictionary decisions = experimentBucketMap.ToDictionary( - keySelector: kvp => kvp.Key, - elementSelector: kvp => new Decision(kvp.Value[UserProfile.VARIATION_ID_KEY])); + var experimentBucketMap = + (Dictionary>)map[ + UserProfile.EXPERIMENT_BUCKET_MAP_KEY]; + var decisions = experimentBucketMap.ToDictionary( + kvp => kvp.Key, + kvp => new Decision(kvp.Value[UserProfile.VARIATION_ID_KEY])); return new UserProfile( - userId: (string)map[UserProfile.USER_ID_KEY], - experimentBucketMap: decisions); + (string)map[UserProfile.USER_ID_KEY], + decisions); } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/ClientConfigHandler.cs b/OptimizelySDK/ClientConfigHandler.cs index c9d277861..62bde77f5 100644 --- a/OptimizelySDK/ClientConfigHandler.cs +++ b/OptimizelySDK/ClientConfigHandler.cs @@ -21,79 +21,46 @@ namespace OptimizelySDK public class HttpProjectConfigElement : ConfigurationElement { [ConfigurationProperty("sdkKey", IsRequired = true, IsKey = true)] - public string SDKKey - { - get { return (string)base["sdkKey"]; } - } + public string SDKKey => (string)base["sdkKey"]; [ConfigurationProperty("url")] - public string Url - { - get { return (string)base["url"]; } - } + public string Url => (string)base["url"]; [ConfigurationProperty("format")] - public string Format - { - get { return (string)base["format"]; } - } + public string Format => (string)base["format"]; [ConfigurationProperty("pollingInterval")] - public int PollingInterval - { - get { return base["pollingInterval"] is int ? (int)base["pollingInterval"] : 0; } - } + public int PollingInterval => + base["pollingInterval"] is int ? (int)base["pollingInterval"] : 0; [ConfigurationProperty("blockingTimeOutPeriod")] - public int BlockingTimeOutPeriod - { - get { return base["blockingTimeOutPeriod"] is int ? (int)base["blockingTimeOutPeriod"] : 0; } - } + public int BlockingTimeOutPeriod => + base["blockingTimeOutPeriod"] is int ? (int)base["blockingTimeOutPeriod"] : 0; [ConfigurationProperty("autoUpdate")] - public bool AutoUpdate - { - get { return (bool)base["autoUpdate"]; } - } + public bool AutoUpdate => (bool)base["autoUpdate"]; [ConfigurationProperty("defaultStart")] - public bool DefaultStart - { - get { return (bool)base["defaultStart"]; } - } + public bool DefaultStart => (bool)base["defaultStart"]; [ConfigurationProperty("datafileAccessToken")] - public string DatafileAccessToken - { - get { return (string)base["datafileAccessToken"]; } - } + public string DatafileAccessToken => (string)base["datafileAccessToken"]; } public class BatchEventProcessorElement : ConfigurationElement { [ConfigurationProperty("batchSize")] - public int BatchSize - { - get { return (int)base["batchSize"]; } - } + public int BatchSize => (int)base["batchSize"]; [ConfigurationProperty("flushInterval")] - public int FlushInterval - { - get { return base["flushInterval"] is int ? (int)base["flushInterval"] : 0; } - } + public int FlushInterval => base["flushInterval"] is int ? (int)base["flushInterval"] : 0; [ConfigurationProperty("timeoutInterval")] - public int TimeoutInterval - { - get { return base["timeoutInterval"] is int ? (int)base["timeoutInterval"] : 0; } - } + public int TimeoutInterval => + base["timeoutInterval"] is int ? (int)base["timeoutInterval"] : 0; [ConfigurationProperty("defaultStart")] - public bool DefaultStart - { - get { return (bool)base["defaultStart"]; } - } + public bool DefaultStart => (bool)base["defaultStart"]; } public class OptimizelySDKConfigSection : ConfigurationSection @@ -101,14 +68,15 @@ public class OptimizelySDKConfigSection : ConfigurationSection [ConfigurationProperty("HttpProjectConfig")] public HttpProjectConfigElement HttpProjectConfig { - get { return (HttpProjectConfigElement)base["HttpProjectConfig"]; } - set { base["HttpProjectConfig"] = value; } + get => (HttpProjectConfigElement)base["HttpProjectConfig"]; + set => base["HttpProjectConfig"] = value; } [ConfigurationProperty("BatchEventProcessor")] - public BatchEventProcessorElement BatchEventProcessor { - get { return (BatchEventProcessorElement)(base["BatchEventProcessor"]); } - set { base["BatchEventProcessor"] = value; } + public BatchEventProcessorElement BatchEventProcessor + { + get => (BatchEventProcessorElement)base["BatchEventProcessor"]; + set => base["BatchEventProcessor"] = value; } } } diff --git a/OptimizelySDK/Config/DatafileProjectConfig.cs b/OptimizelySDK/Config/DatafileProjectConfig.cs index 4fd9bb4b0..cec0fa715 100644 --- a/OptimizelySDK/Config/DatafileProjectConfig.cs +++ b/OptimizelySDK/Config/DatafileProjectConfig.cs @@ -44,7 +44,7 @@ public enum OPTLYSDKVersion { V2 = 2, V3 = 3, - V4 = 4 + V4 = 4, } /// @@ -119,7 +119,7 @@ public enum OPTLYSDKVersion { OPTLYSDKVersion.V2, OPTLYSDKVersion.V3, - OPTLYSDKVersion.V4 + OPTLYSDKVersion.V4, }; //========================= Mappings =========================== @@ -129,14 +129,14 @@ public enum OPTLYSDKVersion /// private Dictionary _GroupIdMap; - public Dictionary GroupIdMap { get { return _GroupIdMap; } } + public Dictionary GroupIdMap => _GroupIdMap; /// /// Associative array of experiment key to Experiment(s) in the datafile /// private Dictionary _ExperimentKeyMap; - public Dictionary ExperimentKeyMap { get { return _ExperimentKeyMap; } } + public Dictionary ExperimentKeyMap => _ExperimentKeyMap; /// /// Associative array of experiment ID to Experiment(s) in the datafile @@ -144,7 +144,7 @@ public enum OPTLYSDKVersion private Dictionary _ExperimentIdMap = new Dictionary(); - public Dictionary ExperimentIdMap { get { return _ExperimentIdMap; } } + public Dictionary ExperimentIdMap => _ExperimentIdMap; /// /// Associative array of experiment key to associative array of variation key to variations @@ -152,10 +152,8 @@ private Dictionary _ExperimentIdMap private Dictionary> _VariationKeyMap = new Dictionary>(); - public Dictionary> VariationKeyMap - { - get { return _VariationKeyMap; } - } + public Dictionary> VariationKeyMap => + _VariationKeyMap; /// /// Associative array of experiment ID to associative array of variation key to variations @@ -163,10 +161,8 @@ public Dictionary> VariationKeyMap private Dictionary> _VariationKeyMapByExperimentId = new Dictionary>(); - public Dictionary> VariationKeyMapByExperimentId - { - get { return _VariationKeyMapByExperimentId; } - } + public Dictionary> VariationKeyMapByExperimentId => + _VariationKeyMapByExperimentId; /// /// Associative array of experiment ID to associative array of variation key to variations @@ -174,10 +170,8 @@ public Dictionary> VariationKeyMapByExperi private Dictionary> _VariationIdMapByExperimentId = new Dictionary>(); - public Dictionary> VariationKeyIdByExperimentId - { - get { return _VariationIdMapByExperimentId; } - } + public Dictionary> VariationKeyIdByExperimentId => + _VariationIdMapByExperimentId; /// /// Associative array of experiment key to associative array of variation ID to variations @@ -185,45 +179,42 @@ public Dictionary> VariationKeyIdByExperim private Dictionary> _VariationIdMap = new Dictionary>(); - public Dictionary> VariationIdMap - { - get { return _VariationIdMap; } - } + public Dictionary> VariationIdMap => _VariationIdMap; /// /// Associative array of event key to Event(s) in the datafile /// private Dictionary _EventKeyMap; - public Dictionary EventKeyMap { get { return _EventKeyMap; } } + public Dictionary EventKeyMap => _EventKeyMap; /// /// Associative array of attribute key to Attribute(s) in the datafile /// private Dictionary _AttributeKeyMap; - public Dictionary AttributeKeyMap { get { return _AttributeKeyMap; } } + public Dictionary AttributeKeyMap => _AttributeKeyMap; /// /// Associative array of audience ID to Audience(s) in the datafile /// private Dictionary _AudienceIdMap; - public Dictionary AudienceIdMap { get { return _AudienceIdMap; } } + public Dictionary AudienceIdMap => _AudienceIdMap; /// /// Associative array of Feature Key to Feature(s) in the datafile /// private Dictionary _FeatureKeyMap; - public Dictionary FeatureKeyMap { get { return _FeatureKeyMap; } } + public Dictionary FeatureKeyMap => _FeatureKeyMap; /// /// Associative array of Rollout ID to Rollout(s) in the datafile /// private Dictionary _RolloutIdMap; - public Dictionary RolloutIdMap { get { return _RolloutIdMap; } } + public Dictionary RolloutIdMap => _RolloutIdMap; /// /// Associative array of experiment IDs that exist in any feature @@ -238,10 +229,8 @@ public Dictionary> VariationIdMap private Dictionary> _FlagVariationMap = new Dictionary>(); - public Dictionary> FlagVariationMap - { - get { return _FlagVariationMap; } - } + public Dictionary> FlagVariationMap => + _FlagVariationMap; //========================= Interfaces =========================== @@ -323,34 +312,36 @@ private void Initialize() Integrations = Integrations ?? new Integration[0]; _ExperimentKeyMap = new Dictionary(); - _GroupIdMap = ConfigParser.GenerateMap(entities: Groups, - getKey: g => g.Id.ToString(), clone: true); - _ExperimentIdMap = ConfigParser.GenerateMap(entities: Experiments, - getKey: e => e.Id, clone: true); + _GroupIdMap = ConfigParser.GenerateMap(Groups, + g => g.Id.ToString(), true); + _ExperimentIdMap = ConfigParser.GenerateMap(Experiments, + e => e.Id, true); _EventKeyMap = - ConfigParser.GenerateMap(entities: Events, getKey: e => e.Key, - clone: true); - _AttributeKeyMap = ConfigParser.GenerateMap(entities: Attributes, - getKey: a => a.Key, clone: true); - _AudienceIdMap = ConfigParser.GenerateMap(entities: Audiences, - getKey: a => a.Id.ToString(), clone: true); - _FeatureKeyMap = ConfigParser.GenerateMap(entities: FeatureFlags, - getKey: f => f.Key, clone: true); - _RolloutIdMap = ConfigParser.GenerateMap(entities: Rollouts, - getKey: r => r.Id.ToString(), clone: true); + ConfigParser.GenerateMap(Events, e => e.Key, + true); + _AttributeKeyMap = ConfigParser.GenerateMap(Attributes, + a => a.Key, true); + _AudienceIdMap = ConfigParser.GenerateMap(Audiences, + a => a.Id.ToString(), true); + _FeatureKeyMap = ConfigParser.GenerateMap(FeatureFlags, + f => f.Key, true); + _RolloutIdMap = ConfigParser.GenerateMap(Rollouts, + r => r.Id.ToString(), true); // Overwrite similar items in audience id map with typed audience id map. - var typedAudienceIdMap = ConfigParser.GenerateMap(entities: TypedAudiences, - getKey: a => a.Id.ToString(), clone: true); + var typedAudienceIdMap = ConfigParser.GenerateMap(TypedAudiences, + a => a.Id.ToString(), true); foreach (var item in typedAudienceIdMap) + { _AudienceIdMap[item.Key] = item.Value; + } - foreach (Group group in Groups) + foreach (var group in Groups) { var experimentsInGroup = - ConfigParser.GenerateMap(group.Experiments, getKey: e => e.Id, - clone: true); - foreach (Experiment experiment in experimentsInGroup.Values) + ConfigParser.GenerateMap(group.Experiments, e => e.Id, + true); + foreach (var experiment in experimentsInGroup.Values) { experiment.GroupId = group.Id; experiment.GroupPolicy = group.Policy; @@ -359,10 +350,12 @@ private void Initialize() // RJE: I believe that this is equivalent to this: // $this->_experimentKeyMap = array_merge($this->_experimentKeyMap, $experimentsInGroup); foreach (var experiment in experimentsInGroup.Values) + { _ExperimentIdMap[experiment.Id] = experiment; + } } - foreach (Experiment experiment in _ExperimentIdMap.Values) + foreach (var experiment in _ExperimentIdMap.Values) { _VariationKeyMap[experiment.Key] = new Dictionary(); _VariationIdMap[experiment.Key] = new Dictionary(); @@ -373,7 +366,7 @@ private void Initialize() if (experiment.Variations != null) { - foreach (Variation variation in experiment.Variations) + foreach (var variation in experiment.Variations) { _VariationKeyMap[experiment.Key][variation.Key] = variation; _VariationIdMap[experiment.Key][variation.Id] = variation; @@ -437,7 +430,7 @@ private void Initialize() { ExperimentFeatureMap[experimentId] = new List { - feature.Id + feature.Id, }; } } @@ -470,7 +463,7 @@ public static ProjectConfig Create(string content, ILogger logger, IErrorHandler errorHandler ) { - DatafileProjectConfig config = GetConfig(content); + var config = GetConfig(content); config.Logger = logger ?? new NoOpLogger(); config.ErrorHandler = errorHandler ?? new NoOpErrorHandler(logger); @@ -483,19 +476,25 @@ IErrorHandler errorHandler private static DatafileProjectConfig GetConfig(string configData) { if (configData == null) + { throw new ConfigParseException("Unable to parse null datafile."); + } if (string.IsNullOrEmpty(configData)) + { throw new ConfigParseException("Unable to parse empty datafile."); + } var config = JsonConvert.DeserializeObject(configData); config._datafile = configData; if (SupportedVersions.TrueForAll((supportedVersion) => !(((int)supportedVersion).ToString() == config.Version))) + { throw new ConfigParseException( $@"This version of the C# SDK does not support the given datafile version: { config.Version}"); + } return config; } @@ -510,12 +509,14 @@ private static DatafileProjectConfig GetConfig(string configData) public Group GetGroup(string groupId) { if (_GroupIdMap.ContainsKey(groupId)) + { return _GroupIdMap[groupId]; + } - string message = $@"Group ID ""{groupId}"" is not in datafile."; + var message = $@"Group ID ""{groupId}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidGroupException("Provided group is not in datafile.")); + new InvalidGroupException("Provided group is not in datafile.")); return new Group(); } @@ -527,12 +528,14 @@ public Group GetGroup(string groupId) public Experiment GetExperimentFromKey(string experimentKey) { if (_ExperimentKeyMap.ContainsKey(experimentKey)) + { return _ExperimentKeyMap[experimentKey]; + } - string message = $@"Experiment key ""{experimentKey}"" is not in datafile."; + var message = $@"Experiment key ""{experimentKey}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidExperimentException( + new InvalidExperimentException( "Provided experiment is not in datafile.")); return new Experiment(); } @@ -545,12 +548,14 @@ public Experiment GetExperimentFromKey(string experimentKey) public Experiment GetExperimentFromId(string experimentId) { if (_ExperimentIdMap.ContainsKey(experimentId)) + { return _ExperimentIdMap[experimentId]; + } - string message = $@"Experiment ID ""{experimentId}"" is not in datafile."; + var message = $@"Experiment ID ""{experimentId}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidExperimentException( + new InvalidExperimentException( "Provided experiment is not in datafile.")); return new Experiment(); } @@ -563,12 +568,14 @@ public Experiment GetExperimentFromId(string experimentId) public Entity.Event GetEvent(string eventKey) { if (_EventKeyMap.ContainsKey(eventKey)) + { return _EventKeyMap[eventKey]; + } - string message = $@"Event key ""{eventKey}"" is not in datafile."; + var message = $@"Event key ""{eventKey}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidEventException("Provided event is not in datafile.")); + new InvalidEventException("Provided event is not in datafile.")); return new Entity.Event(); } @@ -580,12 +587,14 @@ public Entity.Event GetEvent(string eventKey) public Audience GetAudience(string audienceId) { if (_AudienceIdMap.ContainsKey(audienceId)) + { return _AudienceIdMap[audienceId]; + } - string message = $@"Audience ID ""{audienceId}"" is not in datafile."; + var message = $@"Audience ID ""{audienceId}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidAudienceException("Provided audience is not in datafile.")); + new InvalidAudienceException("Provided audience is not in datafile.")); return new Audience(); } @@ -597,12 +606,14 @@ public Audience GetAudience(string audienceId) public Attribute GetAttribute(string attributeKey) { if (_AttributeKeyMap.ContainsKey(attributeKey)) + { return _AttributeKeyMap[attributeKey]; + } - string message = $@"Attribute key ""{attributeKey}"" is not in datafile."; + var message = $@"Attribute key ""{attributeKey}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidAttributeException("Provided attribute is not in datafile.")); + new InvalidAttributeException("Provided attribute is not in datafile.")); return new Attribute(); } @@ -617,13 +628,15 @@ public Variation GetVariationFromKey(string experimentKey, string variationKey) { if (_VariationKeyMap.ContainsKey(experimentKey) && _VariationKeyMap[experimentKey].ContainsKey(variationKey)) + { return _VariationKeyMap[experimentKey][variationKey]; + } - string message = $@"No variation key ""{variationKey + var message = $@"No variation key ""{variationKey }"" defined in datafile for experiment ""{experimentKey}""."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidVariationException("Provided variation is not in datafile.")); + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -638,13 +651,15 @@ public Variation GetVariationFromKeyByExperimentId(string experimentId, string v { if (_VariationKeyMapByExperimentId.ContainsKey(experimentId) && _VariationKeyMapByExperimentId[experimentId].ContainsKey(variationKey)) + { return _VariationKeyMapByExperimentId[experimentId][variationKey]; + } - string message = $@"No variation key ""{variationKey + var message = $@"No variation key ""{variationKey }"" defined in datafile for experiment ""{experimentId}""."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidVariationException("Provided variation is not in datafile.")); + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -659,13 +674,15 @@ public Variation GetVariationFromId(string experimentKey, string variationId) { if (_VariationIdMap.ContainsKey(experimentKey) && _VariationIdMap[experimentKey].ContainsKey(variationId)) + { return _VariationIdMap[experimentKey][variationId]; + } - string message = $@"No variation ID ""{variationId + var message = $@"No variation ID ""{variationId }"" defined in datafile for experiment ""{experimentKey}""."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidVariationException("Provided variation is not in datafile.")); + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -680,13 +697,15 @@ public Variation GetVariationFromIdByExperimentId(string experimentId, string va { if (_VariationIdMapByExperimentId.ContainsKey(experimentId) && _VariationIdMapByExperimentId[experimentId].ContainsKey(variationId)) + { return _VariationIdMapByExperimentId[experimentId][variationId]; + } - string message = $@"No variation ID ""{variationId + var message = $@"No variation ID ""{variationId }"" defined in datafile for experiment ""{experimentId}""."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidVariationException("Provided variation is not in datafile.")); + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -698,12 +717,14 @@ public Variation GetVariationFromIdByExperimentId(string experimentId, string va public FeatureFlag GetFeatureFlagFromKey(string featureKey) { if (_FeatureKeyMap.ContainsKey(featureKey)) + { return _FeatureKeyMap[featureKey]; + } - string message = $@"Feature key ""{featureKey}"" is not in datafile."; + var message = $@"Feature key ""{featureKey}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidFeatureException("Provided feature is not in datafile.")); + new InvalidFeatureException("Provided feature is not in datafile.")); return new FeatureFlag(); } @@ -741,12 +762,14 @@ public Rollout GetRolloutFromId(string rolloutId) } if (_RolloutIdMap.ContainsKey(rolloutId)) + { return _RolloutIdMap[rolloutId]; + } - string message = $@"Rollout ID ""{rolloutId}"" is not in datafile."; + var message = $@"Rollout ID ""{rolloutId}"" is not in datafile."; Logger.Log(LogLevel.ERROR, message); ErrorHandler.HandleError( - new Exceptions.InvalidRolloutException("Provided rollout is not in datafile.")); + new InvalidRolloutException("Provided rollout is not in datafile.")); return new Rollout(); } @@ -763,10 +786,12 @@ public string GetAttributeId(string attributeKey) { var attribute = _AttributeKeyMap[attributeKey]; if (hasReservedPrefix) + { Logger.Log(LogLevel.WARN, $@"Attribute {attributeKey} unexpectedly has reserved prefix { RESERVED_ATTRIBUTE_PREFIX }; using attribute ID instead of reserved attribute name."); + } return attribute.Id; } diff --git a/OptimizelySDK/Config/FallbackProjectConfigManager.cs b/OptimizelySDK/Config/FallbackProjectConfigManager.cs index efe1a5d51..f30aa9b24 100644 --- a/OptimizelySDK/Config/FallbackProjectConfigManager.cs +++ b/OptimizelySDK/Config/FallbackProjectConfigManager.cs @@ -48,24 +48,12 @@ public ProjectConfig GetConfig() /// /// SDK Key for Fallback is not used and always null /// - public virtual string SdkKey - { - get - { - return null; - } - } + public virtual string SdkKey => null; /// /// Access to current cached project configuration /// /// ProjectConfig instance - public virtual ProjectConfig CachedProjectConfig - { - get - { - return ProjectConfig; - } - } + public virtual ProjectConfig CachedProjectConfig => ProjectConfig; } } diff --git a/OptimizelySDK/Config/HttpProjectConfigManager.cs b/OptimizelySDK/Config/HttpProjectConfigManager.cs index a3d88f6cc..bcfce2fdd 100644 --- a/OptimizelySDK/Config/HttpProjectConfigManager.cs +++ b/OptimizelySDK/Config/HttpProjectConfigManager.cs @@ -86,7 +86,7 @@ public static System.Net.Http.HttpClientHandler GetHttpClientHandler() var handler = new System.Net.Http.HttpClientHandler() { AutomaticDecompression = System.Net.DecompressionMethods.GZip | - System.Net.DecompressionMethods.Deflate + System.Net.DecompressionMethods.Deflate, }; return handler; @@ -118,7 +118,9 @@ private string GetRemoteDatafileResponse() // Send If-Modified-Since header if Last-Modified-Since header contains any value. if (!string.IsNullOrEmpty(LastModifiedSince)) + { request.Headers.Add("If-Modified-Since", LastModifiedSince); + } if (!string.IsNullOrEmpty(DatafileAccessToken)) { @@ -139,11 +141,15 @@ private string GetRemoteDatafileResponse() } // Update Last-Modified header if provided. - if (result.Headers.TryGetValues("Last-Modified", out IEnumerable values)) + if (result.Headers.TryGetValues("Last-Modified", out var values)) + { LastModifiedSince = values.First(); + } if (result.StatusCode == System.Net.HttpStatusCode.NotModified) + { return null; + } var content = result.Content.ReadAsStringAsync(); content.Wait(); @@ -157,21 +163,27 @@ private string GetRemoteDatafileResponse() // Send If-Modified-Since header if Last-Modified-Since header contains any value. if (!string.IsNullOrEmpty(LastModifiedSince)) + { request.Headers.Add("If-Modified-Since", LastModifiedSince); + } + var result = (System.Net.HttpWebResponse)request.GetResponse(); - - if (result.StatusCode != System.Net.HttpStatusCode.OK) { + + if (result.StatusCode != System.Net.HttpStatusCode.OK) + { Logger.Log(LogLevel.ERROR, $"Error fetching datafile \"{result.StatusCode}\""); } + var lastModified = result.Headers.GetValues("Last-Modified"); - if(!string.IsNullOrEmpty(lastModified.First())) + if (!string.IsNullOrEmpty(lastModified.First())) { LastModifiedSince = lastModified.First(); } var encoding = System.Text.Encoding.ASCII; - using (var reader = new System.IO.StreamReader(result.GetResponseStream(), encoding)) { - string responseText = reader.ReadToEnd(); + using (var reader = new System.IO.StreamReader(result.GetResponseStream(), encoding)) + { + var responseText = reader.ReadToEnd(); return responseText; } } @@ -186,7 +198,9 @@ protected override ProjectConfig Poll() var datafile = GetRemoteDatafileResponse(); if (datafile == null) + { return null; + } return DatafileProjectConfig.Create(datafile, Logger, ErrorHandler); } @@ -245,7 +259,7 @@ public Builder WithSdkKey(string sdkKey) #if !NET40 && !NET35 public Builder WithAccessToken(string accessToken) { - this.DatafileAccessToken = accessToken; + DatafileAccessToken = accessToken; return this; } @@ -330,10 +344,14 @@ public HttpProjectConfigManager Build(bool defer) HttpProjectConfigManager configManager = null; if (Logger == null) + { Logger = new DefaultLogger(); + } if (ErrorHandler == null) + { ErrorHandler = new DefaultErrorHandler(Logger, false); + } if (string.IsNullOrEmpty(Format)) { @@ -418,11 +436,15 @@ public HttpProjectConfigManager Build(bool defer) if (StartByDefault) + { configManager.Start(); + } // Optionally block until config is available. if (!defer) + { configManager.GetConfig(); + } return configManager; } diff --git a/OptimizelySDK/Config/PollingProjectConfigManager.cs b/OptimizelySDK/Config/PollingProjectConfigManager.cs index 671e8c134..1b5e9d62a 100644 --- a/OptimizelySDK/Config/PollingProjectConfigManager.cs +++ b/OptimizelySDK/Config/PollingProjectConfigManager.cs @@ -101,7 +101,11 @@ public void Start() /// public void Stop() { - if (Disposed) return; + if (Disposed) + { + return; + } + // don't call now and onwards. SchedulerService.Change(-1, -1); @@ -116,13 +120,16 @@ public void Stop() /// ProjectConfig public ProjectConfig GetConfig() { - if (Disposed) return null; + if (Disposed) + { + return null; + } if (IsStarted) { try { - bool isCompleted = CompletableConfigManager.Task.Wait(BlockingTimeout); + var isCompleted = CompletableConfigManager.Task.Wait(BlockingTimeout); if (!isCompleted) { // Don't wait next time. @@ -150,13 +157,7 @@ public ProjectConfig GetConfig() /// Access to current cached project configuration /// /// ProjectConfig instance - public virtual ProjectConfig CachedProjectConfig - { - get - { - return CurrentProjectConfig; - } - } + public virtual ProjectConfig CachedProjectConfig => CurrentProjectConfig; /// /// Sets the latest available ProjectConfig valid instance. @@ -166,12 +167,16 @@ public virtual ProjectConfig CachedProjectConfig public bool SetConfig(ProjectConfig projectConfig) { if (projectConfig == null) + { return false; + } var previousVersion = CurrentProjectConfig == null ? "null" : CurrentProjectConfig.Revision; if (projectConfig.Revision == previousVersion) + { return false; + } CurrentProjectConfig = projectConfig; SetOptimizelyConfig(CurrentProjectConfig); @@ -209,7 +214,10 @@ public OptimizelyConfig GetOptimizelyConfig() public virtual void Dispose() { - if (Disposed) return; + if (Disposed) + { + return; + } SchedulerService.Change(-1, -1); SchedulerService.Dispose(); @@ -231,7 +239,9 @@ public virtual void Run() // during in-flight, if PollingProjectConfigManagerStopped, then don't need to set. if (IsStarted) + { SetConfig(config); + } } catch (Exception exception) { diff --git a/OptimizelySDK/Entity/Attribute.cs b/OptimizelySDK/Entity/Attribute.cs index df3517274..231c09783 100644 --- a/OptimizelySDK/Entity/Attribute.cs +++ b/OptimizelySDK/Entity/Attribute.cs @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Entity { - public class Attribute : IdKeyEntity - { - } + public class Attribute : IdKeyEntity { } } diff --git a/OptimizelySDK/Entity/Audience.cs b/OptimizelySDK/Entity/Audience.cs index 8dd2673f8..467ebf932 100644 --- a/OptimizelySDK/Entity/Audience.cs +++ b/OptimizelySDK/Entity/Audience.cs @@ -49,7 +49,9 @@ public ICondition ConditionList get { if (Conditions == null) + { return null; + } if (_decodedConditions == null) { @@ -78,14 +80,20 @@ public string ConditionsString get { if (Conditions == null) + { return null; + } if (_conditionsString == null) { if (Conditions is JToken token) + { _conditionsString = token.ToString(Formatting.None); + } else + { _conditionsString = Conditions.ToString(); + } } return _conditionsString; diff --git a/OptimizelySDK/Entity/Entity.cs b/OptimizelySDK/Entity/Entity.cs index 6acefe9d3..a339356ae 100644 --- a/OptimizelySDK/Entity/Entity.cs +++ b/OptimizelySDK/Entity/Entity.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; namespace OptimizelySDK.Entity @@ -24,4 +25,4 @@ public object Clone() return MemberwiseClone(); } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Entity/Event.cs b/OptimizelySDK/Entity/Event.cs index e10b46d2b..2543b74fb 100644 --- a/OptimizelySDK/Entity/Event.cs +++ b/OptimizelySDK/Entity/Event.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Entity { public class Event : IdKeyEntity diff --git a/OptimizelySDK/Entity/EventTags.cs b/OptimizelySDK/Entity/EventTags.cs index 774aa05de..7422f4f9b 100644 --- a/OptimizelySDK/Entity/EventTags.cs +++ b/OptimizelySDK/Entity/EventTags.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Collections.Generic; using OptimizelySDK.Logger; @@ -20,15 +21,22 @@ namespace OptimizelySDK.Entity { public class EventTags : Dictionary { - public EventTags FilterNullValues(ILogger logger) { - EventTags answer = new EventTags(); - foreach (KeyValuePair pair in this) { - if (pair.Value != null) { + public EventTags FilterNullValues(ILogger logger) + { + var answer = new EventTags(); + foreach (var pair in this) + { + if (pair.Value != null) + { answer[pair.Key] = pair.Value; - } else { - logger.Log(LogLevel.ERROR, $"[EventTags] Null value for key {pair.Key} removed and will not be sent to results."); + } + else + { + logger.Log(LogLevel.ERROR, + $"[EventTags] Null value for key {pair.Key} removed and will not be sent to results."); } } + return answer; } } diff --git a/OptimizelySDK/Entity/Experiment.cs b/OptimizelySDK/Entity/Experiment.cs index e291fa5b3..d7a73c4f1 100644 --- a/OptimizelySDK/Entity/Experiment.cs +++ b/OptimizelySDK/Entity/Experiment.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Collections.Generic; using Newtonsoft.Json.Linq; using OptimizelySDK.Utils; @@ -23,9 +24,9 @@ namespace OptimizelySDK.Entity { public class Experiment : IdKeyEntity { - const string STATUS_RUNNING = "Running"; + private const string STATUS_RUNNING = "Running"; - const string MUTEX_GROUP_POLICY = "random"; + private const string MUTEX_GROUP_POLICY = "random"; /// /// Experiment Status @@ -52,10 +53,10 @@ public class Experiment : IdKeyEntity /// public Dictionary ForcedVariations { get; set; } - /// - /// ForcedVariations for the experiment - /// - public Dictionary UserIdToKeyVariations { get { return ForcedVariations; } } + /// + /// ForcedVariations for the experiment + /// + public Dictionary UserIdToKeyVariations => ForcedVariations; /// /// Policy of the experiment group @@ -77,17 +78,22 @@ public ICondition AudienceIdsList get { if (AudienceIds == null || AudienceIds.Length == 0) + { return null; - + } + if (_audienceIdsList == null) { var conditions = new List(); foreach (var audienceId in AudienceIds) - conditions.Add(new AudienceIdCondition() { AudienceId = (string)audienceId }); + { + conditions.Add( + new AudienceIdCondition() { AudienceId = (string)audienceId }); + } _audienceIdsList = new OrCondition() { Conditions = conditions.ToArray() }; } - + return _audienceIdsList; } } @@ -102,10 +108,14 @@ public string AudienceIdsString get { if (AudienceIds == null) + { return null; + } if (_audienceIdsString == null) + { _audienceIdsString = JsonConvert.SerializeObject(AudienceIds, Formatting.None); + } return _audienceIdsString; } @@ -131,14 +141,23 @@ public ICondition AudienceConditionsList get { if (AudienceConditions == null) + { return null; + } if (_audienceConditionsList == null) { if (AudienceConditions is string) - _audienceConditionsList = ConditionParser.ParseAudienceConditions(JToken.Parse((string)AudienceConditions)); + { + _audienceConditionsList = + ConditionParser.ParseAudienceConditions( + JToken.Parse((string)AudienceConditions)); + } else - _audienceConditionsList = ConditionParser.ParseAudienceConditions((JToken)AudienceConditions); + { + _audienceConditionsList = + ConditionParser.ParseAudienceConditions((JToken)AudienceConditions); + } } return _audienceConditionsList; @@ -155,48 +174,73 @@ public string AudienceConditionsString get { if (AudienceConditions == null) + { return null; + } if (_audienceConditionsString == null) { if (AudienceConditions is JToken token) + { _audienceConditionsString = token.ToString(Formatting.None); + } else + { _audienceConditionsString = AudienceConditions.ToString(); + } } return _audienceConditionsString; } } - bool isGenerateKeyMapCalled = false; + private bool isGenerateKeyMapCalled = false; private Dictionary _VariationKeyToVariationMap; - public Dictionary VariationKeyToVariationMap { - get { - if (!isGenerateKeyMapCalled) GenerateVariationKeyMap(); + + public Dictionary VariationKeyToVariationMap + { + get + { + if (!isGenerateKeyMapCalled) + { + GenerateVariationKeyMap(); + } + return _VariationKeyToVariationMap; } } - private Dictionary _VariationIdToVariationMap; - public Dictionary VariationIdToVariationMap { + private Dictionary _VariationIdToVariationMap; + + public Dictionary VariationIdToVariationMap + { get { - if (!isGenerateKeyMapCalled) GenerateVariationKeyMap(); + if (!isGenerateKeyMapCalled) + { + GenerateVariationKeyMap(); + } + return _VariationIdToVariationMap; } } public void GenerateVariationKeyMap() { - if (Variations == null) return; - _VariationIdToVariationMap = ConfigParser.GenerateMap(entities: Variations, getKey: a => a.Id, clone: true); - _VariationKeyToVariationMap = ConfigParser.GenerateMap(entities: Variations, getKey: a => a.Key, clone: true); + if (Variations == null) + { + return; + } + + _VariationIdToVariationMap = + ConfigParser.GenerateMap(Variations, a => a.Id, true); + _VariationKeyToVariationMap = + ConfigParser.GenerateMap(Variations, a => a.Key, true); isGenerateKeyMapCalled = true; } - // Code from PHP, need to build traffic and variations from config + // Code from PHP, need to build traffic and variations from config #if false /** * @param $variations array Variations in experiment. @@ -211,31 +255,22 @@ public function setVariations($variations) */ public function setTrafficAllocation($trafficAllocation) { - $this->_trafficAllocation = ConfigParser::generateMap($trafficAllocation, null, TrafficAllocation::class); + $this->_trafficAllocation = + ConfigParser::generateMap($trafficAllocation, null, TrafficAllocation::class); } #endif - /// - /// Determine if experiment is in a mutually exclusive group - /// - public bool IsInMutexGroup - { - get - { - return !string.IsNullOrEmpty(GroupPolicy) && GroupPolicy == MUTEX_GROUP_POLICY; - } - } + /// + /// Determine if experiment is in a mutually exclusive group + /// + public bool IsInMutexGroup => + !string.IsNullOrEmpty(GroupPolicy) && GroupPolicy == MUTEX_GROUP_POLICY; /// /// Determine if experiment is running or not /// - public bool IsExperimentRunning - { - get - { - return !string.IsNullOrEmpty(Status) && Status == STATUS_RUNNING; - } - } + public bool IsExperimentRunning => + !string.IsNullOrEmpty(Status) && Status == STATUS_RUNNING; /// /// Determin if user is forced variation of experiment diff --git a/OptimizelySDK/Entity/FeatureDecision.cs b/OptimizelySDK/Entity/FeatureDecision.cs index f93c5c46f..e768cc5a9 100644 --- a/OptimizelySDK/Entity/FeatureDecision.cs +++ b/OptimizelySDK/Entity/FeatureDecision.cs @@ -24,7 +24,7 @@ public class FeatureDecision public Experiment Experiment { get; } public Variation Variation { get; } public string Source { get; } - + public FeatureDecision(Experiment experiment, Variation variation, string source) { Experiment = experiment; diff --git a/OptimizelySDK/Entity/FeatureFlag.cs b/OptimizelySDK/Entity/FeatureFlag.cs index 2067f17ea..7c410167b 100644 --- a/OptimizelySDK/Entity/FeatureFlag.cs +++ b/OptimizelySDK/Entity/FeatureFlag.cs @@ -26,27 +26,29 @@ public class FeatureFlag : IdKeyEntity public List ExperimentIds { get; set; } private List _Variables; + public List Variables { - get - { - return _Variables; - } + get => _Variables; set { _Variables = value; // Generating Feature Variable key map. if (_Variables != null) - VariableKeyToFeatureVariableMap = ConfigParser.GenerateMap(entities: _Variables, getKey: v => v.Key, clone: true); + { + VariableKeyToFeatureVariableMap = + ConfigParser.GenerateMap(_Variables, v => v.Key, true); + } } } - + public Dictionary VariableKeyToFeatureVariableMap { get; set; } public FeatureVariable GetFeatureVariableFromKey(string variableKey) { - if (VariableKeyToFeatureVariableMap != null && VariableKeyToFeatureVariableMap.ContainsKey(variableKey)) + if (VariableKeyToFeatureVariableMap != null && + VariableKeyToFeatureVariableMap.ContainsKey(variableKey)) { return VariableKeyToFeatureVariableMap[variableKey]; } diff --git a/OptimizelySDK/Entity/FeatureVariable.cs b/OptimizelySDK/Entity/FeatureVariable.cs index 33f69a72c..d84295734 100644 --- a/OptimizelySDK/Entity/FeatureVariable.cs +++ b/OptimizelySDK/Entity/FeatureVariable.cs @@ -23,11 +23,11 @@ public class FeatureVariable : IdKeyEntity public const string DOUBLE_TYPE = "double"; public const string BOOLEAN_TYPE = "boolean"; public const string JSON_TYPE = "json"; - + public enum VariableStatus { ACTIVE, - ARCHIVED + ARCHIVED, } @@ -37,17 +37,12 @@ public enum VariableStatus public string SubType { - get - { - return _subType; - } - set - { - _subType = value; - } + get => _subType; + set => _subType = value; } private string _type; + public string Type { get @@ -56,12 +51,10 @@ public string Type { return JSON_TYPE; } + return _type; } - set - { - _type = value; - } + set => _type = value; } public VariableStatus Status { get; set; } @@ -73,7 +66,8 @@ public string Type /// Variable type. public static string GetFeatureVariableTypeName(string variableType) { - switch (variableType) { + switch (variableType) + { case BOOLEAN_TYPE: return "GetFeatureVariableBoolean"; case DOUBLE_TYPE: diff --git a/OptimizelySDK/Entity/FeatureVariableUsage.cs b/OptimizelySDK/Entity/FeatureVariableUsage.cs index 388699eb0..ee4d7b264 100644 --- a/OptimizelySDK/Entity/FeatureVariableUsage.cs +++ b/OptimizelySDK/Entity/FeatureVariableUsage.cs @@ -27,6 +27,5 @@ public class FeatureVariableUsage : Entity /// Audience Name /// public string Value { get; set; } - } } diff --git a/OptimizelySDK/Entity/ForcedVariation.cs b/OptimizelySDK/Entity/ForcedVariation.cs index 58214f243..071c5c910 100644 --- a/OptimizelySDK/Entity/ForcedVariation.cs +++ b/OptimizelySDK/Entity/ForcedVariation.cs @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Entity { - public class ForcedVariation : IdKeyEntity - { - } + public class ForcedVariation : IdKeyEntity { } } diff --git a/OptimizelySDK/Entity/Group.cs b/OptimizelySDK/Entity/Group.cs index f9d4e3058..61a2b7d3d 100644 --- a/OptimizelySDK/Entity/Group.cs +++ b/OptimizelySDK/Entity/Group.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Entity { public class Group : Entity @@ -43,9 +44,9 @@ public class Group : Entity */ public function setTrafficAllocation($trafficAllocation) { - $this->_trafficAllocation = ConfigParser::generateMap($trafficAllocation, null, TrafficAllocation::class); + $this->_trafficAllocation = + ConfigParser::generateMap($trafficAllocation, null, TrafficAllocation::class); } #endif - } } diff --git a/OptimizelySDK/Entity/IdKeyEntity.cs b/OptimizelySDK/Entity/IdKeyEntity.cs index 3074ec770..66f7b890d 100644 --- a/OptimizelySDK/Entity/IdKeyEntity.cs +++ b/OptimizelySDK/Entity/IdKeyEntity.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; namespace OptimizelySDK.Entity @@ -36,7 +37,9 @@ public override bool Equals(object other) { var entity = other as IdKeyEntity; if (entity == null || other.GetType() != GetType()) + { return false; + } return Id == entity.Id && Key == entity.Key; } diff --git a/OptimizelySDK/Entity/Integration.cs b/OptimizelySDK/Entity/Integration.cs index e056f1f55..1c56c76d0 100644 --- a/OptimizelySDK/Entity/Integration.cs +++ b/OptimizelySDK/Entity/Integration.cs @@ -21,14 +21,14 @@ namespace OptimizelySDK.Entity public class Integration : IdKeyEntity { public string Host { get; set; } - + public string PublicKey { get; set; } public override string ToString() { var sb = new StringBuilder(); sb.AppendFormat("Integration{{key='{0}'", Key); - + if (!string.IsNullOrEmpty(Host)) { sb.AppendFormat(", host='{0}'", Host); @@ -40,7 +40,7 @@ public override string ToString() } sb.Append("}"); - + return sb.ToString(); } } diff --git a/OptimizelySDK/Entity/Result.cs b/OptimizelySDK/Entity/Result.cs index 17a63133b..478208264 100644 --- a/OptimizelySDK/Entity/Result.cs +++ b/OptimizelySDK/Entity/Result.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.OptimizelyDecisions; namespace OptimizelySDK.Entity @@ -29,14 +30,14 @@ public static Result NewResult(T resultObject, DecisionReasons decisionReason public Result SetReasons(DecisionReasons decisionReasons) { - DecisionReasons = decisionReasons; + DecisionReasons = decisionReasons; return this; } public static Result NullResult(DecisionReasons decisionReasons) { - return NewResult(default(T), decisionReasons); + return NewResult(default, decisionReasons); } } } diff --git a/OptimizelySDK/Entity/TrafficAllocation.cs b/OptimizelySDK/Entity/TrafficAllocation.cs index 95c16e689..20663520c 100644 --- a/OptimizelySDK/Entity/TrafficAllocation.cs +++ b/OptimizelySDK/Entity/TrafficAllocation.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Entity { public class TrafficAllocation : Entity diff --git a/OptimizelySDK/Entity/UserAttributes.cs b/OptimizelySDK/Entity/UserAttributes.cs index c11e1009b..d39581cce 100644 --- a/OptimizelySDK/Entity/UserAttributes.cs +++ b/OptimizelySDK/Entity/UserAttributes.cs @@ -13,18 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Collections.Generic; namespace OptimizelySDK.Entity { public class UserAttributes : Dictionary { - public UserAttributes() : base() - { - } + public UserAttributes() : base() { } - public UserAttributes(IDictionary dictionary) : base(dictionary) - { - } + public UserAttributes(IDictionary dictionary) : base(dictionary) { } } } diff --git a/OptimizelySDK/Entity/Variation.cs b/OptimizelySDK/Entity/Variation.cs index 2083794e4..cd646250d 100644 --- a/OptimizelySDK/Entity/Variation.cs +++ b/OptimizelySDK/Entity/Variation.cs @@ -26,25 +26,31 @@ public class Variation : IdKeyEntity [Newtonsoft.Json.JsonProperty("variables")] public List FeatureVariableUsageInstances { - get - { - return _FeatureVariableUsageInstances; - } + get => _FeatureVariableUsageInstances; set { _FeatureVariableUsageInstances = value; // Generating Variable Usage key map. if (_FeatureVariableUsageInstances != null) - VariableIdToVariableUsageInstanceMap = ConfigParser.GenerateMap(entities: _FeatureVariableUsageInstances, getKey: v => v.Id.ToString(), clone: true); + { + VariableIdToVariableUsageInstanceMap = + ConfigParser.GenerateMap( + _FeatureVariableUsageInstances, v => v.Id.ToString(), true); + } } } - public Dictionary VariableIdToVariableUsageInstanceMap { get; set; } + public Dictionary VariableIdToVariableUsageInstanceMap + { + get; + set; + } public FeatureVariableUsage GetFeatureVariableUsageFromId(string variableId) { - if (VariableIdToVariableUsageInstanceMap != null && VariableIdToVariableUsageInstanceMap.ContainsKey(variableId)) + if (VariableIdToVariableUsageInstanceMap != null && + VariableIdToVariableUsageInstanceMap.ContainsKey(variableId)) { return VariableIdToVariableUsageInstanceMap[variableId]; } @@ -54,12 +60,6 @@ public FeatureVariableUsage GetFeatureVariableUsageFromId(string variableId) public bool? FeatureEnabled { get; set; } - public bool IsFeatureEnabled - { - get - { - return FeatureEnabled ?? false; - } - } + public bool IsFeatureEnabled => FeatureEnabled ?? false; } } diff --git a/OptimizelySDK/ErrorHandler/DefaultErrorHandler.cs b/OptimizelySDK/ErrorHandler/DefaultErrorHandler.cs index bbfe61464..1c73a26e6 100644 --- a/OptimizelySDK/ErrorHandler/DefaultErrorHandler.cs +++ b/OptimizelySDK/ErrorHandler/DefaultErrorHandler.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Logger; using System; @@ -34,16 +35,21 @@ public DefaultErrorHandler(ILogger logger = null, bool throwExceptions = true) public void HandleError(Exception exception) { if (Logger != null) + { Logger.Log(LogLevel.ERROR, exception.Message); + } if (ThrowExceptions) + { throw exception; + } } /// /// An optional Logger include exceptions in your log /// private ILogger Logger; + private bool ThrowExceptions; } } diff --git a/OptimizelySDK/ErrorHandler/IErrorHandler.cs b/OptimizelySDK/ErrorHandler/IErrorHandler.cs index 93efda07d..d34111d04 100644 --- a/OptimizelySDK/ErrorHandler/IErrorHandler.cs +++ b/OptimizelySDK/ErrorHandler/IErrorHandler.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; namespace OptimizelySDK.ErrorHandler diff --git a/OptimizelySDK/ErrorHandler/NoOpErrorHandler.cs b/OptimizelySDK/ErrorHandler/NoOpErrorHandler.cs index 1f5b7e9d0..c33174962 100644 --- a/OptimizelySDK/ErrorHandler/NoOpErrorHandler.cs +++ b/OptimizelySDK/ErrorHandler/NoOpErrorHandler.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Logger; using System; @@ -21,8 +22,6 @@ namespace OptimizelySDK.ErrorHandler public class NoOpErrorHandler : DefaultErrorHandler { public NoOpErrorHandler(ILogger logger = null) - : base(logger: logger, throwExceptions: false) - { - } + : base(logger, false) { } } } diff --git a/OptimizelySDK/Event/BatchEventProcessor.cs b/OptimizelySDK/Event/BatchEventProcessor.cs index 51526454c..209b8fde8 100644 --- a/OptimizelySDK/Event/BatchEventProcessor.cs +++ b/OptimizelySDK/Event/BatchEventProcessor.cs @@ -37,7 +37,7 @@ namespace OptimizelySDK.Event * the BlockingQueue and buffers them for either a configured batch size or for a * maximum duration before the resulting LogEvent is sent to the NotificationManager. */ - public class BatchEventProcessor: EventProcessor, IDisposable + public class BatchEventProcessor : EventProcessor, IDisposable { private const int DEFAULT_BATCH_SIZE = 10; private const int DEFAULT_QUEUE_CAPACITY = 1000; @@ -56,7 +56,7 @@ public class BatchEventProcessor: EventProcessor, IDisposable public bool IsStarted { get; private set; } private Thread Executer; - + public ILogger Logger { get; protected set; } public IErrorHandler ErrorHandler { get; protected set; } public NotificationCenter NotificationCenter { get; set; } @@ -64,10 +64,10 @@ public class BatchEventProcessor: EventProcessor, IDisposable private readonly object mutex = new object(); public IEventDispatcher EventDispatcher { get; private set; } - public BlockingCollection EventQueue { get; private set; } + public BlockingCollection EventQueue { get; private set; } private List CurrentBatch = new List(); private long FlushingIntervalDeadline; - + public void Start() { if (IsStarted && !Disposed) @@ -76,7 +76,8 @@ public void Start() return; } - FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + (long)FlushInterval.TotalMilliseconds; + FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + + (long)FlushInterval.TotalMilliseconds; Executer = new Thread(() => Run()); Executer.Start(); IsStarted = true; @@ -94,12 +95,13 @@ public virtual void Run() { if (DateTime.Now.MillisecondsSince1970() > FlushingIntervalDeadline) { - Logger.Log(LogLevel.DEBUG, $"Deadline exceeded flushing current batch, {DateTime.Now.Millisecond}, {FlushingIntervalDeadline}."); + Logger.Log(LogLevel.DEBUG, + $"Deadline exceeded flushing current batch, {DateTime.Now.Millisecond}, {FlushingIntervalDeadline}."); FlushQueue(); } - - if (!EventQueue.TryTake(out object item, 50)) - { + + if (!EventQueue.TryTake(out var item, 50)) + { Thread.Sleep(50); continue; } @@ -118,34 +120,41 @@ public virtual void Run() } if (item is UserEvent userEvent) + { AddToBatch(userEvent); + } } } catch (InvalidOperationException e) { // An InvalidOperationException means that Take() was called on a completed collection - Logger.Log(LogLevel.DEBUG, "Unable to take item from eventQueue: " + e.GetAllMessages()); + Logger.Log(LogLevel.DEBUG, + "Unable to take item from eventQueue: " + e.GetAllMessages()); } catch (Exception exception) { - Logger.Log(LogLevel.ERROR, "Uncaught exception processing buffer. Error: " + exception.GetAllMessages()); + Logger.Log(LogLevel.ERROR, + "Uncaught exception processing buffer. Error: " + exception.GetAllMessages()); } finally { - Logger.Log(LogLevel.INFO, "Exiting processing loop. Attempting to flush pending events."); + Logger.Log(LogLevel.INFO, + "Exiting processing loop. Attempting to flush pending events."); FlushQueue(); } } public void Flush() { - FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + (long)FlushInterval.TotalMilliseconds; + FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + + (long)FlushInterval.TotalMilliseconds; EventQueue.Add(FLUSH_SIGNAL); } private void FlushQueue() { - FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + (long)FlushInterval.TotalMilliseconds; + FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + + (long)FlushInterval.TotalMilliseconds; if (CurrentBatch.Count == 0) { @@ -158,11 +167,12 @@ private void FlushQueue() toProcessBatch = new List(CurrentBatch); CurrentBatch.Clear(); } - - LogEvent logEvent = EventFactory.CreateLogEvent(toProcessBatch.ToArray(), Logger); - NotificationCenter?.SendNotifications(NotificationCenter.NotificationType.LogEvent, logEvent); + var logEvent = EventFactory.CreateLogEvent(toProcessBatch.ToArray(), Logger); + + NotificationCenter?.SendNotifications(NotificationCenter.NotificationType.LogEvent, + logEvent); try { @@ -179,21 +189,29 @@ private void FlushQueue() /// public void Stop() { - if (Disposed) return; + if (Disposed) + { + return; + } EventQueue.Add(SHUTDOWN_SIGNAL); - + if (!Executer.Join(TimeoutInterval)) - Logger.Log(LogLevel.ERROR, $"Timeout exceeded attempting to close for {TimeoutInterval.Milliseconds} ms"); + { + Logger.Log(LogLevel.ERROR, + $"Timeout exceeded attempting to close for {TimeoutInterval.Milliseconds} ms"); + } IsStarted = false; Logger.Log(LogLevel.WARN, $"Stopping scheduler."); } - - public void Process(UserEvent userEvent) { + + public void Process(UserEvent userEvent) + { Logger.Log(LogLevel.DEBUG, "Received userEvent: " + userEvent); - if (Disposed) { + if (Disposed) + { Logger.Log(LogLevel.WARN, "Executor shutdown, not accepting tasks."); return; } @@ -203,7 +221,7 @@ public void Process(UserEvent userEvent) { Logger.Log(LogLevel.WARN, "Payload not accepted by the queue."); } } - + private void AddToBatch(UserEvent userEvent) { if (ShouldSplit(userEvent)) @@ -214,19 +232,26 @@ private void AddToBatch(UserEvent userEvent) // Reset the deadline if starting a new batch. if (CurrentBatch.Count == 0) - FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + (long)FlushInterval.TotalMilliseconds; + { + FlushingIntervalDeadline = DateTime.Now.MillisecondsSince1970() + + (long)FlushInterval.TotalMilliseconds; + } - lock (mutex) { + lock (mutex) + { CurrentBatch.Add(userEvent); } - - if (CurrentBatch.Count >= BatchSize) { + + if (CurrentBatch.Count >= BatchSize) + { FlushQueue(); } } - private bool ShouldSplit(UserEvent userEvent) { - if (CurrentBatch.Count == 0) { + private bool ShouldSplit(UserEvent userEvent) + { + if (CurrentBatch.Count == 0) + { return false; } @@ -236,10 +261,11 @@ private bool ShouldSplit(UserEvent userEvent) { currentContext = CurrentBatch.Last().Context; } - EventContext newContext = userEvent.Context; + var newContext = userEvent.Context; // Revisions should match - if (currentContext.Revision != newContext.Revision) { + if (currentContext.Revision != newContext.Revision) + { return true; } @@ -251,10 +277,13 @@ private bool ShouldSplit(UserEvent userEvent) { return false; } - + public void Dispose() { - if (Disposed) return; + if (Disposed) + { + return; + } Stop(); Disposed = true; @@ -262,7 +291,8 @@ public void Dispose() public class Builder { - private BlockingCollection EventQueue = new BlockingCollection(DEFAULT_QUEUE_CAPACITY); + private BlockingCollection EventQueue = + new BlockingCollection(DEFAULT_QUEUE_CAPACITY); private IEventDispatcher EventDispatcher; private int BatchSize; @@ -306,7 +336,7 @@ public Builder WithErrorHandler(IErrorHandler errorHandler = null) return this; } - + public Builder WithLogger(ILogger logger = null) { Logger = logger; @@ -347,19 +377,25 @@ public BatchEventProcessor Build(bool start) var batchEventProcessor = new BatchEventProcessor(); batchEventProcessor.Logger = Logger ?? new NoOpLogger(); batchEventProcessor.ErrorHandler = ErrorHandler ?? new NoOpErrorHandler(Logger); - batchEventProcessor.EventDispatcher = EventDispatcher ?? new DefaultEventDispatcher(Logger); + batchEventProcessor.EventDispatcher = + EventDispatcher ?? new DefaultEventDispatcher(Logger); batchEventProcessor.EventQueue = EventQueue; - batchEventProcessor.NotificationCenter = NotificationCenter; - batchEventProcessor.BatchSize = BatchSize < 1 ? BatchEventProcessor.DEFAULT_BATCH_SIZE : BatchSize; - batchEventProcessor.FlushInterval = FlushInterval <= TimeSpan.FromSeconds(0) ? BatchEventProcessor.DEFAULT_FLUSH_INTERVAL : FlushInterval; - batchEventProcessor.TimeoutInterval = TimeoutInterval <= TimeSpan.FromSeconds(0) ? BatchEventProcessor.DEFAULT_TIMEOUT_INTERVAL : TimeoutInterval; + batchEventProcessor.NotificationCenter = NotificationCenter; + batchEventProcessor.BatchSize = BatchSize < 1 ? DEFAULT_BATCH_SIZE : BatchSize; + batchEventProcessor.FlushInterval = FlushInterval <= TimeSpan.FromSeconds(0) ? + DEFAULT_FLUSH_INTERVAL : + FlushInterval; + batchEventProcessor.TimeoutInterval = TimeoutInterval <= TimeSpan.FromSeconds(0) ? + DEFAULT_TIMEOUT_INTERVAL : + TimeoutInterval; if (start) + { batchEventProcessor.Start(); + } return batchEventProcessor; } } - } } diff --git a/OptimizelySDK/Event/Builder/EventBuilder.cs b/OptimizelySDK/Event/Builder/EventBuilder.cs index fd08d3278..c0b1c07fc 100644 --- a/OptimizelySDK/Event/Builder/EventBuilder.cs +++ b/OptimizelySDK/Event/Builder/EventBuilder.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Bucketing; using OptimizelySDK.Entity; using OptimizelySDK.Logger; @@ -36,8 +37,9 @@ public class EventBuilder private const string ACTIVATE_EVENT_KEY = "campaign_activated"; - private static readonly Dictionary HTTP_HEADERS = new Dictionary - { + private static readonly Dictionary HTTP_HEADERS = + new Dictionary + { { "Content-Type", "application/json" }, }; @@ -68,15 +70,17 @@ public void ResetParams() /// ProjectConfig Configuration for the project /// string ID of user /// associative array of Attributes for the user - private Dictionary GetCommonParams(ProjectConfig config, string userId, UserAttributes userAttributes) + private Dictionary GetCommonParams(ProjectConfig config, string userId, + UserAttributes userAttributes + ) { var comonParams = new Dictionary(); var visitor = new Dictionary { - { "snapshots", new object[0]}, - { "visitor_id", userId }, - { "attributes", new object[0] } + { "snapshots", new object[0] }, + { "visitor_id", userId }, + { "attributes", new object[0] }, }; comonParams[Params.VISITORS] = new object[] { visitor }; @@ -91,16 +95,18 @@ private Dictionary GetCommonParams(ProjectConfig config, string var userFeatures = new List>(); //Omit attribute values that are not supported by the log endpoint. - foreach (var validUserAttribute in userAttributes.Where(attribute => Validator.IsUserAttributeValid(attribute))) - { + foreach (var validUserAttribute in userAttributes.Where(attribute => + Validator.IsUserAttributeValid(attribute))) + { var attributeId = config.GetAttributeId(validUserAttribute.Key); - if (!string.IsNullOrEmpty(attributeId)) { + if (!string.IsNullOrEmpty(attributeId)) + { userFeatures.Add(new Dictionary { { "entity_id", attributeId }, { "key", validUserAttribute.Key }, { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, - { "value", validUserAttribute.Value} + { "value", validUserAttribute.Value }, }); } } @@ -109,11 +115,11 @@ private Dictionary GetCommonParams(ProjectConfig config, string { userFeatures.Add(new Dictionary { - { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, - { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, - { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, - { "value", config.BotFiltering} - }); + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, + { "value", config.BotFiltering }, + }); } visitor["attributes"] = userFeatures; @@ -121,31 +127,32 @@ private Dictionary GetCommonParams(ProjectConfig config, string return comonParams; } - private Dictionary GetImpressionParams(Experiment experiment, string variationId) + private Dictionary GetImpressionParams(Experiment experiment, + string variationId + ) { - var impressionEvent = new Dictionary(); var decisions = new object[] { - new Dictionary - { - { Params.CAMPAIGN_ID, experiment?.LayerId }, - { Params.EXPERIMENT_ID, experiment?.Id ?? string.Empty }, - { Params.VARIATION_ID, variationId } - } + new Dictionary + { + { Params.CAMPAIGN_ID, experiment?.LayerId }, + { Params.EXPERIMENT_ID, experiment?.Id ?? string.Empty }, + { Params.VARIATION_ID, variationId }, + }, }; var events = new object[] { - new Dictionary - { - { "entity_id", experiment?.LayerId }, - { "timestamp", DateTimeUtils.SecondsSince1970*1000 }, - { "key", ACTIVATE_EVENT_KEY }, - { "uuid", Guid.NewGuid() } - } + new Dictionary + { + { "entity_id", experiment?.LayerId }, + { "timestamp", DateTimeUtils.SecondsSince1970 * 1000 }, + { "key", ACTIVATE_EVENT_KEY }, + { "uuid", Guid.NewGuid() }, + }, }; impressionEvent[Params.DECISIONS] = decisions; @@ -154,51 +161,61 @@ private Dictionary GetImpressionParams(Experiment experiment, st return impressionEvent; } - private List GetConversionParams(ProjectConfig config, string eventKey, string userId, Dictionary eventTags) + private List GetConversionParams(ProjectConfig config, string eventKey, + string userId, Dictionary eventTags + ) { - var conversionEventParams = new List(); var snapshot = new Dictionary(); var eventDict = new Dictionary - { - { Params.ENTITY_ID, config.EventKeyMap[eventKey].Id }, - { Params.TIMESTAMP, DateTimeUtils.SecondsSince1970*1000 }, - { "uuid", Guid.NewGuid() }, - { "key", eventKey } - }; + { + { Params.ENTITY_ID, config.EventKeyMap[eventKey].Id }, + { Params.TIMESTAMP, DateTimeUtils.SecondsSince1970 * 1000 }, + { "uuid", Guid.NewGuid() }, + { "key", eventKey }, + }; - if (eventTags != null) { + if (eventTags != null) + { var revenue = EventTagUtils.GetRevenueValue(eventTags, Logger); - if (revenue != null) { + if (revenue != null) + { eventDict[EventTagUtils.REVENUE_EVENT_METRIC_NAME] = revenue; } var eventValue = EventTagUtils.GetNumericValue(eventTags, Logger); - if (eventValue != null) { + if (eventValue != null) + { eventDict[EventTagUtils.VALUE_EVENT_METRIC_NAME] = eventValue; } if (eventTags.Any()) + { eventDict["tags"] = eventTags; + } } - snapshot[Params.EVENTS] = new object[]{ - eventDict - }; + snapshot[Params.EVENTS] = new object[] + { + eventDict, + }; conversionEventParams.Add(snapshot); return conversionEventParams; } - private Dictionary GetImpressionOrConversionParamsWithCommonParams(Dictionary commonParams, object[] conversionOrImpressionOnlyParams) + private Dictionary GetImpressionOrConversionParamsWithCommonParams( + Dictionary commonParams, object[] conversionOrImpressionOnlyParams + ) { var visitors = commonParams[Params.VISITORS] as object[]; - if (visitors.Length > 0) { + if (visitors.Length > 0) + { var visitor = visitors[0] as Dictionary; visitor["snapshots"] = conversionOrImpressionOnlyParams; } @@ -215,14 +232,18 @@ private Dictionary GetImpressionOrConversionParamsWithCommonPara /// User Id /// associative array of attributes for the user /// LogEvent object to be sent to dispatcher - public virtual LogEvent CreateImpressionEvent(ProjectConfig config, Experiment experiment, string variationId, - string userId, UserAttributes userAttributes) + public virtual LogEvent CreateImpressionEvent(ProjectConfig config, Experiment experiment, + string variationId, + string userId, UserAttributes userAttributes + ) { - - var commonParams = GetCommonParams(config, userId, userAttributes ?? new UserAttributes()); + var commonParams = + GetCommonParams(config, userId, userAttributes ?? new UserAttributes()); var impressionOnlyParams = GetImpressionParams(experiment, variationId); - var impressionParams = GetImpressionOrConversionParamsWithCommonParams(commonParams, new object[] { impressionOnlyParams }); + var impressionParams = + GetImpressionOrConversionParamsWithCommonParams(commonParams, + new object[] { impressionOnlyParams }); return new LogEvent(IMPRESSION_ENDPOINT, impressionParams, HTTP_VERB, HTTP_HEADERS); } @@ -237,13 +258,18 @@ public virtual LogEvent CreateImpressionEvent(ProjectConfig config, Experiment e /// associative array of Attributes for the user /// Dict representing metadata associated with the event. /// LogEvent object to be sent to dispatcher - public virtual LogEvent CreateConversionEvent(ProjectConfig config, string eventKey, string userId, UserAttributes userAttributes, EventTags eventTags) + public virtual LogEvent CreateConversionEvent(ProjectConfig config, string eventKey, + string userId, UserAttributes userAttributes, EventTags eventTags + ) { - var commonParams = GetCommonParams(config, userId, userAttributes ?? new UserAttributes()); + var commonParams = + GetCommonParams(config, userId, userAttributes ?? new UserAttributes()); - var conversionOnlyParams = GetConversionParams(config, eventKey, userId, eventTags).ToArray(); + var conversionOnlyParams = + GetConversionParams(config, eventKey, userId, eventTags).ToArray(); - var conversionParams = GetImpressionOrConversionParamsWithCommonParams(commonParams, conversionOnlyParams); + var conversionParams = + GetImpressionOrConversionParamsWithCommonParams(commonParams, conversionOnlyParams); return new LogEvent(CONVERSION_ENDPOINT, conversionParams, HTTP_VERB, HTTP_HEADERS); } diff --git a/OptimizelySDK/Event/Builder/Params.cs b/OptimizelySDK/Event/Builder/Params.cs index f3b476881..7cafce0c2 100644 --- a/OptimizelySDK/Event/Builder/Params.cs +++ b/OptimizelySDK/Event/Builder/Params.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Event.Entity; using System; @@ -21,23 +22,23 @@ namespace OptimizelySDK.Event.Builder [Obsolete("This class is deprecated.")] public static class Params { - public const string ACCOUNT_ID = "account_id"; - public const string ANONYMIZE_IP = "anonymize_ip"; - public const string CAMPAIGN_ID = "campaign_id"; - public const string CLIENT_ENGINE = "client_name"; - public const string CLIENT_VERSION = "client_version"; - public const string DECISIONS = "decisions"; - public const string ENRICH_DECISIONS = "enrich_decisions"; - public const string ENTITY_ID = "entity_id"; - public const string EVENTS = "events"; - public const string EXPERIMENT_ID = "experiment_id"; - public const string METADATA = "metadata"; - public const string PROJECT_ID = "project_id"; - public const string REVISION = "revision"; - public const string TIME = "timestamp"; - public const string TIMESTAMP = "timestamp"; - public const string VARIATION_ID = "variation_id"; - public const string VISITOR_ID = "visitorId"; - public const string VISITORS = "visitors"; + public const string ACCOUNT_ID = "account_id"; + public const string ANONYMIZE_IP = "anonymize_ip"; + public const string CAMPAIGN_ID = "campaign_id"; + public const string CLIENT_ENGINE = "client_name"; + public const string CLIENT_VERSION = "client_version"; + public const string DECISIONS = "decisions"; + public const string ENRICH_DECISIONS = "enrich_decisions"; + public const string ENTITY_ID = "entity_id"; + public const string EVENTS = "events"; + public const string EXPERIMENT_ID = "experiment_id"; + public const string METADATA = "metadata"; + public const string PROJECT_ID = "project_id"; + public const string REVISION = "revision"; + public const string TIME = "timestamp"; + public const string TIMESTAMP = "timestamp"; + public const string VARIATION_ID = "variation_id"; + public const string VISITOR_ID = "visitorId"; + public const string VISITORS = "visitors"; } } diff --git a/OptimizelySDK/Event/Dispatcher/DefaultEventDispatcher.cs b/OptimizelySDK/Event/Dispatcher/DefaultEventDispatcher.cs index d875c5cd1..20d7197af 100644 --- a/OptimizelySDK/Event/Dispatcher/DefaultEventDispatcher.cs +++ b/OptimizelySDK/Event/Dispatcher/DefaultEventDispatcher.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Event.Dispatcher { /// diff --git a/OptimizelySDK/Event/Dispatcher/HttpClientEventDispatcher45.cs b/OptimizelySDK/Event/Dispatcher/HttpClientEventDispatcher45.cs index 829158159..f35ba4086 100644 --- a/OptimizelySDK/Event/Dispatcher/HttpClientEventDispatcher45.cs +++ b/OptimizelySDK/Event/Dispatcher/HttpClientEventDispatcher45.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #if !NET35 && !NET40 using OptimizelySDK.Logger; using OptimizelySDK.Utils; @@ -46,18 +47,23 @@ private async void DispatchEventAsync(LogEvent logEvent) { try { - string json = logEvent.GetParamsAsJson(); + var json = logEvent.GetParamsAsJson(); var request = new HttpRequestMessage { RequestUri = new Uri(logEvent.Url), Method = HttpMethod.Post, // The Content-Type header applies to the Content, not the Request itself - Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"), + Content = + new StringContent(json, System.Text.Encoding.UTF8, "application/json"), }; foreach (var h in logEvent.Headers) + { if (h.Key.ToLower() != "content-type") + { request.Content.Headers.Add(h.Key, h.Value); + } + } var result = await Client.SendAsync(request); result.EnsureSuccessStatusCode(); @@ -79,4 +85,4 @@ public void DispatchEvent(LogEvent logEvent) } } } -#endif \ No newline at end of file +#endif diff --git a/OptimizelySDK/Event/Dispatcher/IEventDispatcher.cs b/OptimizelySDK/Event/Dispatcher/IEventDispatcher.cs index fb875e348..06d9ef607 100644 --- a/OptimizelySDK/Event/Dispatcher/IEventDispatcher.cs +++ b/OptimizelySDK/Event/Dispatcher/IEventDispatcher.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Event.Dispatcher { public interface IEventDispatcher diff --git a/OptimizelySDK/Event/Dispatcher/WebRequestEventDispatcher35.cs b/OptimizelySDK/Event/Dispatcher/WebRequestEventDispatcher35.cs index f5e09de09..f1c282c97 100644 --- a/OptimizelySDK/Event/Dispatcher/WebRequestEventDispatcher35.cs +++ b/OptimizelySDK/Event/Dispatcher/WebRequestEventDispatcher35.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #if NET35 || NET40 using System; using System.IO; @@ -41,9 +42,12 @@ public void DispatchEvent(LogEvent logEvent) Request.Method = logEvent.HttpVerb; foreach (var h in logEvent.Headers) - if(!WebHeaderCollection.IsRestricted(h.Key)){ + { + if (!WebHeaderCollection.IsRestricted(h.Key)) + { Request.Headers[h.Key] = h.Value; - } + } + } Request.ContentType = "application/json"; @@ -54,7 +58,8 @@ public void DispatchEvent(LogEvent logEvent) streamWriter.Close(); } - IAsyncResult result = Request.BeginGetResponse(new AsyncCallback(FinaliseHttpAsyncRequest), this); + var result = + Request.BeginGetResponse(new AsyncCallback(FinaliseHttpAsyncRequest), this); } private static void FinaliseHttpAsyncRequest(IAsyncResult result) @@ -70,10 +75,10 @@ private void FinalizeRequest(IAsyncResult result) if (response.StatusCode == HttpStatusCode.OK) { // Read the results, even though we don't need it. - Stream responseStream = response.GetResponseStream(); + var responseStream = response.GetResponseStream(); var streamEncoder = System.Text.Encoding.UTF8; - StreamReader responseReader = new StreamReader(responseStream, streamEncoder); - string data = responseReader.ReadToEnd(); + var responseReader = new StreamReader(responseStream, streamEncoder); + var data = responseReader.ReadToEnd(); } else { @@ -83,4 +88,4 @@ private void FinalizeRequest(IAsyncResult result) } } } -#endif \ No newline at end of file +#endif diff --git a/OptimizelySDK/Event/Entity/ConversionEvent.cs b/OptimizelySDK/Event/Entity/ConversionEvent.cs index 2042b1485..259189438 100644 --- a/OptimizelySDK/Event/Entity/ConversionEvent.cs +++ b/OptimizelySDK/Event/Entity/ConversionEvent.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Entity; using OptimizelySDK.Utils; using System; @@ -48,7 +49,7 @@ public Builder WithUserId(string userId) UserId = userId; return this; - } + } public Builder WithEventContext(EventContext eventContext) { @@ -106,6 +107,5 @@ public ConversionEvent Build() return conversionEvent; } } - } } diff --git a/OptimizelySDK/Event/Entity/Decision.cs b/OptimizelySDK/Event/Entity/Decision.cs index 08657a332..7d309f1ff 100644 --- a/OptimizelySDK/Event/Entity/Decision.cs +++ b/OptimizelySDK/Event/Entity/Decision.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Newtonsoft.Json; namespace OptimizelySDK.Event.Entity @@ -21,15 +22,21 @@ public class Decision { [JsonProperty("campaign_id")] public string CampaignId { get; private set; } + [JsonProperty("experiment_id")] public string ExperimentId { get; private set; } + [JsonProperty("metadata")] public DecisionMetadata Metadata { get; private set; } + [JsonProperty("variation_id")] public string VariationId { get; private set; } - public Decision() {} - public Decision(string campaignId, string experimentId, string variationId, DecisionMetadata metadata = null) + public Decision() { } + + public Decision(string campaignId, string experimentId, string variationId, + DecisionMetadata metadata = null + ) { CampaignId = campaignId; ExperimentId = experimentId; diff --git a/OptimizelySDK/Event/Entity/DecisionMetadata.cs b/OptimizelySDK/Event/Entity/DecisionMetadata.cs index 9f4b44280..88b0a27ca 100644 --- a/OptimizelySDK/Event/Entity/DecisionMetadata.cs +++ b/OptimizelySDK/Event/Entity/DecisionMetadata.cs @@ -26,16 +26,22 @@ public class DecisionMetadata { [JsonProperty("flag_key")] public string FlagKey { get; private set; } + [JsonProperty("rule_key")] public string RuleKey { get; private set; } + [JsonProperty("rule_type")] public string RuleType { get; private set; } + [JsonProperty("variation_key")] public string VariationKey { get; private set; } + [JsonProperty("enabled")] public bool Enabled { get; private set; } - public DecisionMetadata(string flagKey, string ruleKey, string ruleType, string variationKey = "", bool enabled = false) + public DecisionMetadata(string flagKey, string ruleKey, string ruleType, + string variationKey = "", bool enabled = false + ) { FlagKey = flagKey; RuleKey = ruleKey; diff --git a/OptimizelySDK/Event/Entity/EventBatch.cs b/OptimizelySDK/Event/Entity/EventBatch.cs index ed5f9f16a..0fe1fec29 100644 --- a/OptimizelySDK/Event/Entity/EventBatch.cs +++ b/OptimizelySDK/Event/Entity/EventBatch.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Collections.Generic; using Newtonsoft.Json; @@ -57,7 +58,7 @@ public class Builder public EventBatch Build() { - EventBatch eventBatch = new EventBatch(); + var eventBatch = new EventBatch(); eventBatch.AccountId = AccountId; eventBatch.ProjectId = ProjectId; eventBatch.Revision = Revision; diff --git a/OptimizelySDK/Event/Entity/EventContext.cs b/OptimizelySDK/Event/Entity/EventContext.cs index 7385817c7..44b776442 100644 --- a/OptimizelySDK/Event/Entity/EventContext.cs +++ b/OptimizelySDK/Event/Entity/EventContext.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Newtonsoft.Json; namespace OptimizelySDK.Event.Entity @@ -23,7 +24,7 @@ namespace OptimizelySDK.Event.Entity public class EventContext { [JsonProperty("account_id")] - public string AccountId {get; protected set;} + public string AccountId { get; protected set; } [JsonProperty("project_id")] public string ProjectId { get; protected set; } @@ -92,6 +93,5 @@ public EventContext Build() return eventContext; } } - } } diff --git a/OptimizelySDK/Event/Entity/ImpressionEvent.cs b/OptimizelySDK/Event/Entity/ImpressionEvent.cs index 5d525b2d7..8d31d785a 100644 --- a/OptimizelySDK/Event/Entity/ImpressionEvent.cs +++ b/OptimizelySDK/Event/Entity/ImpressionEvent.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Entity; using OptimizelySDK.Utils; using System; @@ -117,6 +118,5 @@ public ImpressionEvent Build() return impressionEvent; } } - } } diff --git a/OptimizelySDK/Event/Entity/Snapshot.cs b/OptimizelySDK/Event/Entity/Snapshot.cs index 525059dcc..7ad6001ea 100644 --- a/OptimizelySDK/Event/Entity/Snapshot.cs +++ b/OptimizelySDK/Event/Entity/Snapshot.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Newtonsoft.Json; namespace OptimizelySDK.Event.Entity @@ -25,7 +26,7 @@ public class Snapshot [JsonProperty("events")] public SnapshotEvent[] Events { get; private set; } - public Snapshot(SnapshotEvent[] events, Decision[] decisions= null) + public Snapshot(SnapshotEvent[] events, Decision[] decisions = null) { Events = events; Decisions = decisions; diff --git a/OptimizelySDK/Event/Entity/SnapshotEvent.cs b/OptimizelySDK/Event/Entity/SnapshotEvent.cs index 4fe136111..a620ca419 100644 --- a/OptimizelySDK/Event/Entity/SnapshotEvent.cs +++ b/OptimizelySDK/Event/Entity/SnapshotEvent.cs @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Newtonsoft.Json; using OptimizelySDK.Entity; + namespace OptimizelySDK.Event.Entity { /// @@ -25,7 +27,7 @@ public class SnapshotEvent [JsonProperty("entity_id")] public string EntityId { get; private set; } - [JsonProperty(PropertyName ="uuid")] + [JsonProperty(PropertyName = "uuid")] public string UUID { get; private set; } [JsonProperty("key")] @@ -35,7 +37,7 @@ public class SnapshotEvent public long TimeStamp { get; private set; } // The following properties are for Conversion that's why ignore if null. - [JsonProperty("revenue", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("revenue", NullValueHandling = NullValueHandling.Ignore)] public int? Revenue { get; private set; } [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] @@ -56,7 +58,7 @@ public class Builder public SnapshotEvent Build() { - SnapshotEvent snapshotEvent = new SnapshotEvent(); + var snapshotEvent = new SnapshotEvent(); snapshotEvent.EntityId = EntityId; snapshotEvent.UUID = UUID; snapshotEvent.Key = Key; @@ -115,6 +117,5 @@ public Builder WithEventTags(EventTags eventTags) return this; } } - } } diff --git a/OptimizelySDK/Event/Entity/UserEvent.cs b/OptimizelySDK/Event/Entity/UserEvent.cs index 784b84c0e..dd1568cd2 100644 --- a/OptimizelySDK/Event/Entity/UserEvent.cs +++ b/OptimizelySDK/Event/Entity/UserEvent.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; namespace OptimizelySDK.Event.Entity diff --git a/OptimizelySDK/Event/Entity/Visitor.cs b/OptimizelySDK/Event/Entity/Visitor.cs index c60e8ac13..c6799aa15 100644 --- a/OptimizelySDK/Event/Entity/Visitor.cs +++ b/OptimizelySDK/Event/Entity/Visitor.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Newtonsoft.Json; namespace OptimizelySDK.Event.Entity diff --git a/OptimizelySDK/Event/Entity/VisitorAttribute.cs b/OptimizelySDK/Event/Entity/VisitorAttribute.cs index 69151477c..0f881469d 100644 --- a/OptimizelySDK/Event/Entity/VisitorAttribute.cs +++ b/OptimizelySDK/Event/Entity/VisitorAttribute.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Newtonsoft.Json; namespace OptimizelySDK.Event.Entity @@ -31,12 +32,12 @@ public class VisitorAttribute [JsonProperty("value")] public object Value { get; set; } - public VisitorAttribute (string entityId, string key, string type, object value) + public VisitorAttribute(string entityId, string key, string type, object value) { EntityId = entityId; Key = key; Type = type; - Value = value; + Value = value; } } } diff --git a/OptimizelySDK/Event/EventFactory.cs b/OptimizelySDK/Event/EventFactory.cs index e0981651d..841b650ff 100644 --- a/OptimizelySDK/Event/EventFactory.cs +++ b/OptimizelySDK/Event/EventFactory.cs @@ -33,7 +33,10 @@ namespace OptimizelySDK.Event public class EventFactory { private const string CUSTOM_ATTRIBUTE_FEATURE_TYPE = "custom"; - public const string EVENT_ENDPOINT = "https://logx.optimizely.com/v1/events"; // Should be part of the datafile + + public const string + EVENT_ENDPOINT = + "https://logx.optimizely.com/v1/events"; // Should be part of the datafile private const string ACTIVATE_EVENT_KEY = "campaign_activated"; @@ -43,8 +46,8 @@ public class EventFactory /// The UserEvent entity /// The ILogger entity /// LogEvent instance - public static LogEvent CreateLogEvent(UserEvent userEvent, ILogger logger) { - + public static LogEvent CreateLogEvent(UserEvent userEvent, ILogger logger) + { return CreateLogEvent(new UserEvent[] { userEvent }, logger); } @@ -54,50 +57,56 @@ public static LogEvent CreateLogEvent(UserEvent userEvent, ILogger logger) { /// The UserEvent array /// The ILogger entity /// LogEvent instance - public static LogEvent CreateLogEvent(UserEvent[] userEvents, ILogger logger) { - - EventBatch.Builder builder = new EventBatch.Builder(); - - List visitors = new List(userEvents.Count()); + public static LogEvent CreateLogEvent(UserEvent[] userEvents, ILogger logger) + { + var builder = new EventBatch.Builder(); - foreach (UserEvent userEvent in userEvents) { + var visitors = new List(userEvents.Count()); - if (userEvent is ImpressionEvent) { - visitors.Add(CreateVisitor((ImpressionEvent) userEvent)); + foreach (var userEvent in userEvents) + { + if (userEvent is ImpressionEvent) + { + visitors.Add(CreateVisitor((ImpressionEvent)userEvent)); } - else if (userEvent is ConversionEvent) { - visitors.Add(CreateVisitor((ConversionEvent) userEvent, logger)); + else if (userEvent is ConversionEvent) + { + visitors.Add(CreateVisitor((ConversionEvent)userEvent, logger)); } - else { + else + { //TODO: Need to log a message, invalid UserEvent added in a list. continue; } - + var userContext = userEvent.Context; - builder - .WithClientName(userContext.ClientName) - .WithClientVersion(userContext.ClientVersion) - .WithAccountId(userContext.AccountId) - .WithAnonymizeIP(userContext.AnonymizeIP) - .WithProjectID(userContext.ProjectId) - .WithRevision(userContext.Revision) - .WithEnrichDecisions(true); + builder.WithClientName(userContext.ClientName). + WithClientVersion(userContext.ClientVersion). + WithAccountId(userContext.AccountId). + WithAnonymizeIP(userContext.AnonymizeIP). + WithProjectID(userContext.ProjectId). + WithRevision(userContext.Revision). + WithEnrichDecisions(true); } - if (visitors.Count == 0) { + if (visitors.Count == 0) + { return null; } builder.WithVisitors(visitors.ToArray()); - EventBatch eventBatch = builder.Build(); + var eventBatch = builder.Build(); - var eventBatchDictionary = JObject.FromObject(eventBatch).ToObject>(); + var eventBatchDictionary = + JObject.FromObject(eventBatch).ToObject>(); - return new LogEvent(EVENT_ENDPOINT, eventBatchDictionary, "POST", headers: new Dictionary { - { "Content-Type", "application/json" } - }); + return new LogEvent(EVENT_ENDPOINT, eventBatchDictionary, "POST", + new Dictionary + { + { "Content-Type", "application/json" }, + }); } /// @@ -105,29 +114,30 @@ public static LogEvent CreateLogEvent(UserEvent[] userEvents, ILogger logger) { /// /// The ImpressionEvent entity /// Visitor instance if ImpressionEvent is valid, null otherwise - private static Visitor CreateVisitor(ImpressionEvent impressionEvent) { - - if (impressionEvent == null) { + private static Visitor CreateVisitor(ImpressionEvent impressionEvent) + { + if (impressionEvent == null) + { return null; } - Decision decision = new Decision(impressionEvent.Experiment?.LayerId, + var decision = new Decision(impressionEvent.Experiment?.LayerId, impressionEvent.Experiment?.Id ?? string.Empty, impressionEvent.Variation?.Id, impressionEvent.Metadata); - SnapshotEvent snapshotEvent = new SnapshotEvent.Builder() - .WithUUID(impressionEvent.UUID) - .WithEntityId(impressionEvent.Experiment?.LayerId) - .WithKey(ACTIVATE_EVENT_KEY) - .WithTimeStamp(impressionEvent.Timestamp) - .Build(); + var snapshotEvent = new SnapshotEvent.Builder().WithUUID(impressionEvent.UUID). + WithEntityId(impressionEvent.Experiment?.LayerId). + WithKey(ACTIVATE_EVENT_KEY). + WithTimeStamp(impressionEvent.Timestamp). + Build(); - Snapshot snapshot = new Snapshot( + var snapshot = new Snapshot( new SnapshotEvent[] { snapshotEvent }, new Decision[] { decision }); - - var visitor = new Visitor(new Snapshot[] { snapshot }, impressionEvent.VisitorAttributes, impressionEvent.UserId); + + var visitor = new Visitor(new Snapshot[] { snapshot }, + impressionEvent.VisitorAttributes, impressionEvent.UserId); return visitor; } @@ -138,28 +148,30 @@ private static Visitor CreateVisitor(ImpressionEvent impressionEvent) { /// The ConversionEvent entity /// The ILogger entity /// Visitor instance if ConversionEvent is valid, null otherwise - private static Visitor CreateVisitor(ConversionEvent conversionEvent, ILogger logger) { - if (conversionEvent == null) { + private static Visitor CreateVisitor(ConversionEvent conversionEvent, ILogger logger) + { + if (conversionEvent == null) + { return null; } - EventContext userContext = conversionEvent.Context; + var userContext = conversionEvent.Context; var revenue = EventTagUtils.GetRevenueValue(conversionEvent.EventTags, logger) as int?; var value = EventTagUtils.GetNumericValue(conversionEvent.EventTags, logger) as float?; - SnapshotEvent snapshotEvent = new SnapshotEvent.Builder() - .WithUUID(conversionEvent.UUID) - .WithEntityId(conversionEvent.Event.Id) - .WithKey(conversionEvent.Event?.Key) - .WithTimeStamp(conversionEvent.Timestamp) - .WithRevenue(revenue) - .WithValue(value) - .WithEventTags(conversionEvent.EventTags) - .Build(); - - - Snapshot snapshot = new Snapshot(new SnapshotEvent[] { snapshotEvent }); - - var visitor = new Visitor(new Snapshot[] { snapshot }, conversionEvent.VisitorAttributes, conversionEvent.UserId); + var snapshotEvent = new SnapshotEvent.Builder().WithUUID(conversionEvent.UUID). + WithEntityId(conversionEvent.Event.Id). + WithKey(conversionEvent.Event?.Key). + WithTimeStamp(conversionEvent.Timestamp). + WithRevenue(revenue). + WithValue(value). + WithEventTags(conversionEvent.EventTags). + Build(); + + + var snapshot = new Snapshot(new SnapshotEvent[] { snapshotEvent }); + + var visitor = new Visitor(new Snapshot[] { snapshot }, + conversionEvent.VisitorAttributes, conversionEvent.UserId); return visitor; } @@ -170,34 +182,40 @@ private static Visitor CreateVisitor(ConversionEvent conversionEvent, ILogger lo /// The user's attributes /// ProjectConfig instance /// VisitorAttribute array if config is valid, null otherwise - public static VisitorAttribute[] BuildAttributeList(UserAttributes userAttributes, ProjectConfig config) - { + public static VisitorAttribute[] BuildAttributeList(UserAttributes userAttributes, + ProjectConfig config + ) + { if (config == null) + { return null; + } - List attributesList = new List(); + var attributesList = new List(); if (userAttributes != null) { - foreach (var validUserAttribute in userAttributes.Where(attribute => Validator.IsUserAttributeValid(attribute))) { - + foreach (var validUserAttribute in userAttributes.Where(attribute => + Validator.IsUserAttributeValid(attribute))) + { var attributeId = config.GetAttributeId(validUserAttribute.Key); - if (!string.IsNullOrEmpty(attributeId)) { - attributesList.Add(new VisitorAttribute(entityId: attributeId, key: validUserAttribute.Key, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, value: validUserAttribute.Value)); + if (!string.IsNullOrEmpty(attributeId)) + { + attributesList.Add(new VisitorAttribute(attributeId, validUserAttribute.Key, + CUSTOM_ATTRIBUTE_FEATURE_TYPE, validUserAttribute.Value)); } } } //checks if botFiltering value is not set in the project config file. - if (config.BotFiltering.HasValue) { - - attributesList.Add(new VisitorAttribute(entityId: ControlAttributes.BOT_FILTERING_ATTRIBUTE, - key: ControlAttributes.BOT_FILTERING_ATTRIBUTE, type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, value: config.BotFiltering)); + if (config.BotFiltering.HasValue) + { + attributesList.Add(new VisitorAttribute(ControlAttributes.BOT_FILTERING_ATTRIBUTE, + ControlAttributes.BOT_FILTERING_ATTRIBUTE, CUSTOM_ATTRIBUTE_FEATURE_TYPE, + config.BotFiltering)); } return attributesList.ToArray(); } } } - diff --git a/OptimizelySDK/Event/ForwardingEventProcessor.cs b/OptimizelySDK/Event/ForwardingEventProcessor.cs index 67da64501..a9199d32e 100644 --- a/OptimizelySDK/Event/ForwardingEventProcessor.cs +++ b/OptimizelySDK/Event/ForwardingEventProcessor.cs @@ -30,7 +30,10 @@ public class ForwardingEventProcessor : EventProcessor private IEventDispatcher EventDispatcher; private NotificationCenter NotificationCenter; - public ForwardingEventProcessor(IEventDispatcher eventDispatcher, NotificationCenter notificationCenter, ILogger logger= null, IErrorHandler errorHandler = null) + public ForwardingEventProcessor(IEventDispatcher eventDispatcher, + NotificationCenter notificationCenter, ILogger logger = null, + IErrorHandler errorHandler = null + ) { EventDispatcher = eventDispatcher; NotificationCenter = notificationCenter; @@ -40,16 +43,18 @@ public ForwardingEventProcessor(IEventDispatcher eventDispatcher, NotificationCe public void Process(UserEvent userEvent) { - var logEvent = EventFactory.CreateLogEvent(userEvent, Logger); + var logEvent = EventFactory.CreateLogEvent(userEvent, Logger); try { EventDispatcher.DispatchEvent(logEvent); - NotificationCenter?.SendNotifications(NotificationCenter.NotificationType.LogEvent, logEvent); + NotificationCenter?.SendNotifications(NotificationCenter.NotificationType.LogEvent, + logEvent); } catch (Exception ex) { - Logger.Log(LogLevel.ERROR, $"Error dispatching event: {logEvent.GetParamsAsJson()}. {ex.Message}"); + Logger.Log(LogLevel.ERROR, + $"Error dispatching event: {logEvent.GetParamsAsJson()}. {ex.Message}"); ErrorHandler.HandleError(ex); } } diff --git a/OptimizelySDK/Event/LogEvent.cs b/OptimizelySDK/Event/LogEvent.cs index 4a9b76fb8..b7c716971 100644 --- a/OptimizelySDK/Event/LogEvent.cs +++ b/OptimizelySDK/Event/LogEvent.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Collections.Generic; using System.Linq; @@ -48,7 +49,9 @@ public string GetParamsAsJson() /// /// LogEvent Construtor /// - public LogEvent(string url, Dictionary parameters, string httpVerb, Dictionary headers) + public LogEvent(string url, Dictionary parameters, string httpVerb, + Dictionary headers + ) { Url = url; Params = parameters; diff --git a/OptimizelySDK/Event/UserEventFactory.cs b/OptimizelySDK/Event/UserEventFactory.cs index c601b0331..f073237a8 100644 --- a/OptimizelySDK/Event/UserEventFactory.cs +++ b/OptimizelySDK/Event/UserEventFactory.cs @@ -35,16 +35,18 @@ public class UserEventFactory /// The user's attributes /// ImpressionEvent instance public static ImpressionEvent CreateImpressionEvent(ProjectConfig projectConfig, - Experiment activatedExperiment, - string variationId, - string userId, - UserAttributes userAttributes, - string flagKey, - string ruleType, - bool enabled = false) + Experiment activatedExperiment, + string variationId, + string userId, + UserAttributes userAttributes, + string flagKey, + string ruleType, + bool enabled = false + ) { - Variation variation = projectConfig.GetVariationFromId(activatedExperiment?.Key, variationId); - return CreateImpressionEvent(projectConfig, activatedExperiment, variation, userId, userAttributes, flagKey, ruleType, enabled); + var variation = projectConfig.GetVariationFromId(activatedExperiment?.Key, variationId); + return CreateImpressionEvent(projectConfig, activatedExperiment, variation, userId, + userAttributes, flagKey, ruleType, enabled); } /// @@ -59,45 +61,46 @@ public static ImpressionEvent CreateImpressionEvent(ProjectConfig projectConfig, /// experiment or featureDecision source /// ImpressionEvent instance public static ImpressionEvent CreateImpressionEvent(ProjectConfig projectConfig, - Experiment activatedExperiment, - Variation variation, - string userId, - UserAttributes userAttributes, - string flagKey, - string ruleType, - bool enabled = false) + Experiment activatedExperiment, + Variation variation, + string userId, + UserAttributes userAttributes, + string flagKey, + string ruleType, + bool enabled = false + ) { - if ((ruleType == FeatureDecision.DECISION_SOURCE_ROLLOUT || variation == null) && !projectConfig.SendFlagDecisions) + if ((ruleType == FeatureDecision.DECISION_SOURCE_ROLLOUT || variation == null) && + !projectConfig.SendFlagDecisions) { return null; } - var eventContext = new EventContext.Builder() - .WithProjectId(projectConfig.ProjectId) - .WithAccountId(projectConfig.AccountId) - .WithAnonymizeIP(projectConfig.AnonymizeIP) - .WithRevision(projectConfig.Revision) - .Build(); + var eventContext = new EventContext.Builder().WithProjectId(projectConfig.ProjectId). + WithAccountId(projectConfig.AccountId). + WithAnonymizeIP(projectConfig.AnonymizeIP). + WithRevision(projectConfig.Revision). + Build(); - var variationKey = ""; - var ruleKey = ""; + var variationKey = ""; + var ruleKey = ""; if (variation != null) { variationKey = variation.Key; ruleKey = activatedExperiment?.Key ?? string.Empty; } + var metadata = new DecisionMetadata(flagKey, ruleKey, ruleType, variationKey, enabled); - return new ImpressionEvent.Builder() - .WithEventContext(eventContext) - .WithBotFilteringEnabled(projectConfig.BotFiltering) - .WithExperiment(activatedExperiment) - .WithMetadata(metadata) - .WithUserId(userId) - .WithVariation(variation) - .WithVisitorAttributes(EventFactory.BuildAttributeList(userAttributes, projectConfig)) - .Build(); - + return new ImpressionEvent.Builder().WithEventContext(eventContext). + WithBotFilteringEnabled(projectConfig.BotFiltering). + WithExperiment(activatedExperiment). + WithMetadata(metadata). + WithUserId(userId). + WithVariation(variation). + WithVisitorAttributes( + EventFactory.BuildAttributeList(userAttributes, projectConfig)). + Build(); } /// @@ -109,29 +112,28 @@ public static ImpressionEvent CreateImpressionEvent(ProjectConfig projectConfig, /// The user's attributes /// Array Hash representing metadata associated with the event. /// ConversionEvent instance - public static ConversionEvent CreateConversionEvent(ProjectConfig projectConfig, - string eventKey, - string userId, - UserAttributes userAttributes, - EventTags eventTags) + public static ConversionEvent CreateConversionEvent(ProjectConfig projectConfig, + string eventKey, + string userId, + UserAttributes userAttributes, + EventTags eventTags + ) { - - - var eventContext = new EventContext.Builder() - .WithProjectId(projectConfig.ProjectId) - .WithAccountId(projectConfig.AccountId) - .WithAnonymizeIP(projectConfig.AnonymizeIP) - .WithRevision(projectConfig.Revision) - .Build(); + var eventContext = new EventContext.Builder().WithProjectId(projectConfig.ProjectId). + WithAccountId(projectConfig.AccountId). + WithAnonymizeIP(projectConfig.AnonymizeIP). + WithRevision(projectConfig.Revision). + Build(); - return new ConversionEvent.Builder() - .WithBotFilteringEnabled(projectConfig.BotFiltering) - .WithEventContext(eventContext) - .WithEventTags(eventTags) - .WithEvent(projectConfig.GetEvent(eventKey)) - .WithUserId(userId) - .WithVisitorAttributes(EventFactory.BuildAttributeList(userAttributes, projectConfig)) - .Build(); + return new ConversionEvent.Builder(). + WithBotFilteringEnabled(projectConfig.BotFiltering). + WithEventContext(eventContext). + WithEventTags(eventTags). + WithEvent(projectConfig.GetEvent(eventKey)). + WithUserId(userId). + WithVisitorAttributes( + EventFactory.BuildAttributeList(userAttributes, projectConfig)). + Build(); } } } diff --git a/OptimizelySDK/Exceptions/OptimizelyException.cs b/OptimizelySDK/Exceptions/OptimizelyException.cs index 1cad8c449..ad9cf4d59 100644 --- a/OptimizelySDK/Exceptions/OptimizelyException.cs +++ b/OptimizelySDK/Exceptions/OptimizelyException.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; @@ -21,114 +22,84 @@ namespace OptimizelySDK.Exceptions public class OptimizelyException : Exception { public OptimizelyException(string message) - : base(message) - { - } + : base(message) { } } public class OptimizelyRuntimeException : OptimizelyException { public OptimizelyRuntimeException(string message) - : base(message) - { - } + : base(message) { } } - + public class InvalidJsonException : OptimizelyException { public InvalidJsonException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidAttributeException : OptimizelyException { public InvalidAttributeException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidAudienceException : OptimizelyException { public InvalidAudienceException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidEventException : OptimizelyException { public InvalidEventException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidExperimentException : OptimizelyException { public InvalidExperimentException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidGroupException : OptimizelyException { public InvalidGroupException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidInputException : OptimizelyException { public InvalidInputException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidVariationException : OptimizelyException { public InvalidVariationException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidFeatureException : OptimizelyException { public InvalidFeatureException(string message) - : base(message) - { - } + : base(message) { } } public class InvalidRolloutException : OptimizelyException { public InvalidRolloutException(string message) - : base(message) - { - } + : base(message) { } } public class ConfigParseException : OptimizelyException { public ConfigParseException(string message) - : base(message) - { - - } + : base(message) { } } public class ParseException : OptimizelyException { public ParseException(string message) - : base(message) - { - - } + : base(message) { } } } diff --git a/OptimizelySDK/ForcedDecisionsStore.cs b/OptimizelySDK/ForcedDecisionsStore.cs index 7097a1211..1b842fbc6 100644 --- a/OptimizelySDK/ForcedDecisionsStore.cs +++ b/OptimizelySDK/ForcedDecisionsStore.cs @@ -33,6 +33,7 @@ static ForcedDecisionsStore() { NullForcedDecisionStore = new ForcedDecisionsStore(); } + public ForcedDecisionsStore() { ForcedDecisionsMap = new Dictionary(); @@ -50,16 +51,13 @@ internal static ForcedDecisionsStore NullForcedDecision() public ForcedDecisionsStore(ForcedDecisionsStore forcedDecisionsStore) { - ForcedDecisionsMap = new Dictionary(forcedDecisionsStore.ForcedDecisionsMap); + ForcedDecisionsMap = + new Dictionary(forcedDecisionsStore. + ForcedDecisionsMap); } - public int Count - { - get - { - return ForcedDecisionsMap.Count; - } - } + public int Count => ForcedDecisionsMap.Count; + public bool Remove(OptimizelyDecisionContext context) { return ForcedDecisionsMap.Remove(context.GetKey()); @@ -75,10 +73,12 @@ public OptimizelyForcedDecision this[OptimizelyDecisionContext context] get { if (context != null && context.IsValid - && ForcedDecisionsMap.TryGetValue(context.GetKey(), out OptimizelyForcedDecision flagForcedDecision)) + && ForcedDecisionsMap.TryGetValue(context.GetKey(), + out var flagForcedDecision)) { return flagForcedDecision; } + return null; } set @@ -88,7 +88,6 @@ public OptimizelyForcedDecision this[OptimizelyDecisionContext context] ForcedDecisionsMap[context.GetKey()] = value; } } - } } } diff --git a/OptimizelySDK/Logger/DefaultLogger.cs b/OptimizelySDK/Logger/DefaultLogger.cs index a7d250a94..6c8cb4c7c 100644 --- a/OptimizelySDK/Logger/DefaultLogger.cs +++ b/OptimizelySDK/Logger/DefaultLogger.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Diagnostics; namespace OptimizelySDK.Logger @@ -24,7 +25,7 @@ public class DefaultLogger : ILogger { public void Log(LogLevel level, string message) { - string line = $"[{level}] : {message}"; + var line = $"[{level}] : {message}"; Debug.WriteLine(line); } } diff --git a/OptimizelySDK/Logger/ILogger.cs b/OptimizelySDK/Logger/ILogger.cs index 13c8828f8..2a3a45973 100644 --- a/OptimizelySDK/Logger/ILogger.cs +++ b/OptimizelySDK/Logger/ILogger.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Logger { public enum LogLevel diff --git a/OptimizelySDK/Logger/NoOpLogger.cs b/OptimizelySDK/Logger/NoOpLogger.cs index a8f6b45f1..9e81b92d8 100644 --- a/OptimizelySDK/Logger/NoOpLogger.cs +++ b/OptimizelySDK/Logger/NoOpLogger.cs @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + namespace OptimizelySDK.Logger { public class NoOpLogger : ILogger { - public void Log(LogLevel level, string message) - { - } + public void Log(LogLevel level, string message) { } } } diff --git a/OptimizelySDK/Notifications/NotificationCenter.cs b/OptimizelySDK/Notifications/NotificationCenter.cs index cce0ba657..e6c1f3499 100644 --- a/OptimizelySDK/Notifications/NotificationCenter.cs +++ b/OptimizelySDK/Notifications/NotificationCenter.cs @@ -33,11 +33,11 @@ public class NotificationCenter /// public enum NotificationType { - Activate, // Activate called. - Track, // Track called. - Decision, // A decision is made in the system. i.e. user activation, feature access or feature-variable value retrieval. + Activate, // Activate called. + Track, // Track called. + Decision, // A decision is made in the system. i.e. user activation, feature access or feature-variable value retrieval. OptimizelyConfigUpdate, // When datafile is updated using HttpProjectConfigManager. - LogEvent // LogEvent notification sends on flushing batch-event. // When datafile is updated using HttpProjectConfigManager. + LogEvent, // LogEvent notification sends on flushing batch-event. // When datafile is updated using HttpProjectConfigManager. }; /// @@ -49,8 +49,10 @@ public enum NotificationType /// The variation entity /// The impression event [Obsolete("ActivateCallback is deprecated. Use DecisionCallback instead.")] - public delegate void ActivateCallback(Experiment experiment, string userId, UserAttributes userAttributes, - Variation variation, LogEvent logEvent); + public delegate void ActivateCallback(Experiment experiment, string userId, + UserAttributes userAttributes, + Variation variation, LogEvent logEvent + ); /// /// Delegate for track notifcations. @@ -60,8 +62,10 @@ public delegate void ActivateCallback(Experiment experiment, string userId, User /// Associative array of attributes for the user /// Associative array of EventTags representing metadata associated with the event /// The conversion event - public delegate void TrackCallback(string eventKey, string userId, UserAttributes userAttributes, EventTags eventTags, - LogEvent logEvent); + public delegate void TrackCallback(string eventKey, string userId, + UserAttributes userAttributes, EventTags eventTags, + LogEvent logEvent + ); /// /// Delegate for decision notifications. @@ -70,7 +74,9 @@ public delegate void TrackCallback(string eventKey, string userId, UserAttribute /// The user identifier /// Associative array of attributes for the user /// Dictionary containing decision information - public delegate void DecisionCallback(string type, string userId, UserAttributes userAttributes, Dictionary decisionInfo); + public delegate void DecisionCallback(string type, string userId, + UserAttributes userAttributes, Dictionary decisionInfo + ); /// /// Delegate for project config update. @@ -95,10 +101,13 @@ public delegate void TrackCallback(string eventKey, string userId, UserAttribute /// /// Property representing total notifications count. /// - public int NotificationsCount { - get { - int notificationsCount = 0; - foreach (var notificationsMap in Notifications.Values) { + public int NotificationsCount + { + get + { + var notificationsCount = 0; + foreach (var notificationsMap in Notifications.Values) + { notificationsCount += notificationsMap.Count; } @@ -114,14 +123,17 @@ public NotificationCenter(ILogger logger = null) { Logger = logger ?? new NoOpLogger(); - foreach (NotificationType notificationType in Enum.GetValues(typeof(NotificationType))) { + foreach (NotificationType notificationType in Enum.GetValues(typeof(NotificationType))) + { Notifications[notificationType] = new Dictionary(); } } public int GetNotificationCount(NotificationType notificationType) { - return Notifications.ContainsKey(notificationType) ? Notifications[notificationType].Count : 0; + return Notifications.ContainsKey(notificationType) ? + Notifications[notificationType].Count : + 0; } /// @@ -132,10 +144,14 @@ public int GetNotificationCount(NotificationType notificationType) /// int | 0 for invalid notification type, -1 for adding existing notification /// or the notification id of newly added notification. [Obsolete("ActivateCallback is deprecated. Use DecisionCallback instead.")] - public int AddNotification(NotificationType notificationType, ActivateCallback activateCallback) + public int AddNotification(NotificationType notificationType, + ActivateCallback activateCallback + ) { if (!IsNotificationTypeValid(notificationType, NotificationType.Activate)) + { return 0; + } return AddNotification(notificationType, (object)activateCallback); } @@ -150,7 +166,9 @@ public int AddNotification(NotificationType notificationType, ActivateCallback a public int AddNotification(NotificationType notificationType, TrackCallback trackCallback) { if (!IsNotificationTypeValid(notificationType, NotificationType.Track)) + { return 0; + } return AddNotification(notificationType, (object)trackCallback); } @@ -162,10 +180,14 @@ public int AddNotification(NotificationType notificationType, TrackCallback trac /// Callback function to call when event gets triggered /// int | 0 for invalid notification type, -1 for adding existing notification /// or the notification id of newly added notification. - public int AddNotification(NotificationType notificationType, DecisionCallback decisionCallback) + public int AddNotification(NotificationType notificationType, + DecisionCallback decisionCallback + ) { if (!IsNotificationTypeValid(notificationType, NotificationType.Decision)) + { return 0; + } return AddNotification(notificationType, (object)decisionCallback); } @@ -177,10 +199,14 @@ public int AddNotification(NotificationType notificationType, DecisionCallback d /// Callback function to call when event gets triggered /// 0 for invalid notification type, -1 for adding existing notification /// or the notification id of newly added notification. - public int AddNotification(NotificationType notificationType, OptimizelyConfigUpdateCallback optimizelyConfigUpdate) + public int AddNotification(NotificationType notificationType, + OptimizelyConfigUpdateCallback optimizelyConfigUpdate + ) { if (!IsNotificationTypeValid(notificationType, NotificationType.OptimizelyConfigUpdate)) + { return 0; + } return AddNotification(notificationType, (object)optimizelyConfigUpdate); } @@ -192,10 +218,14 @@ public int AddNotification(NotificationType notificationType, OptimizelyConfigUp /// Callback function to call when event gets triggered /// 0 for invalid notification type, -1 for adding existing notification /// or the notification id of newly added notification. - public int AddNotification(NotificationType notificationType, LogEventCallback logEventCallback) + public int AddNotification(NotificationType notificationType, + LogEventCallback logEventCallback + ) { if (!IsNotificationTypeValid(notificationType, NotificationType.LogEvent)) + { return 0; + } return AddNotification(notificationType, (object)logEventCallback); } @@ -206,10 +236,15 @@ public int AddNotification(NotificationType notificationType, LogEventCallback l /// Provided notification type /// expected notification type /// true if notification type is valid, false otherwise - private bool IsNotificationTypeValid(NotificationType providedNotificationType, NotificationType expectedNotificationType) + private bool IsNotificationTypeValid(NotificationType providedNotificationType, + NotificationType expectedNotificationType + ) { - if (providedNotificationType != expectedNotificationType) { - Logger.Log(LogLevel.ERROR, $@"Invalid notification type provided for ""{expectedNotificationType}"" callback."); + if (providedNotificationType != expectedNotificationType) + { + Logger.Log(LogLevel.ERROR, + $@"Invalid notification type provided for ""{expectedNotificationType + }"" callback."); return false; } @@ -226,11 +261,17 @@ private int AddNotification(NotificationType notificationType, object notificati { var notificationHoldersList = Notifications[notificationType]; - if (!Notifications.ContainsKey(notificationType) || Notifications[notificationType].Count == 0) + if (!Notifications.ContainsKey(notificationType) || + Notifications[notificationType].Count == 0) + { Notifications[notificationType][NotificationId] = notificationCallback; - else { - foreach (var notification in this.Notifications[notificationType]) { - if ((Delegate)notification.Value == (Delegate)notificationCallback) { + } + else + { + foreach (var notification in Notifications[notificationType]) + { + if ((Delegate)notification.Value == (Delegate)notificationCallback) + { Logger.Log(LogLevel.ERROR, "The notification callback already exists."); return -1; } @@ -239,7 +280,7 @@ private int AddNotification(NotificationType notificationType, object notificati Notifications[notificationType][NotificationId] = notificationCallback; } - int retVal = NotificationId; + var retVal = NotificationId; NotificationId += 1; return retVal; @@ -252,8 +293,11 @@ private int AddNotification(NotificationType notificationType, object notificati /// Returns true if found and removed, false otherwise. public bool RemoveNotification(int notificationId) { - foreach (var key in Notifications.Keys) { - if (Notifications[key] != null && Notifications[key].Any(notification => notification.Key == notificationId)) { + foreach (var key in Notifications.Keys) + { + if (Notifications[key] != null && Notifications[key]. + Any(notification => notification.Key == notificationId)) + { Notifications[key].Remove(notificationId); return true; } @@ -276,7 +320,8 @@ public void ClearNotifications(NotificationType notificationType) /// public void ClearAllNotifications() { - foreach (var notificationsMap in Notifications.Values) { + foreach (var notificationsMap in Notifications.Values) + { notificationsMap.Clear(); } } @@ -288,14 +333,19 @@ public void ClearAllNotifications() /// Arguments to pass in notification callbacks public void SendNotifications(NotificationType notificationType, params object[] args) { - foreach (var notification in Notifications[notificationType]) { - try { - Delegate d = notification.Value as Delegate; + foreach (var notification in Notifications[notificationType]) + { + try + { + var d = notification.Value as Delegate; d.DynamicInvoke(args); - } catch (Exception exception) { - Logger.Log(LogLevel.ERROR, "Problem calling notify callback. Error: " + exception.Message); + } + catch (Exception exception) + { + Logger.Log(LogLevel.ERROR, + "Problem calling notify callback. Error: " + exception.Message); } } } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Notifications/NotificationCenterRegistry.cs b/OptimizelySDK/Notifications/NotificationCenterRegistry.cs index ecbbda84d..21181708d 100644 --- a/OptimizelySDK/Notifications/NotificationCenterRegistry.cs +++ b/OptimizelySDK/Notifications/NotificationCenterRegistry.cs @@ -71,7 +71,7 @@ public static void RemoveNotificationCenter(string sdkKey) lock (_mutex) { if (_notificationCenters.TryGetValue(sdkKey, - out NotificationCenter notificationCenter)) + out var notificationCenter)) { notificationCenter.ClearAllNotifications(); _notificationCenters.Remove(sdkKey); diff --git a/OptimizelySDK/Odp/Entity/Error.cs b/OptimizelySDK/Odp/Entity/Error.cs index d5b0ba66a..1ef6deda9 100644 --- a/OptimizelySDK/Odp/Entity/Error.cs +++ b/OptimizelySDK/Odp/Entity/Error.cs @@ -25,17 +25,17 @@ public class Error /// Human-readable message from the error /// public string Message { get; set; } - + /// /// Points of failure producing the error /// public Location[] Locations { get; set; } - + /// /// Files or urls producing the error /// public string[] Path { get; set; } - + /// /// Additional technical error information /// diff --git a/OptimizelySDK/Odp/Entity/Location.cs b/OptimizelySDK/Odp/Entity/Location.cs index 2483ff6be..b2e4eab89 100644 --- a/OptimizelySDK/Odp/Entity/Location.cs +++ b/OptimizelySDK/Odp/Entity/Location.cs @@ -25,7 +25,7 @@ public class Location /// Code or data line number /// public int Line { get; set; } - + /// /// Code or data column number /// diff --git a/OptimizelySDK/Odp/Entity/Node.cs b/OptimizelySDK/Odp/Entity/Node.cs index 745c0f3d4..94999d19f 100644 --- a/OptimizelySDK/Odp/Entity/Node.cs +++ b/OptimizelySDK/Odp/Entity/Node.cs @@ -25,7 +25,7 @@ public class Node /// Descriptive label of a node /// public string Name { get; set; } - + /// /// Status of the node /// diff --git a/OptimizelySDK/Odp/Entity/Response.cs b/OptimizelySDK/Odp/Entity/Response.cs index 303d0dc15..75c8bc9cb 100644 --- a/OptimizelySDK/Odp/Entity/Response.cs +++ b/OptimizelySDK/Odp/Entity/Response.cs @@ -34,12 +34,6 @@ public class Response /// /// Determines if an error exists /// - public bool HasErrors - { - get - { - return Errors != null && Errors.Length > 0; - } - } + public bool HasErrors => Errors != null && Errors.Length > 0; } } diff --git a/OptimizelySDK/Odp/ICache.cs b/OptimizelySDK/Odp/ICache.cs index 92a76fd64..be133b0d5 100644 --- a/OptimizelySDK/Odp/ICache.cs +++ b/OptimizelySDK/Odp/ICache.cs @@ -16,7 +16,7 @@ namespace OptimizelySDK.Odp { - public interface ICache + public interface ICache where T : class { void Save(string key, T value); diff --git a/OptimizelySDK/Odp/LruCache.cs b/OptimizelySDK/Odp/LruCache.cs index 11e387427..2148b9315 100644 --- a/OptimizelySDK/Odp/LruCache.cs +++ b/OptimizelySDK/Odp/LruCache.cs @@ -209,11 +209,11 @@ internal string[] CurrentCacheKeysForTesting() /// /// For Testing Only: Retrieve the current cache timout /// - internal TimeSpan TimeoutForTesting { get { return _timeout; } } + internal TimeSpan TimeoutForTesting => _timeout; /// /// For Testing Only: Retrieve hte current maximum cache size /// - internal int MaxSizeForTesting { get { return _maxSize; } } + internal int MaxSizeForTesting => _maxSize; } } diff --git a/OptimizelySDK/Odp/OdpEventManager.cs b/OptimizelySDK/Odp/OdpEventManager.cs index 3039e7f25..c83dd3250 100644 --- a/OptimizelySDK/Odp/OdpEventManager.cs +++ b/OptimizelySDK/Odp/OdpEventManager.cs @@ -151,12 +151,15 @@ protected virtual void Run() // otherwise wait for the new event indefinitely if (_currentBatch.Count > 0) { - _eventQueue.TryTake(out item, (int)(_flushingIntervalDeadline - DateTime.Now.MillisecondsSince1970())); + _eventQueue.TryTake(out item, + (int)(_flushingIntervalDeadline - + DateTime.Now.MillisecondsSince1970())); } else { item = _eventQueue.Take(); - Thread.Sleep(1); // TODO: need to figure out why this is allowing item to read shutdown signal. + // TODO: need to figure out why this is allowing item to read shutdown signal. + Thread.Sleep(1); } if (item == null) @@ -167,6 +170,7 @@ protected virtual void Run() _logger.Log(LogLevel.DEBUG, $"Flushing queue."); FlushQueue(); } + continue; } else if (item == _shutdownSignal) @@ -184,7 +188,6 @@ protected virtual void Run() { AddToBatch(odpEvent); } - } } catch (InvalidOperationException ioe) @@ -520,10 +523,10 @@ public OdpEventManager Build() var manager = new OdpEventManager(); manager._eventQueue = _eventQueue; manager._odpEventApiManager = _odpEventApiManager; - manager._flushInterval = (_flushInterval > TimeSpan.Zero) ? + manager._flushInterval = _flushInterval > TimeSpan.Zero ? _flushInterval : Constants.DEFAULT_FLUSH_INTERVAL; - manager._batchSize = (_flushInterval == TimeSpan.Zero) ? + manager._batchSize = _flushInterval == TimeSpan.Zero ? 1 : Constants.DEFAULT_BATCH_SIZE; manager._timeoutInterval = _timeoutInterval <= TimeSpan.Zero ? @@ -568,16 +571,16 @@ public OdpEventManager Build() /// For Testing Only: Read the current ODP config /// /// Current ODP settings - internal OdpConfig OdpConfigForTesting { get { return _odpConfig; } } + internal OdpConfig OdpConfigForTesting => _odpConfig; /// /// For Testing Only: Read the current flush interval /// - internal TimeSpan FlushIntervalForTesting { get { return _flushInterval; } } + internal TimeSpan FlushIntervalForTesting => _flushInterval; /// /// For Testing Only: Read the current timeout interval /// - internal TimeSpan TimeoutIntervalForTesting { get { return _timeoutInterval; } } + internal TimeSpan TimeoutIntervalForTesting => _timeoutInterval; } } diff --git a/OptimizelySDK/Odp/OdpSegmentApiManager.cs b/OptimizelySDK/Odp/OdpSegmentApiManager.cs index ec7fdae31..3889da931 100644 --- a/OptimizelySDK/Odp/OdpSegmentApiManager.cs +++ b/OptimizelySDK/Odp/OdpSegmentApiManager.cs @@ -131,10 +131,10 @@ public string[] FetchSegments(string apiKey, string apiHost, OdpUserKeyType user return null; } - return segments.Data.Customer.Audiences.Edges - .Where(e => e.Node.State == BaseCondition.QUALIFIED) - .Select(e => e.Node.Name) - .ToArray(); + return segments.Data.Customer.Audiences.Edges. + Where(e => e.Node.State == BaseCondition.QUALIFIED). + Select(e => e.Node.Name). + ToArray(); } /// @@ -155,10 +155,9 @@ IEnumerable segmentsToCheck ""userId"": ""{userValue}"", ""audiences"": {audiences} } - }" - .Replace("{userKey}", userKey) - .Replace("{userValue}", userValue) - .Replace("{audiences}", JsonConvert.SerializeObject(segmentsToCheck)); + }".Replace("{userKey}", userKey). + Replace("{userValue}", userValue). + Replace("{audiences}", JsonConvert.SerializeObject(segmentsToCheck)); } /// diff --git a/OptimizelySDK/Odp/OdpSegmentManager.cs b/OptimizelySDK/Odp/OdpSegmentManager.cs index 381f315b4..efc5654ee 100644 --- a/OptimizelySDK/Odp/OdpSegmentManager.cs +++ b/OptimizelySDK/Odp/OdpSegmentManager.cs @@ -165,6 +165,6 @@ public void ResetCache() /// /// For Testing Only: Retrieve the current segment cache /// - internal ICache> SegmentsCacheForTesting { get { return _segmentsCache; } } + internal ICache> SegmentsCacheForTesting => _segmentsCache; } } diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index 16d6d8f34..d26a2e05d 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -80,13 +80,7 @@ public class Optimizely : IOptimizely, IDisposable /// It returns true if the ProjectConfig is valid otherwise false. /// Also, it may block execution if GetConfig() blocks execution to get ProjectConfig. /// - public bool IsValid - { - get - { - return ProjectConfigManager?.GetConfig() != null; - } - } + public bool IsValid => ProjectConfigManager?.GetConfig() != null; public static String SDK_VERSION { @@ -94,26 +88,20 @@ public static String SDK_VERSION { // Example output: "2.1.0" . Should be kept in synch with NuGet package version. #if NET35 || NET40 - Assembly assembly = Assembly.GetExecutingAssembly(); + var assembly = Assembly.GetExecutingAssembly(); #else - Assembly assembly = typeof(Optimizely).GetTypeInfo().Assembly; + var assembly = typeof(Optimizely).GetTypeInfo().Assembly; #endif // Microsoft Major.Minor.Build.Revision // Semantic Major.Minor.Patch - Version version = assembly.GetName().Version; - String answer = String.Format("{0}.{1}.{2}", version.Major, version.Minor, + var version = assembly.GetName().Version; + var answer = String.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); return answer; } } - public static String SDK_TYPE - { - get - { - return "csharp-sdk"; - } - } + public static String SDK_TYPE => "csharp-sdk"; public const string USER_ID = "User Id"; public const string EXPERIMENT_KEY = "Experiment Key"; @@ -156,7 +144,7 @@ public Optimizely(string datafile, null, eventProcessor, defaultDecideOptions, odpManager); #else InitializeComponents(eventDispatcher, logger, errorHandler, userProfileService, - null, eventProcessor, defaultDecideOptions); + null, eventProcessor, defaultDecideOptions); #endif if (ValidateInputs(datafile, skipJsonValidation)) @@ -177,11 +165,15 @@ public Optimizely(string datafile, } catch (Exception ex) { - string error = String.Empty; + var error = String.Empty; if (ex.GetType() == typeof(ConfigParseException)) + { error = ex.Message; + } else + { error = "Provided 'datafile' is in an invalid format. " + ex.Message; + } Logger.Log(LogLevel.ERROR, error); ErrorHandler.HandleError(ex); @@ -230,8 +222,8 @@ public Optimizely(ProjectConfigManager configManager, if (ProjectConfigManager.SdkKey != null) { - NotificationCenterRegistry.GetNotificationCenter(configManager.SdkKey, logger) - ?.AddNotification(NotificationCenter.NotificationType.OptimizelyConfigUpdate, + NotificationCenterRegistry.GetNotificationCenter(configManager.SdkKey, logger)?. + AddNotification(NotificationCenter.NotificationType.OptimizelyConfigUpdate, () => { projectConfig = ProjectConfigManager.CachedProjectConfig; @@ -297,10 +289,13 @@ public Variation Activate(string experimentKey, string userId, return null; } - var inputValues = new Dictionary { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; + var inputValues = new Dictionary + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; if (!ValidateStringInputs(inputValues)) + { return null; + } var experiment = config.GetExperimentFromKey(experimentKey); @@ -354,10 +349,13 @@ public void Track(string eventKey, string userId, UserAttributes userAttributes return; } - var inputValues = new Dictionary { { USER_ID, userId }, { EVENT_KEY, eventKey } }; + var inputValues = new Dictionary + { { USER_ID, userId }, { EVENT_KEY, eventKey } }; if (!ValidateStringInputs(inputValues)) + { return; + } var eevent = config.GetEvent(eventKey); @@ -422,26 +420,33 @@ private Variation GetVariation(string experimentKey, string userId, ProjectConfi return null; } - var inputValues = new Dictionary { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; + var inputValues = new Dictionary + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; if (!ValidateStringInputs(inputValues)) + { return null; + } - Experiment experiment = config.GetExperimentFromKey(experimentKey); + var experiment = config.GetExperimentFromKey(experimentKey); if (experiment.Key == null) + { return null; + } + userAttributes = userAttributes ?? new UserAttributes(); var userContext = CreateUserContextCopy(userId, userAttributes); - var variation = DecisionService.GetVariation(experiment, userContext, config)?.ResultObject; + var variation = DecisionService.GetVariation(experiment, userContext, config)?. + ResultObject; var decisionInfo = new Dictionary { { "experimentKey", experimentKey }, { "variationKey", variation?.Key }, }; - var decisionNotificationType = config.IsFeatureExperiment(experiment.Id) - ? DecisionNotificationTypes.FEATURE_TEST - : DecisionNotificationTypes.AB_TEST; + var decisionNotificationType = config.IsFeatureExperiment(experiment.Id) ? + DecisionNotificationTypes.FEATURE_TEST : + DecisionNotificationTypes.AB_TEST; NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, decisionNotificationType, userId, userAttributes, decisionInfo); @@ -465,7 +470,8 @@ public bool SetForcedVariation(string experimentKey, string userId, string varia return false; } - var inputValues = new Dictionary { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; + var inputValues = new Dictionary + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; return ValidateStringInputs(inputValues) && DecisionService.SetForcedVariation(experimentKey, userId, variationKey, config); } @@ -484,10 +490,13 @@ public Variation GetForcedVariation(string experimentKey, string userId) return null; } - var inputValues = new Dictionary { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; + var inputValues = new Dictionary + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; if (!ValidateStringInputs(inputValues)) + { return null; + } return DecisionService.GetForcedVariation(experimentKey, userId, config).ResultObject; } @@ -516,24 +525,31 @@ public virtual bool IsFeatureEnabled(string featureKey, string userId, return false; } - var inputValues = new Dictionary { { USER_ID, userId }, { FEATURE_KEY, featureKey } }; + var inputValues = new Dictionary + { { USER_ID, userId }, { FEATURE_KEY, featureKey } }; if (!ValidateStringInputs(inputValues)) + { return false; + } var featureFlag = config.GetFeatureFlagFromKey(featureKey); if (string.IsNullOrEmpty(featureFlag.Key)) + { return false; + } if (!Validator.IsFeatureFlagValid(config, featureFlag)) + { return false; + } - bool featureEnabled = false; + var featureEnabled = false; var sourceInfo = new Dictionary(); var decision = DecisionService.GetVariationForFeature(featureFlag, CreateUserContextCopy(userId, userAttributes), - config) - .ResultObject; + config). + ResultObject; var variation = decision?.Variation; var decisionSource = decision?.Source ?? FeatureDecision.DECISION_SOURCE_ROLLOUT; @@ -558,11 +574,15 @@ public virtual bool IsFeatureEnabled(string featureKey, string userId, } if (featureEnabled == true) + { Logger.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is enabled for user ""{userId}""."); + } else + { Logger.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is not enabled for user ""{userId}""."); + } var decisionInfo = new Dictionary { @@ -601,20 +621,24 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var Logger.Log(LogLevel.ERROR, $@"Datafile has invalid format. Failing '{ FeatureVariable.GetFeatureVariableTypeName(variableType)}'."); - return default(T); + return default; } var inputValues = new Dictionary { - { USER_ID, userId }, { FEATURE_KEY, featureKey }, { VARIABLE_KEY, variableKey } + { USER_ID, userId }, { FEATURE_KEY, featureKey }, { VARIABLE_KEY, variableKey }, }; if (!ValidateStringInputs(inputValues)) - return default(T); + { + return default; + } var featureFlag = config.GetFeatureFlagFromKey(featureKey); if (string.IsNullOrEmpty(featureFlag.Key)) - return default(T); + { + return default; + } var featureVariable = featureFlag.GetFeatureVariableFromKey(variableKey); if (featureVariable == null) @@ -622,22 +646,22 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var Logger.Log(LogLevel.ERROR, $@"No feature variable was found for key ""{variableKey}"" in feature flag ""{ featureKey}""."); - return default(T); + return default; } else if (featureVariable.Type != variableType) { Logger.Log(LogLevel.ERROR, $@"Variable is of type ""{featureVariable.Type }"", but you requested it as type ""{variableType}""."); - return default(T); + return default; } var featureEnabled = false; var variableValue = featureVariable.DefaultValue; var decision = DecisionService.GetVariationForFeature(featureFlag, CreateUserContextCopy(userId, userAttributes), - config) - .ResultObject; + config). + ResultObject; if (decision?.Variation != null) { @@ -690,9 +714,10 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var { "featureEnabled", featureEnabled }, { "variableKey", variableKey }, { - "variableValue", typeCastedValue is OptimizelyJSON - ? ((OptimizelyJSON)typeCastedValue).ToDictionary() - : typeCastedValue + "variableValue", + typeCastedValue is OptimizelyJSON ? + ((OptimizelyJSON)typeCastedValue).ToDictionary() : + typeCastedValue }, { "variableType", variableType.ToString().ToLower() }, { "source", decision?.Source }, @@ -796,10 +821,12 @@ public OptimizelyUserContext CreateUserContext(string userId, UserAttributes userAttributes = null ) { - var inputValues = new Dictionary { { USER_ID, userId }, }; + var inputValues = new Dictionary { { USER_ID, userId } }; if (!ValidateStringInputs(inputValues)) + { return null; + } return new OptimizelyUserContext(this, userId, userAttributes, ErrorHandler, Logger); } @@ -808,14 +835,17 @@ private OptimizelyUserContext CreateUserContextCopy(string userId, UserAttributes userAttributes = null ) { - var inputValues = new Dictionary { { USER_ID, userId }, }; + var inputValues = new Dictionary { { USER_ID, userId } }; if (!ValidateStringInputs(inputValues)) + { return null; + } - return new OptimizelyUserContext(this, userId, userAttributes, null, null, ErrorHandler, Logger + return new OptimizelyUserContext(this, userId, userAttributes, null, null, ErrorHandler, + Logger #if USE_ODP - , shouldIdentifyUser: false + , false #endif ); } @@ -913,7 +943,7 @@ OptimizelyDecideOption[] options { foreach (var featureVariable in flag?.Variables) { - string variableValue = featureVariable.DefaultValue; + var variableValue = featureVariable.DefaultValue; if (featureEnabled) { var featureVariableUsageInstance = @@ -928,7 +958,9 @@ OptimizelyDecideOption[] options GetTypeCastedVariableValue(variableValue, featureVariable.Type); if (typeCastedValue is OptimizelyJSON) + { typeCastedValue = ((OptimizelyJSON)typeCastedValue).ToDictionary(); + } variableMap.Add(featureVariable.Key, typeCastedValue); } @@ -944,8 +976,9 @@ OptimizelyDecideOption[] options featureEnabled); } - var reasonsToReport = decisionReasons.ToReport(allOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS)) - .ToArray(); + var reasonsToReport = decisionReasons. + ToReport(allOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS)). + ToArray(); var variationKey = decision?.Variation?.Key; // TODO: add ruleKey values when available later. use a copy of experimentKey until then. @@ -959,7 +992,7 @@ OptimizelyDecideOption[] options { "variationKey", variationKey }, { "ruleKey", ruleKey }, { "reasons", reasonsToReport }, - { "decisionEventDispatched", decisionEventDispatched } + { "decisionEventDispatched", decisionEventDispatched }, }; NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, @@ -1018,7 +1051,7 @@ OptimizelyDecideOption[] options var allOptions = GetAllOptions(options); - foreach (string key in keys) + foreach (var key in keys) { var decision = Decide(user, key, options); if (!allOptions.Contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || @@ -1033,7 +1066,7 @@ OptimizelyDecideOption[] options private OptimizelyDecideOption[] GetAllOptions(OptimizelyDecideOption[] options) { - OptimizelyDecideOption[] copiedOptions = DefaultDecideOptions; + var copiedOptions = DefaultDecideOptions; if (options != null) { copiedOptions = options.Union(DefaultDecideOptions).ToArray(); @@ -1117,7 +1150,7 @@ private bool SendImpressionEvent(Experiment experiment, Variation variation, str /// List of the feature keys that are enabled for the user. public List GetEnabledFeatures(string userId, UserAttributes userAttributes = null) { - List enabledFeaturesList = new List(); + var enabledFeaturesList = new List(); var config = ProjectConfigManager?.GetConfig(); @@ -1129,13 +1162,17 @@ public List GetEnabledFeatures(string userId, UserAttributes userAttribu } if (!ValidateStringInputs(new Dictionary { { USER_ID, userId } })) + { return enabledFeaturesList; + } foreach (var feature in config.FeatureKeyMap.Values) { var featureKey = feature.Key; if (IsFeatureEnabled(featureKey, userId, userAttributes)) + { enabledFeaturesList.Add(featureKey); + } } return enabledFeaturesList; @@ -1180,7 +1217,9 @@ public OptimizelyJSON GetAllFeatureVariables(string featureKey, string userId, } if (!Validator.IsFeatureFlagValid(config, featureFlag)) + { return null; + } var featureEnabled = false; var decisionResult = DecisionService.GetVariationForFeature(featureFlag, @@ -1213,7 +1252,7 @@ public OptimizelyJSON GetAllFeatureVariables(string featureKey, string userId, var valuesMap = new Dictionary(); foreach (var featureVariable in featureFlag.Variables) { - string variableValue = featureVariable.DefaultValue; + var variableValue = featureVariable.DefaultValue; if (featureEnabled) { var featureVariableUsageInstance = @@ -1228,7 +1267,9 @@ public OptimizelyJSON GetAllFeatureVariables(string featureKey, string userId, GetTypeCastedVariableValue(variableValue, featureVariable.Type); if (typeCastedValue is OptimizelyJSON) + { typeCastedValue = ((OptimizelyJSON)typeCastedValue).ToDictionary(); + } valuesMap.Add(featureVariable.Key, typeCastedValue); } @@ -1331,7 +1372,7 @@ Dictionary data /// True if all values are valid, false otherwise private bool ValidateStringInputs(Dictionary inputs) { - bool isValid = true; + var isValid = true; // Empty user Id is valid value. if (inputs.ContainsKey(USER_ID)) @@ -1363,18 +1404,18 @@ private object GetTypeCastedVariableValue(string value, string type) switch (type) { case FeatureVariable.BOOLEAN_TYPE: - bool.TryParse(value, out bool booleanValue); + bool.TryParse(value, out var booleanValue); result = booleanValue; break; case FeatureVariable.DOUBLE_TYPE: double.TryParse(value, System.Globalization.NumberStyles.Number, - System.Globalization.CultureInfo.InvariantCulture, out double doubleValue); + System.Globalization.CultureInfo.InvariantCulture, out var doubleValue); result = doubleValue; break; case FeatureVariable.INTEGER_TYPE: - int.TryParse(value, out int intValue); + int.TryParse(value, out var intValue); result = intValue; break; @@ -1388,8 +1429,10 @@ private object GetTypeCastedVariableValue(string value, string type) } if (result == null) + { Logger.Log(LogLevel.ERROR, $@"Unable to cast variable value ""{value}"" to type ""{type}""."); + } return result; } diff --git a/OptimizelySDK/OptimizelyDecisionContext.cs b/OptimizelySDK/OptimizelyDecisionContext.cs index 24842d14d..69b1d6f80 100644 --- a/OptimizelySDK/OptimizelyDecisionContext.cs +++ b/OptimizelySDK/OptimizelyDecisionContext.cs @@ -37,27 +37,28 @@ public class OptimizelyDecisionContext /// /// Flag key of the context. /// - public string FlagKey { get { return flagKey; } } + public string FlagKey => flagKey; /// /// Rule key, it can be experiment or rollout key and nullable. /// - public string RuleKey { get { return ruleKey; } } + public string RuleKey => ruleKey; - public OptimizelyDecisionContext(string flagKey, string ruleKey= null) + public OptimizelyDecisionContext(string flagKey, string ruleKey = null) { - if (flagKey != null) { + if (flagKey != null) + { IsValid = true; } + this.flagKey = flagKey; this.ruleKey = ruleKey; } public string GetKey() { - return string.Format("{0}{1}{2}", FlagKey, OPTI_KEY_DIVIDER, RuleKey ?? OPTI_NULL_RULE_KEY); + return string.Format("{0}{1}{2}", FlagKey, OPTI_KEY_DIVIDER, + RuleKey ?? OPTI_NULL_RULE_KEY); } - - } } diff --git a/OptimizelySDK/OptimizelyDecisions/DecisionMessage.cs b/OptimizelySDK/OptimizelyDecisions/DecisionMessage.cs index 1f2c3301b..58cd4867c 100644 --- a/OptimizelySDK/OptimizelyDecisions/DecisionMessage.cs +++ b/OptimizelySDK/OptimizelyDecisions/DecisionMessage.cs @@ -20,7 +20,10 @@ public class DecisionMessage { public const string SDK_NOT_READY = "Optimizely SDK not configured properly yet."; public const string FLAG_KEY_INVALID = "No flag was found for key \"{0}\"."; - public const string VARIABLE_VALUE_INVALID = "Variable value for key \"{0}\" is invalid or wrong type."; + + public const string VARIABLE_VALUE_INVALID = + "Variable value for key \"{0}\" is invalid or wrong type."; + public static string Reason(string format, params object[] args) { return string.Format(format, args); diff --git a/OptimizelySDK/OptimizelyDecisions/DecisionReasons.cs b/OptimizelySDK/OptimizelyDecisions/DecisionReasons.cs index 568759050..7e3704577 100644 --- a/OptimizelySDK/OptimizelyDecisions/DecisionReasons.cs +++ b/OptimizelySDK/OptimizelyDecisions/DecisionReasons.cs @@ -26,13 +26,13 @@ public class DecisionReasons public void AddError(string format, params object[] args) { - string message = string.Format(format, args); + var message = string.Format(format, args); Errors.Add(message); } public string AddInfo(string format, params object[] args) { - string message = string.Format(format, args); + var message = string.Format(format, args); Infos.Add(message); return message; @@ -40,7 +40,10 @@ public string AddInfo(string format, params object[] args) public static DecisionReasons operator +(DecisionReasons a, DecisionReasons b) { - if (b == null) return a; + if (b == null) + { + return a; + } a.Errors.AddRange(b.Errors); a.Infos.AddRange(b.Infos); @@ -50,12 +53,13 @@ public string AddInfo(string format, params object[] args) public List ToReport(bool includeReasons = false) { - List reasons = new List(Errors); + var reasons = new List(Errors); - if (includeReasons) { + if (includeReasons) + { reasons.AddRange(Infos); } - + return reasons; } } diff --git a/OptimizelySDK/OptimizelyDecisions/OptimizelyDecideOption.cs b/OptimizelySDK/OptimizelyDecisions/OptimizelyDecideOption.cs index bdf139158..b0ec5307b 100644 --- a/OptimizelySDK/OptimizelyDecisions/OptimizelyDecideOption.cs +++ b/OptimizelySDK/OptimizelyDecisions/OptimizelyDecideOption.cs @@ -22,6 +22,6 @@ public enum OptimizelyDecideOption ENABLED_FLAGS_ONLY, IGNORE_USER_PROFILE_SERVICE, INCLUDE_REASONS, - EXCLUDE_VARIABLES + EXCLUDE_VARIABLES, } } diff --git a/OptimizelySDK/OptimizelyDecisions/OptimizelyDecision.cs b/OptimizelySDK/OptimizelyDecisions/OptimizelyDecision.cs index dfc571aca..84bf27472 100644 --- a/OptimizelySDK/OptimizelyDecisions/OptimizelyDecision.cs +++ b/OptimizelySDK/OptimizelyDecisions/OptimizelyDecision.cs @@ -29,7 +29,7 @@ public class OptimizelyDecision /// variation key for optimizely decision. /// public string VariationKey { get; private set; } - + /// /// boolean value indicating if the flag is enabled or not. /// @@ -39,34 +39,35 @@ public class OptimizelyDecision /// collection of variables associated with the decision. /// public OptimizelyJSON Variables { get; private set; } - + /// /// rule key of the decision. /// public string RuleKey { get; private set; } - + /// /// flag key for which the decision was made. /// public string FlagKey { get; private set; } - + /// /// user context for which the decision was made. /// public OptimizelyUserContext UserContext { get; private set; } - + /// /// an array of error/info/debug messages describing why the decision has been made. /// public string[] Reasons { get; private set; } public OptimizelyDecision(string variationKey, - bool enabled, - OptimizelyJSON variables, - string ruleKey, - string flagKey, - OptimizelyUserContext userContext, - string[] reasons) + bool enabled, + OptimizelyJSON variables, + string ruleKey, + string flagKey, + OptimizelyUserContext userContext, + string[] reasons + ) { VariationKey = variationKey; Enabled = enabled; @@ -87,7 +88,8 @@ public static OptimizelyDecision NewErrorDecision(string key, OptimizelyUserContext optimizelyUserContext, string error, IErrorHandler errorHandler, - ILogger logger) + ILogger logger + ) { return new OptimizelyDecision( null, diff --git a/OptimizelySDK/OptimizelyFactory.cs b/OptimizelySDK/OptimizelyFactory.cs index 8e78bcf8e..e1aa280aa 100644 --- a/OptimizelySDK/OptimizelyFactory.cs +++ b/OptimizelySDK/OptimizelyFactory.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; #if !NETSTANDARD1_6 && !NET35 using System.Configuration; @@ -72,53 +73,67 @@ public static Optimizely NewDefaultInstance() OptimizelySDKConfigSection OptlySDKConfigSection = null; try { - OptlySDKConfigSection = ConfigurationManager.GetSection(ConfigSectionName) as OptimizelySDKConfigSection; + OptlySDKConfigSection = + ConfigurationManager.GetSection(ConfigSectionName) as + OptimizelySDKConfigSection; } catch (ConfigurationErrorsException ex) { - logger.Log(LogLevel.ERROR, "Invalid App.Config. Unable to initialize optimizely instance" + ex.Message); - return null; - } + logger.Log(LogLevel.ERROR, + "Invalid App.Config. Unable to initialize optimizely instance" + ex.Message); + return null; + } - HttpProjectConfigElement httpProjectConfigElement = OptlySDKConfigSection.HttpProjectConfig; + var httpProjectConfigElement = OptlySDKConfigSection.HttpProjectConfig; - if (httpProjectConfigElement == null) return null; + if (httpProjectConfigElement == null) + { + return null; + } var errorHandler = new DefaultErrorHandler(logger, false); var eventDispatcher = new DefaultEventDispatcher(logger); var builder = new HttpProjectConfigManager.Builder(); var notificationCenter = new NotificationCenter(); - var configManager = builder - .WithSdkKey(httpProjectConfigElement.SDKKey) - .WithUrl(httpProjectConfigElement.Url) - .WithFormat(httpProjectConfigElement.Format) - .WithPollingInterval(TimeSpan.FromMilliseconds(httpProjectConfigElement.PollingInterval)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(httpProjectConfigElement.BlockingTimeOutPeriod)) + var configManager = builder.WithSdkKey(httpProjectConfigElement.SDKKey). + WithUrl(httpProjectConfigElement.Url). + WithFormat(httpProjectConfigElement.Format). + WithPollingInterval( + TimeSpan.FromMilliseconds(httpProjectConfigElement.PollingInterval)). + WithBlockingTimeoutPeriod( + TimeSpan.FromMilliseconds(httpProjectConfigElement.BlockingTimeOutPeriod)) #if !NET40 && !NET35 - .WithAccessToken(httpProjectConfigElement.DatafileAccessToken) + . + WithAccessToken(httpProjectConfigElement.DatafileAccessToken) #endif - .WithLogger(logger) - .WithErrorHandler(errorHandler) - .WithNotificationCenter(notificationCenter) - .Build(true); + . + WithLogger(logger). + WithErrorHandler(errorHandler). + WithNotificationCenter(notificationCenter). + Build(true); EventProcessor eventProcessor = null; var batchEventProcessorElement = OptlySDKConfigSection.BatchEventProcessor; - if (batchEventProcessorElement == null) return null; - - eventProcessor = new BatchEventProcessor.Builder() - .WithMaxBatchSize(batchEventProcessorElement.BatchSize) - .WithFlushInterval(TimeSpan.FromMilliseconds(batchEventProcessorElement.FlushInterval)) - .WithTimeoutInterval(TimeSpan.FromMilliseconds(batchEventProcessorElement.TimeoutInterval)) - .WithLogger(logger) - .WithEventDispatcher(eventDispatcher) - .WithNotificationCenter(notificationCenter) - .Build(); - - return NewDefaultInstance(configManager, notificationCenter, eventDispatcher, errorHandler, logger, eventProcessor: eventProcessor); + if (batchEventProcessorElement == null) + { + return null; + } + eventProcessor = new BatchEventProcessor.Builder(). + WithMaxBatchSize(batchEventProcessorElement.BatchSize). + WithFlushInterval( + TimeSpan.FromMilliseconds(batchEventProcessorElement.FlushInterval)). + WithTimeoutInterval( + TimeSpan.FromMilliseconds(batchEventProcessorElement.TimeoutInterval)). + WithLogger(logger). + WithEventDispatcher(eventDispatcher). + WithNotificationCenter(notificationCenter). + Build(); + + return NewDefaultInstance(configManager, notificationCenter, eventDispatcher, + errorHandler, logger, eventProcessor: eventProcessor); } #endif @@ -127,7 +142,9 @@ public static Optimizely NewDefaultInstance(string sdkKey) return NewDefaultInstance(sdkKey, null); } #if !NET40 && !NET35 - public static Optimizely NewDefaultInstance(string sdkKey, string fallback, string datafileAuthToken) + public static Optimizely NewDefaultInstance(string sdkKey, string fallback, + string datafileAuthToken + ) { var logger = OptimizelyLogger ?? new DefaultLogger(); var errorHandler = new DefaultErrorHandler(logger, false); @@ -135,30 +152,29 @@ public static Optimizely NewDefaultInstance(string sdkKey, string fallback, stri var builder = new HttpProjectConfigManager.Builder(); var notificationCenter = new NotificationCenter(); - var configManager = builder - .WithSdkKey(sdkKey) - .WithDatafile(fallback) - .WithLogger(logger) - .WithPollingInterval(PollingInterval) - .WithBlockingTimeoutPeriod(BlockingTimeOutPeriod) - .WithErrorHandler(errorHandler) - .WithAccessToken(datafileAuthToken) - .WithNotificationCenter(notificationCenter) - .Build(true); + var configManager = builder.WithSdkKey(sdkKey). + WithDatafile(fallback). + WithLogger(logger). + WithPollingInterval(PollingInterval). + WithBlockingTimeoutPeriod(BlockingTimeOutPeriod). + WithErrorHandler(errorHandler). + WithAccessToken(datafileAuthToken). + WithNotificationCenter(notificationCenter). + Build(true); EventProcessor eventProcessor = null; #if !NETSTANDARD1_6 && !NET35 - eventProcessor = new BatchEventProcessor.Builder() - .WithLogger(logger) - .WithMaxBatchSize(MaxEventBatchSize) - .WithFlushInterval(MaxEventFlushInterval) - .WithEventDispatcher(eventDispatcher) - .WithNotificationCenter(notificationCenter) - .Build(); + eventProcessor = new BatchEventProcessor.Builder().WithLogger(logger). + WithMaxBatchSize(MaxEventBatchSize). + WithFlushInterval(MaxEventFlushInterval). + WithEventDispatcher(eventDispatcher). + WithNotificationCenter(notificationCenter). + Build(); #endif - return NewDefaultInstance(configManager, notificationCenter, eventDispatcher, errorHandler, logger, eventProcessor: eventProcessor); + return NewDefaultInstance(configManager, notificationCenter, eventDispatcher, + errorHandler, logger, eventProcessor: eventProcessor); } #endif public static Optimizely NewDefaultInstance(string sdkKey, string fallback) @@ -169,35 +185,38 @@ public static Optimizely NewDefaultInstance(string sdkKey, string fallback) var builder = new HttpProjectConfigManager.Builder(); var notificationCenter = new NotificationCenter(); - var configManager = builder - .WithSdkKey(sdkKey) - .WithDatafile(fallback) - .WithLogger(logger) - .WithPollingInterval(PollingInterval) - .WithBlockingTimeoutPeriod(BlockingTimeOutPeriod) - .WithErrorHandler(errorHandler) - .WithNotificationCenter(notificationCenter) - .Build(true); + var configManager = builder.WithSdkKey(sdkKey). + WithDatafile(fallback). + WithLogger(logger). + WithPollingInterval(PollingInterval). + WithBlockingTimeoutPeriod(BlockingTimeOutPeriod). + WithErrorHandler(errorHandler). + WithNotificationCenter(notificationCenter). + Build(true); EventProcessor eventProcessor = null; #if !NETSTANDARD1_6 && !NET35 - eventProcessor = new BatchEventProcessor.Builder() - .WithLogger(logger) - .WithMaxBatchSize(MaxEventBatchSize) - .WithFlushInterval(MaxEventFlushInterval) - .WithEventDispatcher(eventDispatcher) - .WithNotificationCenter(notificationCenter) - .Build(); + eventProcessor = new BatchEventProcessor.Builder().WithLogger(logger). + WithMaxBatchSize(MaxEventBatchSize). + WithFlushInterval(MaxEventFlushInterval). + WithEventDispatcher(eventDispatcher). + WithNotificationCenter(notificationCenter). + Build(); #endif - return NewDefaultInstance(configManager, notificationCenter, eventDispatcher, errorHandler, logger, eventProcessor: eventProcessor); + return NewDefaultInstance(configManager, notificationCenter, eventDispatcher, + errorHandler, logger, eventProcessor: eventProcessor); } - public static Optimizely NewDefaultInstance(ProjectConfigManager configManager, NotificationCenter notificationCenter = null, IEventDispatcher eventDispatcher = null, - IErrorHandler errorHandler = null, ILogger logger = null, UserProfileService userprofileService = null, EventProcessor eventProcessor = null) + public static Optimizely NewDefaultInstance(ProjectConfigManager configManager, + NotificationCenter notificationCenter = null, IEventDispatcher eventDispatcher = null, + IErrorHandler errorHandler = null, ILogger logger = null, + UserProfileService userprofileService = null, EventProcessor eventProcessor = null + ) { - return new Optimizely(configManager, notificationCenter, eventDispatcher, logger, errorHandler, userprofileService, eventProcessor); + return new Optimizely(configManager, notificationCenter, eventDispatcher, logger, + errorHandler, userprofileService, eventProcessor); } } } diff --git a/OptimizelySDK/OptimizelyForcedDecision.cs b/OptimizelySDK/OptimizelyForcedDecision.cs index 83d2901ff..9be6ef071 100644 --- a/OptimizelySDK/OptimizelyForcedDecision.cs +++ b/OptimizelySDK/OptimizelyForcedDecision.cs @@ -25,6 +25,6 @@ public OptimizelyForcedDecision(string variationKey) _variationKey = variationKey; } - public string VariationKey { get { return _variationKey; } } + public string VariationKey => _variationKey; } } diff --git a/OptimizelySDK/OptimizelyJSON.cs b/OptimizelySDK/OptimizelyJSON.cs index 5af90e124..003cc1cfa 100644 --- a/OptimizelySDK/OptimizelyJSON.cs +++ b/OptimizelySDK/OptimizelyJSON.cs @@ -48,7 +48,9 @@ public OptimizelyJSON(string payload, IErrorHandler errorHandler, ILogger logger } } - public OptimizelyJSON(Dictionary dict, IErrorHandler errorHandler, ILogger logger) + public OptimizelyJSON(Dictionary dict, IErrorHandler errorHandler, + ILogger logger + ) { try { @@ -64,7 +66,7 @@ public OptimizelyJSON(Dictionary dict, IErrorHandler errorHandle } } - override public string ToString() + public override string ToString() { return Payload; } @@ -93,26 +95,32 @@ public T GetValue(string jsonPath) { return GetObject(Dict); } + var path = jsonPath.Split('.'); var currentObject = Dict; - for (int i = 0; i < path.Length - 1; i++) + for (var i = 0; i < path.Length - 1; i++) { currentObject = currentObject[path[i]] as Dictionary; } + return GetObject(currentObject[path[path.Length - 1]]); } catch (KeyNotFoundException exception) { Logger.Log(LogLevel.ERROR, "Value for JSON key not found."); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + ErrorHandler.HandleError( + new Exceptions.OptimizelyRuntimeException(exception.Message)); } catch (Exception exception) { - Logger.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."); - ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message)); + Logger.Log(LogLevel.ERROR, + "Value for path could not be assigned to provided type."); + ErrorHandler.HandleError( + new Exceptions.OptimizelyRuntimeException(exception.Message)); } - return default(T); + + return default; } private T GetObject(object o) @@ -121,6 +129,7 @@ private T GetObject(object o) { deserializedObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)); } + return deserializedObj; } @@ -133,12 +142,14 @@ private object ConvertIntoCollection(object o) { if (o is JObject jo) { - return jo.ToObject>().ToDictionary(k => k.Key, v => ConvertIntoCollection(v.Value)); + return jo.ToObject>(). + ToDictionary(k => k.Key, v => ConvertIntoCollection(v.Value)); } else if (o is JArray ja) { return ja.ToObject>().Select(ConvertIntoCollection).ToList(); } + return o; } } diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index bdf89ec49..9fbe2dd26 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -1,214 +1,214 @@  - - - Debug - AnyCPU - {4DDE7FAA-110D-441C-AB3B-3F31B593E8BF} - Library - Properties - OptimizelySDK - OptimizelySDK - v4.5 - 512 - - 1.2.1 - ..\keypair.snk - - - true - full - false - bin\Debug\ - TRACE;DEBUG - prompt - 4 - false - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - NET35 - NET40 - - - - - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NJsonSchema.8.30.6304.31883\lib\net45\NJsonSchema.dll - True - - - - - - - - - - - ..\packages\murmurhash-signed.1.0.2\lib\net45\MurmurHash.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + Debug + AnyCPU + {4DDE7FAA-110D-441C-AB3B-3F31B593E8BF} + Library + Properties + OptimizelySDK + OptimizelySDK + v4.5 + 512 + + 1.2.1 + ..\keypair.snk + + + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + false + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + NET35 + NET40 + + + + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NJsonSchema.8.30.6304.31883\lib\net45\NJsonSchema.dll + True + + + + + + + + + + + ..\packages\murmurhash-signed.1.0.2\lib\net45\MurmurHash.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index 89fc3cac4..5c0cfb385 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.cs @@ -63,16 +63,13 @@ public class OptimizelyUserContext : IDisposable public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, IErrorHandler errorHandler, ILogger logger - ) : this(optimizely, userId, userAttributes, null, null, errorHandler, logger) - { - } + ) : this(optimizely, userId, userAttributes, null, null, errorHandler, logger) { } public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, IErrorHandler errorHandler, ILogger logger - ) : this(optimizely, userId, userAttributes, forcedDecisionsStore, null, errorHandler, logger) - { - } + ) : this(optimizely, userId, userAttributes, forcedDecisionsStore, null, errorHandler, + logger) { } public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, diff --git a/OptimizelySDK/OptlyConfig/OptimizelyAttribute.cs b/OptimizelySDK/OptlyConfig/OptimizelyAttribute.cs index 5e0fb921f..eacfb57bb 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyAttribute.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyAttribute.cs @@ -18,7 +18,5 @@ namespace OptimizelySDK.OptlyConfig { - public class OptimizelyAttribute : IdKeyEntity - { - } + public class OptimizelyAttribute : IdKeyEntity { } } diff --git a/OptimizelySDK/OptlyConfig/OptimizelyConfig.cs b/OptimizelySDK/OptlyConfig/OptimizelyConfig.cs index 7b682898e..4ea8648d0 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyConfig.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyConfig.cs @@ -39,7 +39,10 @@ public class OptimizelyConfig private string _datafile; - public OptimizelyConfig(string revision, IDictionary experimentsMap, IDictionary featuresMap, string datafile = null) + public OptimizelyConfig(string revision, + IDictionary experimentsMap, + IDictionary featuresMap, string datafile = null + ) { Revision = revision; ExperimentsMap = experimentsMap; @@ -47,7 +50,11 @@ public OptimizelyConfig(string revision, IDictionary experimentsMap, IDictionary featuresMap, string datafile = null) + public OptimizelyConfig(string revision, string sdkKey, string environmentKey, + OptimizelyAttribute[] attributes, OptimizelyAudience[] audiences, + OptimizelyEvent[] events, IDictionary experimentsMap, + IDictionary featuresMap, string datafile = null + ) { Revision = revision; SDKKey = sdkKey; diff --git a/OptimizelySDK/OptlyConfig/OptimizelyConfigService.cs b/OptimizelySDK/OptlyConfig/OptimizelyConfigService.cs index 3f65afdd1..eb9cc92b1 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyConfigService.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyConfigService.cs @@ -37,6 +37,7 @@ public OptimizelyConfigService(ProjectConfig projectConfig) { return; } + featureIdVariablesMap = GetFeatureVariablesByIdMap(projectConfig); var attributes = GetAttributes(projectConfig); var audiences = GetAudiences(projectConfig); @@ -68,18 +69,22 @@ private OptimizelyEvent[] GetEvents(ProjectConfig projectConfig) optimizelyEvent.ExperimentIds = ev.ExperimentIds; optimizelyEvents.Add(optimizelyEvent); } + return optimizelyEvents.ToArray(); } private OptimizelyAudience[] GetAudiences(ProjectConfig projectConfig) { - var typedAudiences = projectConfig.TypedAudiences?.Select(aud => new OptimizelyAudience(aud.Id, - aud.Name, - JsonConvert.SerializeObject(aud.Conditions))); + var typedAudiences = projectConfig.TypedAudiences?.Select(aud => + new OptimizelyAudience(aud.Id, + aud.Name, + JsonConvert.SerializeObject(aud.Conditions))); var typedAudienceIds = typedAudiences.Select(ta => ta.Id).ToList(); - var filteredAudiencesArr = Array.FindAll(projectConfig.Audiences, aud => !aud.Id.Equals("$opt_dummy_audience") + var filteredAudiencesArr = Array.FindAll(projectConfig.Audiences, aud => + !aud.Id.Equals("$opt_dummy_audience") && !typedAudienceIds.Contains(aud.Id)); - var optimizelyAudience = filteredAudiencesArr.Select(aud => new OptimizelyAudience(aud.Id, aud.Name, aud.Conditions)); + var optimizelyAudience = filteredAudiencesArr.Select(aud => + new OptimizelyAudience(aud.Id, aud.Name, aud.Conditions)); optimizelyAudience = optimizelyAudience.Concat(typedAudiences).OrderBy(aud => aud.Name); @@ -96,6 +101,7 @@ private OptimizelyAttribute[] GetAttributes(ProjectConfig projectConfig) attribute.Key = attr.Key; attributes.Add(attribute); } + return attributes.ToArray(); } @@ -104,8 +110,9 @@ private OptimizelyAttribute[] GetAttributes(ProjectConfig projectConfig) /// /// /// Map of experiment key. - - private IDictionary GetExperimentsKeyMap(IDictionary experimentsMapById) + private IDictionary GetExperimentsKeyMap( + IDictionary experimentsMapById + ) { var experimentKeyMaps = new Dictionary(); @@ -113,6 +120,7 @@ private IDictionary GetExperimentsKeyMap(IDictiona { experimentKeyMaps[experiment.Key] = experiment; } + return experimentKeyMaps; } @@ -121,17 +129,23 @@ private IDictionary GetExperimentsKeyMap(IDictiona /// /// The project config /// Dictionary | Dictionary of experiment key and value as experiment object - private IDictionary GetExperimentsMapById(ProjectConfig projectConfig) + private IDictionary GetExperimentsMapById( + ProjectConfig projectConfig + ) { var experimentsMap = new Dictionary(); var featureVariableIdMap = GetVariableIdMap(projectConfig); var experiments = projectConfig?.Experiments?.ToList(); - experiments = projectConfig?.Groups?.SelectMany(g => g.Experiments).Concat(experiments)?.ToList(); + experiments = projectConfig?.Groups?.SelectMany(g => g.Experiments). + Concat(experiments)?. + ToList(); - foreach (Experiment experiment in experiments) + foreach (var experiment in experiments) { - var featureId = projectConfig.GetExperimentFeatureList(experiment.Id)?.FirstOrDefault(); - var variationsMap = GetVariationsMap(experiment.Variations, featureVariableIdMap, featureId); + var featureId = projectConfig.GetExperimentFeatureList(experiment.Id)?. + FirstOrDefault(); + var variationsMap = GetVariationsMap(experiment.Variations, featureVariableIdMap, + featureId); var experimentAudience = GetExperimentAudiences(experiment, projectConfig); var optimizelyExperiment = new OptimizelyExperiment(experiment.Id, experiment.Key, @@ -151,12 +165,14 @@ private IDictionary GetExperimentsMapById(ProjectC /// The map of feature variables and id /// feature Id of the feature /// Dictionary | Dictionary of experiment key and value as experiment object - private IDictionary GetVariationsMap(IEnumerable variations, + private IDictionary GetVariationsMap( + IEnumerable variations, IDictionary featureVariableIdMap, - string featureId) + string featureId + ) { var variationsMap = new Dictionary(); - foreach (Variation variation in variations) + foreach (var variation in variations) { var variablesMap = MergeFeatureVariables( featureVariableIdMap, @@ -184,20 +200,23 @@ private IDictionary GetVariationsMap(IEnumerableisFeatureEnabled of variation /// Dictionary | Dictionary of FeatureVariable key and value as FeatureVariable object private IDictionary MergeFeatureVariables( - IDictionary variableIdMap, - string featureId, - IEnumerable featureVariableUsages, - bool isFeatureEnabled) + IDictionary variableIdMap, + string featureId, + IEnumerable featureVariableUsages, + bool isFeatureEnabled + ) { var variablesMap = new Dictionary(); if (!string.IsNullOrEmpty(featureId)) { - variablesMap = featureIdVariablesMap[featureId]?.Select(f => new OptimizelyVariable(f.Id, + variablesMap = featureIdVariablesMap[featureId]?. + Select(f => new OptimizelyVariable(f.Id, f.Key, f.Type.ToString().ToLower(), f.DefaultValue) - ).ToDictionary(k => k.Key, v => v); + ). + ToDictionary(k => k.Key, v => v); foreach (var featureVariableUsage in featureVariableUsages) { @@ -205,7 +224,9 @@ private IDictionary MergeFeatureVariables( var optimizelyVariable = new OptimizelyVariable(featureVariableUsage.Id, defaultVariable.Key, defaultVariable.Type.ToString().ToLower(), - isFeatureEnabled ? featureVariableUsage.Value : defaultVariable.DefaultValue); + isFeatureEnabled ? + featureVariableUsage.Value : + defaultVariable.DefaultValue); variablesMap[defaultVariable.Key] = optimizelyVariable; } @@ -220,19 +241,29 @@ private IDictionary MergeFeatureVariables( /// The project config /// Dictionary of experiment Id as key and value as experiment object /// Dictionary | Dictionary of FeatureFlag key and value as OptimizelyFeature object - private IDictionary GetFeaturesMap(ProjectConfig projectConfig, IDictionary experimentsMapById) + private IDictionary GetFeaturesMap(ProjectConfig projectConfig, + IDictionary experimentsMapById + ) { var FeaturesMap = new Dictionary(); foreach (var featureFlag in projectConfig.FeatureFlags) { - var experimentRules = featureFlag.ExperimentIds.Select(experimentId => experimentsMapById[experimentId]).ToList(); - - var featureVariableMap = featureFlag.Variables.Select(v => (OptimizelyVariable)v).ToDictionary(k => k.Key, v => v) ?? new Dictionary(); - - var featureExperimentMap = experimentRules.ToDictionary(experiment => experiment.Key, experiment => experiment); + var experimentRules = featureFlag.ExperimentIds. + Select(experimentId => experimentsMapById[experimentId]). + ToList(); + + var featureVariableMap = + featureFlag.Variables.Select(v => (OptimizelyVariable)v). + ToDictionary(k => k.Key, v => v) ?? + new Dictionary(); + + var featureExperimentMap = + experimentRules.ToDictionary(experiment => experiment.Key, + experiment => experiment); var rollout = projectConfig.GetRolloutFromId(featureFlag.RolloutId); - var deliveryRules = GetDeliveryRules(featureFlag.Id, rollout.Experiments, projectConfig); + var deliveryRules = + GetDeliveryRules(featureFlag.Id, rollout.Experiments, projectConfig); var optimizelyFeature = new OptimizelyFeature(featureFlag.Id, featureFlag.Key, @@ -254,9 +285,12 @@ private IDictionary GetFeaturesMap(ProjectConfig proj /// /// The project config /// Dictionary | Dictionary of FeatureFlag key and value as list of all FeatureVariable inside it - private IDictionary> GetFeatureVariablesByIdMap(ProjectConfig projectConfig) + private IDictionary> GetFeatureVariablesByIdMap( + ProjectConfig projectConfig + ) { - var featureIdVariablesMap = projectConfig?.FeatureFlags?.ToDictionary(k => k.Id, v => v.Variables); + var featureIdVariablesMap = + projectConfig?.FeatureFlags?.ToDictionary(k => k.Id, v => v.Variables); return featureIdVariablesMap ?? new Dictionary>(); } @@ -273,7 +307,9 @@ private string GetExperimentAudiences(Experiment experiment, ProjectConfig proje { return ""; } - var s = JsonConvert.DeserializeObject>(experiment.AudienceConditionsString); + + var s = JsonConvert.DeserializeObject>(experiment. + AudienceConditionsString); return GetSerializedAudiences(s, projectConfig.AudienceIdMap); } @@ -294,50 +330,63 @@ private string GetExperimentAudiences(Experiment experiment, ProjectConfig proje /// List of audience conditions in experiment /// The audience Id map /// string | Serialized audience in which IDs are replaced with audience name. - private string GetSerializedAudiences(List audienceConditions, Dictionary audienceIdMap) + private string GetSerializedAudiences(List audienceConditions, + Dictionary audienceIdMap + ) { - StringBuilder sAudience = new StringBuilder(""); + var sAudience = new StringBuilder(""); if (audienceConditions != null) { - string cond = ""; + var cond = ""; foreach (var item in audienceConditions) { var subAudience = ""; // Checks if item is list of conditions means if it is sub audience if (item is JArray) { - subAudience = GetSerializedAudiences(((JArray)item).ToObject>(), audienceIdMap); + subAudience = + GetSerializedAudiences(((JArray)item).ToObject>(), + audienceIdMap); subAudience = "(" + subAudience + ")"; } - else if (AUDIENCE_CONDITIONS.Contains(item.ToString())) // Checks if item is an audience condition + else if + (AUDIENCE_CONDITIONS. + Contains(item.ToString())) // Checks if item is an audience condition { cond = item.ToString().ToUpper(); } else - { // Checks if item is audience id + { + // Checks if item is audience id var itemStr = item.ToString(); - var audienceName = audienceIdMap.ContainsKey(itemStr) ? audienceIdMap[itemStr].Name : itemStr; + var audienceName = audienceIdMap.ContainsKey(itemStr) ? + audienceIdMap[itemStr].Name : + itemStr; // if audience condition is "NOT" then add "NOT" at start. Otherwise check if there is already audience id in sAudience then append condition between saudience and item if (!string.IsNullOrEmpty(sAudience.ToString()) || cond.Equals("NOT")) { cond = string.IsNullOrEmpty(cond) ? "OR" : cond; - sAudience = string.IsNullOrEmpty(sAudience.ToString()) ? new StringBuilder(cond + " \"" + audienceIdMap[itemStr]?.Name + "\"") : - sAudience.Append(" " + cond + " \"" + audienceName + "\""); + sAudience = string.IsNullOrEmpty(sAudience.ToString()) ? + new StringBuilder( + cond + " \"" + audienceIdMap[itemStr]?.Name + "\"") : + sAudience.Append(" " + cond + " \"" + audienceName + "\""); } else { sAudience = new StringBuilder("\"" + audienceName + "\""); } } + // Checks if sub audience is empty or not if (!string.IsNullOrEmpty(subAudience)) { if (!string.IsNullOrEmpty(sAudience.ToString()) || cond == "NOT") { cond = string.IsNullOrEmpty(cond) ? "OR" : cond; - sAudience = string.IsNullOrEmpty(sAudience.ToString()) ? new StringBuilder(cond + " " + subAudience) : - sAudience.Append(" " + cond + " " + subAudience); + sAudience = string.IsNullOrEmpty(sAudience.ToString()) ? + new StringBuilder(cond + " " + subAudience) : + sAudience.Append(" " + cond + " " + subAudience); } else { @@ -346,6 +395,7 @@ private string GetSerializedAudiences(List audienceConditions, Dictionar } } } + return sAudience.ToString(); } @@ -356,8 +406,10 @@ private string GetSerializedAudiences(List audienceConditions, Dictionar /// Experiments /// Project Config /// List | List of Optimizely rollout experiments. - private List GetDeliveryRules(string featureId, IEnumerable experiments, - ProjectConfig projectConfig) + private List GetDeliveryRules(string featureId, + IEnumerable experiments, + ProjectConfig projectConfig + ) { if (experiments == null) { @@ -371,11 +423,11 @@ private List GetDeliveryRules(string featureId, IEnumerabl foreach (var experiment in experiments) { var optimizelyExperiment = new OptimizelyExperiment( - id: experiment.Id, - key: experiment.Key, - audiences: GetExperimentAudiences(experiment, projectConfig), - variationsMap: GetVariationsMap(experiment.Variations, featureVariableIdMap, featureId) - ); + experiment.Id, + experiment.Key, + GetExperimentAudiences(experiment, projectConfig), + GetVariationsMap(experiment.Variations, featureVariableIdMap, featureId) + ); deliveryRules.Add(optimizelyExperiment); } @@ -389,7 +441,8 @@ private List GetDeliveryRules(string featureId, IEnumerabl /// Dictionary | Dictionary of FeatureVariableId as key and value as object of FeatureVariable private IDictionary GetVariableIdMap(ProjectConfig projectConfig) { - var featureVariablesIdMap = projectConfig?.FeatureFlags?.SelectMany(f => f.Variables).ToDictionary(k => k.Id, v => v); + var featureVariablesIdMap = projectConfig?.FeatureFlags?.SelectMany(f => f.Variables). + ToDictionary(k => k.Id, v => v); return featureVariablesIdMap ?? new Dictionary(); } diff --git a/OptimizelySDK/OptlyConfig/OptimizelyExperiment.cs b/OptimizelySDK/OptlyConfig/OptimizelyExperiment.cs index 7b0cb84e4..8b81b5558 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyExperiment.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyExperiment.cs @@ -20,11 +20,12 @@ namespace OptimizelySDK.OptlyConfig { public class OptimizelyExperiment : Entity.IdKeyEntity { - public IDictionary VariationsMap { get; private set; } public string Audiences { get; private set; } - public OptimizelyExperiment(string id, string key, string audiences, IDictionary variationsMap) + public OptimizelyExperiment(string id, string key, string audiences, + IDictionary variationsMap + ) { Id = id; Key = key; diff --git a/OptimizelySDK/OptlyConfig/OptimizelyFeature.cs b/OptimizelySDK/OptlyConfig/OptimizelyFeature.cs index b69221e69..67ffcb6b8 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyFeature.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyFeature.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; using System.Collections.Generic; @@ -20,15 +21,19 @@ namespace OptimizelySDK.OptlyConfig { public class OptimizelyFeature : Entity.IdKeyEntity { - public List ExperimentRules { get; private set; } public List DeliveryRules { get; private set; } [Obsolete("Use experimentRules and deliveryRules.")] public IDictionary ExperimentsMap { get; private set; } + public IDictionary VariablesMap { get; private set; } - public OptimizelyFeature(string id, string key, List experimentRules, List deliveryRules, IDictionary experimentsMap, IDictionary variablesMap) + public OptimizelyFeature(string id, string key, List experimentRules, + List deliveryRules, + IDictionary experimentsMap, + IDictionary variablesMap + ) { Id = id; Key = key; diff --git a/OptimizelySDK/OptlyConfig/OptimizelyVariable.cs b/OptimizelySDK/OptlyConfig/OptimizelyVariable.cs index b17435547..d3315d2ee 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyVariable.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyVariable.cs @@ -18,7 +18,7 @@ namespace OptimizelySDK.OptlyConfig { - public class OptimizelyVariable : Entity.IdKeyEntity + public class OptimizelyVariable : IdKeyEntity { public string Type { get; private set; } public string Value { get; private set; } @@ -31,7 +31,9 @@ public OptimizelyVariable(string id, string key, string type, string value) Value = value; } - public OptimizelyVariable(FeatureVariable featureVariable, FeatureVariableUsage featureVariableUsage) + public OptimizelyVariable(FeatureVariable featureVariable, + FeatureVariableUsage featureVariableUsage + ) { Id = featureVariable.Id; Key = featureVariable.Key; @@ -42,7 +44,7 @@ public OptimizelyVariable(FeatureVariable featureVariable, FeatureVariableUsage public static explicit operator OptimizelyVariable(FeatureVariable featureVariable) { - return new OptimizelyVariable(featureVariable, null); + return new OptimizelyVariable(featureVariable, null); } } } diff --git a/OptimizelySDK/OptlyConfig/OptimizelyVariation.cs b/OptimizelySDK/OptlyConfig/OptimizelyVariation.cs index 19b66482d..aa752f8cf 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyVariation.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyVariation.cs @@ -23,9 +23,12 @@ public class OptimizelyVariation : Entity.IdKeyEntity { [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public bool? FeatureEnabled { get; private set; } + public IDictionary VariablesMap { get; private set; } - public OptimizelyVariation(string id, string key, bool? featureEnabled, IDictionary variablesMap) + public OptimizelyVariation(string id, string key, bool? featureEnabled, + IDictionary variablesMap + ) { Id = id; Key = key; diff --git a/OptimizelySDK/Utils/CollectionExtensions.cs b/OptimizelySDK/Utils/CollectionExtensions.cs index 5c2cad153..9a8771bd8 100644 --- a/OptimizelySDK/Utils/CollectionExtensions.cs +++ b/OptimizelySDK/Utils/CollectionExtensions.cs @@ -46,7 +46,7 @@ this Dictionary left, Dictionary right return left; } - foreach (KeyValuePair kvp in right.Where( + foreach (var kvp in right.Where( kvp => !left.ContainsKey(kvp.Key))) { left.Add(kvp.Key, kvp.Value); diff --git a/OptimizelySDK/Utils/ConditionParser.cs b/OptimizelySDK/Utils/ConditionParser.cs index d9a147ef0..adceabca7 100644 --- a/OptimizelySDK/Utils/ConditionParser.cs +++ b/OptimizelySDK/Utils/ConditionParser.cs @@ -28,38 +28,48 @@ public static class ConditionParser /// /// const string Representing AND operator. /// - const string AND_OPERATOR = "and"; + private const string AND_OPERATOR = "and"; /// /// const string Representing OR operator. /// - const string OR_OPERATOR = "or"; + private const string OR_OPERATOR = "or"; /// /// const string Representing NOT operator. /// - const string NOT_OPERATOR = "not"; + private const string NOT_OPERATOR = "not"; public static ICondition ParseAudienceConditions(JToken audienceConditions) { if (audienceConditions.Type != JTokenType.Array) + { return new AudienceIdCondition { AudienceId = (string)audienceConditions }; + } var conditionsArray = audienceConditions as JArray; if (conditionsArray.Count == 0) + { return new EmptyCondition(); + } var startIndex = 0; var conditionOperator = GetOperator(conditionsArray.First.ToString()); if (conditionOperator != null) + { startIndex = 1; + } else + { conditionOperator = OR_OPERATOR; + } - List conditions = new List(); - for (int i = startIndex; i < conditionsArray.Count; ++i) + var conditions = new List(); + for (var i = startIndex; i < conditionsArray.Count; ++i) + { conditions.Add(ParseAudienceConditions(conditionsArray[i])); + } return GetConditions(conditions, conditionOperator); } @@ -67,6 +77,7 @@ public static ICondition ParseAudienceConditions(JToken audienceConditions) public static ICondition ParseConditions(JToken conditionObj) { if (conditionObj.Type != JTokenType.Array) + { return new BaseCondition { Match = conditionObj["match"]?.ToString(), @@ -74,18 +85,23 @@ public static ICondition ParseConditions(JToken conditionObj) Name = conditionObj["name"].ToString(), Value = conditionObj["value"]?.ToObject(), }; + } var startIndex = 0; var conditionsArray = conditionObj as JArray; var conditionOperator = GetOperator(conditionsArray.First.ToString()); if (conditionOperator != null) + { startIndex = 1; + } else + { conditionOperator = OR_OPERATOR; + } - List conditions = new List(); - for (int i = startIndex; i < conditionsArray.Count; ++i) + var conditions = new List(); + for (var i = startIndex; i < conditionsArray.Count; ++i) { conditions.Add(ParseConditions(conditionsArray[i])); } @@ -95,7 +111,7 @@ public static ICondition ParseConditions(JToken conditionObj) public static string GetOperator(object condition) { - string conditionOperator = (string)condition; + var conditionOperator = (string)condition; switch (conditionOperator) { case OR_OPERATOR: @@ -107,7 +123,8 @@ public static string GetOperator(object condition) } } - public static ICondition GetConditions(List conditions, string conditionOperator) + public static ICondition GetConditions(List conditions, string conditionOperator + ) { ICondition condition = null; switch (conditionOperator) @@ -119,7 +136,8 @@ public static ICondition GetConditions(List conditions, string condi condition = new OrCondition() { Conditions = conditions.ToArray() }; break; case NOT_OPERATOR: - condition = new NotCondition() { Condition = conditions.Count == 0 ? null : conditions[0] }; + condition = new NotCondition() + { Condition = conditions.Count == 0 ? null : conditions[0] }; break; default: break; diff --git a/OptimizelySDK/Utils/ConfigParser.cs b/OptimizelySDK/Utils/ConfigParser.cs index 1d1b73c04..318ff1b6e 100644 --- a/OptimizelySDK/Utils/ConfigParser.cs +++ b/OptimizelySDK/Utils/ConfigParser.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; using System.Collections.Generic; using System.Linq; @@ -28,9 +29,11 @@ public static class ConfigParser where T : ICloneable /// A function to return the key value from the entity /// Whether or not to clone the original entity /// associative array of key => entity - public static Dictionary GenerateMap(IEnumerable entities, Func getKey, bool clone) + public static Dictionary GenerateMap(IEnumerable entities, + Func getKey, bool clone + ) { return entities.ToDictionary(e => getKey(e), e => clone ? (T)e.Clone() : e); } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Utils/ControlAttributes.cs b/OptimizelySDK/Utils/ControlAttributes.cs index 9fc7a5baf..327e9ef21 100644 --- a/OptimizelySDK/Utils/ControlAttributes.cs +++ b/OptimizelySDK/Utils/ControlAttributes.cs @@ -19,7 +19,7 @@ namespace OptimizelySDK.Utils public class ControlAttributes { public const string BOT_FILTERING_ATTRIBUTE = "$opt_bot_filtering"; - public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; - public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; + public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; + public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; } } diff --git a/OptimizelySDK/Utils/DateTimeUtils.cs b/OptimizelySDK/Utils/DateTimeUtils.cs index 305c16e36..4a217c3d6 100644 --- a/OptimizelySDK/Utils/DateTimeUtils.cs +++ b/OptimizelySDK/Utils/DateTimeUtils.cs @@ -7,13 +7,8 @@ public static class DateTimeUtils /// /// Helper to compute Unix time (i.e. since Jan 1, 1970) /// - public static long SecondsSince1970 - { - get - { - return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; - } - } + public static long SecondsSince1970 => + (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; public static long MillisecondsSince1970(this DateTime dateTime) { diff --git a/OptimizelySDK/Utils/EventTagUtils.cs b/OptimizelySDK/Utils/EventTagUtils.cs index 64482d338..a2a094a81 100644 --- a/OptimizelySDK/Utils/EventTagUtils.cs +++ b/OptimizelySDK/Utils/EventTagUtils.cs @@ -26,10 +26,10 @@ public class EventTagUtils public static object GetRevenueValue(Dictionary eventTags, ILogger logger) { - int result = 0; - bool isCasted = false; - string logMessage = string.Empty; - LogLevel logLevel = LogLevel.INFO; + var result = 0; + var isCasted = false; + var logMessage = string.Empty; + var logLevel = LogLevel.INFO; if (eventTags == null) { @@ -58,20 +58,26 @@ public static object GetRevenueValue(Dictionary eventTags, ILogg } if (logger != null) + { logger.Log(logLevel, logMessage); + } if (isCasted) + { return result; + } return null; } - public static object GetNumericValue(Dictionary eventTags, ILogger logger = null) + public static object GetNumericValue(Dictionary eventTags, + ILogger logger = null + ) { float refVar = 0; - bool isCasted = false; - string logMessage = string.Empty; - LogLevel logLevel = LogLevel.INFO; + var isCasted = false; + var logMessage = string.Empty; + var logLevel = LogLevel.INFO; if (eventTags == null) { @@ -92,10 +98,16 @@ public static object GetNumericValue(Dictionary eventTags, ILogg { logMessage = "Provided numeric value is boolean which is an invalid format."; logLevel = LogLevel.ERROR; - } else if (!(eventTags[VALUE_EVENT_METRIC_NAME] is int) && !(eventTags[VALUE_EVENT_METRIC_NAME] is string) && !(eventTags[VALUE_EVENT_METRIC_NAME] is float) - && !(eventTags[VALUE_EVENT_METRIC_NAME] is decimal) && !(eventTags[VALUE_EVENT_METRIC_NAME] is double) && !(eventTags[VALUE_EVENT_METRIC_NAME] is long) - && !(eventTags[VALUE_EVENT_METRIC_NAME] is short) && !(eventTags[VALUE_EVENT_METRIC_NAME] is uint)) - { + } + else if (!(eventTags[VALUE_EVENT_METRIC_NAME] is int) && + !(eventTags[VALUE_EVENT_METRIC_NAME] is string) && + !(eventTags[VALUE_EVENT_METRIC_NAME] is float) + && !(eventTags[VALUE_EVENT_METRIC_NAME] is decimal) && + !(eventTags[VALUE_EVENT_METRIC_NAME] is double) && + !(eventTags[VALUE_EVENT_METRIC_NAME] is long) + && !(eventTags[VALUE_EVENT_METRIC_NAME] is short) && + !(eventTags[VALUE_EVENT_METRIC_NAME] is uint)) + { logMessage = "Numeric metric value is not in integer, float, or string form."; logLevel = LogLevel.ERROR; } @@ -108,14 +120,16 @@ public static object GetNumericValue(Dictionary eventTags, ILogg { if (!float.TryParse(eventTags[VALUE_EVENT_METRIC_NAME].ToString(), out refVar)) { - logMessage = $"Provided numeric value {eventTags[VALUE_EVENT_METRIC_NAME]} is in an invalid format."; + logMessage = + $"Provided numeric value {eventTags[VALUE_EVENT_METRIC_NAME]} is in an invalid format."; logLevel = LogLevel.ERROR; } else { if (float.IsInfinity(refVar)) { - logMessage = $"Provided numeric value {eventTags[VALUE_EVENT_METRIC_NAME]} is in an invalid format."; + logMessage = + $"Provided numeric value {eventTags[VALUE_EVENT_METRIC_NAME]} is in an invalid format."; logLevel = LogLevel.ERROR; } else @@ -127,10 +141,12 @@ public static object GetNumericValue(Dictionary eventTags, ILogg } if (logger != null) + { logger.Log(logLevel, logMessage); + } object o = refVar; - if(isCasted && eventTags[VALUE_EVENT_METRIC_NAME] is float) + if (isCasted && eventTags[VALUE_EVENT_METRIC_NAME] is float) { // Special case, maximum value when passed and gone through tryparse, it loses precision. o = eventTags[VALUE_EVENT_METRIC_NAME]; @@ -140,4 +156,3 @@ public static object GetNumericValue(Dictionary eventTags, ILogg } } } - \ No newline at end of file diff --git a/OptimizelySDK/Utils/ExceptionExtensions.cs b/OptimizelySDK/Utils/ExceptionExtensions.cs index 8b08cb93f..f6f3f7256 100644 --- a/OptimizelySDK/Utils/ExceptionExtensions.cs +++ b/OptimizelySDK/Utils/ExceptionExtensions.cs @@ -23,9 +23,13 @@ public static class ExceptionExtensions public static string GetAllMessages(this Exception exception, string separator = "\n") { if (exception.InnerException == null) + { return exception.Message; + } - return (string.IsNullOrEmpty(exception.Message) ? "" : $"{exception.Message}{separator}{GetAllMessages(exception.InnerException, separator)}"); + return string.IsNullOrEmpty(exception.Message) ? + "" : + $"{exception.Message}{separator}{GetAllMessages(exception.InnerException, separator)}"; } } } diff --git a/OptimizelySDK/Utils/ExperimentUtils.cs b/OptimizelySDK/Utils/ExperimentUtils.cs index cffd0721f..05fd1022d 100644 --- a/OptimizelySDK/Utils/ExperimentUtils.cs +++ b/OptimizelySDK/Utils/ExperimentUtils.cs @@ -25,7 +25,6 @@ public class ExperimentUtils { public static bool IsExperimentActive(Experiment experiment, ILogger logger) { - if (!experiment.IsExperimentRunning) { logger.Log(LogLevel.INFO, $"Experiment \"{experiment.Key}\" is not running."); @@ -51,7 +50,8 @@ public static Result DoesUserMeetAudienceConditions(ProjectConfig config, OptimizelyUserContext user, string loggingKeyType, string loggingKey, - ILogger logger) + ILogger logger + ) { var reasons = new DecisionReasons(); if (user == null) @@ -63,21 +63,29 @@ public static Result DoesUserMeetAudienceConditions(ProjectConfig config, if (experiment.AudienceConditionsList != null) { expConditions = experiment.AudienceConditionsList; - logger.Log(LogLevel.DEBUG, $@"Evaluating audiences for {loggingKeyType} ""{loggingKey}"": {experiment.AudienceConditionsString}."); + logger.Log(LogLevel.DEBUG, + $@"Evaluating audiences for {loggingKeyType} ""{loggingKey}"": { + experiment.AudienceConditionsString}."); } else { expConditions = experiment.AudienceIdsList; - logger.Log(LogLevel.DEBUG, $@"Evaluating audiences for {loggingKeyType} ""{loggingKey}"": {experiment.AudienceIdsString}."); + logger.Log(LogLevel.DEBUG, + $@"Evaluating audiences for {loggingKeyType} ""{loggingKey}"": { + experiment.AudienceIdsString}."); } // If there are no audiences, return true because that means ALL users are included in the experiment. if (expConditions == null) + { return Result.NewResult(true, reasons); + } var result = expConditions.Evaluate(config, user, logger).GetValueOrDefault(); var resultText = result.ToString().ToUpper(); - logger.Log(LogLevel.INFO, reasons.AddInfo($@"Audiences for {loggingKeyType} ""{loggingKey}"" collectively evaluated to {resultText}")); + logger.Log(LogLevel.INFO, + reasons.AddInfo($@"Audiences for {loggingKeyType} ""{loggingKey + }"" collectively evaluated to {resultText}")); return Result.NewResult(result, reasons); } } diff --git a/OptimizelySDK/Utils/Schema.cs b/OptimizelySDK/Utils/Schema.cs index 6a854bf36..2ee82c500 100644 --- a/OptimizelySDK/Utils/Schema.cs +++ b/OptimizelySDK/Utils/Schema.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.IO; using System.Reflection; @@ -25,7 +26,9 @@ internal class Schema public static string GetSchemaJson() { if (cache != null) + { return cache; + } #if NET35 || NET40 var assembly = Assembly.GetExecutingAssembly(); #else @@ -33,9 +36,11 @@ public static string GetSchemaJson() #endif const string resourceName = "OptimizelySDK.Utils.schema.json"; - using (Stream stream = assembly.GetManifestResourceStream(resourceName)) - using (StreamReader reader = new StreamReader(stream)) + using (var stream = assembly.GetManifestResourceStream(resourceName)) + using (var reader = new StreamReader(stream)) + { return cache = reader.ReadToEnd(); + } } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Utils/Validator.cs b/OptimizelySDK/Utils/Validator.cs index fd79ca167..b0b36c18f 100644 --- a/OptimizelySDK/Utils/Validator.cs +++ b/OptimizelySDK/Utils/Validator.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Entity; using System; using System.Collections.Generic; @@ -37,11 +38,9 @@ public static bool ValidateJSONSchema(string configJson, string schemaJson = nul { try { - return !NJsonSchema.JsonSchema4 - .FromJsonAsync(schemaJson ?? Schema.GetSchemaJson()) - .Result - .Validate(configJson) - .Any(); + return !NJsonSchema.JsonSchema4.FromJsonAsync(schemaJson ?? Schema.GetSchemaJson()). + Result.Validate(configJson). + Any(); } catch (Newtonsoft.Json.JsonReaderException) { @@ -75,10 +74,10 @@ public static bool IsAttributeValid(Attribute attribute) return !int.TryParse(attribute.Key, out key); } - public static bool AreEventTagsValid(Dictionary eventTags) { + public static bool AreEventTagsValid(Dictionary eventTags) + { int key; return eventTags.All(tag => !int.TryParse(tag.Key, out key)); - } /// @@ -93,15 +92,19 @@ public static bool IsFeatureFlagValid(ProjectConfig projectConfig, FeatureFlag f var experimentIds = featureFlag.ExperimentIds; if (experimentIds == null || experimentIds.Count <= 1) + { return true; + } var groupId = projectConfig.GetExperimentFromId(experimentIds[0]).GroupId; - for (int i = 1; i < experimentIds.Count; i++) + for (var i = 1; i < experimentIds.Count; i++) { // Every experiment should have the same group Id. if (projectConfig.GetExperimentFromId(experimentIds[i]).GroupId != groupId) + { return false; + } } return true; @@ -114,8 +117,9 @@ public static bool IsFeatureFlagValid(ProjectConfig projectConfig, FeatureFlag f /// true if attribute key is not null and value is one of the supported type, false otherwise public static bool IsUserAttributeValid(KeyValuePair attribute) { - return (attribute.Key != null) && - (attribute.Value is string || attribute.Value is bool || IsValidNumericValue(attribute.Value)); + return attribute.Key != null && + (attribute.Value is string || attribute.Value is bool || + IsValidNumericValue(attribute.Value)); } /// @@ -125,9 +129,11 @@ public static bool IsUserAttributeValid(KeyValuePair attribute) /// public static bool IsNumericType(object value) { - return value is byte || value is sbyte || value is char || value is short || value is ushort - || value is int || value is uint || value is long || value is ulong || value is float - || value is double || value is decimal; + return value is byte || value is sbyte || value is char || value is short || + value is ushort + || value is int || value is uint || value is long || value is ulong || + value is float + || value is double || value is decimal; } /// @@ -140,8 +146,11 @@ public static bool IsValidNumericValue(object value) if (IsNumericType(value)) { var doubleValue = Convert.ToDouble(value); - if (double.IsInfinity(doubleValue) || double.IsNaN(doubleValue) || Math.Abs(doubleValue) > OPT_NUMBER_LIMIT) + if (double.IsInfinity(doubleValue) || double.IsNaN(doubleValue) || + Math.Abs(doubleValue) > OPT_NUMBER_LIMIT) + { return false; + } return true; } @@ -150,4 +159,3 @@ public static bool IsValidNumericValue(object value) } } } - diff --git a/OptimizelySDK/packages.config b/OptimizelySDK/packages.config index c87329134..d5dd6846e 100644 --- a/OptimizelySDK/packages.config +++ b/OptimizelySDK/packages.config @@ -1,6 +1,6 @@  - - - - \ No newline at end of file + + + + From 2994d097e2242a39cdccbe2f5447c679e74c6db2 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 14 Feb 2023 08:41:21 -0500 Subject: [PATCH 3/7] Full style format OptimizelySDK.Tests.csproj All unit tests pass --- OptimizelySDK.Tests/App.config | 77 +- OptimizelySDK.Tests/Assertions.cs | 475 ++-- .../ConditionEvaluationTest.cs | 1233 +++++++--- .../AudienceConditionsTests/ConditionsTest.cs | 38 +- .../AudienceConditionsTests/SegmentsTests.cs | 6 +- OptimizelySDK.Tests/BucketerTest.cs | 215 +- .../ClientConfigHandlerTest.cs | 10 +- .../FallbackProjectConfigManagerTest.cs | 3 +- .../HttpProjectConfigManagerTest.cs | 438 ++-- .../PollingProjectConfigManagerTest.cs | 34 +- .../ConfigTest/ProjectConfigProps.cs | 31 +- .../TestPollingProjectConfigManager.cs | 45 +- OptimizelySDK.Tests/DecisionServiceTest.cs | 1116 ++++++--- .../DefaultErrorHandlerTest.cs | 11 +- .../EntityTests/FeatureVariableTest.cs | 17 +- .../EventTests/BatchEventProcessorTest.cs | 76 +- .../EventTests/CanonicalEvent.cs | 50 +- .../EventTests/DefaultEventDispatcherTest.cs | 16 +- .../EventTests/EventBuilderTest.cs | 1719 +++++++------- .../EventTests/EventEntitiesTest.cs | 268 +-- .../EventTests/EventFactoryTest.cs | 2030 +++++++++-------- .../EventTests/EventProcessorProps.cs | 22 +- .../ForwardingEventProcessorTest.cs | 32 +- .../EventTests/LogEventTest.cs | 20 +- .../EventTests/TestEventDispatcher.cs | 36 +- .../EventTests/UserEventFactoryTest.cs | 44 +- .../ForcedDecisionsStoreTest.cs | 13 +- .../IntegrationOdpDatafile.json | 756 +++--- ...IntegrationOdpWithOtherFieldsDatafile.json | 762 ++++--- OptimizelySDK.Tests/InvalidEventDispatcher.cs | 6 +- .../NotificationCenterTests.cs | 197 +- .../OdpTests/HttpClientTestUtil.cs | 16 +- OptimizelySDK.Tests/OdpTests/LruCacheTest.cs | 4 +- .../OdpTests/OdpEventApiManagerTest.cs | 6 +- .../OdpTests/OdpEventManagerTests.cs | 41 +- .../OdpTests/OdpManagerTest.cs | 5 +- .../OdpTests/OdpSegmentManagerTest.cs | 20 +- .../OptimizelyConfigTest.cs | 715 +++--- .../OptimizelyDecisionTest.cs | 66 +- OptimizelySDK.Tests/OptimizelyFactoryTest.cs | 89 +- OptimizelySDK.Tests/OptimizelyJSONTest.cs | 163 +- .../OptimizelySDK.Tests.csproj | 184 +- OptimizelySDK.Tests/OptimizelyTest.cs | 294 +-- .../OptimizelyUserContextTest.cs | 96 +- OptimizelySDK.Tests/ProjectConfigTest.cs | 174 +- OptimizelySDK.Tests/TestBucketer.cs | 9 +- OptimizelySDK.Tests/TestData.json | 1918 ++++++++-------- OptimizelySDK.Tests/Utils/Reflection.cs | 27 +- .../Utils/TestConversionExtensions.cs | 23 +- OptimizelySDK.Tests/Utils/TestData.cs | 146 +- .../Utils/TestHttpProjectConfigManagerUtil.cs | 35 +- .../UtilsTests/ConditionParserTest.cs | 21 +- .../UtilsTests/EventTagUtilsTest.cs | 303 ++- .../UtilsTests/ExceptionExtensionsTest.cs | 8 +- .../UtilsTests/ExperimentUtilsTest.cs | 240 +- .../UtilsTests/PrivateObject.cs | 21 +- .../UtilsTests/ValidatorTest.cs | 80 +- OptimizelySDK.Tests/ValidEventDispatcher.cs | 9 +- OptimizelySDK.Tests/packages.config | 16 +- OptimizelySDK.Tests/similar_exp_keys.json | 47 +- .../similar_rule_keys_bucketing.json | 42 +- .../typed_audience_datafile.json | 936 +++++--- .../unsupported_version_datafile.json | 89 +- 63 files changed, 9147 insertions(+), 6492 deletions(-) diff --git a/OptimizelySDK.Tests/App.config b/OptimizelySDK.Tests/App.config index 3c9414802..14d4908f4 100644 --- a/OptimizelySDK.Tests/App.config +++ b/OptimizelySDK.Tests/App.config @@ -5,44 +5,47 @@ --> - -
- - - - - - + +
+ + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/OptimizelySDK.Tests/Assertions.cs b/OptimizelySDK.Tests/Assertions.cs index 3f95e42b2..daf672985 100644 --- a/OptimizelySDK.Tests/Assertions.cs +++ b/OptimizelySDK.Tests/Assertions.cs @@ -36,10 +36,14 @@ public class Assertions { #region Basic asserts - public static bool HasItems(IEnumerable expected, IEnumerable actual, bool allowNull = true) + public static bool HasItems(IEnumerable expected, IEnumerable actual, + bool allowNull = true + ) { if (allowNull && expected == null && actual == null) + { return false; + } Assert.AreEqual(expected.Count(), actual.Count()); @@ -51,40 +55,50 @@ public static void AreEquivalent(IEnumerable expected, IEnumerable - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { Assert.AreEqual(z.Expected, z.Actual); - }; + } + + ; } } - public static void AreEquivalent(Dictionary expected, Dictionary actual) + public static void AreEquivalent(Dictionary expected, + Dictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEqual(KeyValuePair expected, KeyValuePair actual) + public static void AreEqual(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); Assert.AreEqual(expected.Value, actual.Value); @@ -95,7 +109,9 @@ public static void AreEqual(OptimizelyJSON expected, OptimizelyJSON actual) Assert.AreEqual(expected.ToString(), actual.ToString()); } - private static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + private static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); Assert.AreEqual(expected.Value, actual.Value); @@ -105,7 +121,9 @@ private static void AreEquivalent(KeyValuePair expected, KeyValu #region - public static void AreEqual(OptimizelyForcedDecision expected, OptimizelyForcedDecision actual) + public static void AreEqual(OptimizelyForcedDecision expected, + OptimizelyForcedDecision actual + ) { Assert.AreEqual(expected.VariationKey, actual.VariationKey); } @@ -114,22 +132,27 @@ public static void AreEqual(OptimizelyForcedDecision expected, OptimizelyForcedD #region OptimizelyAttribute - public static void AreEquivalent(OptimizelyAttribute[] expected, OptimizelyAttribute[] actual) + public static void AreEquivalent(OptimizelyAttribute[] expected, + OptimizelyAttribute[] actual + ) { Assert.AreEqual(expected.Count(), actual.Count()); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } public static void AreEqual(OptimizelyAttribute expected, OptimizelyAttribute actual) @@ -142,22 +165,26 @@ public static void AreEqual(OptimizelyAttribute expected, OptimizelyAttribute ac #region OptimizelyAudience - private static void AreEquivalent(OptimizelyAudience[] expected, OptimizelyAudience[] actual) + private static void AreEquivalent(OptimizelyAudience[] expected, OptimizelyAudience[] actual + ) { Assert.AreEqual(expected.Count(), actual.Count()); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } private static void AreEqual(OptimizelyAudience expected, OptimizelyAudience actual) @@ -197,18 +224,21 @@ private static void AreEquivalent(UserAttributes expected, UserAttributes actual { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEquivalent(z.Expected, z.Actual); - }; + } + + ; } #endregion @@ -219,18 +249,21 @@ public static void AreEquivalent(OptimizelyEvent[] expected, OptimizelyEvent[] a { Assert.AreEqual(expected.Count(), actual.Count()); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } private static void AreEqual(OptimizelyEvent expected, OptimizelyEvent actual) @@ -254,25 +287,32 @@ public static void AreEqual(OptimizelyDecision expected, OptimizelyDecision actu Assert.AreEqual(expected.VariationKey, actual.VariationKey); } - public static void AreEquivalent(IDictionary expected, IDictionary actual) + public static void AreEquivalent(IDictionary expected, + IDictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEquivalent(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + public static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -282,22 +322,27 @@ public static void AreEquivalent(KeyValuePair expect #region OptimizelyExperiement - public static void AreEquivalent(List expected, List actual) + public static void AreEquivalent(List expected, + List actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } public static void AreEqual(OptimizelyExperiment expected, OptimizelyExperiment actual) @@ -307,25 +352,32 @@ public static void AreEqual(OptimizelyExperiment expected, OptimizelyExperiment Assert.AreEqual(expected.Audiences, actual.Audiences); } - public static void AreEquivalent(IDictionary expected, IDictionary actual) + public static void AreEquivalent(IDictionary expected, + IDictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEquivalent(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + public static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -335,25 +387,32 @@ public static void AreEquivalent(KeyValuePair expe #region OptimizelyFeature - public static void AreEquivalent(IDictionary expected, IDictionary actual) + public static void AreEquivalent(IDictionary expected, + IDictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEquivalent(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + public static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -373,25 +432,32 @@ public static void AreEqual(OptimizelyFeature expected, OptimizelyFeature actual #region OptimizelyVariable - public static void AreEquivalent(IDictionary expected, IDictionary actual) + public static void AreEquivalent(IDictionary expected, + IDictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEquivalent(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + public static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -446,17 +512,20 @@ public static void AreEqual(FeatureDecision expected, FeatureDecision actual) #region FeatureFlags - public static void AreEquivalent(Dictionary expected, Dictionary actual) + public static void AreEquivalent(Dictionary expected, + Dictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { @@ -464,7 +533,9 @@ public static void AreEquivalent(Dictionary expected, Dicti } } - public static void AreEqual(KeyValuePair expected, KeyValuePair actual) + public static void AreEqual(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -475,7 +546,8 @@ public static void AreEqual(FeatureFlag expected, FeatureFlag actual) Assert.AreEqual(expected.Id, actual.Id); Assert.AreEqual(expected.Key, actual.Key); Assert.AreEqual(expected.RolloutId, actual.RolloutId); - AreEquivalent(expected.VariableKeyToFeatureVariableMap, actual.VariableKeyToFeatureVariableMap); + AreEquivalent(expected.VariableKeyToFeatureVariableMap, + actual.VariableKeyToFeatureVariableMap); } #endregion FeatureFlags @@ -492,17 +564,20 @@ public static void AreEqual(FeatureVariable expected, FeatureVariable actual) Assert.AreEqual(expected.Type, actual.Type); } - public static void AreEquivalent(Dictionary expected, Dictionary actual) + public static void AreEquivalent(Dictionary expected, + Dictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { @@ -510,7 +585,9 @@ public static void AreEquivalent(Dictionary expected, D } } - public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + public static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -526,121 +603,154 @@ public static void AreEqual(Variation expected, Variation actual) Assert.AreEqual(expected.Key, actual.Key); Assert.AreEqual(expected.FeatureEnabled, actual.FeatureEnabled); - AreEquivalent(expected.FeatureVariableUsageInstances, actual.FeatureVariableUsageInstances); - AreEquivalent(expected.VariableIdToVariableUsageInstanceMap, actual.VariableIdToVariableUsageInstanceMap); + AreEquivalent(expected.FeatureVariableUsageInstances, + actual.FeatureVariableUsageInstances); + AreEquivalent(expected.VariableIdToVariableUsageInstanceMap, + actual.VariableIdToVariableUsageInstanceMap); } - public static void AreEquivalent(Dictionary expected, Dictionary actual) + public static void AreEquivalent(Dictionary expected, + Dictionary actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEquivalent(List>> expected, List>> actual) + public static void AreEquivalent(List>> expected, + List>> actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEquivalent(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEquivalent(IDictionary> expected, IDictionary> actual) + public static void AreEquivalent(IDictionary> expected, + IDictionary> actual + ) { Assert.AreEqual(expected.Count, actual.Count); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { AreEqual(z.Expected, z.Actual); - }; + } + + ; } - public static void AreEqual(KeyValuePair expected, KeyValuePair actual) + public static void AreEqual(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); } - public static void AreEqual(KeyValuePair> expected, KeyValuePair> actual) + public static void AreEqual(KeyValuePair> expected, + KeyValuePair> actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEquivalent(expected.Value, actual.Value); } - public static void AreEquivalent(KeyValuePair> expected, KeyValuePair> actual) + public static void AreEquivalent(KeyValuePair> expected, + KeyValuePair> actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEquivalent(expected.Value, actual.Value); } - public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + public static void AreEquivalent(IEnumerable expected, + IEnumerable actual + ) { Assert.AreEqual(expected.Count(), actual.Count()); expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList().ForEach((item) => - { - AreEqual(item.Expected, item.Actual); - }); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(). + ForEach((item) => + { + AreEqual(item.Expected, item.Actual); + }); } #endregion Variations #region FeatureVariableUsage - public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + public static void AreEquivalent(IEnumerable expected, + IEnumerable actual + ) { if (HasItems(expected, actual)) { expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList().ForEach((item) => - { - AreEqual(item.Expected, item.Actual); - }); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(). + ForEach((item) => + { + AreEqual(item.Expected, item.Actual); + }); } } - public static void AreEquivalent(Dictionary expected, Dictionary actual) + public static void AreEquivalent(Dictionary expected, + Dictionary actual + ) { if (expected == null && actual == null) { @@ -649,19 +759,23 @@ public static void AreEquivalent(Dictionary expect Assert.AreEqual(expected.Count(), actual.Count()); expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList().ForEach((item) => - { - AreEquivalent(item.Expected, item.Actual); - }); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(). + ForEach((item) => + { + AreEquivalent(item.Expected, item.Actual); + }); } - public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + public static void AreEquivalent(KeyValuePair expected, + KeyValuePair actual + ) { Assert.AreEqual(expected.Key, actual.Key); AreEqual(expected.Value, actual.Value); @@ -677,22 +791,27 @@ public static void AreEqual(FeatureVariableUsage expected, FeatureVariableUsage #region TrafficAllocation - public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + public static void AreEquivalent(IEnumerable expected, + IEnumerable actual + ) { Assert.AreEqual(expected.Count(), actual.Count()); var zipped = expected.Zip(actual, (e, a) => - { - return new { - Expected = e, - Actual = a - }; - }).ToList(); + return new + { + Expected = e, + Actual = a, + }; + }). + ToList(); foreach (var z in zipped) { Assert.AreEqual(z.Expected, z.Actual); - }; + } + + ; } public static void AreEqual(TrafficAllocation expected, TrafficAllocation actual) diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs index 9d84e4b42..307af73ef 100644 --- a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs +++ b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs @@ -28,23 +28,73 @@ namespace OptimizelySDK.Tests.AudienceConditionsTests [TestFixture] public class ConditionEvaluationTest { - private BaseCondition LegacyCondition = new BaseCondition { Name = "device_type", Value = "iPhone", Type = "custom_attribute" }; - private BaseCondition ExistsCondition = new BaseCondition { Name = "input_value", Match = "exists", Type = "custom_attribute" }; - private BaseCondition SubstrCondition = new BaseCondition { Name = "location", Value = "USA", Match = "substring", Type = "custom_attribute" }; - private BaseCondition GTCondition = new BaseCondition { Name = "distance_gt", Value = 10, Match = "gt", Type = "custom_attribute" }; - private BaseCondition GECondition = new BaseCondition { Name = "distance_ge", Value = 10, Match = "ge", Type = "custom_attribute" }; - private BaseCondition LTCondition = new BaseCondition { Name = "distance_lt", Value = 10, Match = "lt", Type = "custom_attribute" }; - private BaseCondition LECondition = new BaseCondition { Name = "distance_le", Value = 10, Match = "le", Type = "custom_attribute" }; - private BaseCondition ExactStrCondition = new BaseCondition { Name = "browser_type", Value = "firefox", Match = "exact", Type = "custom_attribute" }; - private BaseCondition ExactBoolCondition = new BaseCondition { Name = "is_registered_user", Value = false, Match = "exact", Type = "custom_attribute" }; - private BaseCondition ExactDecimalCondition = new BaseCondition { Name = "pi_value", Value = 3.14, Match = "exact", Type = "custom_attribute" }; - private BaseCondition ExactIntCondition = new BaseCondition { Name = "lasers_count", Value = 9000, Match = "exact", Type = "custom_attribute" }; - private BaseCondition InfinityIntCondition = new BaseCondition { Name = "max_num_value", Value = 9223372036854775807, Match = "exact", Type = "custom_attribute" }; - private BaseCondition SemVerLTCondition = new BaseCondition { Name = "semversion_lt", Value = "3.7.1", Match = "semver_lt", Type = "custom_attribute" }; - private BaseCondition SemVerGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.1", Match = "semver_gt", Type = "custom_attribute" }; - private BaseCondition SemVerEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3.7.1", Match = "semver_eq", Type = "custom_attribute" }; - private BaseCondition SemVerGECondition = new BaseCondition { Name = "semversion_ge", Value = "3.7.1", Match = "semver_ge", Type = "custom_attribute" }; - private BaseCondition SemVerLECondition = new BaseCondition { Name = "semversion_le", Value = "3.7.1", Match = "semver_le", Type = "custom_attribute" }; + private BaseCondition LegacyCondition = new BaseCondition + { Name = "device_type", Value = "iPhone", Type = "custom_attribute" }; + + private BaseCondition ExistsCondition = new BaseCondition + { Name = "input_value", Match = "exists", Type = "custom_attribute" }; + + private BaseCondition SubstrCondition = new BaseCondition + { Name = "location", Value = "USA", Match = "substring", Type = "custom_attribute" }; + + private BaseCondition GTCondition = new BaseCondition + { Name = "distance_gt", Value = 10, Match = "gt", Type = "custom_attribute" }; + + private BaseCondition GECondition = new BaseCondition + { Name = "distance_ge", Value = 10, Match = "ge", Type = "custom_attribute" }; + + private BaseCondition LTCondition = new BaseCondition + { Name = "distance_lt", Value = 10, Match = "lt", Type = "custom_attribute" }; + + private BaseCondition LECondition = new BaseCondition + { Name = "distance_le", Value = 10, Match = "le", Type = "custom_attribute" }; + + private BaseCondition ExactStrCondition = new BaseCondition + { + Name = "browser_type", Value = "firefox", Match = "exact", Type = "custom_attribute", + }; + + private BaseCondition ExactBoolCondition = new BaseCondition + { + Name = "is_registered_user", Value = false, Match = "exact", Type = "custom_attribute", + }; + + private BaseCondition ExactDecimalCondition = new BaseCondition + { Name = "pi_value", Value = 3.14, Match = "exact", Type = "custom_attribute" }; + + private BaseCondition ExactIntCondition = new BaseCondition + { Name = "lasers_count", Value = 9000, Match = "exact", Type = "custom_attribute" }; + + private BaseCondition InfinityIntCondition = new BaseCondition + { + Name = "max_num_value", Value = 9223372036854775807, Match = "exact", + Type = "custom_attribute", + }; + + private BaseCondition SemVerLTCondition = new BaseCondition + { + Name = "semversion_lt", Value = "3.7.1", Match = "semver_lt", Type = "custom_attribute", + }; + + private BaseCondition SemVerGTCondition = new BaseCondition + { + Name = "semversion_gt", Value = "3.7.1", Match = "semver_gt", Type = "custom_attribute", + }; + + private BaseCondition SemVerEQCondition = new BaseCondition + { + Name = "semversion_eq", Value = "3.7.1", Match = "semver_eq", Type = "custom_attribute", + }; + + private BaseCondition SemVerGECondition = new BaseCondition + { + Name = "semversion_ge", Value = "3.7.1", Match = "semver_ge", Type = "custom_attribute", + }; + + private BaseCondition SemVerLECondition = new BaseCondition + { + Name = "semversion_le", Value = "3.7.1", Match = "semver_le", Type = "custom_attribute", + }; private ILogger Logger; private Mock LoggerMock; @@ -64,121 +114,263 @@ public void TestEvaluateWithDifferentTypedAttributes() { var userAttributes = new UserAttributes { - {"browser_type", "firefox" }, - {"is_registered_user", false }, - {"distance_gt", 15 }, - {"pi_value", 3.14 }, + { "browser_type", "firefox" }, + { "is_registered_user", false }, + { "distance_gt", 15 }, + { "pi_value", 3.14 }, }; - Assert.That(ExactStrCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); - Assert.That(ExactBoolCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); - Assert.That(GTCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); - Assert.That(ExactDecimalCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); + Assert.That(ExactStrCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), + Is.True); + Assert.That(ExactBoolCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), + Is.True); + Assert.That(GTCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), + Is.True); + Assert.That( + ExactDecimalCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), + Is.True); } [Test] public void TestEvaluateWithNoMatchType() { - Assert.That(LegacyCondition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), Is.True); + Assert.That( + LegacyCondition.Evaluate(null, + new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), + Is.True); // Assumes exact evaluator if no match type is provided. - Assert.That(LegacyCondition.Evaluate(null, new UserAttributes { { "device_type", "IPhone" } }.ToUserContext(), Logger), Is.False); + Assert.That( + LegacyCondition.Evaluate(null, + new UserAttributes { { "device_type", "IPhone" } }.ToUserContext(), Logger), + Is.False); } [Test] public void TestEvaluateWithInvalidTypeProperty() { - BaseCondition condition = new BaseCondition { Name = "input_value", Value = "Android", Match = "exists", Type = "invalid_type" }; - Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), Is.Null); + var condition = new BaseCondition + { + Name = "input_value", Value = "Android", Match = "exists", Type = "invalid_type", + }; + Assert.That( + condition.Evaluate(null, + new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), + Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + $@"Audience condition ""{condition + }"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); } [Test] public void TestEvaluateWithMissingTypeProperty() { - var condition = new BaseCondition { Name = "input_value", Value = "Android", Match = "exists" }; - Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), Is.Null); + var condition = new BaseCondition + { Name = "input_value", Value = "Android", Match = "exists" }; + Assert.That( + condition.Evaluate(null, + new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), + Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + $@"Audience condition ""{condition + }"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); } [Test] public void TestEvaluateWithInvalidMatchProperty() { - BaseCondition condition = new BaseCondition { Name = "device_type", Value = "Android", Match = "invalid_match", Type = "custom_attribute" }; - Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "Android" } }.ToUserContext(), Logger), Is.Null); - - LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); - } - - [Test] - public void TestEvaluateLogsWarningAndReturnNullWhenAttributeIsNotProvidedAndConditionTypeIsNotExists() - { - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); - - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because no value was passed for user attribute ""is_registered_user""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because no value was passed for user attribute ""location""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_lt""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_gt""."), Times.Once); - } - - [Test] - public void TestEvaluateLogsAndReturnNullWhenAttributeValueIsNullAndConditionTypeIsNotExists() - { - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", null } }.ToUserContext(), Logger), Is.Null); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", null } }.ToUserContext(), Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", null } }.ToUserContext(), Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", null } }.ToUserContext(), Logger), Is.Null); - - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a null value was passed for user attribute ""is_registered_user""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a null value was passed for user attribute ""location""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_lt""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_gt""."), Times.Once); - } - - [Test] - public void TestEvaluateReturnsFalseAndDoesNotLogForExistsConditionWhenAttributeIsNotProvided() - { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes().ToUserContext(), Logger), Is.False); + var condition = new BaseCondition + { + Name = "device_type", Value = "Android", Match = "invalid_match", + Type = "custom_attribute", + }; + Assert.That( + condition.Evaluate(null, + new UserAttributes { { "device_type", "Android" } }.ToUserContext(), Logger), + Is.Null); + + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + $@"Audience condition ""{condition + }"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); + } + + [Test] + public void + TestEvaluateLogsWarningAndReturnNullWhenAttributeIsNotProvidedAndConditionTypeIsNotExists() + { + Assert.That( + ExactBoolCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), + Is.Null); + Assert.That( + SubstrCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), + Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), + Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), + Is.Null); + + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because no value was passed for user attribute ""is_registered_user""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because no value was passed for user attribute ""location""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_lt""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_gt""."), + Times.Once); + } + + [Test] + public void + TestEvaluateLogsAndReturnNullWhenAttributeValueIsNullAndConditionTypeIsNotExists() + { + Assert.That( + ExactBoolCondition.Evaluate(null, + new UserAttributes { { "is_registered_user", null } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + SubstrCondition.Evaluate(null, + new UserAttributes { { "location", null } }.ToUserContext(), Logger), Is.Null); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", null } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", null } }.ToUserContext(), Logger), + Is.Null); + + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a null value was passed for user attribute ""is_registered_user""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a null value was passed for user attribute ""location""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_lt""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_gt""."), + Times.Once); + } + + [Test] + public void + TestEvaluateReturnsFalseAndDoesNotLogForExistsConditionWhenAttributeIsNotProvided() + { + Assert.That( + ExistsCondition.Evaluate(null, new UserAttributes().ToUserContext(), Logger), + Is.False); LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Never); } [Test] public void TestEvaluateLogsWarningAndReturnNullWhenAttributeTypeIsInvalid() { - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", 5 } }.ToUserContext(), Logger), Is.Null); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", false } }.ToUserContext(), Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid" } }.ToUserContext(), Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", true } }.ToUserContext(), Logger), Is.Null); - - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a value of type ""Int32"" was passed for user attribute ""is_registered_user""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""location""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_lt""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""distance_gt""."), Times.Once); + Assert.That( + ExactBoolCondition.Evaluate(null, + new UserAttributes { { "is_registered_user", 5 } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + SubstrCondition.Evaluate(null, + new UserAttributes { { "location", false } }.ToUserContext(), Logger), Is.Null); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", "invalid" } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", true } }.ToUserContext(), Logger), + Is.Null); + + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a value of type ""Int32"" was passed for user attribute ""is_registered_user""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""location""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_lt""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""distance_gt""."), + Times.Once); } [Test] public void TestEvaluateLogsWarningAndReturnNullWhenConditionTypeIsInvalid() { - var invalidCondition = new BaseCondition { Name = "is_registered_user", Value = new string[] { }, Match = "exact", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "is_registered_user", true } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":[]} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); - - invalidCondition = new BaseCondition { Name = "location", Value = 25, Match = "substring", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "location", "USA" } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":25} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); - - invalidCondition = new BaseCondition { Name = "distance_lt", Value = "invalid", Match = "lt", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "distance_lt", 5 } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":""invalid""} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); - - invalidCondition = new BaseCondition { Name = "distance_gt", Value = "invalid", Match = "gt", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid" } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":""invalid""} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); + var invalidCondition = new BaseCondition + { + Name = "is_registered_user", Value = new string[] { }, Match = "exact", + Type = "custom_attribute", + }; + Assert.That( + invalidCondition.Evaluate(null, + new UserAttributes { { "is_registered_user", true } }.ToUserContext(), Logger), + Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":[]} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); + + invalidCondition = new BaseCondition + { Name = "location", Value = 25, Match = "substring", Type = "custom_attribute" }; + Assert.That( + invalidCondition.Evaluate(null, + new UserAttributes { { "location", "USA" } }.ToUserContext(), Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":25} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); + + invalidCondition = new BaseCondition + { + Name = "distance_lt", Value = "invalid", Match = "lt", Type = "custom_attribute", + }; + Assert.That( + invalidCondition.Evaluate(null, + new UserAttributes { { "distance_lt", 5 } }.ToUserContext(), Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":""invalid""} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); + + invalidCondition = new BaseCondition + { + Name = "distance_gt", Value = "invalid", Match = "gt", Type = "custom_attribute", + }; + Assert.That( + invalidCondition.Evaluate(null, + new UserAttributes { { "distance_gt", "invalid" } }.ToUserContext(), Logger), + Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":""invalid""} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); } #endregion // Evaluate Tests @@ -188,45 +380,109 @@ public void TestEvaluateLogsWarningAndReturnNullWhenConditionTypeIsInvalid() [Test] public void TestExactMatcherReturnsFalseWhenAttributeValueDoesNotMatch() { - Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", "chrome" } }.ToUserContext(), Logger), Is.False); - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", true } }.ToUserContext(), Logger), Is.False); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", 2.5 } }.ToUserContext(), Logger), Is.False); - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", 55 } }.ToUserContext(), Logger), Is.False); + Assert.That( + ExactStrCondition.Evaluate(null, + new UserAttributes { { "browser_type", "chrome" } }.ToUserContext(), Logger), + Is.False); + Assert.That( + ExactBoolCondition.Evaluate(null, + new UserAttributes { { "is_registered_user", true } }.ToUserContext(), Logger), + Is.False); + Assert.That( + ExactDecimalCondition.Evaluate(null, + new UserAttributes { { "pi_value", 2.5 } }.ToUserContext(), Logger), Is.False); + Assert.That( + ExactIntCondition.Evaluate(null, + new UserAttributes { { "lasers_count", 55 } }.ToUserContext(), Logger), + Is.False); } [Test] public void TestExactMatcherReturnsNullWhenTypeMismatch() { - Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", true } }.ToUserContext(), Logger), Is.Null); - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", "abcd" } }.ToUserContext(), Logger), Is.Null); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", false } }.ToUserContext(), Logger), Is.Null); - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", "infinity" } }.ToUserContext(), Logger), Is.Null); - - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""browser_type"",""value"":""firefox""} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""browser_type""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""is_registered_user""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""pi_value""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""lasers_count""."), Times.Once); + Assert.That( + ExactStrCondition.Evaluate(null, + new UserAttributes { { "browser_type", true } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + ExactBoolCondition.Evaluate(null, + new UserAttributes { { "is_registered_user", "abcd" } }.ToUserContext(), + Logger), Is.Null); + Assert.That( + ExactDecimalCondition.Evaluate(null, + new UserAttributes { { "pi_value", false } }.ToUserContext(), Logger), Is.Null); + Assert.That( + ExactIntCondition.Evaluate(null, + new UserAttributes { { "lasers_count", "infinity" } }.ToUserContext(), Logger), + Is.Null); + + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""browser_type"",""value"":""firefox""} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""browser_type""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""is_registered_user""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""pi_value""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""lasers_count""."), + Times.Once); } [Test] public void TestExactMatcherReturnsNullForOutOfBoundNumericValues() { - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", double.NegativeInfinity } }.ToUserContext(), Logger), Is.Null); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", Math.Pow(2, 53) + 2 } }.ToUserContext(), Logger), Is.Null); - Assert.That(InfinityIntCondition.Evaluate(null, new UserAttributes { { "max_num_value", 15 } }.ToUserContext(), Logger), Is.Null); - - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because the number value for user attribute ""lasers_count"" is not in the range [-2^53, +2^53]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because the number value for user attribute ""pi_value"" is not in the range [-2^53, +2^53]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""max_num_value"",""value"":9223372036854775807} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); + Assert.That( + ExactIntCondition.Evaluate(null, + new UserAttributes + { { "lasers_count", double.NegativeInfinity } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + ExactDecimalCondition.Evaluate(null, + new UserAttributes { { "pi_value", Math.Pow(2, 53) + 2 } }.ToUserContext(), + Logger), Is.Null); + Assert.That( + InfinityIntCondition.Evaluate(null, + new UserAttributes { { "max_num_value", 15 } }.ToUserContext(), Logger), + Is.Null); + + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because the number value for user attribute ""lasers_count"" is not in the range [-2^53, +2^53]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because the number value for user attribute ""pi_value"" is not in the range [-2^53, +2^53]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""max_num_value"",""value"":9223372036854775807} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), + Times.Once); } [Test] public void TestExactMatcherReturnsTrueWhenAttributeValueMatches() { - Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", "firefox" } }.ToUserContext(), Logger), Is.True); - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", false } }.ToUserContext(), Logger), Is.True); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", 3.14 } }.ToUserContext(), Logger), Is.True); - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", 9000 } }.ToUserContext(), Logger), Is.True); + Assert.That( + ExactStrCondition.Evaluate(null, + new UserAttributes { { "browser_type", "firefox" } }.ToUserContext(), Logger), + Is.True); + Assert.That( + ExactBoolCondition.Evaluate(null, + new UserAttributes { { "is_registered_user", false } }.ToUserContext(), Logger), + Is.True); + Assert.That( + ExactDecimalCondition.Evaluate(null, + new UserAttributes { { "pi_value", 3.14 } }.ToUserContext(), Logger), Is.True); + Assert.That( + ExactIntCondition.Evaluate(null, + new UserAttributes { { "lasers_count", 9000 } }.ToUserContext(), Logger), + Is.True); } #endregion // ExactMatcher Tests @@ -236,22 +492,37 @@ public void TestExactMatcherReturnsTrueWhenAttributeValueMatches() [Test] public void TestExistsMatcherReturnsFalseWhenAttributeIsNotProvided() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.False); + Assert.That( + ExistsCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), + Is.False); } [Test] public void TestExistsMatcherReturnsFalseWhenAttributeIsNull() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", null } }.ToUserContext(), Logger), Is.False); + Assert.That( + ExistsCondition.Evaluate(null, + new UserAttributes { { "input_value", null } }.ToUserContext(), Logger), + Is.False); } [Test] public void TestExistsMatcherReturnsTrueWhenAttributeValueIsProvided() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", "" } }.ToUserContext(), Logger), Is.True); - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", "iPhone" } }.ToUserContext(), Logger), Is.True); - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", 10 } }.ToUserContext(), Logger), Is.True); - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", false } }.ToUserContext(), Logger), Is.True); + Assert.That( + ExistsCondition.Evaluate(null, + new UserAttributes { { "input_value", "" } }.ToUserContext(), Logger), Is.True); + Assert.That( + ExistsCondition.Evaluate(null, + new UserAttributes { { "input_value", "iPhone" } }.ToUserContext(), Logger), + Is.True); + Assert.That( + ExistsCondition.Evaluate(null, + new UserAttributes { { "input_value", 10 } }.ToUserContext(), Logger), Is.True); + Assert.That( + ExistsCondition.Evaluate(null, + new UserAttributes { { "input_value", false } }.ToUserContext(), Logger), + Is.True); } #endregion // ExistsMatcher Tests @@ -261,21 +532,34 @@ public void TestExistsMatcherReturnsTrueWhenAttributeValueIsProvided() [Test] public void TestSubstringMatcherReturnsFalseWhenAttributeValueIsNotASubstring() { - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "Los Angeles" } }.ToUserContext(), Logger), Is.False); + Assert.That( + SubstrCondition.Evaluate(null, + new UserAttributes { { "location", "Los Angeles" } }.ToUserContext(), Logger), + Is.False); } [Test] public void TestSubstringMatcherReturnsNullWhenAttributeValueIsNotAString() { - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", 10.5 } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a value of type ""Double"" was passed for user attribute ""location""."), Times.Once); + Assert.That( + SubstrCondition.Evaluate(null, + new UserAttributes { { "location", 10.5 } }.ToUserContext(), Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a value of type ""Double"" was passed for user attribute ""location""."), + Times.Once); } [Test] public void TestSubstringMatcherReturnsTrueWhenAttributeValueIsASubstring() { - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "USA" } }.ToUserContext(), Logger), Is.True); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "San Francisco, USA" } }.ToUserContext(), Logger), Is.True); + Assert.That( + SubstrCondition.Evaluate(null, + new UserAttributes { { "location", "USA" } }.ToUserContext(), Logger), Is.True); + Assert.That( + SubstrCondition.Evaluate(null, + new UserAttributes { { "location", "San Francisco, USA" } }.ToUserContext(), + Logger), Is.True); } #endregion // SubstringMatcher Tests @@ -285,81 +569,138 @@ public void TestSubstringMatcherReturnsTrueWhenAttributeValueIsASubstring() [Test] public void TestGTMatcherReturnsFalseWhenAttributeValueIsLessThanOrEqualToConditionValue() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 5 } }.ToUserContext(), Logger), Is.False); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 10 } }.ToUserContext(), Logger), Is.False); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", 5 } }.ToUserContext(), Logger), Is.False); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", 10 } }.ToUserContext(), Logger), + Is.False); } [Test] public void TestGTMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid_type" } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_gt""."), Times.Once); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", "invalid_type" } }.ToUserContext(), + Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_gt""."), + Times.Once); } [Test] public void TestGTMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", double.PositiveInfinity } }.ToUserContext(), Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", Math.Pow(2, 53) + 2 } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_gt"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes + { { "distance_gt", double.PositiveInfinity } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", Math.Pow(2, 53) + 2 } }.ToUserContext(), + Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_gt"" is not in the range [-2^53, +2^53]."), + Times.Exactly(2)); } [Test] public void TestGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValue() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 15 } }.ToUserContext(), Logger), Is.True); + Assert.That( + GTCondition.Evaluate(null, + new UserAttributes { { "distance_gt", 15 } }.ToUserContext(), Logger), Is.True); } [Test] public void TestSemVerGTTargetBetaComplex() { - var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "2.1.3-beta+1", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "2.1.3-beta+1.2.3" } }.ToUserContext(), Logger) ?? false); + var semverGTCondition = new BaseCondition + { + Name = "semversion_gt", Value = "2.1.3-beta+1", Match = "semver_gt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "2.1.3-beta+1.2.3" } }.ToUserContext(), + Logger) ?? false); } [Test] public void TestSemVerGTCompareAgainstPreReleaseToPreRelease() { - var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.1-prerelease+build", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1-prerelease+rc" } }.ToUserContext(), Logger) ?? false); + var semverGTCondition = new BaseCondition + { + Name = "semversion_gt", Value = "3.7.1-prerelease+build", Match = "semver_gt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.1-prerelease+rc" } }.ToUserContext(), + Logger) ?? false); } [Test] public void TestSemVerGTComparePrereleaseSmallerThanBuild() { - var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.1-prerelease", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1+build" } }.ToUserContext(), Logger) ?? false); + var semverGTCondition = new BaseCondition + { + Name = "semversion_gt", Value = "3.7.1-prerelease", Match = "semver_gt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.1+build" } }.ToUserContext(), + Logger) ?? false); } + #endregion // GTMatcher Tests #region GEMatcher Tests [Test] - public void TestGEMatcherReturnsFalseWhenAttributeValueIsLessButTrueForEqualToConditionValue() + public void + TestGEMatcherReturnsFalseWhenAttributeValueIsLessButTrueForEqualToConditionValue() { - Assert.IsFalse(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 5 } }.ToUserContext(), Logger)?? true); - Assert.IsTrue(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 10 } }.ToUserContext(), Logger)?? false); + Assert.IsFalse(GECondition.Evaluate(null, + new UserAttributes { { "distance_ge", 5 } }.ToUserContext(), Logger) ?? true); + Assert.IsTrue(GECondition.Evaluate(null, + new UserAttributes { { "distance_ge", 10 } }.ToUserContext(), Logger) ?? false); } [Test] public void TestGEMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", "invalid_type" } }.ToUserContext(), Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""ge"",""name"":""distance_ge"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_ge""."), Times.Once); + Assert.IsNull(GECondition.Evaluate(null, + new UserAttributes { { "distance_ge", "invalid_type" } }.ToUserContext(), Logger)); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""ge"",""name"":""distance_ge"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_ge""."), + Times.Once); } [Test] public void TestGEMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", double.PositiveInfinity } }.ToUserContext(), Logger)); - Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", Math.Pow(2, 53) + 2 } }.ToUserContext(), Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""ge"",""name"":""distance_ge"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_ge"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); + Assert.IsNull(GECondition.Evaluate(null, + new UserAttributes { { "distance_ge", double.PositiveInfinity } }.ToUserContext(), + Logger)); + Assert.IsNull(GECondition.Evaluate(null, + new UserAttributes { { "distance_ge", Math.Pow(2, 53) + 2 } }.ToUserContext(), + Logger)); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""ge"",""name"":""distance_ge"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_ge"" is not in the range [-2^53, +2^53]."), + Times.Exactly(2)); } [Test] public void TestGEMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValue() { - Assert.IsTrue(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 15 } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(GECondition.Evaluate(null, + new UserAttributes { { "distance_ge", 15 } }.ToUserContext(), Logger) ?? false); } #endregion // GEMatcher Tests @@ -367,279 +708,555 @@ public void TestGEMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValu #region LTMatcher Tests [Test] - public void TestLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToConditionValue() + public void + TestLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToConditionValue() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 15 } }.ToUserContext(), Logger), Is.False); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 10 } }.ToUserContext(), Logger), Is.False); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", 15 } }.ToUserContext(), Logger), + Is.False); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", 10 } }.ToUserContext(), Logger), + Is.False); } [Test] public void TestLTMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid_type" } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_lt""."), Times.Once); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", "invalid_type" } }.ToUserContext(), + Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_lt""."), + Times.Once); } [Test] public void TestLTMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", double.NegativeInfinity } }.ToUserContext(), Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", -Math.Pow(2, 53) - 2 } }.ToUserContext(), Logger), Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_lt"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes + { { "distance_lt", double.NegativeInfinity } }.ToUserContext(), Logger), + Is.Null); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", -Math.Pow(2, 53) - 2 } }.ToUserContext(), + Logger), Is.Null); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_lt"" is not in the range [-2^53, +2^53]."), + Times.Exactly(2)); } [Test] public void TestLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 5 } }.ToUserContext(), Logger), Is.True); + Assert.That( + LTCondition.Evaluate(null, + new UserAttributes { { "distance_lt", 5 } }.ToUserContext(), Logger), Is.True); } [Test] public void TestSemVerLTTargetBuildComplex() { - var semverLTCondition = new BaseCondition { Name = "semversion_lt", Value = "2.1.3-beta+1.2.3", Match = "semver_lt", Type = "custom_attribute" }; - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.1.3-beta+1" } }.ToUserContext(), Logger) ?? false); + var semverLTCondition = new BaseCondition + { + Name = "semversion_lt", Value = "2.1.3-beta+1.2.3", Match = "semver_lt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "2.1.3-beta+1" } }.ToUserContext(), + Logger) ?? false); } [Test] public void TestSemVerLTCompareMultipleDash() { - var semverLTCondition = new BaseCondition { Name = "semversion_lt", Value = "2.1.3-beta-1.2.3", Match = "semver_lt", Type = "custom_attribute" }; - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.1.3-beta-1" } }.ToUserContext(), Logger) ?? false); + var semverLTCondition = new BaseCondition + { + Name = "semversion_lt", Value = "2.1.3-beta-1.2.3", Match = "semver_lt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "2.1.3-beta-1" } }.ToUserContext(), + Logger) ?? false); } #endregion // LTMatcher Tests #region LEMatcher Tests + [Test] - public void TestLEMatcherReturnsFalseWhenAttributeValueIsGreaterAndTrueIfEqualToConditionValue() + public void + TestLEMatcherReturnsFalseWhenAttributeValueIsGreaterAndTrueIfEqualToConditionValue() { - Assert.IsFalse(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 15 } }.ToUserContext(), Logger) ?? true); - Assert.IsTrue(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 10 } }.ToUserContext(), Logger) ?? false); + Assert.IsFalse(LECondition.Evaluate(null, + new UserAttributes { { "distance_le", 15 } }.ToUserContext(), Logger) ?? true); + Assert.IsTrue(LECondition.Evaluate(null, + new UserAttributes { { "distance_le", 10 } }.ToUserContext(), Logger) ?? false); } [Test] public void TestLEMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", "invalid_type" } }.ToUserContext(), Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""le"",""name"":""distance_le"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_le""."), Times.Once); + Assert.IsNull(LECondition.Evaluate(null, + new UserAttributes { { "distance_le", "invalid_type" } }.ToUserContext(), Logger)); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""le"",""name"":""distance_le"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_le""."), + Times.Once); } [Test] public void TestLEMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", double.NegativeInfinity } }.ToUserContext(), Logger)); - Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", -Math.Pow(2, 53) - 2 } }.ToUserContext(), Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""le"",""name"":""distance_le"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_le"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); + Assert.IsNull(LECondition.Evaluate(null, + new UserAttributes { { "distance_le", double.NegativeInfinity } }.ToUserContext(), + Logger)); + Assert.IsNull(LECondition.Evaluate(null, + new UserAttributes { { "distance_le", -Math.Pow(2, 53) - 2 } }.ToUserContext(), + Logger)); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + @"Audience condition {""type"":""custom_attribute"",""match"":""le"",""name"":""distance_le"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_le"" is not in the range [-2^53, +2^53]."), + Times.Exactly(2)); } [Test] public void TestLEMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() { - Assert.IsTrue(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 5 } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(LECondition.Evaluate(null, + new UserAttributes { { "distance_le", 5 } }.ToUserContext(), Logger) ?? false); } #endregion // LEMatcher Tests #region SemVerLTMatcher Tests + [Test] - public void TestSemVerLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToConditionValue() + public void + TestSemVerLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToConditionValue() { - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.2" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.1" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.8" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "4" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3.7.2" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3.7.1" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3.8" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "4" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() { - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.1-beta" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.7.1" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3.7.0" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, + new UserAttributes + { { "semversion_lt", "3.7.1-beta" } }.ToUserContext(), Logger) ?? + false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "2.7.1" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3.7" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValueBeta() { - var semverLTCondition = new BaseCondition { Name = "semversion_lt", Value = "3.7.0-beta.2.3", Match = "semver_lt", Type = "custom_attribute" }; - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0-beta.2.1" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0-beta" } }.ToUserContext(), Logger) ?? false); + var semverLTCondition = new BaseCondition + { + Name = "semversion_lt", Value = "3.7.0-beta.2.3", Match = "semver_lt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverLTCondition.Evaluate(null, + new UserAttributes { { "semversion_lt", "3.7.0-beta.2.1" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverLTCondition.Evaluate(null, + new UserAttributes + { { "semversion_lt", "3.7.0-beta" } }.ToUserContext(), Logger) ?? + false); } + #endregion // SemVerLTMatcher Tests #region SemVerGTMatcher Tests + [Test] - public void TestSemVerGTMatcherReturnsFalseWhenAttributeValueIsLessThanOrEqualToConditionValue() + public void + TestSemVerGTMatcherReturnsFalseWhenAttributeValueIsLessThanOrEqualToConditionValue() { - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0" } }.ToUserContext(), Logger)?? true); - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1" } }.ToUserContext(), Logger)?? true); - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.6" } }.ToUserContext(), Logger)?? true); - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "2" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.0" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.1" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.6" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "2" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValue() { - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.2" } }.ToUserContext(), Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.2-beta" } }.ToUserContext(), Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "4.7.1" } }.ToUserContext(), Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.8" } }.ToUserContext(), Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "4" } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.2" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, + new UserAttributes + { { "semversion_gt", "3.7.2-beta" } }.ToUserContext(), Logger) ?? + false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "4.7.1" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.8" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "4" } }.ToUserContext(), Logger) ?? false); + } + + [Test] + public void + TestSemVerGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValueBeta() + { + var semverGTCondition = new BaseCondition + { + Name = "semversion_gt", Value = "3.7.0-beta.2.3", Match = "semver_gt", + Type = "custom_attribute", + }; + Assert.IsTrue(semverGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.0-beta.2.4" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverGTCondition.Evaluate(null, + new UserAttributes { { "semversion_gt", "3.7.0" } }.ToUserContext(), + Logger) ?? + false); } - [Test] - public void TestSemVerGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValueBeta() - { - var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.0-beta.2.3", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0-beta.2.4" } }.ToUserContext(), Logger)?? false); - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0" } }.ToUserContext(), Logger)?? false); - } #endregion // SemVerGTMatcher Tests #region SemVerEQMatcher Tests + [Test] public void TestSemVerEQMatcherReturnsFalseWhenAttributeValueIsNotEqualToConditionValue() { - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.0" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.2" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.6" } }.ToUserContext(), Logger)?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2" } }.ToUserContext(), Logger)?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "4" } }.ToUserContext(), Logger)?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.7.0" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.7.2" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.6" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "2" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "4" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValue() { - Assert.IsTrue(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.7.1" } }.ToUserContext(), + Logger) ?? + false); } [Test] - public void TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValueMajorOnly() + public void + TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValueMajorOnly() { - var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.0.0" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.1" } }.ToUserContext(), Logger) ?? false); + var semverEQCondition = new BaseCondition + { + Name = "semversion_eq", Value = "3", Match = "semver_eq", Type = "custom_attribute", + }; + Assert.IsTrue(semverEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.0.0" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(semverEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.1" } }.ToUserContext(), + Logger) ?? + false); } [Test] - public void TestSemVerEQMatcherReturnsFalseOrFalseWhenAttributeValueIsNotEqualToConditionValueMajorOnly() + public void + TestSemVerEQMatcherReturnsFalseOrFalseWhenAttributeValueIsNotEqualToConditionValueMajorOnly() { - var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsFalse(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "4.0" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2" } }.ToUserContext(), Logger) ?? true); + var semverEQCondition = new BaseCondition + { + Name = "semversion_eq", Value = "3", Match = "semver_eq", Type = "custom_attribute", + }; + Assert.IsFalse(semverEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "4.0" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(semverEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "2" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValueBeta() { - var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3.7.0-beta.2.3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.0-beta.2.3" } }.ToUserContext(), Logger) ?? false); + var semverEQCondition = new BaseCondition + { + Name = "semversion_eq", Value = "3.7.0-beta.2.3", Match = "semver_eq", + Type = "custom_attribute", + }; + Assert.IsTrue(semverEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "3.7.0-beta.2.3" } }.ToUserContext(), + Logger) ?? false); } [Test] public void TestSemVerEQTargetBuildIgnores() { - var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "2.1.3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2.1.3+build" } }.ToUserContext(), Logger) ?? false); + var semverEQCondition = new BaseCondition + { + Name = "semversion_eq", Value = "2.1.3", Match = "semver_eq", + Type = "custom_attribute", + }; + Assert.IsTrue(semverEQCondition.Evaluate(null, + new UserAttributes { { "semversion_eq", "2.1.3+build" } }.ToUserContext(), + Logger) ?? false); } + #endregion // SemVerEQMatcher Tests #region SemVerGEMatcher Tests - [Test] - public void TestSemVerGEMatcherReturnsFalseWhenAttributeValueIsNotGreaterOrEqualToConditionValue() - { - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1-beta" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.6" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "2" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3" } }.ToUserContext(), Logger) ?? true); - } [Test] - public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValue() - { - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.2" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.8.1" } }.ToUserContext(), Logger) ?? false); ; - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "4.7.1" } }.ToUserContext(), Logger) ?? false); + public void + TestSemVerGEMatcherReturnsFalseWhenAttributeValueIsNotGreaterOrEqualToConditionValue() + { + Assert.IsFalse(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.0" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.1-beta" } }. + ToUserContext(), Logger) ?? + true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.6" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "2" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3" } }.ToUserContext(), Logger) ?? true); + } + + [Test] + public void + TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValue() + { + Assert.IsTrue(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.1" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.2" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.8.1" } }.ToUserContext(), + Logger) ?? + false); + ; + Assert.IsTrue(SemVerGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "4.7.1" } }.ToUserContext(), + Logger) ?? + false); + } + + [Test] + public void + TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValueMajorOnly() + { + var semverGECondition = new BaseCondition + { + Name = "semversion_ge", Value = "3", Match = "semver_ge", Type = "custom_attribute", + }; + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.0" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.0.0" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "4.0" } }.ToUserContext(), + Logger) ?? + false); } [Test] - public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValueMajorOnly() + public void + TestSemVerGEMatcherReturnsFalseWhenAttributeValueIsNotGreaterOrEqualToConditionValueMajorOnly() { - var semverGECondition = new BaseCondition { Name = "semversion_ge", Value = "3", Match = "semver_ge", Type = "custom_attribute" }; - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.0.0" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "4.0" } }.ToUserContext(), Logger) ?? false); + var semverGECondition = new BaseCondition + { + Name = "semversion_ge", Value = "3", Match = "semver_ge", Type = "custom_attribute", + }; + Assert.IsFalse(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "2" } }.ToUserContext(), Logger) ?? true); } [Test] - public void TestSemVerGEMatcherReturnsFalseWhenAttributeValueIsNotGreaterOrEqualToConditionValueMajorOnly() + public void + TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValueBeta() { - var semverGECondition = new BaseCondition { Name = "semversion_ge", Value = "3", Match = "semver_ge", Type = "custom_attribute" }; - Assert.IsFalse(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "2" } }.ToUserContext(), Logger) ?? true); + var semverGECondition = new BaseCondition + { + Name = "semversion_ge", Value = "3.7.0-beta.2.3", Match = "semver_ge", + Type = "custom_attribute", + }; + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.0-beta.2.4" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3+1.2.3" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, + new UserAttributes { { "semversion_ge", "3.7.1-beta.2.3" } }.ToUserContext(), + Logger) ?? false); } - [Test] - public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValueBeta() - { - var semverGECondition = new BaseCondition { Name = "semversion_ge", Value = "3.7.0-beta.2.3", Match = "semver_ge", Type = "custom_attribute" }; - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.4" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3+1.2.3" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1-beta.2.3" } }.ToUserContext(), Logger) ?? false); - } #endregion // SemVerGEMatcher Tests #region SemVerLEMatcher Tests - [Test] - public void TestSemVerLEMatcherReturnsFalseWhenAttributeValueIsNotLessOrEqualToConditionValue() - { - Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.2" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.8" } }.ToUserContext(), Logger) ?? true); - Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "4" } }.ToUserContext(), Logger) ?? true); - } [Test] - public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValue() + public void + TestSemVerLEMatcherReturnsFalseWhenAttributeValueIsNotLessOrEqualToConditionValue() { - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.6.1" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "2.7.1" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1-beta" } }.ToUserContext(), Logger) ?? false); + Assert.IsFalse(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.2" } }.ToUserContext(), + Logger) ?? + true); + Assert.IsFalse(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.8" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "4" } }.ToUserContext(), Logger) ?? true); } [Test] - public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValueMajorOnly() + public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValue() { - var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute" }; - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.4" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1-beta" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.0.0" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "2.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.1" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.0" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.6.1" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "2.7.1" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, + new UserAttributes + { { "semversion_le", "3.7.1-beta" } }.ToUserContext(), Logger) ?? + false); + } + + [Test] + public void + TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValueMajorOnly() + { + var semverLECondition = new BaseCondition + { + Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute", + }; + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.0-beta.2.4" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes + { { "semversion_le", "3.7.1-beta" } }.ToUserContext(), Logger) ?? + false); + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.0.0" } }.ToUserContext(), + Logger) ?? + false); + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "2.0" } }.ToUserContext(), + Logger) ?? + false); + } + + [Test] + public void + TestSemVerLEMatcherReturnsFalseWhenAttributeValueIsNotLessOrEqualToConditionValueMajorOnly() + { + var semverLECondition = new BaseCondition + { + Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute", + }; + Assert.IsFalse(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "4" } }.ToUserContext(), Logger) ?? true); } [Test] - public void TestSemVerLEMatcherReturnsFalseWhenAttributeValueIsNotLessOrEqualToConditionValueMajorOnly() + public void + TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValueBeta() { - var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute" }; - Assert.IsFalse(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "4" } }.ToUserContext(), Logger) ?? true); + var semverLECondition = new BaseCondition + { + Name = "semversion_le", Value = "3.7.0-beta.2.3", Match = "semver_le", + Type = "custom_attribute", + }; + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.0-beta.2.2" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.0-beta.2.3" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.7.0-beta.2.2+1.2.3" } }.ToUserContext(), + Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", "3.6.1-beta.2.3+1.2" } }.ToUserContext(), + Logger) ?? false); } - [Test] - public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValueBeta() - { - var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3.7.0-beta.2.3", Match = "semver_le", Type = "custom_attribute" }; - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.2" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.3" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.2+1.2.3" } }.ToUserContext(), Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.6.1-beta.2.3+1.2" } }.ToUserContext(), Logger) ?? false); - } #endregion // SemVerLEMatcher Tests #region SemVer Invalid Scenarios @@ -647,16 +1264,26 @@ public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToCondi [Test] public void TestInvalidSemVersions() { - var invalidValues = new string[] {"-", ".", "..", "+", "+test", " ", "2 .3. 0", "2.", - ".2.2", "3.7.2.2", "3.x", ",", "+build-prerelese"}; - var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute" }; - foreach(var invalidValue in invalidValues) { - Assert.IsNull(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", invalidValue } }.ToUserContext(), Logger), $"returned for {invalidValue}"); + var invalidValues = new string[] + { + "-", ".", "..", "+", "+test", " ", "2 .3. 0", "2.", + ".2.2", "3.7.2.2", "3.x", ",", "+build-prerelese", + }; + var semverLECondition = new BaseCondition + { + Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute", + }; + foreach (var invalidValue in invalidValues) + { + Assert.IsNull( + semverLECondition.Evaluate(null, + new UserAttributes { { "semversion_le", invalidValue } }.ToUserContext(), + Logger), $"returned for {invalidValue}"); } } #endregion - + #region Qualified Tests [Test] @@ -670,13 +1297,17 @@ public void QualifiedConditionWithNonStringValueShouldFail() }; var result = condition.Evaluate(null, null, Logger); - + Assert.That(result, Is.Null); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" has a qualified match but invalid value."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.WARN, + $@"Audience condition ""{condition + }"" has a qualified match but invalid value."), Times.Once); } - - + + private const string QUALIFIED_VALUE = "part-of-the-rebellion"; + private readonly List _qualifiedSegments = new List() { QUALIFIED_VALUE, @@ -693,7 +1324,7 @@ public void QualifiedConditionWithMatchingValueShouldBeTrue() }; var result = condition.Evaluate(null, _qualifiedSegments.ToUserContext(), Logger); - + Assert.True(result.HasValue && result.Value); LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Never); } @@ -709,11 +1340,11 @@ public void QualifiedConditionWithNonMatchingValueShouldBeFalse() }; var result = condition.Evaluate(null, _qualifiedSegments.ToUserContext(), Logger); - + Assert.True(result.HasValue && result.Value == false); - LoggerMock.Verify(l => l.Log( It.IsAny(), It.IsAny()), Times.Never); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Never); } - + #endregion } } diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs index af311b8ea..ee77568c2 100644 --- a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs +++ b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs @@ -41,11 +41,17 @@ public class ConditionsTest public void Initialize() { TrueConditionMock = new Mock(); - TrueConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns(true); + TrueConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), + It.IsAny(), It.IsAny())). + Returns(true); FalseConditionMock = new Mock(); - FalseConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); + FalseConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), + It.IsAny(), It.IsAny())). + Returns(false); NullConditionMock = new Mock(); - NullConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns((bool?)null); + NullConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), + It.IsAny(), It.IsAny())). + Returns((bool?)null); TrueCondition = TrueConditionMock.Object; FalseCondition = FalseConditionMock.Object; @@ -61,7 +67,7 @@ public void Initialize() [Test] public void TestAndEvaluatorReturnsTrueWhenAllOperandsEvaluateToTrue() { - ICondition[] conditions = new ICondition[] { TrueCondition, TrueCondition }; + var conditions = new ICondition[] { TrueCondition, TrueCondition }; var andCondition = new AndCondition { Conditions = conditions }; Assert.That(andCondition.Evaluate(null, null, Logger), Is.True); @@ -70,19 +76,21 @@ public void TestAndEvaluatorReturnsTrueWhenAllOperandsEvaluateToTrue() [Test] public void TestAndEvaluatorReturnsFalseWhenAnyOperandEvaluatesToFalse() { - ICondition[] conditions = new ICondition[] { FalseCondition, TrueCondition }; + var conditions = new ICondition[] { FalseCondition, TrueCondition }; var andCondition = new AndCondition { Conditions = conditions }; Assert.That(andCondition.Evaluate(null, null, Logger), Is.False); // Should not be called due to short circuiting. - TrueConditionMock.Verify(condition => condition.Evaluate(It.IsAny(), It.IsAny(), Logger), Times.Never); + TrueConditionMock.Verify( + condition => condition.Evaluate(It.IsAny(), + It.IsAny(), Logger), Times.Never); } [Test] public void TestAndEvaluatorReturnsNullWhenAllOperandsEvaluateToNull() { - ICondition[] conditions = new ICondition[] { NullCondition, NullCondition }; + var conditions = new ICondition[] { NullCondition, NullCondition }; var andCondition = new AndCondition { Conditions = conditions }; Assert.That(andCondition.Evaluate(null, null, Logger), Is.Null); @@ -91,7 +99,7 @@ public void TestAndEvaluatorReturnsNullWhenAllOperandsEvaluateToNull() [Test] public void TestAndEvaluatorReturnsNullWhenOperandsEvaluateToTrueAndNull() { - ICondition[] conditions = new ICondition[] { TrueCondition, NullCondition, TrueCondition }; + var conditions = new ICondition[] { TrueCondition, NullCondition, TrueCondition }; var andCondition = new AndCondition { Conditions = conditions }; Assert.That(andCondition.Evaluate(null, null, Logger), Is.Null); @@ -100,7 +108,7 @@ public void TestAndEvaluatorReturnsNullWhenOperandsEvaluateToTrueAndNull() [Test] public void TestAndEvaluatorReturnsFalseWhenOperandsEvaluateToFalseAndNull() { - ICondition[] conditions = new ICondition[] { NullCondition, FalseCondition, NullCondition }; + var conditions = new ICondition[] { NullCondition, FalseCondition, NullCondition }; var andCondition = new AndCondition { Conditions = conditions }; Assert.That(andCondition.Evaluate(null, null, Logger), Is.False); @@ -109,7 +117,7 @@ public void TestAndEvaluatorReturnsFalseWhenOperandsEvaluateToFalseAndNull() [Test] public void TestAndEvaluatorReturnsFalseWhenOperandsEvaluateToTrueFalseAndNull() { - ICondition[] conditions = new ICondition[] { TrueCondition, FalseCondition, NullCondition }; + var conditions = new ICondition[] { TrueCondition, FalseCondition, NullCondition }; var andCondition = new AndCondition { Conditions = conditions }; Assert.That(andCondition.Evaluate(null, null, Logger), Is.False); @@ -122,7 +130,7 @@ public void TestAndEvaluatorReturnsFalseWhenOperandsEvaluateToTrueFalseAndNull() [Test] public void TestOrEvaluatorReturnsTrueWhenAnyOperandEvaluatesToTrue() { - ICondition[] conditions = new ICondition[] { TrueCondition, FalseCondition, NullCondition }; + var conditions = new ICondition[] { TrueCondition, FalseCondition, NullCondition }; var orCondition = new OrCondition { Conditions = conditions }; Assert.That(orCondition.Evaluate(null, null, Logger), Is.True); @@ -131,7 +139,7 @@ public void TestOrEvaluatorReturnsTrueWhenAnyOperandEvaluatesToTrue() [Test] public void TestOrEvaluatorReturnsFalseWhenAllOperandsEvaluatesToFalse() { - ICondition[] conditions = new ICondition[] { FalseCondition, FalseCondition }; + var conditions = new ICondition[] { FalseCondition, FalseCondition }; var orCondition = new OrCondition { Conditions = conditions }; Assert.That(orCondition.Evaluate(null, null, Logger), Is.False); @@ -140,7 +148,7 @@ public void TestOrEvaluatorReturnsFalseWhenAllOperandsEvaluatesToFalse() [Test] public void TestOrEvaluatorReturnsNullWhenOperandsEvaluateToFalseAndNull() { - ICondition[] conditions = new ICondition[] { FalseCondition, NullCondition, FalseCondition }; + var conditions = new ICondition[] { FalseCondition, NullCondition, FalseCondition }; var orCondition = new OrCondition { Conditions = conditions }; Assert.That(orCondition.Evaluate(null, null, Logger), Is.Null); @@ -149,7 +157,7 @@ public void TestOrEvaluatorReturnsNullWhenOperandsEvaluateToFalseAndNull() [Test] public void TestOrEvaluatorReturnsTrueWhenOperandsEvaluateToTrueAndNull() { - ICondition[] conditions = new ICondition[] { TrueCondition, NullCondition, TrueCondition }; + var conditions = new ICondition[] { TrueCondition, NullCondition, TrueCondition }; var orCondition = new OrCondition { Conditions = conditions }; Assert.That(orCondition.Evaluate(null, null, Logger), Is.True); @@ -158,7 +166,7 @@ public void TestOrEvaluatorReturnsTrueWhenOperandsEvaluateToTrueAndNull() [Test] public void TestOrEvaluatorReturnsTrueWhenOperandsEvaluateToFalseTrueAndNull() { - ICondition[] conditions = new ICondition[] { FalseCondition, NullCondition, TrueCondition }; + var conditions = new ICondition[] { FalseCondition, NullCondition, TrueCondition }; var orCondition = new OrCondition { Conditions = conditions }; Assert.That(orCondition.Evaluate(null, null, Logger), Is.True); diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs b/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs index 42eccab23..d32d73079 100644 --- a/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs +++ b/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs @@ -128,7 +128,8 @@ public void ShouldNotFindOdpSegmentsFromConditions() { Conditions = new[] { - _customExactMatchCondition, _customExactMatchCondition, _customExactMatchCondition, + _customExactMatchCondition, _customExactMatchCondition, + _customExactMatchCondition, }, }; @@ -151,7 +152,8 @@ public void ShouldFindAndDedupeNestedOdpSegments() { Conditions = new ICondition[] { - _secondThirdPartyOdpQualifiedMatchCondition, _firstThirdPartyOdpQualifiedMatchCondition, + _secondThirdPartyOdpQualifiedMatchCondition, + _firstThirdPartyOdpQualifiedMatchCondition, }, }; var orConditions = new OrCondition diff --git a/OptimizelySDK.Tests/BucketerTest.cs b/OptimizelySDK.Tests/BucketerTest.cs index 46ca90c5f..bdd18c556 100644 --- a/OptimizelySDK.Tests/BucketerTest.cs +++ b/OptimizelySDK.Tests/BucketerTest.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Moq; using NUnit.Framework; using OptimizelySDK.Bucketing; @@ -30,8 +31,13 @@ public class BucketerTest private ProjectConfig Config; private DecisionReasons DecisionReasonsObj; private const string TestUserId = "testUserId"; - public string TestBucketingIdControl { get; } = "testBucketingIdControl!"; // generates bucketing number 3741 - public string TestBucketingIdVariation { get; } = "123456789'"; // generates bucketing number 4567 + + public string TestBucketingIdControl { get; } = + "testBucketingIdControl!"; // generates bucketing number 3741 + + public string TestBucketingIdVariation { get; } = + "123456789'"; // generates bucketing number 4567 + public string TestBucketingIdGroupExp2Var2 { get; } = "123456789"; // group_exp_2_var_2 public string TestUserIdBucketsToVariation { get; } = "bucketsToVariation!"; public string TestUserIdBucketsToNoGroup { get; } = "testUserId"; @@ -44,15 +50,13 @@ private class BucketerTestItem public string UserId { get; set; } public string ExperimentId { get; set; } public int ExpectedBucketValue { get; set; } - - public string BucketingId - { - get { return UserId + ExperimentId; } - } + + public string BucketingId => UserId + ExperimentId; public override string ToString() { - return string.Format("UserId: {0}, ExperimentId: {1}, BucketId: {2}, ExpectedBucketValue {3}", + return string.Format( + "UserId: {0}, ExperimentId: {1}, BucketId: {2}, ExpectedBucketValue {3}", UserId, ExperimentId, BucketingId, ExpectedBucketValue); } } @@ -62,7 +66,8 @@ public void Initialize() { LoggerMock = new Mock(); DecisionReasonsObj = new DecisionReasons(); - Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, new ErrorHandler.NoOpErrorHandler()); + Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + new ErrorHandler.NoOpErrorHandler()); } [TestFixtureSetUp] @@ -78,17 +83,37 @@ public void TestGenerateBucketValue() var bucketer = new Bucketer(LoggerMock.Object); foreach (var item in new[] + { + new BucketerTestItem + { + UserId = "ppid1", ExperimentId = "1886780721", + ExpectedBucketValue = 5254, + }, + new BucketerTestItem + { + UserId = "ppid2", ExperimentId = "1886780721", + ExpectedBucketValue = 4299, + }, + new BucketerTestItem + { + UserId = "ppid2", ExperimentId = "1886780722", + ExpectedBucketValue = 2434, + }, + new BucketerTestItem + { + UserId = "ppid3", ExperimentId = "1886780721", + ExpectedBucketValue = 5439, + }, + new BucketerTestItem + { + UserId = + "a very very very very very very very very very very very very very very very long ppd string", + ExperimentId = "1886780721", ExpectedBucketValue = 6128, + }, + }) { - new BucketerTestItem { UserId = "ppid1", ExperimentId = "1886780721", ExpectedBucketValue = 5254 }, - new BucketerTestItem { UserId = "ppid2", ExperimentId = "1886780721", ExpectedBucketValue = 4299 }, - new BucketerTestItem { UserId = "ppid2", ExperimentId = "1886780722", ExpectedBucketValue = 2434 }, - new BucketerTestItem { UserId = "ppid3", ExperimentId = "1886780721", ExpectedBucketValue = 5439 }, - new BucketerTestItem { UserId = "a very very very very very very very very very very very very very very very long ppd string", - ExperimentId = "1886780721", ExpectedBucketValue = 6128 }, - }) - { - int result = bucketer.GenerateBucketValue(item.BucketingId); - Assert.AreEqual(item.ExpectedBucketValue, result, + var result = bucketer.GenerateBucketValue(item.BucketingId); + Assert.AreEqual(item.ExpectedBucketValue, result, string.Format("Unexpected Bucket Value: [{0}] for [{1}]", result, item)); } } @@ -96,90 +121,126 @@ public void TestGenerateBucketValue() [Test] public void TestBucketValidExperimentNotInGroup() { - TestBucketer bucketer = new TestBucketer(LoggerMock.Object); + var bucketer = new TestBucketer(LoggerMock.Object); bucketer.SetBucketValues(new[] { 3000, 7000, 9000 }); - var variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("test_experiment"), TestBucketingIdControl, TestUserId); + var variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("test_experiment"), + TestBucketingIdControl, TestUserId); // control Assert.AreEqual(new Variation { Id = "7722370027", Key = "control" }, variation.ResultObject); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in variation [control] of experiment [test_experiment].")); - Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], "User [testUserId] is in variation [control] of experiment [test_experiment]."); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), + Times.Exactly(2)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is in variation [control] of experiment [test_experiment].")); + Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], + "User [testUserId] is in variation [control] of experiment [test_experiment]."); // variation - variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("test_experiment"), TestBucketingIdControl, TestUserId); + variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("test_experiment"), + TestBucketingIdControl, TestUserId); Assert.AreEqual(new Variation { Id = "7721010009", Key = "variation" }, variation.ResultObject); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(4)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [7000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in variation [variation] of experiment [test_experiment].")); - Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], "User [testUserId] is in variation [variation] of experiment [test_experiment]."); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), + Times.Exactly(4)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [7000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is in variation [variation] of experiment [test_experiment].")); + Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], + "User [testUserId] is in variation [variation] of experiment [test_experiment]."); // no variation - variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("test_experiment"), TestBucketingIdControl, TestUserId); + variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("test_experiment"), + TestBucketingIdControl, TestUserId); Assert.AreEqual(new Variation { }, - variation.ResultObject); + variation.ResultObject); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(6)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), + Times.Exactly(6)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [9000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in no variation.")); Assert.AreEqual(variation.DecisionReasons.ToReport(true).Count, 1); - Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], "User [testUserId] is in no variation."); + Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], + "User [testUserId] is in no variation."); } [Test] public void TestBucketValidExperimentInGroup() { - TestBucketer bucketer = new TestBucketer(LoggerMock.Object); - + var bucketer = new TestBucketer(LoggerMock.Object); + // group_experiment_1 (20% experiment) // variation 1 bucketer.SetBucketValues(new[] { 1000, 4000 }); - var variation = bucketer.Bucket(Config, Config.GetExperimentFromKey("group_experiment_1"), TestBucketingIdControl, TestUserId); + var variation = bucketer.Bucket(Config, + Config.GetExperimentFromKey("group_experiment_1"), TestBucketingIdControl, + TestUserId); Assert.AreEqual(new Variation { Id = "7722260071", Key = "group_exp_1_var_1" }, variation.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [1000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in experiment [group_experiment_1] of group [7722400015].")); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [4000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in variation [group_exp_1_var_1] of experiment [group_experiment_1].")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [1000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is in experiment [group_experiment_1] of group [7722400015].")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [4000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is in variation [group_exp_1_var_1] of experiment [group_experiment_1].")); Assert.AreEqual(variation.DecisionReasons.ToReport(true).Count, 2); - Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], "User [testUserId] is in experiment [group_experiment_1] of group [7722400015]."); - Assert.AreEqual(variation.DecisionReasons.ToReport(true)[1], "User [testUserId] is in variation [group_exp_1_var_1] of experiment [group_experiment_1]."); + Assert.AreEqual(variation.DecisionReasons.ToReport(true)[0], + "User [testUserId] is in experiment [group_experiment_1] of group [7722400015]."); + Assert.AreEqual(variation.DecisionReasons.ToReport(true)[1], + "User [testUserId] is in variation [group_exp_1_var_1] of experiment [group_experiment_1]."); // variation 2 bucketer.SetBucketValues(new[] { 1500, 7000 }); - var variation1 = bucketer.Bucket(Config, Config.GetExperimentFromKey("group_experiment_1"), TestBucketingIdControl, TestUserId); + var variation1 = bucketer.Bucket(Config, + Config.GetExperimentFromKey("group_experiment_1"), TestBucketingIdControl, + TestUserId); Assert.AreEqual(new Variation { Id = "7722360022", Key = "group_exp_1_var_2" }, variation1.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [1500] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in experiment [group_experiment_1] of group [7722400015].")); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [7000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is in variation [group_exp_1_var_1] of experiment [group_experiment_1].")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [1500] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is in experiment [group_experiment_1] of group [7722400015].")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [7000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is in variation [group_exp_1_var_1] of experiment [group_experiment_1].")); Assert.AreEqual(variation1.DecisionReasons.ToReport(true).Count, 2); - Assert.AreEqual(variation1.DecisionReasons.ToReport(true)[0], "User [testUserId] is in experiment [group_experiment_1] of group [7722400015]."); - Assert.AreEqual(variation1.DecisionReasons.ToReport(true)[1], "User [testUserId] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."); + Assert.AreEqual(variation1.DecisionReasons.ToReport(true)[0], + "User [testUserId] is in experiment [group_experiment_1] of group [7722400015]."); + Assert.AreEqual(variation1.DecisionReasons.ToReport(true)[1], + "User [testUserId] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."); // User not in experiment bucketer.SetBucketValues(new[] { 5000, 7000 }); - var variation2 = bucketer.Bucket(Config, Config.GetExperimentFromKey("group_experiment_1"), TestBucketingIdControl, TestUserId); + var variation2 = bucketer.Bucket(Config, + Config.GetExperimentFromKey("group_experiment_1"), TestBucketingIdControl, + TestUserId); Assert.AreEqual(new Variation { }, variation2.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [5000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUserId] is not in experiment [group_experiment_1] of group [7722400015].")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + "Assigned bucket [5000] to user [testUserId] with bucketing ID [testBucketingIdControl!].")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "User [testUserId] is not in experiment [group_experiment_1] of group [7722400015].")); Assert.AreEqual(variation2.DecisionReasons.ToReport(true).Count, 1); - Assert.AreEqual(variation2.DecisionReasons.ToReport(true)[0], "User [testUserId] is not in experiment [group_experiment_1] of group [7722400015]."); + Assert.AreEqual(variation2.DecisionReasons.ToReport(true)[0], + "User [testUserId] is not in experiment [group_experiment_1] of group [7722400015]."); } [Test] public void TestBucketInvalidExperiment() { var bucketer = new Bucketer(LoggerMock.Object); - + Assert.AreEqual(new Variation { }, - bucketer.Bucket(Config, new Experiment(), TestBucketingIdControl, TestUserId).ResultObject); + bucketer.Bucket(Config, new Experiment(), TestBucketingIdControl, TestUserId). + ResultObject); LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Never); } @@ -193,15 +254,18 @@ public void TestBucketWithBucketingId() var expectedVariation2 = new Variation { Id = "7721010009", Key = "variation" }; // make sure that the bucketing ID is used for the variation bucketing and not the user ID - var variationResult = bucketer.Bucket(Config, experiment, TestBucketingIdControl, TestUserIdBucketsToVariation); + var variationResult = bucketer.Bucket(Config, experiment, TestBucketingIdControl, + TestUserIdBucketsToVariation); Assert.AreEqual(expectedVariation, variationResult.ResultObject); Assert.AreEqual(variationResult.DecisionReasons.ToReport().Count, 0); - variationResult = bucketer.Bucket(Config, experiment, TestBucketingIdControl, TestUserIdBucketsToVariation); + variationResult = bucketer.Bucket(Config, experiment, TestBucketingIdControl, + TestUserIdBucketsToVariation); Assert.AreEqual(expectedVariation, variationResult.ResultObject); Assert.AreEqual(variationResult.DecisionReasons.ToReport(true).Count, 1); - Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[0], "User [bucketsToVariation!] is in variation [control] of experiment [test_experiment]."); + Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[0], + "User [bucketsToVariation!] is in variation [control] of experiment [test_experiment]."); } // Test for invalid experiment keys, null variation should be returned @@ -210,8 +274,10 @@ public void TestBucketVariationInvalidExperimentsWithBucketingId() { var bucketer = new Bucketer(LoggerMock.Object); var expectedVariation = new Variation(); - var variationResult = bucketer.Bucket(Config, Config.GetExperimentFromKey("invalid_experiment"), TestBucketingIdVariation, TestUserId); - Assert.AreEqual(expectedVariation, + var variationResult = bucketer.Bucket(Config, + Config.GetExperimentFromKey("invalid_experiment"), TestBucketingIdVariation, + TestUserId); + Assert.AreEqual(expectedVariation, variationResult.ResultObject); Assert.AreEqual(variationResult.DecisionReasons.ToReport().Count, 0); } @@ -222,18 +288,22 @@ public void TestBucketVariationGroupedExperimentsWithBucketingId() { var bucketer = new Bucketer(LoggerMock.Object); var expectedVariation = new Variation(); - var expectedGroupVariation = new Variation{ Id = "7725250007", Key = "group_exp_2_var_2" }; - var variationResult = bucketer.Bucket(Config, Config.GetExperimentFromKey("group_experiment_2"), + var expectedGroupVariation = new Variation + { Id = "7725250007", Key = "group_exp_2_var_2" }; + var variationResult = bucketer.Bucket(Config, + Config.GetExperimentFromKey("group_experiment_2"), TestBucketingIdGroupExp2Var2, TestUserIdBucketsToNoGroup); Assert.AreEqual(expectedGroupVariation, variationResult.ResultObject); Assert.AreEqual(variationResult.DecisionReasons.ToReport().Count, 0); - bucketer.Bucket(Config, Config.GetExperimentFromKey("group_experiment_2"), + bucketer.Bucket(Config, Config.GetExperimentFromKey("group_experiment_2"), TestBucketingIdGroupExp2Var2, TestUserIdBucketsToNoGroup); var report = variationResult.DecisionReasons.ToReport(true); Assert.AreEqual(report.Count, 2); - Assert.AreEqual(report[0], "User [testUserId] is in experiment [group_experiment_2] of group [7722400015]."); - Assert.AreEqual(report[1], "User [testUserId] is in variation [group_exp_2_var_2] of experiment [group_experiment_2]."); + Assert.AreEqual(report[0], + "User [testUserId] is in experiment [group_experiment_2] of group [7722400015]."); + Assert.AreEqual(report[1], + "User [testUserId] is in variation [group_exp_2_var_2] of experiment [group_experiment_2]."); } // Make sure that user gets bucketed into the rollout rule. @@ -244,16 +314,19 @@ public void TestBucketRolloutRule() var rollout = Config.GetRolloutFromId("166660"); var rolloutRule = rollout.Experiments[1]; var expectedVariation = Config.GetVariationFromId(rolloutRule.Key, "177773"); - - var variationResult = bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId); + + var variationResult = + bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId); Assert.True(TestData.CompareObjects(expectedVariation, variationResult.ResultObject)); Assert.AreEqual(variationResult.DecisionReasons.ToReport().Count, 0); - var variationsResult = bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId); + var variationsResult = + bucketer.Bucket(Config, rolloutRule, "testBucketingId", TestUserId); Assert.True(TestData.CompareObjects(expectedVariation, variationsResult.ResultObject)); Assert.AreEqual(variationsResult.DecisionReasons.ToReport(true).Count, 1); - Assert.AreEqual(variationsResult.DecisionReasons.ToReport(true)[0], "User [testUserId] is in variation [177773] of experiment [177772]."); + Assert.AreEqual(variationsResult.DecisionReasons.ToReport(true)[0], + "User [testUserId] is in variation [177773] of experiment [177772]."); } } } diff --git a/OptimizelySDK.Tests/ClientConfigHandlerTest.cs b/OptimizelySDK.Tests/ClientConfigHandlerTest.cs index ce8a2e578..4eaffb0f6 100644 --- a/OptimizelySDK.Tests/ClientConfigHandlerTest.cs +++ b/OptimizelySDK.Tests/ClientConfigHandlerTest.cs @@ -24,11 +24,12 @@ namespace OptimizelySDK.Tests [TestFixture] public class ClientConfigHandlerTest { - [Test] public void TestHTTPAppConfigSection() { - var configSection = ConfigurationManager.GetSection("optlySDKConfigSection") as OptimizelySDKConfigSection; + var configSection = + ConfigurationManager.GetSection("optlySDKConfigSection") as + OptimizelySDKConfigSection; var httpSetting = configSection.HttpProjectConfig; Assert.IsNotNull(httpSetting); Assert.IsTrue(httpSetting.AutoUpdate); @@ -44,7 +45,9 @@ public void TestHTTPAppConfigSection() [Test] public void TestBatchEventAppConfigSection() { - var configSection = ConfigurationManager.GetSection("optlySDKConfigSection") as OptimizelySDKConfigSection; + var configSection = + ConfigurationManager.GetSection("optlySDKConfigSection") as + OptimizelySDKConfigSection; var batchSetting = configSection.BatchEventProcessor; Assert.IsNotNull(batchSetting); Assert.AreEqual(batchSetting.BatchSize, 10); @@ -52,7 +55,6 @@ public void TestBatchEventAppConfigSection() Assert.AreEqual(batchSetting.TimeoutInterval, 10000); Assert.IsTrue(batchSetting.DefaultStart); } - } } #endif diff --git a/OptimizelySDK.Tests/ConfigTest/FallbackProjectConfigManagerTest.cs b/OptimizelySDK.Tests/ConfigTest/FallbackProjectConfigManagerTest.cs index 5610ad092..c40b6551f 100644 --- a/OptimizelySDK.Tests/ConfigTest/FallbackProjectConfigManagerTest.cs +++ b/OptimizelySDK.Tests/ConfigTest/FallbackProjectConfigManagerTest.cs @@ -27,7 +27,8 @@ public class AtomicProjectConfigManagerTest [Test] public void TestStaticProjectConfigManagerReturnsCorrectProjectConfig() { - var expectedConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, null, null); + var expectedConfig = + DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, null, null); ConfigManager = new FallbackProjectConfigManager(expectedConfig); Assert.True(TestData.CompareObjects(expectedConfig, ConfigManager.GetConfig())); diff --git a/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs b/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs index 9a3bbb87b..d7efa0d96 100644 --- a/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs +++ b/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs @@ -34,7 +34,9 @@ public class HttpProjectConfigManagerTest { private Mock LoggerMock; private Mock HttpClientMock; - private Mock NotificationCallbackMock = new Mock(); + + private Mock NotificationCallbackMock = + new Mock(); [SetUp] public void Setup() @@ -45,28 +47,28 @@ public void Setup() TestHttpProjectConfigManagerUtil.SetClientFieldValue(HttpClientMock.Object); LoggerMock.Setup(l => l.Log(It.IsAny(), It.IsAny())); NotificationCallbackMock.Setup(nc => nc.TestConfigUpdateCallback()); - } [Test] public void TestHttpConfigManagerRetreiveProjectConfigByURL() { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(); // This method waits until SendAsync is not triggered. // Time is given here to avoid hanging-up in any worst case. t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); httpManager.Dispose(); } @@ -76,15 +78,17 @@ public void TestHttpConfigManagerWithInvalidStatus() { var t = MockSendAsync(statusCode: HttpStatusCode.Forbidden); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(); - LoggerMock.Verify(_ => _.Log(LogLevel.ERROR, $"Error fetching datafile \"{HttpStatusCode.Forbidden}\""), Times.AtLeastOnce); + LoggerMock.Verify( + _ => _.Log(LogLevel.ERROR, + $"Error fetching datafile \"{HttpStatusCode.Forbidden}\""), Times.AtLeastOnce); httpManager.Dispose(); } @@ -93,7 +97,8 @@ public void TestHttpConfigManagerWithInvalidStatus() public void TestHttpClientHandler() { var httpConfigHandler = HttpProjectConfigManager.HttpClient.GetHttpClientHandler(); - Assert.IsTrue(httpConfigHandler.AutomaticDecompression == (System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip)); + Assert.IsTrue(httpConfigHandler.AutomaticDecompression == + (DecompressionMethods.Deflate | DecompressionMethods.GZip)); } [Test] @@ -101,21 +106,22 @@ public void TestHttpConfigManagerRetreiveProjectConfigGivenEmptyFormatUseDefault { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithFormat("") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithFormat(""). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(); // This "Wait" notifies When SendAsync is triggered. // Time is given here to avoid hanging-up in any worst case. t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); httpManager.Dispose(); } @@ -125,18 +131,19 @@ public void TestHttpConfigManagerRetreiveProjectConfigBySDKKey() { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(); t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); Assert.IsNotNull(httpManager.GetConfig()); httpManager.Dispose(); @@ -147,24 +154,25 @@ public void TestHttpConfigManagerRetreiveProjectConfigByFormat() { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("10192104166") - .WithFormat("https://cdn.optimizely.com/json/{0}.json") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder().WithSdkKey("10192104166"). + WithFormat("https://cdn.optimizely.com/json/{0}.json"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(true); t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/json/10192104166.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://cdn.optimizely.com/json/10192104166.json" ))); - + Assert.IsNotNull(httpManager.GetConfig()); - LoggerMock.Verify(_ => _.Log(LogLevel.DEBUG, "Making datafile request to url \"https://cdn.optimizely.com/json/10192104166.json\"")); + LoggerMock.Verify(_ => _.Log(LogLevel.DEBUG, + "Making datafile request to url \"https://cdn.optimizely.com/json/10192104166.json\"")); httpManager.Dispose(); } @@ -173,24 +181,25 @@ public void TestHttpProjectConfigManagerDoesntRaiseExceptionForDefaultErrorHandl { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("10192104166") - .WithFormat("https://cdn.optimizely.com/json/{0}.json") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder().WithSdkKey("10192104166"). + WithFormat("https://cdn.optimizely.com/json/{0}.json"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(true); t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/json/10192104166.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://cdn.optimizely.com/json/10192104166.json" ))); var datafileConfig = httpManager.GetConfig(); Assert.IsNotNull(datafileConfig); Assert.IsNull(datafileConfig.GetExperimentFromKey("project_config_not_valid").Key); - LoggerMock.Verify(_ => _.Log(LogLevel.DEBUG, "Making datafile request to url \"https://cdn.optimizely.com/json/10192104166.json\"")); + LoggerMock.Verify(_ => _.Log(LogLevel.DEBUG, + "Making datafile request to url \"https://cdn.optimizely.com/json/10192104166.json\"")); httpManager.Dispose(); } @@ -198,17 +207,18 @@ public void TestHttpProjectConfigManagerDoesntRaiseExceptionForDefaultErrorHandl public void TestOnReadyPromiseResolvedImmediatelyWhenDatafileIsProvided() { // Revision - 42 - var t = MockSendAsync(TestData.SimpleABExperimentsDatafile, TimeSpan.FromMilliseconds(100)); + var t = MockSendAsync(TestData.SimpleABExperimentsDatafile, + TimeSpan.FromMilliseconds(100)); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() // Revision - 15 - .WithSdkKey("10192104166") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithStartByDefault() - .Build(); + .WithSdkKey("10192104166"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithStartByDefault(). + Build(); // OnReady waits until is resolved, need to add time in case of deadlock. httpManager.OnReady().Wait(10000); @@ -227,15 +237,16 @@ public void TestOnReadyPromiseResolvedImmediatelyWhenDatafileIsProvided() public void TestOnReadyPromiseWaitsForProjectConfigRetrievalWhenDatafileIsNotProvided() { // Revision - 42 - var t = MockSendAsync(TestData.SimpleABExperimentsDatafile, TimeSpan.FromMilliseconds(1000)); - - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromSeconds(2)) - .WithBlockingTimeoutPeriod(TimeSpan.FromSeconds(1)) - .WithStartByDefault(true) - .Build(); + var t = MockSendAsync(TestData.SimpleABExperimentsDatafile, + TimeSpan.FromMilliseconds(1000)); + + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromSeconds(2)). + WithBlockingTimeoutPeriod(TimeSpan.FromSeconds(1)). + WithStartByDefault(true). + Build(); t.Wait(); // OnReady waits until is resolved, need to add time in case of deadlock. @@ -249,14 +260,15 @@ public void TestHttpConfigManagerDoesNotWaitForTheConfigWhenDeferIsTrue() { var t = MockSendAsync(TestData.Datafile, TimeSpan.FromMilliseconds(150)); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromSeconds(2)) + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromSeconds(2)) // negligible timeout - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)) - .WithStartByDefault() - .Build(false); + . + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)). + WithStartByDefault(). + Build(false); // When blocking timeout is 0 and defer is false and getconfig immediately called // should return null @@ -265,7 +277,7 @@ public void TestHttpConfigManagerDoesNotWaitForTheConfigWhenDeferIsTrue() t.Wait(); // in case deadlock, it will release after 3sec. httpManager.OnReady().Wait(8000); - + HttpClientMock.Verify(_ => _.SendAsync(It.IsAny())); Assert.NotNull(httpManager.GetConfig()); @@ -273,20 +285,22 @@ public void TestHttpConfigManagerDoesNotWaitForTheConfigWhenDeferIsTrue() } #region Notification + [Test] public void TestHttpConfigManagerSendConfigUpdateNotificationWhenProjectConfigGetsUpdated() - { + { var t = MockSendAsync(TestData.Datafile); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(1000)) - .WithStartByDefault(false) - .Build(true); - - httpManager.NotifyOnProjectConfigUpdate += NotificationCallbackMock.Object.TestConfigUpdateCallback; + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(1000)). + WithStartByDefault(false). + Build(true); + + httpManager.NotifyOnProjectConfigUpdate += + NotificationCallbackMock.Object.TestConfigUpdateCallback; httpManager.Start(); Assert.NotNull(httpManager.GetConfig()); @@ -297,130 +311,148 @@ public void TestHttpConfigManagerSendConfigUpdateNotificationWhenProjectConfigGe [Test] public void TestHttpConfigManagerDoesNotSendConfigUpdateNotificationWhenDatafileIsProvided() - { + { var t = MockSendAsync(TestData.Datafile, TimeSpan.FromMilliseconds(100)); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + Build(); - httpManager.NotifyOnProjectConfigUpdate += NotificationCallbackMock.Object.TestConfigUpdateCallback; + httpManager.NotifyOnProjectConfigUpdate += + NotificationCallbackMock.Object.TestConfigUpdateCallback; NotificationCallbackMock.Verify(nc => nc.TestConfigUpdateCallback(), Times.Never); - Assert.NotNull(httpManager.GetConfig()); Assert.NotNull(httpManager.GetConfig()); + Assert.NotNull(httpManager.GetConfig()); + Assert.NotNull(httpManager.GetConfig()); httpManager.Dispose(); } [Test] public void TestDefaultBlockingTimeoutWhileProvidingZero() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(0)) - .WithStartByDefault(true) - .Build(true); - - var fieldInfo = httpManager.GetType().GetField("BlockingTimeout", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(0)). + WithStartByDefault(true). + Build(true); + + var fieldInfo = httpManager.GetType(). + GetField("BlockingTimeout", + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.NonPublic); var expectedBlockingTimeout = (TimeSpan)fieldInfo.GetValue(httpManager); Assert.AreNotEqual(expectedBlockingTimeout.TotalSeconds, TimeSpan.Zero.TotalSeconds); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"Blocking timeout is not valid, using default blocking timeout {TimeSpan.FromSeconds(15).TotalMilliseconds}ms")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"Blocking timeout is not valid, using default blocking timeout {TimeSpan.FromSeconds(15).TotalMilliseconds}ms")); httpManager.Dispose(); } [Test] public void TestDefaultPeriodWhileProvidingZero() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(0)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(1000)) - .WithStartByDefault(true) - .Build(true); - - var fieldInfo = typeof(PollingProjectConfigManager).GetField("PollingInterval", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(0)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(1000)). + WithStartByDefault(true). + Build(true); + + var fieldInfo = typeof(PollingProjectConfigManager).GetField("PollingInterval", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var expectedPollingInterval = (TimeSpan)fieldInfo.GetValue(httpManager); Assert.AreNotEqual(expectedPollingInterval.TotalSeconds, TimeSpan.Zero.TotalSeconds); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"Polling interval is not valid for periodic calls, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"Polling interval is not valid for periodic calls, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); httpManager.Dispose(); } [Test] public void TestDefaultPeriodWhileProvidingNegative() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(-1)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(1000)) - .WithStartByDefault(true) - .Build(true); - - var fieldInfo = typeof(PollingProjectConfigManager).GetField("PollingInterval", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(-1)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(1000)). + WithStartByDefault(true). + Build(true); + + var fieldInfo = typeof(PollingProjectConfigManager).GetField("PollingInterval", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var expectedPollingInterval = (TimeSpan)fieldInfo.GetValue(httpManager); Assert.AreNotEqual(expectedPollingInterval.TotalSeconds, TimeSpan.Zero.TotalSeconds); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"Polling interval is not valid for periodic calls, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"Polling interval is not valid for periodic calls, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); httpManager.Dispose(); } [Test] public void TestDefaultPeriodWhileNotProvidingValue() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .Build(true); - - var fieldInfo = typeof(PollingProjectConfigManager).GetField("PollingInterval", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + Build(true); + + var fieldInfo = typeof(PollingProjectConfigManager).GetField("PollingInterval", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var expectedPollingInterval = (TimeSpan)fieldInfo.GetValue(httpManager); Assert.AreNotEqual(expectedPollingInterval.TotalSeconds, TimeSpan.Zero.TotalSeconds); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"No polling interval provided, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"No polling interval provided, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); httpManager.Dispose(); } [Test] public void TestDefaultBlockingTimeoutWhileNotProvidingValue() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .Build(true); - - var fieldInfo = httpManager.GetType().GetField("BlockingTimeout", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + Build(true); + + var fieldInfo = httpManager.GetType(). + GetField("BlockingTimeout", + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.NonPublic); var expectedBlockingTimeout = (TimeSpan)fieldInfo.GetValue(httpManager); Assert.AreNotEqual(expectedBlockingTimeout.TotalSeconds, TimeSpan.Zero.TotalSeconds); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"No Blocking timeout provided, using default blocking timeout {TimeSpan.FromSeconds(15).TotalMilliseconds}ms")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"No Blocking timeout provided, using default blocking timeout {TimeSpan.FromSeconds(15).TotalMilliseconds}ms")); httpManager.Dispose(); } [Test] public void TestDefaultValuesWhenNotProvided() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .Build(true); - - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"No polling interval provided, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"No Blocking timeout provided, using default blocking timeout {TimeSpan.FromSeconds(15).TotalMilliseconds}ms")); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + Build(true); + + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"No polling interval provided, using default period {TimeSpan.FromMinutes(5).TotalMilliseconds}ms")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"No Blocking timeout provided, using default blocking timeout {TimeSpan.FromSeconds(15).TotalMilliseconds}ms")); httpManager.Dispose(); } @@ -429,19 +461,20 @@ public void TestAuthUrlWhenTokenProvided() { var t = MockSendAsync(); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithAccessToken("datafile1") - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)) - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithAccessToken("datafile1"). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)). + Build(true); // it's to wait if SendAsync is not triggered. t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://config.optimizely.com/datafiles/auth/QBw9gFM8oTn7ogY9ANCC1z.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://config.optimizely.com/datafiles/auth/QBw9gFM8oTn7ogY9ANCC1z.json" ))); httpManager.Dispose(); } @@ -451,17 +484,18 @@ public void TestDefaultUrlWhenTokenNotProvided() { var t = MockSendAsync(); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)) - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)). + Build(true); // it's to wait if SendAsync is not triggered. t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); httpManager.Dispose(); } @@ -471,19 +505,19 @@ public void TestAuthenticationHeaderWhenTokenProvided() { var t = MockSendAsync(); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)) - .WithAccessToken("datafile1") - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)). + WithAccessToken("datafile1"). + Build(true); // it's to wait if SendAsync is not triggered. t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.Headers.Authorization.ToString() == "Bearer datafile1" + It.Is(requestMessage => + requestMessage.Headers.Authorization.ToString() == "Bearer datafile1" ))); httpManager.Dispose(); } @@ -492,27 +526,31 @@ public void TestAuthenticationHeaderWhenTokenProvided() public void TestFormatUrlHigherPriorityThanDefaultUrl() { var t = MockSendAsync(); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithFormat("http://customformat/{0}.json") - .WithAccessToken("datafile1") - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)) - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithFormat("http://customformat/{0}.json"). + WithAccessToken("datafile1"). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(50)). + Build(true); // it's to wait if SendAsync is not triggered. t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => - requestMessage.RequestUri.ToString() == "http://customformat/QBw9gFM8oTn7ogY9ANCC1z.json" + It.Is(requestMessage => + requestMessage.RequestUri.ToString() == + "http://customformat/QBw9gFM8oTn7ogY9ANCC1z.json" ))); httpManager.Dispose(); - } - public Task MockSendAsync(string datafile = null, TimeSpan? delay = null, HttpStatusCode statusCode = HttpStatusCode.OK) + public Task MockSendAsync(string datafile = null, TimeSpan? delay = null, + HttpStatusCode statusCode = HttpStatusCode.OK + ) { - return TestHttpProjectConfigManagerUtil.MockSendAsync(HttpClientMock, datafile, delay, statusCode); + return TestHttpProjectConfigManagerUtil.MockSendAsync(HttpClientMock, datafile, delay, + statusCode); } + #endregion } } diff --git a/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs b/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs index 96603d6b8..41e1769aa 100644 --- a/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs +++ b/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs @@ -85,7 +85,7 @@ public void TestImmediatelyCalledScheduledRequestIfPreviousRequestDelayedInRespo var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(1500), true, LoggerMock.Object, new int[] { - 1200, 500, 500 + 1200, 500, 500, }); configManager.Start(); @@ -110,7 +110,7 @@ public void TestTimedoutIfTakingMorethanBlockingTimeout() var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { - 1300, 500, 500 + 1300, 500, 500, }); configManager.Start(); @@ -128,9 +128,9 @@ public void TestTimedoutOnlyIfSchedulerStarted() var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { - 1300, 500, 500 + 1300, 500, 500, }); - Stopwatch sw = new Stopwatch(); + var sw = new Stopwatch(); sw.Start(); var config = configManager.GetConfig(); sw.Stop(); @@ -146,9 +146,9 @@ public void TestDontTimedoutIfSchedulerNotStarted() var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { - 1300, 500, 500 + 1300, 500, 500, }); - Stopwatch sw = new Stopwatch(); + var sw = new Stopwatch(); sw.Start(); var config = configManager.GetConfig(); sw.Stop(); @@ -167,14 +167,14 @@ public void TestReturnDatafileImmediatelyOnceGetValidDatafileRemotely() { PollingTime = 500, ChangeVersion = false, - ConfigDatafile = projConfig + ConfigDatafile = projConfig, }, new TestPollingData { PollingTime = 500, ChangeVersion = false, - ConfigDatafile = projConfig - } + ConfigDatafile = projConfig, + }, }; var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), @@ -204,20 +204,20 @@ public void TestWaitUntilValidDatfileIsNotGiven() { PollingTime = 50, ChangeVersion = false, - ConfigDatafile = null + ConfigDatafile = null, }, new TestPollingData { PollingTime = 50, ChangeVersion = false, - ConfigDatafile = null + ConfigDatafile = null, }, new TestPollingData { PollingTime = 50, ChangeVersion = false, - ConfigDatafile = projConfig - } + ConfigDatafile = projConfig, + }, }; @@ -241,20 +241,20 @@ public void TestWaitUntilValidDatafileIsNotGivenOrTimedout() { PollingTime = 50, ChangeVersion = false, - ConfigDatafile = null + ConfigDatafile = null, }, new TestPollingData { PollingTime = 50, ChangeVersion = false, - ConfigDatafile = null + ConfigDatafile = null, }, new TestPollingData { PollingTime = 50, ChangeVersion = false, - ConfigDatafile = null - } + ConfigDatafile = null, + }, }; var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(1000), diff --git a/OptimizelySDK.Tests/ConfigTest/ProjectConfigProps.cs b/OptimizelySDK.Tests/ConfigTest/ProjectConfigProps.cs index 2b5e9ff5f..e26414c49 100644 --- a/OptimizelySDK.Tests/ConfigTest/ProjectConfigProps.cs +++ b/OptimizelySDK.Tests/ConfigTest/ProjectConfigProps.cs @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; using OptimizelySDK.Config; using OptimizelySDK.Tests.Utils; @@ -34,27 +35,37 @@ public class ProjectConfigManagerProps public ProjectConfigManagerProps(HttpProjectConfigManager projectConfigManager) { - LastModified = Reflection.GetFieldValue(projectConfigManager, "LastModifiedSince"); - Url = Reflection.GetFieldValue(projectConfigManager, "Url"); - DatafileAccessToken = Reflection.GetFieldValue(projectConfigManager, "DatafileAccessToken"); + LastModified = + Reflection.GetFieldValue(projectConfigManager, + "LastModifiedSince"); + Url = Reflection.GetFieldValue(projectConfigManager, + "Url"); + DatafileAccessToken = + Reflection.GetFieldValue(projectConfigManager, + "DatafileAccessToken"); - AutoUpdate = Reflection.GetPropertyValue(projectConfigManager, "AutoUpdate"); - PollingInterval = Reflection.GetFieldValue(projectConfigManager, "PollingInterval"); - BlockingTimeout = Reflection.GetFieldValue(projectConfigManager, "BlockingTimeout"); + AutoUpdate = + Reflection.GetPropertyValue(projectConfigManager, + "AutoUpdate"); + PollingInterval = + Reflection.GetFieldValue(projectConfigManager, + "PollingInterval"); + BlockingTimeout = + Reflection.GetFieldValue(projectConfigManager, + "BlockingTimeout"); } /// /// To create default instance of expected values. /// - public ProjectConfigManagerProps() - { - - } + public ProjectConfigManagerProps() { } public override bool Equals(object obj) { if (obj == null) + { return false; + } var projectConfigManager = obj as ProjectConfigManagerProps; if (projectConfigManager == null) diff --git a/OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs b/OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs index b35621ae0..6239579ae 100644 --- a/OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs +++ b/OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs @@ -24,51 +24,70 @@ public class TestPollingData { public int PollingTime { get; set; } public ProjectConfig ConfigDatafile { get; set; } - public ProjectConfig ConfigVersioned { - get { + + public ProjectConfig ConfigVersioned + { + get + { if (ConfigDatafile == null) + { return null; + } + if (ChangeVersion) + { ConfigDatafile.Version = DateTime.Now.Ticks.ToString(); + } return ConfigDatafile; } } + public bool ChangeVersion { get; set; } } public class TestPollingProjectConfigManager : PollingProjectConfigManager { - TestPollingData[] PollingData; + private TestPollingData[] PollingData; public int Counter = 0; - public TestPollingProjectConfigManager(TimeSpan period, TimeSpan blockingTimeout, bool autoUpdate, ILogger logger, int[] pollingSequence) + public TestPollingProjectConfigManager(TimeSpan period, TimeSpan blockingTimeout, + bool autoUpdate, ILogger logger, int[] pollingSequence + ) : base(period, blockingTimeout, autoUpdate, logger, null) { - if (pollingSequence != null) { - System.Collections.Generic.List pollingData = new System.Collections.Generic.List(); - foreach (var pollingTime in pollingSequence) { + if (pollingSequence != null) + { + var pollingData = new System.Collections.Generic.List(); + foreach (var pollingTime in pollingSequence) + { pollingData.Add(new TestPollingData { PollingTime = pollingTime }); } - this.PollingData = pollingData.ToArray(); + + PollingData = pollingData.ToArray(); } } - public TestPollingProjectConfigManager(TimeSpan period, TimeSpan blockingTimeout, bool autoUpdate, ILogger logger, TestPollingData[] pollingData, bool startByDefault = true) + public TestPollingProjectConfigManager(TimeSpan period, TimeSpan blockingTimeout, + bool autoUpdate, ILogger logger, TestPollingData[] pollingData, + bool startByDefault = true + ) : base(period, blockingTimeout, autoUpdate, logger, null) { - if (pollingData != null) { - this.PollingData = pollingData; + if (pollingData != null) + { + PollingData = pollingData; } } protected override ProjectConfig Poll() { - TimeSpan waitingTime = TimeSpan.FromMilliseconds(500); + var waitingTime = TimeSpan.FromMilliseconds(500); ProjectConfig response = null; - if (PollingData.Length > Counter) { + if (PollingData.Length > Counter) + { waitingTime = TimeSpan.FromMilliseconds(PollingData[Counter].PollingTime); // Will automatically change version if ChangeVersion is true. response = PollingData[Counter].ConfigVersioned; diff --git a/OptimizelySDK.Tests/DecisionServiceTest.cs b/OptimizelySDK.Tests/DecisionServiceTest.cs index f15d41ea9..f34cd0386 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -57,29 +57,38 @@ public void SetUp() ErrorHandlerMock = new Mock(); UserProfileServiceMock = new Mock(); BucketerMock = new Mock(LoggerMock.Object); - ProjectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, ErrorHandlerMock.Object); + ProjectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + ErrorHandlerMock.Object); WhitelistedExperiment = ProjectConfig.ExperimentIdMap["224"]; WhitelistedVariation = WhitelistedExperiment.VariationKeyToVariationMap["vtag5"]; - DecisionService = new DecisionService(new Bucketer(LoggerMock.Object), ErrorHandlerMock.Object, null, LoggerMock.Object); - DecisionServiceMock = new Mock(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object) { CallBase = true }; - DecisionReasons = new OptimizelySDK.OptimizelyDecisions.DecisionReasons(); + DecisionService = new DecisionService(new Bucketer(LoggerMock.Object), + ErrorHandlerMock.Object, null, LoggerMock.Object); + DecisionServiceMock = new Mock(BucketerMock.Object, + ErrorHandlerMock.Object, null, LoggerMock.Object) { CallBase = true }; + DecisionReasons = new DecisionReasons(); - VariationWithKeyControl = ProjectConfig.GetVariationFromKey("test_experiment", "control"); - VariationWithKeyVariation = ProjectConfig.GetVariationFromKey("test_experiment", "variation"); + VariationWithKeyControl = + ProjectConfig.GetVariationFromKey("test_experiment", "control"); + VariationWithKeyVariation = + ProjectConfig.GetVariationFromKey("test_experiment", "variation"); - Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, ErrorHandlerMock.Object); + Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + ErrorHandlerMock.Object); } [Test] public void TestFindValidatedForcedDecisionReturnsCorrectDecisionWithNullVariation() { - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); var decisionReasons = new DecisionReasons(); - decisionReasons.AddInfo("{0}", "Invalid variation is mapped to flag: flagKey and rule: rule forced decision map."); + decisionReasons.AddInfo("{0}", + "Invalid variation is mapped to flag: flagKey and rule: rule forced decision map."); var expectedResult = Result.NullResult(decisionReasons); var user = optlyObject.CreateUserContext(GenericUserId); @@ -93,115 +102,151 @@ public void TestFindValidatedForcedDecisionReturnsCorrectDecisionWithNullVariati [Test] public void TestGetVariationForcedVariationPrecedesAudienceEval() { - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - Experiment experiment = ProjectConfig.Experiments[8]; - Variation expectedVariation = experiment.Variations[0]; - - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); + var experiment = ProjectConfig.Experiments[8]; + var expectedVariation = experiment.Variations[0]; + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, + LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); // user excluded without audiences and whitelisting - Assert.IsNull(decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig).ResultObject); + Assert.IsNull(decisionService. + GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig). + ResultObject); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(WhitelistedUserId); - var actualVariation = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + var actualVariation = decisionService.GetVariation(experiment, + OptimizelyUserContextMock.Object, ProjectConfig); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" is forced in variation \"vtag5\".", WhitelistedUserId)), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + string.Format("User \"{0}\" is forced in variation \"vtag5\".", + WhitelistedUserId)), Times.Once); // no attributes provided for a experiment that has an audience Assertions.AreEqual(expectedVariation, actualVariation.ResultObject); - BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + BucketerMock.Verify( + _ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never); } [Test] public void TestGetVariationLogsErrorWhenUserProfileMapItsNull() { - Experiment experiment = ProjectConfig.Experiments[8]; - Variation variation = experiment.Variations[0]; + var experiment = ProjectConfig.Experiments[8]; + var variation = experiment.Variations[0]; - Decision decision = new Decision(variation.Id); + var decision = new Decision(variation.Id); Dictionary userProfile = null; UserProfileServiceMock.Setup(up => up.Lookup(WhitelistedUserId)).Returns(userProfile); - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + UserProfileServiceMock.Object, LoggerMock.Object); var options = new OptimizelyDecideOption[] { OptimizelyDecideOption.INCLUDE_REASONS }; OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); - var variationResult = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig, options); - Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[0], "We were unable to get a user profile map from the UserProfileService."); - Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[1], "Audiences for experiment \"etag3\" collectively evaluated to FALSE"); - Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[2], "User \"genericUserId\" does not meet conditions to be in experiment \"etag3\"."); + var variationResult = decisionService.GetVariation(experiment, + OptimizelyUserContextMock.Object, ProjectConfig, options); + Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[0], + "We were unable to get a user profile map from the UserProfileService."); + Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[1], + "Audiences for experiment \"etag3\" collectively evaluated to FALSE"); + Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[2], + "User \"genericUserId\" does not meet conditions to be in experiment \"etag3\"."); } [Test] public void TestGetVariationEvaluatesUserProfileBeforeAudienceTargeting() { - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, + LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); - Experiment experiment = ProjectConfig.Experiments[8]; - Variation variation = experiment.Variations[0]; + var experiment = ProjectConfig.Experiments[8]; + var variation = experiment.Variations[0]; - Decision decision = new Decision(variation.Id); - UserProfile userProfile = new UserProfile(UserProfileId, new Dictionary + var decision = new Decision(variation.Id); + var userProfile = new UserProfile(UserProfileId, new Dictionary { - { experiment.Id, decision } + { experiment.Id, decision }, }); - UserProfileServiceMock.Setup(up => up.Lookup(WhitelistedUserId)).Returns(userProfile.ToMap()); + UserProfileServiceMock.Setup(up => up.Lookup(WhitelistedUserId)). + Returns(userProfile.ToMap()); - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + UserProfileServiceMock.Object, LoggerMock.Object); - decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, + ProjectConfig); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" does not meet conditions to be in experiment \"{1}\".", + LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format( + "User \"{0}\" does not meet conditions to be in experiment \"{1}\".", GenericUserId, experiment.Key)), Times.Once); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); // ensure that a user with a saved user profile, sees the same variation regardless of audience evaluation - decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, + ProjectConfig); - BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + BucketerMock.Verify( + _ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never); } [Test] public void TestGetForcedVariationReturnsForcedVariation() { - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - var expectedVariation = decisionService.GetWhitelistedVariation(WhitelistedExperiment, WhitelistedUserId).ResultObject; + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); + var expectedVariation = decisionService. + GetWhitelistedVariation(WhitelistedExperiment, WhitelistedUserId). + ResultObject; Assertions.AreEqual(WhitelistedVariation, expectedVariation); - Assert.IsTrue(TestData.CompareObjects(WhitelistedVariation, decisionService.GetWhitelistedVariation(WhitelistedExperiment, WhitelistedUserId).ResultObject)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" is forced in variation \"{1}\".", + Assert.IsTrue(TestData.CompareObjects(WhitelistedVariation, + decisionService.GetWhitelistedVariation(WhitelistedExperiment, WhitelistedUserId). + ResultObject)); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format( + "User \"{0}\" is forced in variation \"{1}\".", WhitelistedUserId, WhitelistedVariation.Key)), Times.Exactly(2)); - BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + BucketerMock.Verify( + _ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never); } [Test] public void TestGetForcedVariationWithInvalidVariation() { - string userId = "testUser1"; - string invalidVariationKey = "invalidVarKey"; + var userId = "testUser1"; + var invalidVariationKey = "invalidVarKey"; - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); var variations = new Variation[] { - new Variation {Id = "1", Key = "var1" } + new Variation { Id = "1", Key = "var1" }, }; var trafficAllocation = new TrafficAllocation[] { - new TrafficAllocation {EntityId = "1", EndOfRange = 1000 } + new TrafficAllocation { EntityId = "1", EndOfRange = 1000 }, }; var userIdToVariationKeyMap = new Dictionary { - {userId, invalidVariationKey } + { userId, invalidVariationKey }, }; var experiment = new Experiment @@ -213,54 +258,68 @@ public void TestGetForcedVariationWithInvalidVariation() AudienceIds = new string[0], Variations = variations, TrafficAllocation = trafficAllocation, - ForcedVariations = userIdToVariationKeyMap + ForcedVariations = userIdToVariationKeyMap, }; Assert.IsNull(decisionService.GetWhitelistedVariation(experiment, userId).ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - string.Format("Variation \"{0}\" is not in the datafile. Not activating user \"{1}\".", invalidVariationKey, userId)), + string.Format( + "Variation \"{0}\" is not in the datafile. Not activating user \"{1}\".", + invalidVariationKey, userId)), Times.Once); - BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + BucketerMock.Verify( + _ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never); } [Test] public void TestGetForcedVariationReturnsNullWhenUserIsNotWhitelisted() { - Bucketer bucketer = new Bucketer(LoggerMock.Object); - DecisionService decisionService = new DecisionService(bucketer, ErrorHandlerMock.Object, null, LoggerMock.Object); + var bucketer = new Bucketer(LoggerMock.Object); + var decisionService = new DecisionService(bucketer, ErrorHandlerMock.Object, null, + LoggerMock.Object); - Assert.IsNull(decisionService.GetWhitelistedVariation(WhitelistedExperiment, GenericUserId).ResultObject); + Assert.IsNull(decisionService. + GetWhitelistedVariation(WhitelistedExperiment, GenericUserId). + ResultObject); } [Test] public void TestBucketReturnsVariationStoredInUserProfile() { - Experiment experiment = ProjectConfig.Experiments[6]; - Variation variation = experiment.Variations[0]; - Decision decision = new Decision(variation.Id); + var experiment = ProjectConfig.Experiments[6]; + var variation = experiment.Variations[0]; + var decision = new Decision(variation.Id); - UserProfile userProfile = new UserProfile(UserProfileId, new Dictionary + var userProfile = new UserProfile(UserProfileId, new Dictionary { - { experiment.Id, decision } + { experiment.Id, decision }, }); UserProfileServiceMock.Setup(_ => _.Lookup(UserProfileId)).Returns(userProfile.ToMap()); - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + UserProfileServiceMock.Object, LoggerMock.Object); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, + LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - var actualVariation = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + var actualVariation = decisionService.GetVariation(experiment, + OptimizelyUserContextMock.Object, ProjectConfig); Assertions.AreEqual(variation, actualVariation.ResultObject); Assert.AreEqual(actualVariation.DecisionReasons.ToReport(true).Count, 1); - Assert.AreEqual(actualVariation.DecisionReasons.ToReport(true)[0], "Returning previously activated variation \"vtag1\" of experiment \"etag1\" for user \"userProfileId\" from user profile."); + Assert.AreEqual(actualVariation.DecisionReasons.ToReport(true)[0], + "Returning previously activated variation \"vtag1\" of experiment \"etag1\" for user \"userProfileId\" from user profile."); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("Returning previously activated variation \"{0}\" of experiment \"{1}\" for user \"{2}\" from user profile.", + LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format( + "Returning previously activated variation \"{0}\" of experiment \"{1}\" for user \"{2}\" from user profile.", variation.Key, experiment.Key, UserProfileId))); //BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); @@ -269,135 +328,162 @@ public void TestBucketReturnsVariationStoredInUserProfile() [Test] public void TestGetStoredVariationLogsWhenLookupReturnsNull() { - Experiment experiment = ProjectConfig.Experiments[6]; + var experiment = ProjectConfig.Experiments[6]; - UserProfileService userProfileService = UserProfileServiceMock.Object; - UserProfile userProfile = new UserProfile(UserProfileId, new Dictionary()); + var userProfileService = UserProfileServiceMock.Object; + var userProfile = new UserProfile(UserProfileId, new Dictionary()); - Bucketer bucketer = new Bucketer(LoggerMock.Object); + var bucketer = new Bucketer(LoggerMock.Object); UserProfileServiceMock.Setup(_ => _.Lookup(UserProfileId)).Returns(userProfile.ToMap()); - DecisionService decisionService = new DecisionService(bucketer, - ErrorHandlerMock.Object, userProfileService, LoggerMock.Object); + var decisionService = new DecisionService(bucketer, + ErrorHandlerMock.Object, userProfileService, LoggerMock.Object); - Assert.IsNull(decisionService.GetStoredVariation(experiment, userProfile, ProjectConfig).ResultObject); + Assert.IsNull(decisionService. + GetStoredVariation(experiment, userProfile, ProjectConfig). + ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("No previously activated variation of experiment \"{0}\" for user \"{1}\" found in user profile." + LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format( + "No previously activated variation of experiment \"{0}\" for user \"{1}\" found in user profile." , experiment.Key, UserProfileId)), Times.Once); } [Test] public void TestGetStoredVariationReturnsNullWhenVariationIsNoLongerInConfig() { - Experiment experiment = ProjectConfig.Experiments[6]; - string storedVariationId = "missingVariation"; - Decision storedDecision = new Decision(storedVariationId); + var experiment = ProjectConfig.Experiments[6]; + var storedVariationId = "missingVariation"; + var storedDecision = new Decision(storedVariationId); var storedDecisions = new Dictionary(); storedDecisions[experiment.Id] = storedDecision; - UserProfile storedUserProfile = new UserProfile(UserProfileId, storedDecisions); + var storedUserProfile = new UserProfile(UserProfileId, storedDecisions); - Bucketer bucketer = new Bucketer(LoggerMock.Object); + var bucketer = new Bucketer(LoggerMock.Object); - UserProfileServiceMock.Setup(up => up.Lookup(UserProfileId)).Returns(storedUserProfile.ToMap()); + UserProfileServiceMock.Setup(up => up.Lookup(UserProfileId)). + Returns(storedUserProfile.ToMap()); - DecisionService decisionService = new DecisionService(bucketer, ErrorHandlerMock.Object, + var decisionService = new DecisionService(bucketer, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); - Assert.IsNull(decisionService.GetStoredVariation(experiment, storedUserProfile, ProjectConfig).ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" was previously bucketed into variation with ID \"{1}\" for experiment \"{2}\", but no matching variation was found for that user. We will re-bucket the user." + Assert.IsNull(decisionService. + GetStoredVariation(experiment, storedUserProfile, ProjectConfig). + ResultObject); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format( + "User \"{0}\" was previously bucketed into variation with ID \"{1}\" for experiment \"{2}\", but no matching variation was found for that user. We will re-bucket the user." , UserProfileId, storedVariationId, experiment.Id)), Times.Once); } [Test] public void TestGetVariationSavesBucketedVariationIntoUserProfile() { - Experiment experiment = ProjectConfig.Experiments[6]; + var experiment = ProjectConfig.Experiments[6]; var variation = Result.NewResult(experiment.Variations[0], DecisionReasons); - Decision decision = new Decision(variation.ResultObject.Id); + var decision = new Decision(variation.ResultObject.Id); - UserProfile originalUserProfile = new UserProfile(UserProfileId, + var originalUserProfile = new UserProfile(UserProfileId, new Dictionary()); - UserProfileServiceMock.Setup(ups => ups.Lookup(UserProfileId)).Returns(originalUserProfile.ToMap()); + UserProfileServiceMock.Setup(ups => ups.Lookup(UserProfileId)). + Returns(originalUserProfile.ToMap()); - UserProfile expectedUserProfile = new UserProfile(UserProfileId, + var expectedUserProfile = new UserProfile(UserProfileId, new Dictionary { - {experiment.Id, decision } + { experiment.Id, decision }, }); var mockBucketer = new Mock(LoggerMock.Object); - mockBucketer.Setup(m => m.Bucket(ProjectConfig, experiment, UserProfileId, UserProfileId)).Returns(variation); + mockBucketer. + Setup(m => m.Bucket(ProjectConfig, experiment, UserProfileId, UserProfileId)). + Returns(variation); - DecisionService decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); + var decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, + UserProfileServiceMock.Object, LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - Assert.IsTrue(TestData.CompareObjects(variation.ResultObject, decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig).ResultObject)); + Assert.IsTrue(TestData.CompareObjects(variation.ResultObject, + decisionService. + GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig). + ResultObject)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("Saved variation \"{0}\" of experiment \"{1}\" for user \"{2}\".", variation.ResultObject.Id, - experiment.Id, UserProfileId)), Times.Once); - UserProfileServiceMock.Verify(_ => _.Save(It.IsAny>()), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format( + "Saved variation \"{0}\" of experiment \"{1}\" for user \"{2}\".", + variation.ResultObject.Id, + experiment.Id, UserProfileId)), Times.Once); + UserProfileServiceMock.Verify(_ => _.Save(It.IsAny>()), + Times.Once); } [Test] public void TestBucketLogsCorrectlyWhenUserProfileFailsToSave() { - Experiment experiment = ProjectConfig.Experiments[6]; - Variation variation = experiment.Variations[0]; - Decision decision = new Decision(variation.Id); - Bucketer bucketer = new Bucketer(LoggerMock.Object); + var experiment = ProjectConfig.Experiments[6]; + var variation = experiment.Variations[0]; + var decision = new Decision(variation.Id); + var bucketer = new Bucketer(LoggerMock.Object); - UserProfileServiceMock.Setup(up => up.Save(It.IsAny>())).Throws(new System.Exception()); + UserProfileServiceMock.Setup(up => up.Save(It.IsAny>())). + Throws(new System.Exception()); var experimentBucketMap = new Dictionary(); experimentBucketMap[experiment.Id] = decision; - UserProfile expectedUserProfile = new UserProfile(UserProfileId, experimentBucketMap); - UserProfile saveUserProfile = new UserProfile(UserProfileId, new Dictionary()); + var expectedUserProfile = new UserProfile(UserProfileId, experimentBucketMap); + var saveUserProfile = + new UserProfile(UserProfileId, new Dictionary()); - DecisionService decisionService = new DecisionService(bucketer, + var decisionService = new DecisionService(bucketer, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); decisionService.SaveVariation(experiment, variation, saveUserProfile); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, string.Format - ("Failed to save variation \"{0}\" of experiment \"{1}\" for user \"{2}\".", variation.Id, experiment.Id, UserProfileId)) + ("Failed to save variation \"{0}\" of experiment \"{1}\" for user \"{2}\".", + variation.Id, experiment.Id, UserProfileId)) , Times.Once); - ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); + ErrorHandlerMock.Verify( + er => er.HandleError(It.IsAny()), + Times.Once); } [Test] public void TestGetVariationSavesANewUserProfile() { - Experiment experiment = ProjectConfig.Experiments[6]; + var experiment = ProjectConfig.Experiments[6]; var variation = Result.NewResult(experiment.Variations[0], DecisionReasons); - Decision decision = new Decision(variation.ResultObject.Id); + var decision = new Decision(variation.ResultObject.Id); - UserProfile expectedUserProfile = new UserProfile(UserProfileId, new Dictionary - { - { experiment.Id, decision } - }); + var expectedUserProfile = new UserProfile(UserProfileId, + new Dictionary + { + { experiment.Id, decision }, + }); var mockBucketer = new Mock(LoggerMock.Object); - mockBucketer.Setup(m => m.Bucket(ProjectConfig, experiment, UserProfileId, UserProfileId)).Returns(variation); + mockBucketer. + Setup(m => m.Bucket(ProjectConfig, experiment, UserProfileId, UserProfileId)). + Returns(variation); Dictionary userProfile = null; UserProfileServiceMock.Setup(up => up.Lookup(UserProfileId)).Returns(userProfile); - DecisionService decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, + var decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - var actualVariation = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + var actualVariation = decisionService.GetVariation(experiment, + OptimizelyUserContextMock.Object, ProjectConfig); Assertions.AreEqual(variation.ResultObject, actualVariation.ResultObject); - UserProfileServiceMock.Verify(_ => _.Save(It.IsAny>()), Times.Once); + UserProfileServiceMock.Verify(_ => _.Save(It.IsAny>()), + Times.Once); } [Test] @@ -407,12 +493,13 @@ public void TestGetVariationUserWithSetForcedVariation() var pausedExperimentKey = "paused_experiment"; var userId = "test_user"; var expectedForcedVariationKey = "variation"; - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" } + { "device_type", "iPhone" }, + { "location", "San Francisco" }, }; optlyObject.Activate(experimentKey, userId, userAttributes); @@ -422,9 +509,12 @@ public void TestGetVariationUserWithSetForcedVariation() Assertions.AreEqual(VariationWithKeyControl, actualVariation); // test valid experiment - Assert.IsTrue(optlyObject.SetForcedVariation(experimentKey, userId, expectedForcedVariationKey), string.Format(@"Set variation to ""{0}"" failed.", expectedForcedVariationKey)); + Assert.IsTrue( + optlyObject.SetForcedVariation(experimentKey, userId, expectedForcedVariationKey), + string.Format(@"Set variation to ""{0}"" failed.", expectedForcedVariationKey)); - var actualForcedVariation = optlyObject.GetVariation(experimentKey, userId, userAttributes); + var actualForcedVariation = + optlyObject.GetVariation(experimentKey, userId, userAttributes); Assertions.AreEqual(VariationWithKeyVariation, actualForcedVariation); @@ -435,8 +525,12 @@ public void TestGetVariationUserWithSetForcedVariation() Assertions.AreEqual(VariationWithKeyControl, actualVariation); // check that a paused experiment returns null - Assert.IsTrue(optlyObject.SetForcedVariation(pausedExperimentKey, userId, expectedForcedVariationKey), string.Format(@"Set variation to ""{0}"" failed.", expectedForcedVariationKey)); - actualForcedVariation = optlyObject.GetVariation(pausedExperimentKey, userId, userAttributes); + Assert.IsTrue( + optlyObject.SetForcedVariation(pausedExperimentKey, userId, + expectedForcedVariationKey), + string.Format(@"Set variation to ""{0}"" failed.", expectedForcedVariationKey)); + actualForcedVariation = + optlyObject.GetVariation(pausedExperimentKey, userId, userAttributes); Assert.IsNull(actualForcedVariation); } @@ -448,57 +542,65 @@ public void TestGetVariationWithBucketingId() var userId = "test_user"; var testUserIdWhitelisted = "user1"; var experimentKey = "test_experiment"; - var testBucketingIdControl = "testBucketingIdControl!"; // generates bucketing number 3741 + var testBucketingIdControl = + "testBucketingIdControl!"; // generates bucketing number 3741 var testBucketingIdVariation = "123456789"; // generates bucketing number 4567 var variationKeyControl = "control"; var testUserAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"company", "Optimizely" }, - {"location", "San Francisco" } + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { "location", "San Francisco" }, }; var userAttributesWithBucketingId = new UserAttributes { - {"device_type", "iPhone"}, - {"company", "Optimizely"}, - {"location", "San Francisco"}, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation} + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { "location", "San Francisco" }, + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation }, }; var userAttributesWithInvalidBucketingId = new UserAttributes { - {"device_type", "iPhone"}, - {"company", "Optimizely"}, - {"location", "San Francisco"}, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, 1.59} + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { "location", "San Francisco" }, + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, 1.59 }, }; var invalidUserAttributesWithBucketingId = new UserAttributes { - {"company", "Optimizely"}, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl} + { "company", "Optimizely" }, + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl }, }; - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); // confirm normal bucketing occurs before setting the bucketing ID - var actualVariation = optlyObject.GetVariation(experimentKey, userId, testUserAttributes); + var actualVariation = + optlyObject.GetVariation(experimentKey, userId, testUserAttributes); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation)); // confirm valid bucketing with bucketing ID set in attributes - actualVariation = optlyObject.GetVariation(experimentKey, userId, userAttributesWithBucketingId); + actualVariation = + optlyObject.GetVariation(experimentKey, userId, userAttributesWithBucketingId); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualVariation)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"BucketingId is valid: \"{testBucketingIdVariation}\"")); + LoggerMock.Verify(l => + l.Log(LogLevel.DEBUG, $"BucketingId is valid: \"{testBucketingIdVariation}\"")); // check audience with invalid bucketing Id - actualVariation = optlyObject.GetVariation(experimentKey, userId, userAttributesWithInvalidBucketingId); + actualVariation = optlyObject.GetVariation(experimentKey, userId, + userAttributesWithInvalidBucketingId); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation)); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, "BucketingID attribute is not a string. Defaulted to userId")); + LoggerMock.Verify(l => l.Log(LogLevel.WARN, + "BucketingID attribute is not a string. Defaulted to userId")); // check invalid audience with bucketing ID - actualVariation = optlyObject.GetVariation(experimentKey, userId, invalidUserAttributesWithBucketingId); + actualVariation = optlyObject.GetVariation(experimentKey, userId, + invalidUserAttributesWithBucketingId); Assert.Null(actualVariation); // check null audience with bucketing Id @@ -506,30 +608,41 @@ public void TestGetVariationWithBucketingId() Assert.Null(actualVariation); // test that an experiment that's not running returns a null variation - actualVariation = optlyObject.GetVariation(pausedExperimentKey, userId, userAttributesWithBucketingId); + actualVariation = optlyObject.GetVariation(pausedExperimentKey, userId, + userAttributesWithBucketingId); Assert.Null(actualVariation); // check forced variation - Assert.IsTrue(optlyObject.SetForcedVariation(experimentKey, userId, variationKeyControl), string.Format("Set variation to \"{0}\" failed.", variationKeyControl)); - actualVariation = optlyObject.GetVariation(experimentKey, userId, userAttributesWithBucketingId); + Assert.IsTrue( + optlyObject.SetForcedVariation(experimentKey, userId, variationKeyControl), + string.Format("Set variation to \"{0}\" failed.", variationKeyControl)); + actualVariation = + optlyObject.GetVariation(experimentKey, userId, userAttributesWithBucketingId); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation)); // check whitelisted variation - actualVariation = optlyObject.GetVariation(experimentKey, testUserIdWhitelisted, userAttributesWithBucketingId); + actualVariation = optlyObject.GetVariation(experimentKey, testUserIdWhitelisted, + userAttributesWithBucketingId); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation)); var bucketerMock = new Mock(LoggerMock.Object); var decision = new Decision("7722370027"); - UserProfile storedUserProfile = new UserProfile(userId, new Dictionary + var storedUserProfile = new UserProfile(userId, new Dictionary { - { "7716830082", decision } + { "7716830082", decision }, }); - UserProfileServiceMock.Setup(up => up.Lookup(userId)).Returns(storedUserProfile.ToMap()); - DecisionService decisionService = new DecisionService(bucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); + UserProfileServiceMock.Setup(up => up.Lookup(userId)). + Returns(storedUserProfile.ToMap()); + var decisionService = new DecisionService(bucketerMock.Object, ErrorHandlerMock.Object, + UserProfileServiceMock.Object, LoggerMock.Object); - actualVariation = optlyObject.GetVariation(experimentKey, userId, userAttributesWithBucketingId); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation), string.Format("Variation \"{0}\" does not match expected user profile variation \"{1}\".", actualVariation?.Key, variationKeyControl)); + actualVariation = + optlyObject.GetVariation(experimentKey, userId, userAttributesWithBucketingId); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation), + string.Format( + "Variation \"{0}\" does not match expected user profile variation \"{1}\".", + actualVariation?.Key, variationKeyControl)); } #region GetVariationForFeatureExperiment Tests @@ -541,11 +654,14 @@ public void TestGetVariationForFeatureExperimentGivenNullExperimentIds() var featureFlag = ProjectConfig.GetFeatureFlagFromKey("empty_feature"); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); - var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes() { }, ProjectConfig, new OptimizelyDecideOption[] { }); + var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, + OptimizelyUserContextMock.Object, new UserAttributes() { }, ProjectConfig, + new OptimizelyDecideOption[] { }); Assert.IsNull(decision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The feature flag \"{featureFlag.Key}\" is not used in any experiments.")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $"The feature flag \"{featureFlag.Key}\" is not used in any experiments.")); } // Should return null and log when the experiment is not in the datafile @@ -558,32 +674,42 @@ public void TestGetVariationForFeatureExperimentGivenExperimentNotInDataFile() Id = booleanFeature.Id, Key = booleanFeature.Key, RolloutId = booleanFeature.RolloutId, - ExperimentIds = new List { "29039203" } + ExperimentIds = new List { "29039203" }, }; OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); - var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes() { }, ProjectConfig, new OptimizelyDecideOption[] { }); + var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, + OptimizelyUserContextMock.Object, new UserAttributes() { }, ProjectConfig, + new OptimizelyDecideOption[] { }); Assert.IsNull(decision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Experiment ID \"29039203\" is not in datafile.")); + LoggerMock.Verify(l => + l.Log(LogLevel.ERROR, "Experiment ID \"29039203\" is not in datafile.")); } // Should return null and log when the user is not bucketed into the feature flag's experiments [Test] public void TestGetVariationForFeatureExperimentGivenNonMutexGroupAndUserNotBucketed() { - var multiVariateExp = ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"); + var multiVariateExp = + ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); - DecisionServiceMock.Setup(ds => ds.GetVariation(multiVariateExp, OptimizelyUserContextMock.Object, ProjectConfig, null)).Returns(null); + DecisionServiceMock. + Setup(ds => ds.GetVariation(multiVariateExp, OptimizelyUserContextMock.Object, + ProjectConfig, null)). + Returns(null); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("multi_variate_feature"); - var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes(), ProjectConfig, new OptimizelyDecideOption[] { }); + var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, + OptimizelyUserContextMock.Object, new UserAttributes(), ProjectConfig, + new OptimizelyDecideOption[] { }); Assert.IsNull(decision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is not bucketed into any of the experiments on the feature \"multi_variate_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The user \"user1\" is not bucketed into any of the experiments on the feature \"multi_variate_feature\".")); } // Should return the variation when the user is bucketed into a variation for the experiment on the feature flag @@ -591,25 +717,36 @@ public void TestGetVariationForFeatureExperimentGivenNonMutexGroupAndUserNotBuck public void TestGetVariationForFeatureExperimentGivenNonMutexGroupAndUserIsBucketed() { var experiment = ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"); - var variation = Result.NewResult(ProjectConfig.GetVariationFromId("test_experiment_multivariate", "122231"), DecisionReasons); - var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST); + var variation = Result.NewResult( + ProjectConfig.GetVariationFromId("test_experiment_multivariate", "122231"), + DecisionReasons); + var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST); var userAttributes = new UserAttributes(); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); - DecisionServiceMock.Setup(ds => ds.GetVariation(ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"), - OptimizelyUserContextMock.Object, ProjectConfig, It.IsAny())).Returns(variation); + DecisionServiceMock.Setup(ds => ds.GetVariation( + ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"), + OptimizelyUserContextMock.Object, ProjectConfig, + It.IsAny())). + Returns(variation); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("multi_variate_feature"); - var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); + var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, + OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, + new OptimizelyDecideOption[] { }); Assert.IsTrue(TestData.CompareObjects(expectedDecision, decision.ResultObject)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is bucketed into experiment \"test_experiment_multivariate\" of feature \"multi_variate_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The user \"user1\" is bucketed into experiment \"test_experiment_multivariate\" of feature \"multi_variate_feature\".")); } // Should return the variation the user is bucketed into when the user is bucketed into one of the experiments @@ -617,23 +754,34 @@ public void TestGetVariationForFeatureExperimentGivenNonMutexGroupAndUserIsBucke public void TestGetVariationForFeatureExperimentGivenMutexGroupAndUserIsBucketed() { var mutexExperiment = ProjectConfig.GetExperimentFromKey("group_experiment_1"); - var variation = Result.NewResult(mutexExperiment.Variations[0], DecisionReasons); + var variation = + Result.NewResult(mutexExperiment.Variations[0], DecisionReasons); var userAttributes = new UserAttributes(); - var expectedDecision = new FeatureDecision(mutexExperiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST); + var expectedDecision = new FeatureDecision(mutexExperiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); - DecisionServiceMock.Setup(ds => ds.GetVariation(ProjectConfig.GetExperimentFromKey("group_experiment_1"), OptimizelyUserContextMock.Object, ProjectConfig)).Returns(variation); + DecisionServiceMock. + Setup(ds => + ds.GetVariation(ProjectConfig.GetExperimentFromKey("group_experiment_1"), + OptimizelyUserContextMock.Object, ProjectConfig)). + Returns(variation); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment( + featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, + new OptimizelyDecideOption[] { }); Assertions.AreEqual(expectedDecision, actualDecision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is bucketed into experiment \"group_experiment_1\" of feature \"boolean_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The user \"user1\" is bucketed into experiment \"group_experiment_1\" of feature \"boolean_feature\".")); } // Should return null and log a message when the user is not bucketed into any of the mutex experiments @@ -643,15 +791,21 @@ public void TestGetVariationForFeatureExperimentGivenMutexGroupAndUserNotBuckete var mutexExperiment = ProjectConfig.GetExperimentFromKey("group_experiment_1"); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); - DecisionServiceMock.Setup(ds => ds.GetVariation(It.IsAny(), It.IsAny(), ProjectConfig, It.IsAny())) - .Returns(Result.NullResult(null)); + DecisionServiceMock. + Setup(ds => ds.GetVariation(It.IsAny(), + It.IsAny(), ProjectConfig, + It.IsAny())). + Returns(Result.NullResult(null)); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes(), ProjectConfig, new OptimizelyDecideOption[] { }); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment( + featureFlag, OptimizelyUserContextMock.Object, new UserAttributes(), ProjectConfig, + new OptimizelyDecideOption[] { }); Assert.IsNull(actualDecision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is not bucketed into any of the experiments on the feature \"boolean_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The user \"user1\" is not bucketed into any of the experiments on the feature \"boolean_feature\".")); } #endregion GetVariationForFeatureExperiment Tests @@ -662,18 +816,24 @@ public void TestGetVariationForFeatureExperimentGivenMutexGroupAndUserNotBuckete [Test] public void TestGetVariationForFeatureRolloutWhenNoRuleInRollouts() { - var projectConfig = DatafileProjectConfig.Create(TestData.EmptyRolloutDatafile, new NoOpLogger(), new NoOpErrorHandler()); + var projectConfig = DatafileProjectConfig.Create(TestData.EmptyRolloutDatafile, + new NoOpLogger(), new NoOpErrorHandler()); Assert.IsNotNull(projectConfig); var featureFlag = projectConfig.FeatureKeyMap["empty_rollout"]; var rollout = projectConfig.GetRolloutFromId(featureFlag.RolloutId); Assert.AreEqual(rollout.Experiments.Count, 0); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "userId1", null, ErrorHandlerMock.Object, LoggerMock.Object); - var decisionService = new DecisionService(new Bucketer(new NoOpLogger()), new NoOpErrorHandler(), null, new NoOpLogger()); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "userId1", null, + ErrorHandlerMock.Object, LoggerMock.Object); + var decisionService = new DecisionService(new Bucketer(new NoOpLogger()), + new NoOpErrorHandler(), null, new NoOpLogger()); - var variation = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, projectConfig); + var variation = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + projectConfig); Assert.IsNull(variation.ResultObject); } @@ -681,7 +841,6 @@ public void TestGetVariationForFeatureRolloutWhenNoRuleInRollouts() [Test] public void TestGetVariationForFeatureRolloutWhenRolloutIsNotInDataFile() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); var invalidRolloutFeature = new FeatureFlag { @@ -689,89 +848,135 @@ public void TestGetVariationForFeatureRolloutWhenRolloutIsNotInDataFile() Id = featureFlag.Id, Key = featureFlag.Key, ExperimentIds = new List(featureFlag.ExperimentIds), - Variables = featureFlag.Variables + Variables = featureFlag.Variables, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, new OptimizelyDecideOption[] { })).Returns(null); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user1", new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + DecisionServiceMock.Setup(ds => + ds.GetVariationForFeatureExperiment(It.IsAny(), + It.IsAny(), It.IsAny(), + ProjectConfig, + new OptimizelyDecideOption[] { })). + Returns(null); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user1", + new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = + DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, + optimizelyUserContext, ProjectConfig); Assert.IsNull(actualDecision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The feature flag \"boolean_feature\" is not used in a rollout.")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The feature flag \"boolean_feature\" is not used in a rollout.")); } // Should return the variation the user is bucketed into when the user is bucketed into the targeting rule [Test] public void TestGetVariationForFeatureRolloutWhenUserIsBucketedInTheTargetingRule() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); + var featureFlag = + ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var experiment = rollout.Experiments[0]; var variation = Result.NewResult(experiment.Variations[0], DecisionReasons); - var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_ROLLOUT); - var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + var userAttributes = new UserAttributes + { + { "browser_type", "chrome" }, }; - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())).Returns(variation); - var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); - - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), + It.IsAny(), + It.IsAny())). + Returns(variation); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", + userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + + var actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); } // Should return the variation the user is bucketed into when the user is bucketed into the "Everyone Else" rule // and the user is not bucketed into the targeting rule [Test] - public void TestGetVariationForFeatureRolloutWhenUserIsNotBucketedInTheTargetingRuleButBucketedToEveryoneElseRule() + public void + TestGetVariationForFeatureRolloutWhenUserIsNotBucketedInTheTargetingRuleButBucketedToEveryoneElseRule() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); + var featureFlag = + ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var experiment = rollout.Experiments[0]; var everyoneElseRule = rollout.Experiments[rollout.Experiments.Count - 1]; - var variation = Result.NewResult(everyoneElseRule.Variations[0], DecisionReasons); - var expectedDecision = new FeatureDecision(everyoneElseRule, variation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var variation = + Result.NewResult(everyoneElseRule.Variations[0], DecisionReasons); + var expectedDecision = new FeatureDecision(everyoneElseRule, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_ROLLOUT); - var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + var userAttributes = new UserAttributes + { + { "browser_type", "chrome" }, }; - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), experiment, It.IsAny(), It.IsAny())).Returns(Result.NullResult(null)); - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), It.IsAny())).Returns(variation); - var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + BucketerMock. + Setup(bm => bm.Bucket(It.IsAny(), experiment, It.IsAny(), + It.IsAny())). + Returns(Result.NullResult(null)); + BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, + It.IsAny(), It.IsAny())). + Returns(variation); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", + userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); } // Should log and return null when the user is not bucketed into the targeting rule // as well as "Everyone Else" rule. [Test] - public void TestGetVariationForFeatureRolloutWhenUserIsNeitherBucketedInTheTargetingRuleNorToEveryoneElseRule() + public void + TestGetVariationForFeatureRolloutWhenUserIsNeitherBucketedInTheTargetingRuleNorToEveryoneElseRule() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); + var featureFlag = + ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); - var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + var userAttributes = new UserAttributes + { + { "browser_type", "chrome" }, }; - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Result.NullResult(null)); - var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + BucketerMock. + Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())). + Returns(Result.NullResult(null)); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", + userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + ProjectConfig); Assert.IsNull(actualDecision.ResultObject); } @@ -780,34 +985,51 @@ public void TestGetVariationForFeatureRolloutWhenUserIsNeitherBucketedInTheTarge [Test] public void TestGetVariationForFeatureRolloutWhenUserDoesNotQualifyForAnyTargetingRule() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); + var featureFlag = + ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var experiment0 = rollout.Experiments[0]; var experiment1 = rollout.Experiments[1]; var everyoneElseRule = rollout.Experiments[rollout.Experiments.Count - 1]; - var variation = Result.NewResult(everyoneElseRule.Variations[0], DecisionReasons); - var expectedDecision = new FeatureDecision(everyoneElseRule, variation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var variation = + Result.NewResult(everyoneElseRule.Variations[0], DecisionReasons); + var expectedDecision = new FeatureDecision(everyoneElseRule, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_ROLLOUT); //BucketerMock.CallBase = true; - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsNotIn(everyoneElseRule), It.IsAny(), It.IsAny())).Returns(Result.NullResult(null)); - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), It.IsAny())).Returns(variation); - var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); + BucketerMock. + Setup(bm => bm.Bucket(It.IsAny(), + It.IsNotIn(everyoneElseRule), It.IsAny(), + It.IsAny())). + Returns(Result.NullResult(null)); + BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, + It.IsAny(), It.IsAny())). + Returns(variation); + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); // Provide null attributes so that user does not qualify for audience. - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", null, ErrorHandlerMock.Object, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", null, + ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"User \"user_1\" does not meet the conditions for targeting rule \"1\".")); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"User \"user_1\" does not meet the conditions for targeting rule \"2\".")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"User \"user_1\" does not meet the conditions for targeting rule \"1\".")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + $"User \"user_1\" does not meet the conditions for targeting rule \"2\".")); } [Test] public void TestGetVariationForFeatureRolloutAudienceAndTrafficeAllocationCheck() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); + var featureFlag = + ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var expWithAudienceiPhoneUsers = rollout.Experiments[1]; @@ -819,48 +1041,65 @@ public void TestGetVariationForFeatureRolloutAudienceAndTrafficeAllocationCheck( var mockBucketer = new Mock(LoggerMock.Object) { CallBase = true }; mockBucketer.Setup(bm => bm.GenerateBucketValue(It.IsAny())).Returns(980); - var decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); + var decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); // Calling with audience iPhone users in San Francisco. - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes - { - { "device_type", "iPhone" }, - { "location", "San Francisco" } - }, ErrorHandlerMock.Object, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, GenericUserId, + new UserAttributes + { + { "device_type", "iPhone" }, + { "location", "San Francisco" }, + }, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + ProjectConfig); // Returned variation id should be '177773' because of audience 'iPhone users in San Francisco'. - var expectedDecision = new FeatureDecision(expWithAudienceiPhoneUsers, varWithAudienceiPhoneUsers, FeatureDecision.DECISION_SOURCE_ROLLOUT); + var expectedDecision = new FeatureDecision(expWithAudienceiPhoneUsers, + varWithAudienceiPhoneUsers, FeatureDecision.DECISION_SOURCE_ROLLOUT); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); // Calling with audience Chrome users. - var optimizelyUserContext2 = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes - { - { "browser_type", "chrome" } - }, ErrorHandlerMock.Object, LoggerMock.Object); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, ProjectConfig); + var optimizelyUserContext2 = new OptimizelyUserContext(optlyObject, GenericUserId, + new UserAttributes + { + { "browser_type", "chrome" }, + }, ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, + ProjectConfig); // Returned variation id should be '177771' because of audience 'Chrome users'. - expectedDecision = new FeatureDecision(expWithAudienceChromeUsers, varWithAudienceChromeUsers, FeatureDecision.DECISION_SOURCE_ROLLOUT); + expectedDecision = new FeatureDecision(expWithAudienceChromeUsers, + varWithAudienceChromeUsers, FeatureDecision.DECISION_SOURCE_ROLLOUT); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); // Calling with no audience. mockBucketer.Setup(bm => bm.GenerateBucketValue(It.IsAny())).Returns(8000); - var optimizelyUserContext3 = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext3, ProjectConfig); + var optimizelyUserContext3 = new OptimizelyUserContext(optlyObject, GenericUserId, + new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext3, + ProjectConfig); // Returned variation id should be of everyone else rule because of no audience. - expectedDecision = new FeatureDecision(expWithNoAudience, varWithNoAudience, FeatureDecision.DECISION_SOURCE_ROLLOUT); + expectedDecision = new FeatureDecision(expWithNoAudience, varWithNoAudience, + FeatureDecision.DECISION_SOURCE_ROLLOUT); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); // Calling with audience 'Chrome users' and traffice allocation '9500'. mockBucketer.Setup(bm => bm.GenerateBucketValue(It.IsAny())).Returns(9500); - var optimizelyUserContext4 = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes - { - { "browser_type", "chrome" } - }, ErrorHandlerMock.Object, LoggerMock.Object); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext4, ProjectConfig); + var optimizelyUserContext4 = new OptimizelyUserContext(optlyObject, GenericUserId, + new UserAttributes + { + { "browser_type", "chrome" }, + }, ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext4, + ProjectConfig); // Returned decision entity should be null because bucket value exceeds traffic allocation of everyone else rule. Assert.Null(actualDecision.ResultObject?.Variation?.Key); @@ -869,41 +1108,77 @@ public void TestGetVariationForFeatureRolloutAudienceAndTrafficeAllocationCheck( [Test] public void TestGetVariationForFeatureRolloutCheckAudienceInEveryoneElseRule() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); + var featureFlag = + ProjectConfig.GetFeatureFlagFromKey("boolean_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var everyoneElseRule = rollout.Experiments[2]; - var variation = Result.NewResult(everyoneElseRule.Variations[0], DecisionReasons); - var expectedDecision = new FeatureDecision(everyoneElseRule, variation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); - - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), WhitelistedUserId)).Returns(variation); - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), GenericUserId)).Returns(Result.NullResult(DecisionReasons)); - - var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var variation = + Result.NewResult(everyoneElseRule.Variations[0], DecisionReasons); + var expectedDecision = new FeatureDecision(everyoneElseRule, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_ROLLOUT); + + BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, + It.IsAny(), WhitelistedUserId)). + Returns(variation); + BucketerMock. + Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, + It.IsAny(), GenericUserId)). + Returns(Result.NullResult(DecisionReasons)); + + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, + null, LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); // Returned variation id should be of everyone else rule as it passes audience Id checking. - var optimizelyUserContext = new OptimizelyUserContext(optlyObject, WhitelistedUserId, null, ErrorHandlerMock.Object, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, WhitelistedUserId, + null, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, + ProjectConfig); Assert.True(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); // Returned variation id should be null. - var optimizelyUserContext2 = new OptimizelyUserContext(optlyObject, GenericUserId, null, ErrorHandlerMock.Object, LoggerMock.Object); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, ProjectConfig); + var optimizelyUserContext2 = new OptimizelyUserContext(optlyObject, GenericUserId, null, + ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, + ProjectConfig); Assert.Null(actualDecision.ResultObject); // Returned variation id should be null as it fails audience Id checking. everyoneElseRule.AudienceIds = new string[] { ProjectConfig.Audiences[0].Id }; - BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), GenericUserId)).Returns(Result.NullResult(DecisionReasons)); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, ProjectConfig) ?? Result.NullResult(DecisionReasons); + BucketerMock. + Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), + It.IsAny(), GenericUserId)). + Returns(Result.NullResult(DecisionReasons)); + actualDecision = + decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, + ProjectConfig) ?? Result.NullResult(DecisionReasons); Assert.Null(actualDecision.ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions for targeting rule \"1\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions for targeting rule \"2\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"1\"."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"2\"."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"3\"."), Times.Exactly(1)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUser1\" does not meet the conditions for targeting rule \"1\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUser1\" does not meet the conditions for targeting rule \"2\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"genericUserId\" does not meet the conditions for targeting rule \"1\"."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"genericUserId\" does not meet the conditions for targeting rule \"2\"."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"genericUserId\" does not meet the conditions for targeting rule \"3\"."), + Times.Exactly(1)); } #endregion GetVariationForFeatureRollout Tests @@ -918,61 +1193,94 @@ public void TestGetVariationForFeatureWhenTheUserIsBucketedIntoFeatureExperiment var expectedExperimentId = featureFlag.ExperimentIds[0]; var expectedExperiment = ProjectConfig.GetExperimentFromId(expectedExperimentId); var variation = expectedExperiment.Variations[0]; - var expectedDecision = Result.NewResult(new FeatureDecision(expectedExperiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), - It.IsAny(), ProjectConfig, It.IsAny())).Returns(expectedDecision); + var expectedDecision = Result.NewResult( + new FeatureDecision(expectedExperiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment( + It.IsAny(), It.IsAny(), + It.IsAny(), ProjectConfig, + It.IsAny())). + Returns(expectedDecision); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, + OptimizelyUserContextMock.Object, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision)); } // Should return the bucketed variation when the user is not bucketed in the feature flag experiment, // but is bucketed into a variation of the feature flag's rollout. [Test] - public void TestGetVariationForFeatureWhenTheUserIsNotBucketedIntoFeatureExperimentAndBucketedToFeatureRollout() + public void + TestGetVariationForFeatureWhenTheUserIsNotBucketedIntoFeatureExperimentAndBucketedToFeatureRollout() { var featureFlag = ProjectConfig.GetFeatureFlagFromKey("string_single_variable_feature"); var rolloutId = featureFlag.RolloutId; var rollout = ProjectConfig.GetRolloutFromId(rolloutId); var expectedExperiment = rollout.Experiments[0]; var variation = expectedExperiment.Variations[0]; - var expectedDecision = Result.NewResult(new FeatureDecision(expectedExperiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), - It.IsAny(), ProjectConfig, It.IsAny())).Returns(Result.NullResult(null)); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), It.IsAny(), - ProjectConfig)).Returns(expectedDecision); - - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var expectedDecision = Result.NewResult( + new FeatureDecision(expectedExperiment, variation, + FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment( + It.IsAny(), It.IsAny(), + It.IsAny(), ProjectConfig, + It.IsAny())). + Returns(Result.NullResult(null)); + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout( + It.IsAny(), It.IsAny(), + ProjectConfig)). + Returns(expectedDecision); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, + LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, + OptimizelyUserContextMock.Object, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"userProfileId\" is bucketed into a rollout for feature flag \"string_single_variable_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The user \"userProfileId\" is bucketed into a rollout for feature flag \"string_single_variable_feature\".")); } // Should return null when the user neither gets bucketed into feature experiment nor in feature rollout. [Test] - public void TestGetVariationForFeatureWhenTheUserIsNeitherBucketedIntoFeatureExperimentNorToFeatureRollout() + public void + TestGetVariationForFeatureWhenTheUserIsNeitherBucketedIntoFeatureExperimentNorToFeatureRollout() { var featureFlag = ProjectConfig.GetFeatureFlagFromKey("string_single_variable_feature"); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, new OptimizelyDecideOption[] { })).Returns(Result.NullResult(null)); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), It.IsAny(), ProjectConfig)).Returns(Result.NullResult(null)); - - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + DecisionServiceMock.Setup(ds => + ds.GetVariationForFeatureExperiment(It.IsAny(), + It.IsAny(), It.IsAny(), + ProjectConfig, + new OptimizelyDecideOption[] { })). + Returns(Result.NullResult(null)); + DecisionServiceMock. + Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), + It.IsAny(), ProjectConfig)). + Returns(Result.NullResult(null)); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, + LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, + OptimizelyUserContextMock.Object, ProjectConfig); Assert.IsNull(actualDecision.ResultObject.Variation); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"userProfileId\" is not bucketed into a rollout for feature flag \"string_single_variable_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "The user \"userProfileId\" is not bucketed into a rollout for feature flag \"string_single_variable_feature\".")); } // Verify that the user is bucketed into the experiment's variation when the user satisfies bucketing and traffic allocation @@ -981,37 +1289,58 @@ public void TestGetVariationForFeatureWhenTheUserIsNeitherBucketedIntoFeatureExp public void TestGetVariationForFeatureWhenTheUserIsBuckedtedInBothExperimentAndRollout() { var featureFlag = ProjectConfig.GetFeatureFlagFromKey("string_single_variable_feature"); - var experiment = ProjectConfig.GetExperimentFromKey("test_experiment_with_feature_rollout"); - var variation = Result.NewResult(ProjectConfig.GetVariationFromId("test_experiment_with_feature_rollout", "122236"), DecisionReasons); - var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST); - var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + var experiment = + ProjectConfig.GetExperimentFromKey("test_experiment_with_feature_rollout"); + var variation = Result.NewResult( + ProjectConfig.GetVariationFromId("test_experiment_with_feature_rollout", "122236"), + DecisionReasons); + var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST); + var userAttributes = new UserAttributes + { + { "browser_type", "chrome" }, }; - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); - OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, + WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig, It.IsAny())).Returns(variation); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); + DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, + OptimizelyUserContextMock.Object, ProjectConfig, + It.IsAny())). + Returns(variation); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment( + featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, + new OptimizelyDecideOption[] { }); // The user is bucketed into feature experiment's variation. Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); var rollout = ProjectConfig.GetRolloutFromId(featureFlag.RolloutId); var rolloutExperiment = rollout.Experiments[0]; - var rolloutVariation = Result.NewResult(rolloutExperiment.Variations[0], DecisionReasons); - var expectedRolloutDecision = new FeatureDecision(rolloutExperiment, rolloutVariation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); - - BucketerMock.Setup(bm => bm.Bucket(ProjectConfig, rolloutExperiment, It.IsAny(), It.IsAny())).Returns(rolloutVariation); - var actualRolloutDecision = DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); + var rolloutVariation = + Result.NewResult(rolloutExperiment.Variations[0], DecisionReasons); + var expectedRolloutDecision = new FeatureDecision(rolloutExperiment, + rolloutVariation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); + + BucketerMock. + Setup(bm => bm.Bucket(ProjectConfig, rolloutExperiment, It.IsAny(), + It.IsAny())). + Returns(rolloutVariation); + var actualRolloutDecision = + DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, + OptimizelyUserContextMock.Object, ProjectConfig); // The user is bucketed into feature rollout's variation. - Assert.IsTrue(TestData.CompareObjects(expectedRolloutDecision, actualRolloutDecision.ResultObject)); + Assert.IsTrue(TestData.CompareObjects(expectedRolloutDecision, + actualRolloutDecision.ResultObject)); - actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); + actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, + OptimizelyUserContextMock.Object, ProjectConfig); // The user is bucketed into feature experiment's variation and not the rollout's variation. Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); @@ -1035,45 +1364,58 @@ public void TestSetGetForcedVariation() var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" } + { "device_type", "iPhone" }, + { "location", "San Francisco" }, }; - var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), + LoggerMock.Object); optlyObject.Activate("test_experiment", "test_user", userAttributes); // invalid experiment key should return a null variation - Assert.False(DecisionService.SetForcedVariation(invalidExperimentKey, userId, expectedVariationKey, Config)); - Assert.Null(DecisionService.GetForcedVariation(invalidExperimentKey, userId, Config).ResultObject); + Assert.False(DecisionService.SetForcedVariation(invalidExperimentKey, userId, + expectedVariationKey, Config)); + Assert.Null(DecisionService.GetForcedVariation(invalidExperimentKey, userId, Config). + ResultObject); // setting a null variation should return a null variation Assert.True(DecisionService.SetForcedVariation(experimentKey, userId, null, Config)); - Assert.Null(DecisionService.GetForcedVariation(experimentKey, userId, Config).ResultObject); + Assert.Null(DecisionService.GetForcedVariation(experimentKey, userId, Config). + ResultObject); // setting an invalid variation should return a null variation - Assert.False(DecisionService.SetForcedVariation(experimentKey, userId, invalidVariationKey, Config)); - Assert.Null(DecisionService.GetForcedVariation(experimentKey, userId, Config).ResultObject); + Assert.False(DecisionService.SetForcedVariation(experimentKey, userId, + invalidVariationKey, Config)); + Assert.Null(DecisionService.GetForcedVariation(experimentKey, userId, Config). + ResultObject); // confirm the forced variation is returned after a set - Assert.True(DecisionService.SetForcedVariation(experimentKey, userId, expectedVariationKey, Config)); - var actualForcedVariation = DecisionService.GetForcedVariation(experimentKey, userId, Config); + Assert.True(DecisionService.SetForcedVariation(experimentKey, userId, + expectedVariationKey, Config)); + var actualForcedVariation = + DecisionService.GetForcedVariation(experimentKey, userId, Config); Assert.AreEqual(expectedVariationKey, actualForcedVariation.ResultObject.Key); // check multiple sets - Assert.True(DecisionService.SetForcedVariation(experimentKey2, userId, expectedVariationKey2, Config)); - var actualForcedVariation2 = DecisionService.GetForcedVariation(experimentKey2, userId, Config); + Assert.True(DecisionService.SetForcedVariation(experimentKey2, userId, + expectedVariationKey2, Config)); + var actualForcedVariation2 = + DecisionService.GetForcedVariation(experimentKey2, userId, Config); Assert.AreEqual(expectedVariationKey2, actualForcedVariation2.ResultObject.Key); // make sure the second set does not overwrite the first set - actualForcedVariation = DecisionService.GetForcedVariation(experimentKey, userId, Config); + actualForcedVariation = + DecisionService.GetForcedVariation(experimentKey, userId, Config); Assert.AreEqual(expectedVariationKey, actualForcedVariation.ResultObject.Key); // make sure unsetting the second experiment-to-variation mapping does not unset the // first experiment-to-variation mapping Assert.True(DecisionService.SetForcedVariation(experimentKey2, userId, null, Config)); - actualForcedVariation = DecisionService.GetForcedVariation(experimentKey, userId, Config); + actualForcedVariation = + DecisionService.GetForcedVariation(experimentKey, userId, Config); Assert.AreEqual(expectedVariationKey, actualForcedVariation.ResultObject.Key); // an invalid user ID should return a null variation - Assert.Null(DecisionService.GetForcedVariation(experimentKey, invalidUserId, Config).ResultObject); + Assert.Null(DecisionService.GetForcedVariation(experimentKey, invalidUserId, Config). + ResultObject); } // test that all the logs in setForcedVariation are getting called @@ -1093,11 +1435,23 @@ public void TestSetForcedVariationLogs() DecisionService.SetForcedVariation(experimentKey, userId, invalidVariationKey, Config); DecisionService.SetForcedVariation(experimentKey, userId, variationKey, Config); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(4)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, string.Format(@"Experiment key ""{0}"" is not in datafile.", invalidExperimentKey))); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, string.Format(@"Variation mapped to experiment ""{0}"" has been removed for user ""{1}"".", experimentKey, userId))); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, string.Format(@"No variation key ""{0}"" defined in datafile for experiment ""{1}"".", invalidVariationKey, experimentKey))); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, string.Format(@"Set variation ""{0}"" for experiment ""{1}"" and user ""{2}"" in the forced variation map.", variationId, experimentId, userId))); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), + Times.Exactly(4)); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, + string.Format(@"Experiment key ""{0}"" is not in datafile.", + invalidExperimentKey))); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + string.Format( + @"Variation mapped to experiment ""{0}"" has been removed for user ""{1}"".", + experimentKey, userId))); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, + string.Format( + @"No variation key ""{0}"" defined in datafile for experiment ""{1}"".", + invalidVariationKey, experimentKey))); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + string.Format( + @"Set variation ""{0}"" for experiment ""{1}"" and user ""{2}"" in the forced variation map.", + variationId, experimentId, userId))); } // test that all the logs in getForcedVariation are getting called @@ -1119,39 +1473,71 @@ public void TestGetForcedVariationLogs() DecisionService.GetForcedVariation(pausedExperimentKey, userId, Config); DecisionService.GetForcedVariation(experimentKey, userId, Config); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(5)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, string.Format(@"Set variation ""{0}"" for experiment ""{1}"" and user ""{2}"" in the forced variation map.", variationId, experimentId, userId))); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, string.Format(@"User ""{0}"" is not in the forced variation map.", invalidUserId))); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, string.Format(@"Experiment key ""{0}"" is not in datafile.", invalidExperimentKey))); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, string.Format(@"No experiment ""{0}"" mapped to user ""{1}"" in the forced variation map.", pausedExperimentKey, userId))); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, string.Format(@"Variation ""{0}"" is mapped to experiment ""{1}"" and user ""{2}"" in the forced variation map", variationKey, experimentKey, userId))); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), + Times.Exactly(5)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + string.Format( + @"Set variation ""{0}"" for experiment ""{1}"" and user ""{2}"" in the forced variation map.", + variationId, experimentId, userId))); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + string.Format(@"User ""{0}"" is not in the forced variation map.", invalidUserId))); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, + string.Format(@"Experiment key ""{0}"" is not in datafile.", + invalidExperimentKey))); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + string.Format( + @"No experiment ""{0}"" mapped to user ""{1}"" in the forced variation map.", + pausedExperimentKey, userId))); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, + string.Format( + @"Variation ""{0}"" is mapped to experiment ""{1}"" and user ""{2}"" in the forced variation map", + variationKey, experimentKey, userId))); } [Test] public void TestSetForcedVariationMultipleSets() { - Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_1", "variation", Config)); - Assert.AreEqual(DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config).ResultObject.Key, "variation"); + Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_1", + "variation", Config)); + Assert.AreEqual( + DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config). + ResultObject.Key, "variation"); // same user, same experiment, different variation - Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_1", "control", Config)); - Assert.AreEqual(DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config).ResultObject.Key, "control"); + Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_1", + "control", Config)); + Assert.AreEqual( + DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config). + ResultObject.Key, "control"); // same user, different experiment - Assert.True(DecisionService.SetForcedVariation("group_experiment_1", "test_user_1", "group_exp_1_var_1", Config)); - Assert.AreEqual(DecisionService.GetForcedVariation("group_experiment_1", "test_user_1", Config).ResultObject.Key, "group_exp_1_var_1"); + Assert.True(DecisionService.SetForcedVariation("group_experiment_1", "test_user_1", + "group_exp_1_var_1", Config)); + Assert.AreEqual( + DecisionService.GetForcedVariation("group_experiment_1", "test_user_1", Config). + ResultObject.Key, "group_exp_1_var_1"); // different user - Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_2", "variation", Config)); - Assert.AreEqual(DecisionService.GetForcedVariation("test_experiment", "test_user_2", Config).ResultObject.Key, "variation"); + Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_2", + "variation", Config)); + Assert.AreEqual( + DecisionService.GetForcedVariation("test_experiment", "test_user_2", Config). + ResultObject.Key, "variation"); // different user, different experiment - Assert.True(DecisionService.SetForcedVariation("group_experiment_1", "test_user_2", "group_exp_1_var_1", Config)); - Assert.AreEqual(DecisionService.GetForcedVariation("group_experiment_1", "test_user_2", Config).ResultObject.Key, "group_exp_1_var_1"); + Assert.True(DecisionService.SetForcedVariation("group_experiment_1", "test_user_2", + "group_exp_1_var_1", Config)); + Assert.AreEqual( + DecisionService.GetForcedVariation("group_experiment_1", "test_user_2", Config). + ResultObject.Key, "group_exp_1_var_1"); // make sure the first user forced variations are still valid - Assert.AreEqual(DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config).ResultObject.Key, "control"); - Assert.AreEqual(DecisionService.GetForcedVariation("group_experiment_1", "test_user_1", Config).ResultObject.Key, "group_exp_1_var_1"); + Assert.AreEqual( + DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config). + ResultObject.Key, "control"); + Assert.AreEqual( + DecisionService.GetForcedVariation("group_experiment_1", "test_user_1", Config). + ResultObject.Key, "group_exp_1_var_1"); } #endregion Forced variation Tests diff --git a/OptimizelySDK.Tests/DefaultErrorHandlerTest.cs b/OptimizelySDK.Tests/DefaultErrorHandlerTest.cs index 07db44935..4124eb089 100644 --- a/OptimizelySDK.Tests/DefaultErrorHandlerTest.cs +++ b/OptimizelySDK.Tests/DefaultErrorHandlerTest.cs @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; using System.Collections.Generic; using Moq; @@ -41,12 +42,12 @@ public void Setup() public void TestErrorHandlerMessage() { DefaultErrorHandler = new DefaultErrorHandler(LoggerMock.Object, false); - string testingException = "Testing exception"; + var testingException = "Testing exception"; try { throw new OptimizelyException("Testing exception"); } - catch(OptimizelyException ex) + catch (OptimizelyException ex) { DefaultErrorHandler.HandleError(ex); } @@ -54,12 +55,11 @@ public void TestErrorHandlerMessage() LoggerMock.Verify(log => log.Log(LogLevel.ERROR, testingException), Times.Once); } - [Test] - [ExpectedException] + [Test, ExpectedException] public void TestErrorHandlerMessageWithThrowException() { DefaultErrorHandler = new DefaultErrorHandler(LoggerMock.Object, true); - string testingException = "Testing and throwing exception"; + var testingException = "Testing and throwing exception"; try { throw new OptimizelyException("Testing exception"); @@ -72,6 +72,5 @@ public void TestErrorHandlerMessageWithThrowException() LoggerMock.Verify(log => log.Log(LogLevel.ERROR, testingException), Times.Once); } - } } diff --git a/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs b/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs index b44b31945..0c25b6baf 100644 --- a/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs +++ b/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs @@ -26,11 +26,18 @@ public class FeatureVariableTest [Test] public void TestFeatureVariableTypeName() { - Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.BOOLEAN_TYPE), "GetFeatureVariableBoolean"); - Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.DOUBLE_TYPE), "GetFeatureVariableDouble"); - Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.INTEGER_TYPE), "GetFeatureVariableInteger"); - Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.STRING_TYPE), "GetFeatureVariableString"); - Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.JSON_TYPE), "GetFeatureVariableJSON"); + Assert.AreEqual( + FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.BOOLEAN_TYPE), + "GetFeatureVariableBoolean"); + Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.DOUBLE_TYPE), + "GetFeatureVariableDouble"); + Assert.AreEqual( + FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.INTEGER_TYPE), + "GetFeatureVariableInteger"); + Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.STRING_TYPE), + "GetFeatureVariableString"); + Assert.AreEqual(FeatureVariable.GetFeatureVariableTypeName(FeatureVariable.JSON_TYPE), + "GetFeatureVariableJSON"); } [Test] diff --git a/OptimizelySDK.Tests/EventTests/BatchEventProcessorTest.cs b/OptimizelySDK.Tests/EventTests/BatchEventProcessorTest.cs index 5e0174a65..9f06208e8 100644 --- a/OptimizelySDK.Tests/EventTests/BatchEventProcessorTest.cs +++ b/OptimizelySDK.Tests/EventTests/BatchEventProcessorTest.cs @@ -16,7 +16,7 @@ namespace OptimizelySDK.Tests.EventTests { [TestFixture] - class BatchEventProcessorTest + internal class BatchEventProcessorTest { private static string TestUserId = "testUserId"; private const string EventName = "purchase"; @@ -24,12 +24,12 @@ class BatchEventProcessorTest public const int MAX_BATCH_SIZE = 10; public const int MAX_DURATION_MS = 1000; public const int TIMEOUT_INTERVAL_MS = 5000; - + private ProjectConfig Config; private Mock LoggerMock; private BlockingCollection eventQueue; private BatchEventProcessor EventProcessor; - private Mock EventDispatcherMock; + private Mock EventDispatcherMock; private NotificationCenter NotificationCenter = new NotificationCenter(); private Mock NotificationCallbackMock; @@ -39,8 +39,9 @@ public void Setup() LoggerMock = new Mock(); LoggerMock.Setup(l => l.Log(It.IsAny(), It.IsAny())); - Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, new ErrorHandler.NoOpErrorHandler()); - + Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + new NoOpErrorHandler()); + eventQueue = new BlockingCollection(100); EventDispatcherMock = new Mock(); @@ -56,7 +57,7 @@ public void TearDown() { EventProcessor.Stop(); } - + [Test] public void TestDrainOnClose() { @@ -68,7 +69,7 @@ public void TestDrainOnClose() eventDispatcher.ExpectConversion(EventName, TestUserId); Thread.Sleep(1500); - + Assert.True(eventDispatcher.CompareEvents()); Assert.AreEqual(0, EventProcessor.EventQueue.Count); } @@ -87,7 +88,8 @@ public void TestFlushOnMaxTimeout() Thread.Sleep(1500); Assert.True(eventDispatcher.CompareEvents()); - Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification."); + Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), + "Exceeded timeout waiting for notification."); Assert.AreEqual(0, EventProcessor.EventQueue.Count); } @@ -95,10 +97,11 @@ public void TestFlushOnMaxTimeout() public void TestFlushMaxBatchSize() { var countdownEvent = new CountdownEvent(1); - var eventDispatcher = new TestEventDispatcher(countdownEvent) { Logger = LoggerMock.Object }; + var eventDispatcher = new TestEventDispatcher(countdownEvent) + { Logger = LoggerMock.Object }; SetEventProcessor(eventDispatcher); - for (int i = 0; i < MAX_BATCH_SIZE; i++) + for (var i = 0; i < MAX_BATCH_SIZE; i++) { UserEvent userEvent = BuildConversionEvent(EventName); EventProcessor.Process(userEvent); @@ -133,7 +136,8 @@ public void TestFlush() Thread.Sleep(1500); Assert.True(eventDispatcher.CompareEvents()); - Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS / 2)), "Exceeded timeout waiting for notification."); + Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS / 2)), + "Exceeded timeout waiting for notification."); Assert.AreEqual(0, EventProcessor.EventQueue.Count); } @@ -150,7 +154,7 @@ public void TestNoOpLoggerIfNotProvided() { EventProcessor = new BatchEventProcessor.Builder().Build(); - Assert.True(EventProcessor.Logger is OptimizelySDK.Logger.NoOpLogger); + Assert.True(EventProcessor.Logger is NoOpLogger); } [Test] @@ -182,7 +186,8 @@ public void TestFlushOnMismatchRevision() Thread.Sleep(1500); Assert.True(eventDispatcher.CompareEvents()); - Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification."); + Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), + "Exceeded timeout waiting for notification."); } [Test] @@ -207,7 +212,8 @@ public void TestFlushOnMismatchProjectId() Thread.Sleep(1500); Assert.True(eventDispatcher.CompareEvents()); - Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification."); + Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), + "Exceeded timeout waiting for notification."); Assert.AreEqual(0, EventProcessor.EventQueue.Count); } @@ -231,7 +237,8 @@ public void TestStopAndStart() EventProcessor.Start(); EventProcessor.Stop(); - Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification."); + Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), + "Exceeded timeout waiting for notification."); } [Test] @@ -277,21 +284,22 @@ public void TestDisposeDontRaiseException() // Need to make sure, after dispose, process shouldn't raise exception EventProcessor.Process(userEvent); - } [Test] public void TestNotificationCenter() { var countdownEvent = new CountdownEvent(1); - NotificationCenter.AddNotification(NotificationCenter.NotificationType.LogEvent, logEvent => countdownEvent.Signal()); + NotificationCenter.AddNotification(NotificationCenter.NotificationType.LogEvent, + logEvent => countdownEvent.Signal()); SetEventProcessor(EventDispatcherMock.Object); UserEvent userEvent = BuildConversionEvent(EventName); EventProcessor.Process(userEvent); - + EventProcessor.Stop(); - Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification."); + Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), + "Exceeded timeout waiting for notification."); } [Test] @@ -310,15 +318,14 @@ public void TestCloseTimeout() private void SetEventProcessor(IEventDispatcher eventDispatcher) { - EventProcessor = new BatchEventProcessor.Builder() - .WithEventQueue(eventQueue) - .WithEventDispatcher(eventDispatcher) - .WithMaxBatchSize(MAX_BATCH_SIZE) - .WithFlushInterval(TimeSpan.FromMilliseconds(MAX_DURATION_MS)) - .WithTimeoutInterval(TimeSpan.FromMilliseconds(TIMEOUT_INTERVAL_MS)) - .WithLogger(LoggerMock.Object) - .WithNotificationCenter(NotificationCenter) - .Build(); + EventProcessor = new BatchEventProcessor.Builder().WithEventQueue(eventQueue). + WithEventDispatcher(eventDispatcher). + WithMaxBatchSize(MAX_BATCH_SIZE). + WithFlushInterval(TimeSpan.FromMilliseconds(MAX_DURATION_MS)). + WithTimeoutInterval(TimeSpan.FromMilliseconds(TIMEOUT_INTERVAL_MS)). + WithLogger(LoggerMock.Object). + WithNotificationCenter(NotificationCenter). + Build(); } private ConversionEvent BuildConversionEvent(string eventName) @@ -326,17 +333,24 @@ private ConversionEvent BuildConversionEvent(string eventName) return BuildConversionEvent(eventName, Config); } - private static ConversionEvent BuildConversionEvent(string eventName, ProjectConfig projectConfig) + private static ConversionEvent BuildConversionEvent(string eventName, + ProjectConfig projectConfig + ) { return UserEventFactory.CreateConversionEvent(projectConfig, eventName, TestUserId, new UserAttributes(), new EventTags()); } } - class CountdownEventDispatcher : IEventDispatcher + internal class CountdownEventDispatcher : IEventDispatcher { public ILogger Logger { get; set; } public CountdownEvent CountdownEvent { get; set; } - public void DispatchEvent(LogEvent logEvent) => Assert.False(!CountdownEvent.Wait(TimeSpan.FromMilliseconds(BatchEventProcessorTest.TIMEOUT_INTERVAL_MS * 2))); + + public void DispatchEvent(LogEvent logEvent) + { + Assert.False(!CountdownEvent.Wait( + TimeSpan.FromMilliseconds(BatchEventProcessorTest.TIMEOUT_INTERVAL_MS * 2))); + } } } diff --git a/OptimizelySDK.Tests/EventTests/CanonicalEvent.cs b/OptimizelySDK.Tests/EventTests/CanonicalEvent.cs index cbefd0667..19d2abcf9 100644 --- a/OptimizelySDK.Tests/EventTests/CanonicalEvent.cs +++ b/OptimizelySDK.Tests/EventTests/CanonicalEvent.cs @@ -14,7 +14,9 @@ public class CanonicalEvent private UserAttributes Attributes; private EventTags Tags; - public CanonicalEvent(string experimentId, string variationId, string eventName, string visitorId, UserAttributes attributes, EventTags tags) + public CanonicalEvent(string experimentId, string variationId, string eventName, + string visitorId, UserAttributes attributes, EventTags tags + ) { ExperimentId = experimentId; VariationId = variationId; @@ -24,29 +26,39 @@ public CanonicalEvent(string experimentId, string variationId, string eventName, Attributes = attributes ?? new UserAttributes(); Tags = tags ?? new EventTags(); } - + public override bool Equals(object obj) { if (obj == null) + { return false; + } - CanonicalEvent canonicalEvent = obj as CanonicalEvent; + var canonicalEvent = obj as CanonicalEvent; if (canonicalEvent == null) + { return false; + } if (ExperimentId != canonicalEvent.ExperimentId || VariationId != canonicalEvent.VariationId || EventName != canonicalEvent.EventName || VisitorId != canonicalEvent.VisitorId) + { return false; + } - if (!Attributes.OrderBy(pair => pair.Key) - .SequenceEqual(canonicalEvent.Attributes.OrderBy(pair => pair.Key))) + if (!Attributes.OrderBy(pair => pair.Key). + SequenceEqual(canonicalEvent.Attributes.OrderBy(pair => pair.Key))) + { return false; + } - if (!Tags.OrderBy(pair => pair.Key) - .SequenceEqual(canonicalEvent.Tags.OrderBy(pair => pair.Key))) + if (!Tags.OrderBy(pair => pair.Key). + SequenceEqual(canonicalEvent.Tags.OrderBy(pair => pair.Key))) + { return false; + } return true; } @@ -54,21 +66,29 @@ public override bool Equals(object obj) public override int GetHashCode() { var hashCode = -907746114; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ExperimentId); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(VariationId); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(EventName); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(VisitorId); - hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Attributes); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Tags); + hashCode = (hashCode * -1521134295) + + EqualityComparer.Default.GetHashCode(ExperimentId); + hashCode = (hashCode * -1521134295) + + EqualityComparer.Default.GetHashCode(VariationId); + hashCode = (hashCode * -1521134295) + + EqualityComparer.Default.GetHashCode(EventName); + hashCode = (hashCode * -1521134295) + + EqualityComparer.Default.GetHashCode(VisitorId); + hashCode = (hashCode * -1521134295) + + EqualityComparer>.Default.GetHashCode(Attributes); + hashCode = (hashCode * -1521134295) + + EqualityComparer.Default.GetHashCode(Tags); return hashCode; } public static bool operator ==(CanonicalEvent lhs, CanonicalEvent rhs) { - if (Object.ReferenceEquals(lhs, null)) + if (ReferenceEquals(lhs, null)) { - if (Object.ReferenceEquals(rhs, null)) + if (ReferenceEquals(rhs, null)) + { return true; + } return false; } diff --git a/OptimizelySDK.Tests/EventTests/DefaultEventDispatcherTest.cs b/OptimizelySDK.Tests/EventTests/DefaultEventDispatcherTest.cs index 6de01dd5d..a9e50fd60 100644 --- a/OptimizelySDK.Tests/EventTests/DefaultEventDispatcherTest.cs +++ b/OptimizelySDK.Tests/EventTests/DefaultEventDispatcherTest.cs @@ -20,22 +20,22 @@ public void TestDispatchEvent() var logEvent = new LogEvent("", new Dictionary { - {"accountId", "1234" }, - {"projectId", "9876" }, - {"visitorId", "testUser" } + { "accountId", "1234" }, + { "projectId", "9876" }, + { "visitorId", "testUser" }, }, "POST", new Dictionary { - {"Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var expectionedOptions = new Dictionary { - {"headers", logEvent.Headers }, - {"json", logEvent.Params }, - {"timeout", 10 }, - {"connect_timeout", 10 } + { "headers", logEvent.Headers }, + { "json", logEvent.Params }, + { "timeout", 10 }, + { "connect_timeout", 10 }, }; //TODO: Have to mock http calls. Will discuss with Randall. diff --git a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs index da731223e..9f5307458 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -40,7 +40,8 @@ public void Setup() { TestUserId = "testUserId"; var logger = new NoOpLogger(); - Config = DatafileProjectConfig.Create(TestData.Datafile, logger, new ErrorHandler.NoOpErrorHandler()); + Config = DatafileProjectConfig.Create(TestData.Datafile, logger, + new ErrorHandler.NoOpErrorHandler()); EventBuilder = new EventBuilder(new Bucketer(logger)); } @@ -52,60 +53,65 @@ public void TestCreateImpressionEventNoAttributes() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "77210100090" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "77210100090" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - {"visitor_id", TestUserId} - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -113,15 +119,15 @@ public void TestCreateImpressionEventNoAttributes() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); - var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "77210100090", TestUserId, null); + var logEvent = EventBuilder.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "77210100090", TestUserId, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); - } [Test] @@ -132,67 +138,72 @@ public void TestCreateImpressionEventWithAttributes() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "77210100090" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "77210100090" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -200,17 +211,18 @@ public void TestCreateImpressionEventWithAttributes() "POST", new Dictionary { - { "Content-Type", "application/json" } - + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; - var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "77210100090", TestUserId, userAttributes); + var logEvent = EventBuilder.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "77210100090", TestUserId, + userAttributes); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -225,88 +237,93 @@ public void TestCreateImpressionEventWithTypedAttributes() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "616727838" }, - {"key", "integer_key" }, - {"type", "custom" }, - {"value", 15} + { "entity_id", "616727838" }, + { "key", "integer_key" }, + { "type", "custom" }, + { "value", 15 }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -314,18 +331,20 @@ public void TestCreateImpressionEventWithTypedAttributes() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"boolean_key", true }, - {"integer_key", 15 }, - {"double_key", 3.14 } + { "device_type", "iPhone" }, + { "boolean_key", true }, + { "integer_key", 15 }, + { "double_key", 3.14 }, }; - var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes); + var logEvent = EventBuilder.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + userAttributes); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); @@ -339,81 +358,86 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -421,7 +445,7 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -439,7 +463,9 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() { "invalid_num_value", Math.Pow(2, 53) + 2 }, }; - var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes); + var logEvent = EventBuilder.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + userAttributes); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); @@ -453,50 +479,54 @@ public void TestCreateConversionEventNoAttributesNoValue() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"enrich_decisions", true} , - {"account_id", "1592310167"}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -505,14 +535,15 @@ public void TestCreateConversionEventNoAttributesNoValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, null); + var logEvent = + EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -527,57 +558,61 @@ public void TestCreateConversionEventWithAttributesNoValue() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"account_id", "1592310167"}, - {"enrich_decisions", true}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -586,19 +621,20 @@ public void TestCreateConversionEventWithAttributesNoValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, null); + var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, + userAttributes, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -613,57 +649,62 @@ public void TestCreateConversionEventNoAttributesWithValue() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { + "tags", new Dictionary { - {"revenue", 42 } + { "revenue", 42 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true}, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -672,20 +713,20 @@ public void TestCreateConversionEventNoAttributesWithValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, new EventTags - { - {"revenue", 42 } - }); + { + { "revenue", 42 }, + }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -700,65 +741,70 @@ public void TestCreateConversionEventWithAttributesWithValue() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { + "tags", new Dictionary { - {"revenue", 42}, - {"non-revenue", "definitely"} + { "revenue", 42 }, + { "non-revenue", "definitely" }, } - } - } + }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -767,25 +813,26 @@ public void TestCreateConversionEventWithAttributesWithValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - { "device_type", "iPhone"}, - {"company", "Optimizely" } + { "device_type", "iPhone" }, + { "company", "Optimizely" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, + var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, + userAttributes, new EventTags { - {"revenue", 42 }, - {"non-revenue", "definitely" } + { "revenue", 42 }, + { "non-revenue", "definitely" }, }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -801,58 +848,63 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { + "tags", new Dictionary { - {"revenue", "42" }, - {"non-revenue", "definitely"} + { "revenue", "42" }, + { "non-revenue", "definitely" }, } - } - } + }, + }, } - } - } + }, + }, } }, { "visitor_id", TestUserId }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -861,19 +913,19 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, new EventTags { - {"revenue", "42" }, - {"non-revenue", "definitely" } + { "revenue", "42" }, + { "non-revenue", "definitely" }, }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -889,59 +941,64 @@ public void TestConversionEventWithNumericTag() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"value", 400.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { "value", 400.0 }, + { + "tags", new Dictionary { - {"revenue", 42 }, - {"value", 400 } + { "revenue", 42 }, + { "value", 400 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -950,20 +1007,20 @@ public void TestConversionEventWithNumericTag() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, new EventTags - { - {"revenue", 42 }, - {"value", 400 } - }); + { + { "revenue", 42 }, + { "value", 400 }, + }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -977,59 +1034,64 @@ public void TestConversionEventWithFalsyNumericAndRevenueValues() var timeStamp = TestData.SecondsSince1970(); var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 0 }, - {"value", 0.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 0 }, + { "value", 0.0 }, + { + "tags", new Dictionary { - {"revenue", 0 }, - {"value", 0.0 } + { "revenue", 0 }, + { "value", 0.0 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1038,20 +1100,20 @@ public void TestConversionEventWithFalsyNumericAndRevenueValues() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, new EventTags - { - {"revenue", 0 }, - {"value", 0.0 } - }); + { + { "revenue", 0 }, + { "value", 0.0 }, + }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1065,59 +1127,64 @@ public void TestConversionEventWithNumericValue1() var timeStamp = TestData.SecondsSince1970(); var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 10 }, - {"value", 1.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 10 }, + { "value", 1.0 }, + { + "tags", new Dictionary { - {"revenue", 10 }, - {"value", 1.0 } + { "revenue", 10 }, + { "value", 1.0 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1126,25 +1193,26 @@ public void TestConversionEventWithNumericValue1() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, new EventTags - { - {"revenue", 10 }, - {"value", 1.0 } - }); + { + { "revenue", 10 }, + { "value", 1.0 }, + }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } + [Test] public void TestConversionEventWithRevenueValue1() { @@ -1152,59 +1220,64 @@ public void TestConversionEventWithRevenueValue1() var timeStamp = TestData.SecondsSince1970(); var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 1 }, - {"value", 10.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 1 }, + { "value", 10.0 }, + { + "tags", new Dictionary { - {"revenue", 1 }, - {"value", 10.0 } + { "revenue", 1 }, + { "value", 10.0 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1213,20 +1286,20 @@ public void TestConversionEventWithRevenueValue1() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, null, new EventTags - { - {"revenue", 1 }, - {"value", 10.0 } - }); + { + { "revenue", 1 }, + { "value", 10.0 }, + }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1242,83 +1315,88 @@ public void TestCreateConversionEventWithBucketingIDAttribute() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"type", "custom" }, - {"value", "variation"} + { "entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "type", "custom" }, + { "value", "variation" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( - "https://logx.optimizely.com/v1/events", - payloadParams, - "POST", - new Dictionary - { - { "Content-Type", "application/json"} - }); + "https://logx.optimizely.com/v1/events", + payloadParams, + "POST", + new Dictionary + { + { "Content-Type", "application/json" }, + }); var userAttributes = new UserAttributes { - { "device_type", "iPhone"}, - {"company", "Optimizely" }, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" } + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" }, }; - var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, null); + var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, + userAttributes, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1333,74 +1411,79 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"type", "custom" }, - {"value", "variation"} + { "entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "type", "custom" }, + { "value", "variation" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -1408,17 +1491,19 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { { "device_type", "iPhone" }, { "company", "Optimizely" }, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" } + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" }, }; - var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes); + var logEvent = EventBuilder.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + userAttributes); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1433,67 +1518,72 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "chrome"} + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "chrome" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -1501,19 +1591,20 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" }, }; var botFilteringEnabledConfig = Config; botFilteringEnabledConfig.BotFiltering = true; var experiment = botFilteringEnabledConfig.GetExperimentFromKey("test_experiment"); - var logEvent = EventBuilder.CreateImpressionEvent(botFilteringEnabledConfig, experiment, "7722370027", TestUserId, userAttributes); + var logEvent = EventBuilder.CreateImpressionEvent(botFilteringEnabledConfig, experiment, + "7722370027", TestUserId, userAttributes); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); @@ -1527,60 +1618,65 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "chrome"} - } + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "chrome" }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -1588,19 +1684,20 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" }, }; var botFilteringDisabledConfig = Config; botFilteringDisabledConfig.BotFiltering = null; var experiment = botFilteringDisabledConfig.GetExperimentFromKey("test_experiment"); - var logEvent = EventBuilder.CreateImpressionEvent(botFilteringDisabledConfig, experiment, "7722370027", TestUserId, userAttributes); + var logEvent = EventBuilder.CreateImpressionEvent(botFilteringDisabledConfig, + experiment, "7722370027", TestUserId, userAttributes); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); @@ -1614,57 +1711,61 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "safari"} + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "safari" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"account_id", "1592310167"}, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1673,21 +1774,22 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var botFilteringEnabledConfig = Config; botFilteringEnabledConfig.BotFiltering = true; - var logEvent = EventBuilder.CreateConversionEvent(botFilteringEnabledConfig, "purchase", TestUserId, userAttributes, null); + var logEvent = EventBuilder.CreateConversionEvent(botFilteringEnabledConfig, "purchase", + TestUserId, userAttributes, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1702,50 +1804,54 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "safari"} - } + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "safari" }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"enrich_decisions", true}, - {"account_id", "1592310167"}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1754,21 +1860,22 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var botFilteringDisabledConfig = Config; botFilteringDisabledConfig.BotFiltering = null; - var logEvent = EventBuilder.CreateConversionEvent(botFilteringDisabledConfig, "purchase", TestUserId, userAttributes, null); + var logEvent = EventBuilder.CreateConversionEvent(botFilteringDisabledConfig, + "purchase", TestUserId, userAttributes, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1780,39 +1887,45 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() { var guid = Guid.NewGuid(); var timeStamp = TestData.SecondsSince1970(); - - var eventInMultiExperimentConfig = DatafileProjectConfig.Create(TestData.SimpleABExperimentsDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + var eventInMultiExperimentConfig = DatafileProjectConfig.Create( + TestData.SimpleABExperimentsDatafile, new NoOpLogger(), + new ErrorHandler.NoOpErrorHandler()); var experimentIdVariationMap = new Dictionary { { - "111127", new Variation{Id="111129", Key="variation"} + "111127", new Variation { Id = "111129", Key = "variation" } }, { - "111130", new Variation{Id="111131", Key="variation"} - } + "111130", new Variation { Id = "111131", Key = "variation" } + }, }; - var logEvent = EventBuilder.CreateConversionEvent(eventInMultiExperimentConfig, "event_with_multiple_running_experiments", "test_user", - new UserAttributes { - {"test_attribute", "test_value"} - }, - new EventTags { - {"revenue", 4200}, - {"value", 1.234}, - {"non-revenue", "abc"} - }); - + var logEvent = EventBuilder.CreateConversionEvent(eventInMultiExperimentConfig, + "event_with_multiple_running_experiments", "test_user", + new UserAttributes + { + { "test_attribute", "test_value" }, + }, + new EventTags + { + { "revenue", 4200 }, + { "value", 1.234 }, + { "non-revenue", "abc" }, + }); + var payloadParams = new Dictionary + { + { "client_version", Optimizely.SDK_VERSION }, + { "project_id", "111001" }, + { "enrich_decisions", true }, + { "account_id", "12001" }, + { "client_name", "csharp-sdk" }, + { "anonymize_ip", false }, + { "revision", eventInMultiExperimentConfig.Revision }, { - {"client_version", Optimizely.SDK_VERSION}, - {"project_id", "111001"}, - {"enrich_decisions", true}, - {"account_id", "12001"}, - {"client_name", "csharp-sdk"}, - {"anonymize_ip", false}, - {"revision", eventInMultiExperimentConfig.Revision}, - {"visitors", new object[] + "visitors", new object[] { //visitors[0] new Dictionary @@ -1823,17 +1936,18 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() { new Dictionary { - {"entity_id", "111094"}, - {"type", "custom"}, - {"value", "test_value"}, - {"key", "test_attribute"} - } + { "entity_id", "111094" }, + { "type", "custom" }, + { "value", "test_value" }, + { "key", "test_attribute" }, + }, } }, //visitors[0].visitor_id - {"visitor_id", "test_user"}, + { "visitor_id", "test_user" }, //visitors[0].snapshots - {"snapshots", new object[] + { + "snapshots", new object[] { //snapshots[0] new Dictionary @@ -1844,34 +1958,32 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() { new Dictionary { - {"uuid", guid}, - {"timestamp", timeStamp}, - {"revenue", 4200}, - {"value", 1.234}, - {"key", "event_with_multiple_running_experiments"}, - {"entity_id", "111095"}, + { "uuid", guid }, + { "timestamp", timeStamp }, + { "revenue", 4200 }, + { "value", 1.234 }, + { + "key", + "event_with_multiple_running_experiments" + }, + { "entity_id", "111095" }, { "tags", new Dictionary { - {"non-revenue", "abc"}, - {"revenue", 4200}, - {"value", 1.234}, + { "non-revenue", "abc" }, + { "revenue", 4200 }, + { "value", 1.234 }, } - } - - } + }, + }, } - } - - } - + }, + }, } - } - - } + }, + }, } - - } + }, }; @@ -1881,7 +1993,7 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1897,71 +2009,75 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"account_id", "1592310167"}, - {"enrich_decisions", true}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1970,7 +2086,7 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -1990,10 +2106,11 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - - var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, null); + + var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", TestUserId, + userAttributes, null); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); diff --git a/OptimizelySDK.Tests/EventTests/EventEntitiesTest.cs b/OptimizelySDK.Tests/EventTests/EventEntitiesTest.cs index 10e5c3e41..d91f40f2c 100644 --- a/OptimizelySDK.Tests/EventTests/EventEntitiesTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventEntitiesTest.cs @@ -39,104 +39,114 @@ public void TestImpressionEventEqualsSerializedPayload() var expectedPayload = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "77210100090" } - } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "77210100090" }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", userId } - } + { "visitor_id", userId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; - EventBatch.Builder builder = new EventBatch.Builder(); - builder.WithAccountId("1592310167") - .WithProjectID("7720880029") - .WithClientVersion(Optimizely.SDK_VERSION) - .WithRevision("15") - .WithClientName("csharp-sdk") - .WithAnonymizeIP(false) - .WithEnrichDecisions(true); + var builder = new EventBatch.Builder(); + builder.WithAccountId("1592310167"). + WithProjectID("7720880029"). + WithClientVersion(Optimizely.SDK_VERSION). + WithRevision("15"). + WithClientName("csharp-sdk"). + WithAnonymizeIP(false). + WithEnrichDecisions(true); - var visitorAttribute1 = new VisitorAttribute(entityId: "7723280020", type: "custom", value: "iPhone", key: "device_type"); - var visitorAttribute2 = new VisitorAttribute(entityId: ControlAttributes.BOT_FILTERING_ATTRIBUTE, type: "custom", value: true, key: ControlAttributes.BOT_FILTERING_ATTRIBUTE); - var snapshotEvent = new SnapshotEvent.Builder() - .WithUUID(guid.ToString()) - .WithEntityId("7719770039") - .WithKey("campaign_activated") - .WithValue(null) - .WithRevenue(null) - .WithTimeStamp(timeStamp) - .WithEventTags(null) - .Build(); + var visitorAttribute1 = new VisitorAttribute("7723280020", type: "custom", + value: "iPhone", key: "device_type"); + var visitorAttribute2 = new VisitorAttribute(ControlAttributes.BOT_FILTERING_ATTRIBUTE, + type: "custom", value: true, key: ControlAttributes.BOT_FILTERING_ATTRIBUTE); + var snapshotEvent = new SnapshotEvent.Builder().WithUUID(guid.ToString()). + WithEntityId("7719770039"). + WithKey("campaign_activated"). + WithValue(null). + WithRevenue(null). + WithTimeStamp(timeStamp). + WithEventTags(null). + Build(); var metadata = new DecisionMetadata("experiment", "experiment_key", "7716830082"); var decision = new Decision("7719770039", "7716830082", "77210100090"); - var snapshot = new Snapshot(events: new SnapshotEvent[] { snapshotEvent }, decisions: new Decision[] { decision }); + var snapshot = new Snapshot(new SnapshotEvent[] { snapshotEvent }, + new Decision[] { decision }); var visitor = new Visitor( - snapshots: new Snapshot[] { - snapshot + new Snapshot[] + { + snapshot, + }, + new VisitorAttribute[] + { + visitorAttribute1, visitorAttribute2, }, - attributes: new VisitorAttribute[]{ - visitorAttribute1, visitorAttribute2}, - visitorId: "test_user"); + "test_user"); builder.WithVisitors(new Visitor[] { visitor }); - EventBatch eventBatch = builder.Build(); + var eventBatch = builder.Build(); // Single Conversion Event TestData.CompareObjects(expectedPayload, eventBatch); } @@ -144,20 +154,20 @@ public void TestImpressionEventEqualsSerializedPayload() [Test] public void TestConversionEventEqualsSerializedPayload() { - var guid = Guid.NewGuid(); var timeStamp = TestData.SecondsSince1970(); var expectdPayload = new Dictionary + { + { "client_version", Optimizely.SDK_VERSION }, + { "project_id", "111001" }, + { "enrich_decisions", true }, + { "account_id", "12001" }, + { "client_name", "csharp-sdk" }, + { "anonymize_ip", false }, + { "revision", "2" }, { - {"client_version", Optimizely.SDK_VERSION}, - {"project_id", "111001"}, - {"enrich_decisions", true}, - {"account_id", "12001"}, - {"client_name", "csharp-sdk"}, - {"anonymize_ip", false}, - {"revision", "2"}, - {"visitors", new object[] + "visitors", new object[] { new Dictionary { @@ -167,17 +177,18 @@ public void TestConversionEventEqualsSerializedPayload() { new Dictionary { - {"entity_id", "111094"}, - {"type", "custom"}, - {"value", "test_value"}, - {"key", "test_attribute"} - } + { "entity_id", "111094" }, + { "type", "custom" }, + { "value", "test_value" }, + { "key", "test_attribute" }, + }, } }, //visitors[0].visitor_id - {"visitor_id", "test_user"}, + { "visitor_id", "test_user" }, //visitors[0].snapshots - {"snapshots", new object[] + { + "snapshots", new object[] { //snapshots[0] new Dictionary @@ -188,79 +199,78 @@ public void TestConversionEventEqualsSerializedPayload() { new Dictionary { - {"uuid", guid}, - {"timestamp", timeStamp}, - {"revenue", 4200}, - {"value", 1.234}, - {"key", "event_with_multiple_running_experiments"}, - {"entity_id", "111095"}, + { "uuid", guid }, + { "timestamp", timeStamp }, + { "revenue", 4200 }, + { "value", 1.234 }, + { + "key", + "event_with_multiple_running_experiments" + }, + { "entity_id", "111095" }, { "tags", new Dictionary { - {"non-revenue", "abc"}, - {"revenue", 4200}, - {"value", 1.234}, + { "non-revenue", "abc" }, + { "revenue", 4200 }, + { "value", 1.234 }, } - } - - } + }, + }, } - } - - } - + }, + }, } - } - - } + }, + }, } - - } + }, }; - EventBatch.Builder builder = new EventBatch.Builder(); - builder.WithAccountId("12001") - .WithProjectID("111001") - .WithClientVersion(Optimizely.SDK_VERSION) - .WithRevision("2") - .WithClientName("csharp-sdk") - .WithAnonymizeIP(false) - .WithEnrichDecisions(true); - - var visitorAttribute = new VisitorAttribute(entityId: "111094", type: "custom", value: "test_value", key: "test_attribute"); + var builder = new EventBatch.Builder(); + builder.WithAccountId("12001"). + WithProjectID("111001"). + WithClientVersion(Optimizely.SDK_VERSION). + WithRevision("2"). + WithClientName("csharp-sdk"). + WithAnonymizeIP(false). + WithEnrichDecisions(true); - var snapshotEvent = new SnapshotEvent.Builder() - .WithUUID(guid.ToString()) - .WithEntityId("111095") - .WithKey("event_with_multiple_running_experiments") - .WithValue((long?)1.234) - .WithRevenue(4200) - .WithTimeStamp(timeStamp) - .WithEventTags(new EventTags + var visitorAttribute = new VisitorAttribute("111094", type: "custom", + value: "test_value", key: "test_attribute"); + + var snapshotEvent = new SnapshotEvent.Builder().WithUUID(guid.ToString()). + WithEntityId("111095"). + WithKey("event_with_multiple_running_experiments"). + WithValue((long?)1.234). + WithRevenue(4200). + WithTimeStamp(timeStamp). + WithEventTags(new EventTags { - {"non-revenue", "abc"}, - {"revenue", 4200}, - {"value", 1.234} - }) - .Build(); + { "non-revenue", "abc" }, + { "revenue", 4200 }, + { "value", 1.234 }, + }). + Build(); - var snapshot = new Snapshot(events: new SnapshotEvent[] { snapshotEvent }); + var snapshot = new Snapshot(new SnapshotEvent[] { snapshotEvent }); var visitor = new Visitor( - snapshots: new Snapshot[] { - snapshot + new Snapshot[] + { + snapshot, }, - attributes: new VisitorAttribute[]{ - visitorAttribute}, - visitorId: "test_user"); + new VisitorAttribute[] + { + visitorAttribute, + }, + "test_user"); builder.WithVisitors(new Visitor[] { visitor }); - EventBatch eventBatch = builder.Build(); + var eventBatch = builder.Build(); // Single Conversion Event TestData.CompareObjects(expectdPayload, eventBatch); } - - } } diff --git a/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs b/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs index b17cff2c4..3199a8ae3 100644 --- a/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs @@ -31,7 +31,6 @@ namespace OptimizelySDK.Tests.EventTests [TestFixture] public class EventFactoryTest { - private string TestUserId = string.Empty; private ProjectConfig Config; private ILogger Logger; @@ -41,7 +40,8 @@ public void Setup() { TestUserId = "testUserId"; var logger = new NoOpLogger(); - Config = DatafileProjectConfig.Create(TestData.Datafile, logger, new ErrorHandler.NoOpErrorHandler()); + Config = DatafileProjectConfig.Create(TestData.Datafile, logger, + new NoOpErrorHandler()); } [Test] @@ -49,17 +49,20 @@ public void TestCreateImpressionEventReturnsNullWhenSendFlagDecisionsIsFalseAndI { Config.SendFlagDecisions = false; var impressionEvent = UserEventFactory.CreateImpressionEvent( - Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, null, "test_feature", "rollout"); + Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + null, "test_feature", "rollout"); Assert.IsNull(impressionEvent); } [Test] - public void TestCreateImpressionEventReturnsNullWhenSendFlagDecisionsIsFalseAndVariationIsNull() + public void + TestCreateImpressionEventReturnsNullWhenSendFlagDecisionsIsFalseAndVariationIsNull() { Config.SendFlagDecisions = false; Variation variation = null; var impressionEvent = UserEventFactory.CreateImpressionEvent( - Config, Config.GetExperimentFromKey("test_experiment"), variation, TestUserId, null, "test_experiment", "experiment"); + Config, Config.GetExperimentFromKey("test_experiment"), variation, TestUserId, null, + "test_experiment", "experiment"); Assert.IsNull(impressionEvent); } @@ -69,79 +72,95 @@ public void TestCreateImpressionEventNoAttributes() var guid = Guid.NewGuid(); var timeStamp = TestData.SecondsSince1970(); - var payloadParams = new Dictionary { + var payloadParams = new Dictionary + { { - "visitors", new object[] { - new Dictionary() { + "visitors", new object[] + { + new Dictionary() + { + { + "snapshots", new object[] { - "snapshots", new object[] { - new Dictionary { + new Dictionary + { + { + "decisions", new object[] { - "decisions", new object[] { - new Dictionary { - { "campaign_id", "7719770039" }, - { "experiment_id", "7716830082" }, - { "variation_id", "7722370027" }, - { "metadata", - new Dictionary { + new Dictionary + { + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", + new Dictionary + { { "rule_type", "experiment" }, { "rule_key", "test_experiment" }, { "flag_key", "test_experiment" }, { "variation_key", "control" }, - { "enabled", false } - } } - } - } - }, + { "enabled", false }, + } + }, + }, + } + }, + { + "events", new object[] { - "events", new object[] { - new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } - } + new Dictionary + { + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } - }, + }, + }, + } + }, + { + "attributes", new object[] { - "attributes", new object[] { - new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } - } - }, - {"visitor_id", TestUserId} - } - } - }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} - }; + new Dictionary + { + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, + } + }, + { "visitor_id", TestUserId }, + }, + } + }, + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, + }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", payloadParams, "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var impressionEvent = UserEventFactory.CreateImpressionEvent( - Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, null, "test_experiment", "experiment"); + Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + null, "test_experiment", "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -152,73 +171,84 @@ public void TestCreateImpressionEventWithAttributes() var guid = Guid.NewGuid(); var timeStamp = TestData.SecondsSince1970(); var variationId = "7722370027"; - var payloadParams = new Dictionary { + var payloadParams = new Dictionary + { { - "visitors", new object[] { - new Dictionary() { + "visitors", new object[] + { + new Dictionary() + { { - "snapshots", new object[] { - new Dictionary { + "snapshots", new object[] + { + new Dictionary + { { - "decisions", new object[] { - new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" }, - { "metadata", new Dictionary { + "decisions", new object[] + { + new Dictionary + { + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", new Dictionary + { { "rule_type", "experiment" }, { "rule_key", "test_experiment" }, { "flag_key", "test_experiment" }, { "variation_key", "control" }, - {"enabled", false } - + { "enabled", false }, } - } - } + }, + }, } }, { - "events", new object[] { - new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + "events", new object[] + { + new Dictionary + { + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, { - "attributes", new object[] { + "attributes", new object[] + { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -226,20 +256,22 @@ public void TestCreateImpressionEventWithAttributes() "POST", new Dictionary { - { "Content-Type", "application/json" } - + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; // - var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), variationId, TestUserId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), variationId, TestUserId, + userAttributes, "test_experiment", "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -252,96 +284,103 @@ public void TestCreateImpressionEventWithTypedAttributes() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" }, - { "metadata", new Dictionary { + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", new Dictionary + { { "rule_type", "experiment" }, { "rule_key", "test_experiment" }, { "flag_key", "test_experiment" }, { "variation_key", "control" }, - {"enabled", false } + { "enabled", false }, } - } - } + }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "616727838" }, - {"key", "integer_key" }, - {"type", "custom" }, - {"value", 15} + { "entity_id", "616727838" }, + { "key", "integer_key" }, + { "type", "custom" }, + { "value", 15 }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -349,20 +388,23 @@ public void TestCreateImpressionEventWithTypedAttributes() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"boolean_key", true }, - {"integer_key", 15 }, - {"double_key", 3.14 } + { "device_type", "iPhone" }, + { "boolean_key", true }, + { "integer_key", 15 }, + { "double_key", 3.14 }, }; - var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + userAttributes, "test_experiment", "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -375,89 +417,96 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" }, - { "metadata", new Dictionary { - { "rule_type", "experiment" }, - { "rule_key", "test_experiment" }, - { "flag_key", "test_experiment" }, - { "variation_key", "control" }, - {"enabled", false } + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", new Dictionary + { + { "rule_type", "experiment" }, + { "rule_key", "test_experiment" }, + { "flag_key", "test_experiment" }, + { "variation_key", "control" }, + { "enabled", false }, } - } - } + }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -465,7 +514,7 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -482,10 +531,13 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() { "nan", double.NaN }, { "invalid_num_value", Math.Pow(2, 53) + 2 }, }; - var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + userAttributes, "test_experiment", "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -498,89 +550,96 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayloadRollout( var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", null }, - {"experiment_id", string.Empty }, - {"variation_id", null }, - { "metadata", new Dictionary { + { "campaign_id", null }, + { "experiment_id", string.Empty }, + { "variation_id", null }, + { + "metadata", new Dictionary + { { "rule_type", "rollout" }, { "rule_key", string.Empty }, { "flag_key", "test_feature" }, { "variation_key", string.Empty }, - { "enabled", false } + { "enabled", false }, } - } - } + }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", null }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", null }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -588,7 +647,7 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayloadRollout( "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -607,10 +666,12 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayloadRollout( }; Variation variation = null; - var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, null, variation, TestUserId, userAttributes, "test_feature", "rollout"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, null, variation, + TestUserId, userAttributes, "test_feature", "rollout"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -623,50 +684,54 @@ public void TestCreateConversionEventNoAttributesNoValue() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"enrich_decisions", true} , - {"account_id", "1592310167"}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -675,17 +740,19 @@ public void TestCreateConversionEventNoAttributesNoValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, null); + var conversionEvent = + UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, null); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -698,57 +765,61 @@ public void TestCreateConversionEventWithAttributesNoValue() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"account_id", "1592310167"}, - {"enrich_decisions", true}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -757,22 +828,25 @@ public void TestCreateConversionEventWithAttributesNoValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, null); + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, userAttributes, null); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); ; + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); + ; Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -785,57 +859,62 @@ public void TestCreateConversionEventNoAttributesWithValue() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { + "tags", new Dictionary { - {"revenue", 42 } + { "revenue", 42 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true}, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -844,22 +923,24 @@ public void TestCreateConversionEventNoAttributesWithValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, null, new EventTags - { - {"revenue", 42 } - }); + { + { "revenue", 42 }, + }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -872,65 +953,70 @@ public void TestCreateConversionEventWithAttributesWithValue() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { + "tags", new Dictionary { - {"revenue", 42}, - {"non-revenue", "definitely"} + { "revenue", 42 }, + { "non-revenue", "definitely" }, } - } - } + }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -939,29 +1025,31 @@ public void TestCreateConversionEventWithAttributesWithValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - { "device_type", "iPhone"}, - {"company", "Optimizely" } + { "device_type", "iPhone" }, + { "company", "Optimizely" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, userAttributes, new EventTags { - {"revenue", 42 }, - {"non-revenue", "definitely" } + { "revenue", 42 }, + { "non-revenue", "definitely" }, }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -974,58 +1062,63 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { + "tags", new Dictionary { - {"revenue", "42" }, - {"non-revenue", "definitely"} + { "revenue", "42" }, + { "non-revenue", "definitely" }, } - } - } + }, + }, } - } - } + }, + }, } }, { "visitor_id", TestUserId }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1034,22 +1127,24 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, null, new EventTags { - {"revenue", "42" }, - {"non-revenue", "definitely" } + { "revenue", "42" }, + { "non-revenue", "definitely" }, }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1062,59 +1157,64 @@ public void TestConversionEventWithNumericTag() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 42 }, - {"value", 400.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 42 }, + { "value", 400.0 }, + { + "tags", new Dictionary { - {"revenue", 42 }, - {"value", 400 } + { "revenue", 42 }, + { "value", 400 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1123,25 +1223,27 @@ public void TestConversionEventWithNumericTag() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, null, new EventTags - { - {"revenue", 42 }, - {"value", 400 } - }); + { + { "revenue", 42 }, + { "value", 400 }, + }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1153,59 +1255,64 @@ public void TestConversionEventWithFalsyNumericAndRevenueValues() var timeStamp = TestData.SecondsSince1970(); var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 0 }, - {"value", 0.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 0 }, + { "value", 0.0 }, + { + "tags", new Dictionary { - {"revenue", 0 }, - {"value", 0.0 } + { "revenue", 0 }, + { "value", 0.0 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1214,23 +1321,25 @@ public void TestConversionEventWithFalsyNumericAndRevenueValues() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, null, new EventTags - { - {"revenue", 0 }, - {"value", 0.0 } - }); + { + { "revenue", 0 }, + { "value", 0.0 }, + }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1242,59 +1351,64 @@ public void TestConversionEventWithNumericValue1() var timeStamp = TestData.SecondsSince1970(); var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 10 }, - {"value", 1.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 10 }, + { "value", 1.0 }, + { + "tags", new Dictionary { - {"revenue", 10 }, - {"value", 1.0 } + { "revenue", 10 }, + { "value", 1.0 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1303,22 +1417,24 @@ public void TestConversionEventWithNumericValue1() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, null, new EventTags - { - {"revenue", 10 }, - {"value", 1.0 } - }); + { + { "revenue", 10 }, + { "value", 1.0 }, + }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1330,59 +1446,64 @@ public void TestConversionEventWithRevenueValue1() var timeStamp = TestData.SecondsSince1970(); var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - {"revenue", 1 }, - {"value", 10.0 }, - {"tags", + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + { "revenue", 1 }, + { "value", 10.0 }, + { + "tags", new Dictionary { - {"revenue", 1 }, - {"value", 10.0 } + { "revenue", 1 }, + { "value", 10.0 }, } - } - } + }, + }, } - } - } + }, + }, } }, - { "attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1391,23 +1512,25 @@ public void TestConversionEventWithRevenueValue1() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, null, + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, null, new EventTags - { - {"revenue", 1 }, - {"value", 10.0 } - }); + { + { "revenue", 1 }, + { "value", 10.0 }, + }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1421,86 +1544,92 @@ public void TestCreateConversionEventWithBucketingIDAttribute() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "purchase" }, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"type", "custom" }, - {"value", "variation"} + { "entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "type", "custom" }, + { "value", "variation" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( - "https://logx.optimizely.com/v1/events", - payloadParams, - "POST", - new Dictionary - { - { "Content-Type", "application/json"} - }); + "https://logx.optimizely.com/v1/events", + payloadParams, + "POST", + new Dictionary + { + { "Content-Type", "application/json" }, + }); var userAttributes = new UserAttributes { - { "device_type", "iPhone"}, - {"company", "Optimizely" }, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" } + { "device_type", "iPhone" }, + { "company", "Optimizely" }, + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, null); + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, userAttributes, null); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1513,82 +1642,89 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" }, - { "metadata", new Dictionary { + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", new Dictionary + { { "rule_type", "experiment" }, { "rule_key", "test_experiment" }, { "flag_key", "test_experiment" }, { "variation_key", "control" }, - {"enabled", false } + { "enabled", false }, } - } - } + }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, - {"type", "custom" }, - {"value", "variation"} + { "entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + { "type", "custom" }, + { "value", "variation" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"enrich_decisions", true}, - {"account_id", "1592310167" }, - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -1596,19 +1732,22 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { { "device_type", "iPhone" }, { "company", "Optimizely" }, - {ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" } + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" }, }; - var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(Config, + Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, + userAttributes, "test_experiment", "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -1621,75 +1760,82 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" }, - { "metadata", new Dictionary { + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", new Dictionary + { { "rule_type", "experiment" }, { "rule_key", "test_experiment" }, { "flag_key", "test_experiment" }, { "variation_key", "control" }, - {"enabled", false } + { "enabled", false }, } - } - } + }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "chrome"} + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "chrome" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -1697,22 +1843,25 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" }, }; var botFilteringEnabledConfig = Config; botFilteringEnabledConfig.BotFiltering = true; var experiment = botFilteringEnabledConfig.GetExperimentFromKey("test_experiment"); - var impressionEvent = UserEventFactory.CreateImpressionEvent(botFilteringEnabledConfig, experiment, "7722370027", TestUserId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(botFilteringEnabledConfig, + experiment, "7722370027", TestUserId, userAttributes, "test_experiment", + "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -1725,68 +1874,75 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() var payloadParams = new Dictionary { - { "visitors", new object[] + { + "visitors", new object[] { new Dictionary() { - { "snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - { "decisions", new object[] + { + "decisions", new object[] { new Dictionary { - {"campaign_id", "7719770039" }, - {"experiment_id", "7716830082" }, - {"variation_id", "7722370027" }, - { "metadata", new Dictionary { + { "campaign_id", "7719770039" }, + { "experiment_id", "7716830082" }, + { "variation_id", "7722370027" }, + { + "metadata", new Dictionary + { { "rule_type", "experiment" }, { "rule_key", "test_experiment" }, { "flag_key", "test_experiment" }, { "variation_key", "control" }, - {"enabled", false } + { "enabled", false }, } - } - } + }, + }, } }, - { "events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7719770039" }, - {"timestamp", timeStamp }, - {"uuid", guid }, - {"key", "campaign_activated" } - } + { "entity_id", "7719770039" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "campaign_activated" }, + }, } - } - } + }, + }, } }, - {"attributes", new object[] + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "chrome"} - } + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "chrome" }, + }, } }, - { "visitor_id", TestUserId } - } + { "visitor_id", TestUserId }, + }, } }, - {"project_id", "7720880029" }, - {"account_id", "1592310167" }, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk" }, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", @@ -1794,22 +1950,25 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" }, }; var botFilteringDisabledConfig = Config; botFilteringDisabledConfig.BotFiltering = null; var experiment = botFilteringDisabledConfig.GetExperimentFromKey("test_experiment"); - var impressionEvent = UserEventFactory.CreateImpressionEvent(botFilteringDisabledConfig, experiment, "7722370027", TestUserId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(botFilteringDisabledConfig, + experiment, "7722370027", TestUserId, userAttributes, "test_experiment", + "experiment"); var logEvent = EventFactory.CreateLogEvent(impressionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, Guid.Parse(impressionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, impressionEvent.Timestamp, + Guid.Parse(impressionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -1822,57 +1981,61 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "safari"} + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "safari" }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"account_id", "1592310167"}, - {"enrich_decisions", true} , - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1881,25 +2044,27 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var botFilteringEnabledConfig = Config; botFilteringEnabledConfig.BotFiltering = true; - var conversionEvent = UserEventFactory.CreateConversionEvent(botFilteringEnabledConfig, "purchase", TestUserId, userAttributes, null); + var conversionEvent = UserEventFactory.CreateConversionEvent(botFilteringEnabledConfig, + "purchase", TestUserId, userAttributes, null); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1912,50 +2077,54 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, - {"type", "custom" }, - {"value", "safari"} - } + { "entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "key", ControlAttributes.USER_AGENT_ATTRIBUTE }, + { "type", "custom" }, + { "value", "safari" }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"enrich_decisions", true}, - {"account_id", "1592310167"}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "enrich_decisions", true }, + { "account_id", "1592310167" }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -1964,25 +2133,27 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes { - {ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" } + { ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" }, }; var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; var botFilteringDisabledConfig = Config; botFilteringDisabledConfig.BotFiltering = null; - var conversionEvent = UserEventFactory.CreateConversionEvent(botFilteringDisabledConfig, "purchase", TestUserId, userAttributes, null); + var conversionEvent = UserEventFactory.CreateConversionEvent(botFilteringDisabledConfig, + "purchase", TestUserId, userAttributes, null); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } @@ -1993,28 +2164,30 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() var guid = Guid.NewGuid(); var timeStamp = TestData.SecondsSince1970(); - var eventInMultiExperimentConfig = DatafileProjectConfig.Create(TestData.SimpleABExperimentsDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + var eventInMultiExperimentConfig = DatafileProjectConfig.Create( + TestData.SimpleABExperimentsDatafile, new NoOpLogger(), new NoOpErrorHandler()); var experimentIdVariationMap = new Dictionary { { - "111127", new Variation{Id="111129", Key="variation"} + "111127", new Variation { Id = "111129", Key = "variation" } }, { - "111130", new Variation{Id="111131", Key="variation"} - } + "111130", new Variation { Id = "111131", Key = "variation" } + }, }; var payloadParams = new Dictionary + { + { "client_version", Optimizely.SDK_VERSION }, + { "project_id", "111001" }, + { "enrich_decisions", true }, + { "account_id", "12001" }, + { "client_name", "csharp-sdk" }, + { "anonymize_ip", false }, + { "revision", eventInMultiExperimentConfig.Revision }, { - {"client_version", Optimizely.SDK_VERSION}, - {"project_id", "111001"}, - {"enrich_decisions", true}, - {"account_id", "12001"}, - {"client_name", "csharp-sdk"}, - {"anonymize_ip", false}, - {"revision", eventInMultiExperimentConfig.Revision}, - {"visitors", new object[] + "visitors", new object[] { //visitors[0] new Dictionary @@ -2025,17 +2198,18 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() { new Dictionary { - {"entity_id", "111094"}, - {"type", "custom"}, - {"value", "test_value"}, - {"key", "test_attribute"} - } + { "entity_id", "111094" }, + { "type", "custom" }, + { "value", "test_value" }, + { "key", "test_attribute" }, + }, } }, //visitors[0].visitor_id - {"visitor_id", "test_user"}, + { "visitor_id", "test_user" }, //visitors[0].snapshots - {"snapshots", new object[] + { + "snapshots", new object[] { //snapshots[0] new Dictionary @@ -2046,34 +2220,32 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() { new Dictionary { - {"uuid", guid}, - {"timestamp", timeStamp}, - {"revenue", 4200}, - {"value", 1.234}, - {"key", "event_with_multiple_running_experiments"}, - {"entity_id", "111095"}, + { "uuid", guid }, + { "timestamp", timeStamp }, + { "revenue", 4200 }, + { "value", 1.234 }, + { + "key", + "event_with_multiple_running_experiments" + }, + { "entity_id", "111095" }, { "tags", new Dictionary { - {"non-revenue", "abc"}, - {"revenue", 4200}, - {"value", 1.234}, + { "non-revenue", "abc" }, + { "revenue", 4200 }, + { "value", 1.234 }, } - } - - } + }, + }, } - } - - } - + }, + }, } - } - - } + }, + }, } - - } + }, }; @@ -2083,20 +2255,25 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, + }); + var conversionEvent = UserEventFactory.CreateConversionEvent( + eventInMultiExperimentConfig, "event_with_multiple_running_experiments", + "test_user", + new UserAttributes + { + { "test_attribute", "test_value" }, + }, + new EventTags + { + { "revenue", 4200 }, + { "value", 1.234 }, + { "non-revenue", "abc" }, }); - var conversionEvent = UserEventFactory.CreateConversionEvent(eventInMultiExperimentConfig, "event_with_multiple_running_experiments", "test_user", - new UserAttributes { - {"test_attribute", "test_value"} - }, - new EventTags { - {"revenue", 4200}, - {"value", 1.234}, - {"non-revenue", "abc"} - }); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedLogEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } @@ -2109,71 +2286,75 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() var payloadParams = new Dictionary { - {"visitors", new object[] + { + "visitors", new object[] { new Dictionary { - {"snapshots", new object[] + { + "snapshots", new object[] { new Dictionary { - {"events", new object[] + { + "events", new object[] { new Dictionary { - {"entity_id", "7718020063"}, - {"timestamp", timeStamp}, - {"uuid", guid}, - {"key", "purchase"}, - } + { "entity_id", "7718020063" }, + { "timestamp", timeStamp }, + { "uuid", guid }, + { "key", "purchase" }, + }, } - } - } + }, + }, } }, - {"visitor_id", TestUserId }, - {"attributes", new object[] + { "visitor_id", TestUserId }, + { + "attributes", new object[] { new Dictionary { - {"entity_id", "7723280020" }, - {"key", "device_type" }, - {"type", "custom" }, - {"value", "iPhone"} + { "entity_id", "7723280020" }, + { "key", "device_type" }, + { "type", "custom" }, + { "value", "iPhone" }, }, new Dictionary { - {"entity_id", "323434545" }, - {"key", "boolean_key" }, - {"type", "custom" }, - {"value", true} + { "entity_id", "323434545" }, + { "key", "boolean_key" }, + { "type", "custom" }, + { "value", true }, }, new Dictionary { - {"entity_id", "808797686" }, - {"key", "double_key" }, - {"type", "custom" }, - {"value", 3.14} + { "entity_id", "808797686" }, + { "key", "double_key" }, + { "type", "custom" }, + { "value", 3.14 }, }, new Dictionary { - {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, - {"type", "custom" }, - {"value", true } - } + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "type", "custom" }, + { "value", true }, + }, } - } - } + }, + }, } }, - {"project_id", "7720880029"}, - {"account_id", "1592310167"}, - {"enrich_decisions", true}, - {"client_name", "csharp-sdk"}, - {"client_version", Optimizely.SDK_VERSION }, - {"revision", "15" }, - {"anonymize_ip", false} + { "project_id", "7720880029" }, + { "account_id", "1592310167" }, + { "enrich_decisions", true }, + { "client_name", "csharp-sdk" }, + { "client_version", Optimizely.SDK_VERSION }, + { "revision", "15" }, + { "anonymize_ip", false }, }; var expectedEvent = new LogEvent( @@ -2182,7 +2363,7 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -2202,14 +2383,15 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() var experimentToVariationMap = new Dictionary { - {"7716830082", new Variation{Id="7722370027", Key="control"} } + { "7716830082", new Variation { Id = "7722370027", Key = "control" } }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", TestUserId, userAttributes, null); + var conversionEvent = UserEventFactory.CreateConversionEvent(Config, "purchase", + TestUserId, userAttributes, null); var logEvent = EventFactory.CreateLogEvent(conversionEvent, Logger); - TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, Guid.Parse(conversionEvent.UUID)); + TestData.ChangeGUIDAndTimeStamp(expectedEvent.Params, conversionEvent.Timestamp, + Guid.Parse(conversionEvent.UUID)); Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); } } } - diff --git a/OptimizelySDK.Tests/EventTests/EventProcessorProps.cs b/OptimizelySDK.Tests/EventTests/EventProcessorProps.cs index 9919af197..33c013c11 100644 --- a/OptimizelySDK.Tests/EventTests/EventProcessorProps.cs +++ b/OptimizelySDK.Tests/EventTests/EventProcessorProps.cs @@ -33,23 +33,28 @@ public class EventProcessorProps public EventProcessorProps(BatchEventProcessor eventProcessor) { var fieldsInfo = Reflection.GetAllFields(eventProcessor.GetType()); - BatchSize = Reflection.GetFieldValue(eventProcessor, "BatchSize", fieldsInfo); - FlushInterval = Reflection.GetFieldValue(eventProcessor, "FlushInterval", fieldsInfo); - TimeoutInterval = Reflection.GetFieldValue(eventProcessor, "TimeoutInterval", fieldsInfo); + BatchSize = + Reflection.GetFieldValue(eventProcessor, "BatchSize", + fieldsInfo); + FlushInterval = + Reflection.GetFieldValue(eventProcessor, + "FlushInterval", fieldsInfo); + TimeoutInterval = + Reflection.GetFieldValue(eventProcessor, + "TimeoutInterval", fieldsInfo); } /// /// To create default instance of expected values. /// - public EventProcessorProps() - { - - } + public EventProcessorProps() { } public override bool Equals(object obj) { if (obj == null) + { return false; + } var eventProcessor = obj as EventProcessorProps; if (eventProcessor == null) @@ -59,7 +64,8 @@ public override bool Equals(object obj) if (BatchSize != eventProcessor.BatchSize || FlushInterval.TotalMilliseconds != eventProcessor.FlushInterval.TotalMilliseconds || - TimeoutInterval.TotalMilliseconds != eventProcessor.TimeoutInterval.TotalMilliseconds) + TimeoutInterval.TotalMilliseconds != + eventProcessor.TimeoutInterval.TotalMilliseconds) { return false; } diff --git a/OptimizelySDK.Tests/EventTests/ForwardingEventProcessorTest.cs b/OptimizelySDK.Tests/EventTests/ForwardingEventProcessorTest.cs index 0661c7ad0..805e92413 100644 --- a/OptimizelySDK.Tests/EventTests/ForwardingEventProcessorTest.cs +++ b/OptimizelySDK.Tests/EventTests/ForwardingEventProcessorTest.cs @@ -12,7 +12,7 @@ namespace OptimizelySDK.Tests.EventTests { [TestFixture] - class ForwardingEventProcessorTest + internal class ForwardingEventProcessorTest { private const string UserId = "userId"; private const string EventName = "purchase"; @@ -21,10 +21,10 @@ class ForwardingEventProcessorTest private TestForwardingEventDispatcher EventDispatcher; private NotificationCenter NotificationCenter = new NotificationCenter(); - Mock LoggerMock; - Mock ErrorHandlerMock; + private Mock LoggerMock; + private Mock ErrorHandlerMock; - ProjectConfig ProjectConfig; + private ProjectConfig ProjectConfig; [SetUp] public void Setup() @@ -35,17 +35,19 @@ public void Setup() ErrorHandlerMock = new Mock(); ErrorHandlerMock.Setup(e => e.HandleError(It.IsAny())); - ProjectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, new NoOpErrorHandler()); + ProjectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + new NoOpErrorHandler()); EventDispatcher = new TestForwardingEventDispatcher { IsUpdated = false }; - EventProcessor = new ForwardingEventProcessor(EventDispatcher, NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object); + EventProcessor = new ForwardingEventProcessor(EventDispatcher, NotificationCenter, + LoggerMock.Object, ErrorHandlerMock.Object); } [Test] public void TestEventHandlerWithConversionEvent() { var userEvent = CreateConversionEvent(EventName); - EventProcessor.Process(userEvent); + EventProcessor.Process(userEvent); Assert.True(EventDispatcher.IsUpdated); } @@ -54,19 +56,22 @@ public void TestEventHandlerWithConversionEvent() [Test] public void TestExceptionWhileDispatching() { - var eventProcessor = new ForwardingEventProcessor(new InvalidEventDispatcher(), NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object); + var eventProcessor = new ForwardingEventProcessor(new InvalidEventDispatcher(), + NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object); var userEvent = CreateConversionEvent(EventName); eventProcessor.Process(userEvent); - - ErrorHandlerMock.Verify(errorHandler => errorHandler.HandleError(It.IsAny()), Times.Once ); + + ErrorHandlerMock.Verify(errorHandler => errorHandler.HandleError(It.IsAny()), + Times.Once); } [Test] public void TestNotifications() { - bool notificationTriggered = false; - NotificationCenter.AddNotification(NotificationCenter.NotificationType.LogEvent, logEvent => notificationTriggered = true); + var notificationTriggered = false; + NotificationCenter.AddNotification(NotificationCenter.NotificationType.LogEvent, + logEvent => notificationTriggered = true); var userEvent = CreateConversionEvent(EventName); EventProcessor.Process(userEvent); @@ -77,7 +82,8 @@ public void TestNotifications() private ConversionEvent CreateConversionEvent(string eventName) { - return UserEventFactory.CreateConversionEvent(ProjectConfig, eventName, UserId, new UserAttributes(), new EventTags()); + return UserEventFactory.CreateConversionEvent(ProjectConfig, eventName, UserId, + new UserAttributes(), new EventTags()); } } } diff --git a/OptimizelySDK.Tests/EventTests/LogEventTest.cs b/OptimizelySDK.Tests/EventTests/LogEventTest.cs index 1ef7e32be..e5f20d324 100644 --- a/OptimizelySDK.Tests/EventTests/LogEventTest.cs +++ b/OptimizelySDK.Tests/EventTests/LogEventTest.cs @@ -1,5 +1,4 @@ - -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using NUnit.Framework; using OptimizelySDK.Event; using System; @@ -17,7 +16,6 @@ public class LogEventTest public static bool CompareObjects(object o1, object o2) { - var str1 = Newtonsoft.Json.JsonConvert.SerializeObject(o1); var str2 = Newtonsoft.Json.JsonConvert.SerializeObject(o2); var jtoken1 = JToken.Parse(Newtonsoft.Json.JsonConvert.SerializeObject(o1)); @@ -30,17 +28,17 @@ public static bool CompareObjects(object o1, object o2) public void Setup() { LogEvent = new LogEvent( - url: "https://logx.optimizely.com", - parameters: new Dictionary + "https://logx.optimizely.com", + new Dictionary { { "accountId", "1234" }, { "projectId", "9876" }, - { "visitorId", "testUser" } + { "visitorId", "testUser" }, }, - httpVerb: "POST", - headers: new Dictionary + "POST", + new Dictionary { - { "Content-type", "application/json" } + { "Content-type", "application/json" }, }); } @@ -57,7 +55,7 @@ public void TestGetParams() { { "accountId", "1234" }, { "projectId", "9876" }, - { "visitorId", "testUser" } + { "visitorId", "testUser" }, }; Assert.IsTrue(CompareObjects(testParams, LogEvent.Params)); } @@ -73,7 +71,7 @@ public void TestGetHeaders() { var headers = new Dictionary { - { "Content-type", "application/json" } + { "Content-type", "application/json" }, }; Assert.IsTrue(CompareObjects(headers, LogEvent.Headers)); } diff --git a/OptimizelySDK.Tests/EventTests/TestEventDispatcher.cs b/OptimizelySDK.Tests/EventTests/TestEventDispatcher.cs index cfeab5d33..a00db04d9 100644 --- a/OptimizelySDK.Tests/EventTests/TestEventDispatcher.cs +++ b/OptimizelySDK.Tests/EventTests/TestEventDispatcher.cs @@ -30,16 +30,20 @@ public TestEventDispatcher(CountdownEvent countdownEvent = null) public bool CompareEvents() { if (ExpectedEvents.Count != ActualEvents.Count) + { return false; + } - for (int count = 0; count < ExpectedEvents.Count; ++count) + for (var count = 0; count < ExpectedEvents.Count; ++count) { var expectedEvent = ExpectedEvents[count]; var actualEvent = ActualEvents[count]; if (expectedEvent != actualEvent) + { return false; + } } return true; @@ -50,12 +54,14 @@ public void DispatchEvent(LogEvent actualLogEvent) Visitor[] visitors = null; if (actualLogEvent.Params.ContainsKey("visitors")) { - JArray jArray = (JArray)actualLogEvent.Params["visitors"]; + var jArray = (JArray)actualLogEvent.Params["visitors"]; visitors = jArray.ToObject(); } if (visitors == null) + { return; + } foreach (var visitor in visitors) { @@ -67,11 +73,15 @@ public void DispatchEvent(LogEvent actualLogEvent) foreach (var @event in snapshot.Events) { var userAttributes = new UserAttributes(); - foreach (var attribute in visitor.Attributes.Where(attr => !attr.Key.StartsWith(DatafileProjectConfig.RESERVED_ATTRIBUTE_PREFIX))) { + foreach (var attribute in visitor.Attributes.Where(attr => + !attr.Key.StartsWith(DatafileProjectConfig. + RESERVED_ATTRIBUTE_PREFIX))) + { userAttributes.Add(attribute.Key, attribute.Value); } - ActualEvents.Add(new CanonicalEvent(decision.ExperimentId, decision.VariationId, @event.Key, + ActualEvents.Add(new CanonicalEvent(decision.ExperimentId, + decision.VariationId, @event.Key, visitor.VisitorId, userAttributes, @event.EventTags)); } } @@ -84,7 +94,8 @@ public void DispatchEvent(LogEvent actualLogEvent) } catch (ObjectDisposedException) { - Logger.Log(LogLevel.ERROR, "The CountdownEvent instance has already been disposed."); + Logger.Log(LogLevel.ERROR, + "The CountdownEvent instance has already been disposed."); } catch (InvalidOperationException) { @@ -92,19 +103,26 @@ public void DispatchEvent(LogEvent actualLogEvent) } } - public void ExpectImpression(string experimentId, string variationId, string userId, UserAttributes attributes = null) + public void ExpectImpression(string experimentId, string variationId, string userId, + UserAttributes attributes = null + ) { Expect(experimentId, variationId, IMPRESSION_EVENT_NAME, userId, attributes, null); } - public void ExpectConversion(string eventName, string userId, UserAttributes attributes = null, EventTags tags = null) + public void ExpectConversion(string eventName, string userId, + UserAttributes attributes = null, EventTags tags = null + ) { Expect(null, null, eventName, userId, attributes, tags); } - private void Expect(string experimentId, string variationId, string eventName, string visitorId, UserAttributes attributes, EventTags tags) + private void Expect(string experimentId, string variationId, string eventName, + string visitorId, UserAttributes attributes, EventTags tags + ) { - var expectedEvent = new CanonicalEvent(experimentId, variationId, eventName, visitorId, attributes, tags); + var expectedEvent = new CanonicalEvent(experimentId, variationId, eventName, visitorId, + attributes, tags); ExpectedEvents.Add(expectedEvent); } } diff --git a/OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs b/OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs index 80acd1a66..ecf42fcad 100644 --- a/OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs +++ b/OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs @@ -38,7 +38,8 @@ public void Setup() LoggerMock = new Mock(); ErrorHandlerMock = new Mock(); - Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, ErrorHandlerMock.Object); + Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + ErrorHandlerMock.Object); } [Test] @@ -49,7 +50,8 @@ public void ImpressionEventTest() var variation = Config.GetVariationFromId(experiment.Key, "77210100090"); var userId = TestUserId; - var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, null, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, + variation, userId, null, "test_experiment", "experiment"); Assert.AreEqual(Config.ProjectId, impressionEvent.Context.ProjectId); Assert.AreEqual(Config.Revision, impressionEvent.Context.Revision); @@ -70,12 +72,14 @@ public void ImpressionEventTestWithAttributes() var variation = Config.GetVariationFromId(experiment.Key, "77210100090"); var userId = TestUserId; - var userAttributes = new UserAttributes { + var userAttributes = new UserAttributes + { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; - var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, userAttributes, "test_experiment", "experiment"); + var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, + variation, userId, userAttributes, "test_experiment", "experiment"); Assert.AreEqual(Config.ProjectId, impressionEvent.Context.ProjectId); Assert.AreEqual(Config.Revision, impressionEvent.Context.Revision); @@ -87,7 +91,8 @@ public void ImpressionEventTestWithAttributes() Assert.AreEqual(userId, impressionEvent.UserId); Assert.AreEqual(Config.BotFiltering, impressionEvent.BotFiltering); - var expectedVisitorAttributes = EventFactory.BuildAttributeList(userAttributes, projectConfig); + var expectedVisitorAttributes = + EventFactory.BuildAttributeList(userAttributes, projectConfig); TestData.CompareObjects(expectedVisitorAttributes, impressionEvent.VisitorAttributes); } @@ -99,12 +104,14 @@ public void ConversionEventTest() var variation = Config.GetVariationFromId(experiment.Key, "77210100090"); var userId = TestUserId; var eventKey = "purchase"; - var userAttributes = new UserAttributes { + var userAttributes = new UserAttributes + { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(projectConfig, eventKey, userId, userAttributes, null); + var conversionEvent = UserEventFactory.CreateConversionEvent(projectConfig, eventKey, + userId, userAttributes, null); Assert.AreEqual(Config.ProjectId, conversionEvent.Context.ProjectId); Assert.AreEqual(Config.Revision, conversionEvent.Context.Revision); @@ -115,7 +122,8 @@ public void ConversionEventTest() Assert.AreEqual(userId, conversionEvent.UserId); Assert.AreEqual(Config.BotFiltering, conversionEvent.BotFiltering); - var expectedVisitorAttributes = EventFactory.BuildAttributeList(userAttributes, projectConfig); + var expectedVisitorAttributes = + EventFactory.BuildAttributeList(userAttributes, projectConfig); TestData.CompareObjects(expectedVisitorAttributes, conversionEvent.VisitorAttributes); } @@ -128,17 +136,20 @@ public void ConversionEventWithEventTagsTest() var userId = TestUserId; var eventKey = "purchase"; - var eventTags = new EventTags { + var eventTags = new EventTags + { { "revenue", 4200 }, { "value", 1.234 }, - { "non-revenue", "abc" } + { "non-revenue", "abc" }, }; - var userAttributes = new UserAttributes { + var userAttributes = new UserAttributes + { { "device_type", "iPhone" }, - { "company", "Optimizely" } + { "company", "Optimizely" }, }; - var conversionEvent = UserEventFactory.CreateConversionEvent(projectConfig, eventKey, userId, userAttributes, eventTags); + var conversionEvent = UserEventFactory.CreateConversionEvent(projectConfig, eventKey, + userId, userAttributes, eventTags); Assert.AreEqual(Config.ProjectId, conversionEvent.Context.ProjectId); Assert.AreEqual(Config.Revision, conversionEvent.Context.Revision); @@ -150,7 +161,8 @@ public void ConversionEventWithEventTagsTest() Assert.AreEqual(Config.BotFiltering, conversionEvent.BotFiltering); Assert.AreEqual(eventTags, conversionEvent.EventTags); - var expectedVisitorAttributes = EventFactory.BuildAttributeList(userAttributes, projectConfig); + var expectedVisitorAttributes = + EventFactory.BuildAttributeList(userAttributes, projectConfig); TestData.CompareObjects(expectedVisitorAttributes, conversionEvent.VisitorAttributes); } } diff --git a/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs b/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs index 6cf2dfc8e..d3e1fbb8d 100644 --- a/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs +++ b/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs @@ -34,8 +34,10 @@ public void ForcedDecisionStoreGetSetForcedDecisionWithBothRuleAndFlagKey() forcedDecisionStore[context2] = expectedForcedDecision2; Assert.AreEqual(forcedDecisionStore.Count, 2); - Assert.AreEqual(forcedDecisionStore[context1].VariationKey, expectedForcedDecision1.VariationKey); - Assert.AreEqual(forcedDecisionStore[context2].VariationKey, expectedForcedDecision2.VariationKey); + Assert.AreEqual(forcedDecisionStore[context1].VariationKey, + expectedForcedDecision1.VariationKey); + Assert.AreEqual(forcedDecisionStore[context2].VariationKey, + expectedForcedDecision2.VariationKey); } [Test] @@ -70,7 +72,8 @@ public void ForcedDecisionStoreGetForcedDecisionWithBothRuleAndFlagKey() forcedDecisionStore[context1] = expectedForcedDecision1; Assert.AreEqual(forcedDecisionStore.Count, 1); - Assert.AreEqual(forcedDecisionStore[context1].VariationKey, expectedForcedDecision1.VariationKey); + Assert.AreEqual(forcedDecisionStore[context1].VariationKey, + expectedForcedDecision1.VariationKey); Assert.IsNull(forcedDecisionStore[NullFlagKeyContext]); } @@ -88,7 +91,8 @@ public void ForcedDecisionStoreRemoveForcedDecisionTrue() Assert.AreEqual(forcedDecisionStore.Count, 2); Assert.IsTrue(forcedDecisionStore.Remove(context2)); Assert.AreEqual(forcedDecisionStore.Count, 1); - Assert.AreEqual(forcedDecisionStore[context1].VariationKey, expectedForcedDecision1.VariationKey); + Assert.AreEqual(forcedDecisionStore[context1].VariationKey, + expectedForcedDecision1.VariationKey); Assert.IsNull(forcedDecisionStore[context2]); } @@ -118,6 +122,5 @@ public void ForcedDecisionStoreRemoveAllForcedDecisionContext() forcedDecisionStore.RemoveAll(); Assert.AreEqual(forcedDecisionStore.Count, 0); } - } } diff --git a/OptimizelySDK.Tests/IntegrationOdpDatafile.json b/OptimizelySDK.Tests/IntegrationOdpDatafile.json index d5c94dc0c..f0eac9985 100644 --- a/OptimizelySDK.Tests/IntegrationOdpDatafile.json +++ b/OptimizelySDK.Tests/IntegrationOdpDatafile.json @@ -1,318 +1,442 @@ { - "version": "4", - "rollouts": [{ - "experiments": [{ - "status": "Running", - "key": "feat_no_vars_rule", - "layerId": "11551226731", - "trafficAllocation": [{ - "entityId": "11557362669", - "endOfRange": 10000 - }], - "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], - "variations": [{ - "variables": [], - "id": "11557362669", - "key": "11557362669", - "featureEnabled": true - }], - "forcedVariations": {}, - "id": "11488548027" - }], - "id": "11551226731" - }, - { - "experiments": [{ - "status": "Paused", - "key": "feat_with_var_rule", - "layerId": "11638870867", - "trafficAllocation": [{ - "entityId": "11475708558", - "endOfRange": 0 - }], - "audienceIds": [], - "variations": [{ - "variables": [], - "id": "11475708558", - "key": "11475708558", - "featureEnabled": false - }], - "forcedVariations": {}, - "id": "11630490911" - }], - "id": "11638870867" - }, - { - "experiments": [{ - "status": "Running", - "key": "11488548028", - "layerId": "11551226732", - "trafficAllocation": [{ - "entityId": "11557362670", - "endOfRange": 10000 - }], - "audienceIds": ["0"], - "audienceConditions": ["and", ["or", "3468206642", "3988293898"], - ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] - ], - "variations": [{ - "variables": [], - "id": "11557362670", - "key": "11557362670", - "featureEnabled": true - }], - "forcedVariations": {}, - "id": "11488548028" - }], - "id": "11551226732" - }, - { - "experiments": [{ - "status": "Paused", - "key": "11630490912", - "layerId": "11638870868", - "trafficAllocation": [{ - "entityId": "11475708559", - "endOfRange": 0 - }], - "audienceIds": [], - "variations": [{ - "variables": [], - "id": "11475708559", - "key": "11475708559", - "featureEnabled": false - }], - "forcedVariations": {}, - "id": "11630490912" - }], - "id": "11638870868" - } - ], - "anonymizeIP": false, - "projectId": "11624721371", - "variables": [], - "featureFlags": [{ - "experimentIds": [], - "rolloutId": "11551226731", - "variables": [], - "id": "11477755619", - "key": "feat_no_vars" - }, - { - "experimentIds": [ - "11564051718" - ], - "rolloutId": "11638870867", - "variables": [{ - "defaultValue": "x", - "type": "string", - "id": "11535264366", - "key": "x" - }], - "id": "11567102051", - "key": "feat_with_var" - }, - { - "experimentIds": [], - "rolloutId": "11551226732", - "variables": [], - "id": "11567102052", - "key": "feat2" - }, - { - "experimentIds": ["1323241599"], - "rolloutId": "11638870868", - "variables": [{ - "defaultValue": "10", - "type": "integer", - "id": "11535264367", - "key": "z" - }], - "id": "11567102053", - "key": "feat2_with_var" - } - ], - "experiments": [{ - "status": "Running", - "key": "feat_with_var_test", - "layerId": "11504144555", - "trafficAllocation": [{ - "entityId": "11617170975", - "endOfRange": 10000 - }], - "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], - "variations": [{ - "variables": [{ - "id": "11535264366", - "value": "xyz" - }], - "id": "11617170975", - "key": "variation_2", - "featureEnabled": true - }], - "forcedVariations": {}, - "id": "11564051718" - }, - { - "id": "1323241597", - "key": "typed_audience_experiment", - "layerId": "1630555627", - "status": "Running", - "variations": [{ - "id": "1423767503", - "key": "A", - "variables": [] - }], - "trafficAllocation": [{ - "entityId": "1423767503", - "endOfRange": 10000 - }], - "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], - "forcedVariations": {} - }, - { - "id": "1323241598", - "key": "audience_combinations_experiment", - "layerId": "1323241598", - "status": "Running", - "variations": [{ - "id": "1423767504", - "key": "A", - "variables": [] - }], - "trafficAllocation": [{ - "entityId": "1423767504", - "endOfRange": 10000 - }], - "audienceIds": ["0"], - "audienceConditions": ["and", ["or", "3468206642", "3988293898"], - ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] - ], - "forcedVariations": {} - }, - { - "id": "1323241599", - "key": "feat2_with_var_test", - "layerId": "1323241600", - "status": "Running", - "variations": [{ - "variables": [{ - "id": "11535264367", - "value": "150" - }], - "id": "1423767505", - "key": "variation_2", - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "1423767505", - "endOfRange": 10000 - }], - "audienceIds": ["0"], - "audienceConditions": ["and", ["or", "3468206642", "3988293898"], - ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] - ], - "forcedVariations": {} - } - ], - "audiences": [ - { - "id": "3468206642", - "name": "exactString", - "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" - }, - { - "id": "3468206645", - "name": "notChrome", - "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]" - }, - { - "id": "3988293898", - "name": "$$dummySubstringString", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3988293899", - "name": "$$dummyExists", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206646", - "name": "$$dummyExactNumber", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206647", - "name": "$$dummyGtNumber", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206644", - "name": "$$dummyLtNumber", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206643", - "name": "$$dummyExactBoolean", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "0", - "name": "$$dummy", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "$opt_dummy_audience", - "name": "dummy_audience", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - } - ], - "typedAudiences": [], - "groups": [], - "integrations": [ - { - "key": "odp", - "host": "https://api.zaius.com", - "publicKey": "fake-public-key" - } - ], - "attributes": [{ - "key": "house", - "id": "594015" - }, - { - "key": "lasers", - "id": "594016" - }, - { - "key": "should_do_it", - "id": "594017" - }, - { - "key": "favorite_ice_cream", - "id": "594018" - } - ], - "botFiltering": false, - "accountId": "4879520872", - "events": [{ - "key": "item_bought", - "id": "594089", - "experimentIds": [ - "11564051718", - "1323241597" - ] - }, - { - "key": "user_signed_up", - "id": "594090", - "experimentIds": [ - "1323241598", - "1323241599" - ] - } - ], - "revision": "3", - "sdkKey": "typedAudienceDatafile", - "environmentKey": "Production" + "version": "4", + "rollouts": [ + { + "experiments": [ + { + "status": "Running", + "key": "feat_no_vars_rule", + "layerId": "11551226731", + "trafficAllocation": [ + { + "entityId": "11557362669", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "3468206642", + "3988293898", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ], + "variations": [ + { + "variables": [], + "id": "11557362669", + "key": "11557362669", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "id": "11488548027" + } + ], + "id": "11551226731" + }, + { + "experiments": [ + { + "status": "Paused", + "key": "feat_with_var_rule", + "layerId": "11638870867", + "trafficAllocation": [ + { + "entityId": "11475708558", + "endOfRange": 0 + } + ], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "11475708558", + "key": "11475708558", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "id": "11630490911" + } + ], + "id": "11638870867" + }, + { + "experiments": [ + { + "status": "Running", + "key": "11488548028", + "layerId": "11551226732", + "trafficAllocation": [ + { + "entityId": "11557362670", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "0" + ], + "audienceConditions": [ + "and", + [ + "or", + "3468206642", + "3988293898" + ], + [ + "or", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ] + ], + "variations": [ + { + "variables": [], + "id": "11557362670", + "key": "11557362670", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "id": "11488548028" + } + ], + "id": "11551226732" + }, + { + "experiments": [ + { + "status": "Paused", + "key": "11630490912", + "layerId": "11638870868", + "trafficAllocation": [ + { + "entityId": "11475708559", + "endOfRange": 0 + } + ], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "11475708559", + "key": "11475708559", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "id": "11630490912" + } + ], + "id": "11638870868" + } + ], + "anonymizeIP": false, + "projectId": "11624721371", + "variables": [], + "featureFlags": [ + { + "experimentIds": [], + "rolloutId": "11551226731", + "variables": [], + "id": "11477755619", + "key": "feat_no_vars" + }, + { + "experimentIds": [ + "11564051718" + ], + "rolloutId": "11638870867", + "variables": [ + { + "defaultValue": "x", + "type": "string", + "id": "11535264366", + "key": "x" + } + ], + "id": "11567102051", + "key": "feat_with_var" + }, + { + "experimentIds": [], + "rolloutId": "11551226732", + "variables": [], + "id": "11567102052", + "key": "feat2" + }, + { + "experimentIds": [ + "1323241599" + ], + "rolloutId": "11638870868", + "variables": [ + { + "defaultValue": "10", + "type": "integer", + "id": "11535264367", + "key": "z" + } + ], + "id": "11567102053", + "key": "feat2_with_var" + } + ], + "experiments": [ + { + "status": "Running", + "key": "feat_with_var_test", + "layerId": "11504144555", + "trafficAllocation": [ + { + "entityId": "11617170975", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "3468206642", + "3988293898", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ], + "variations": [ + { + "variables": [ + { + "id": "11535264366", + "value": "xyz" + } + ], + "id": "11617170975", + "key": "variation_2", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "id": "11564051718" + }, + { + "id": "1323241597", + "key": "typed_audience_experiment", + "layerId": "1630555627", + "status": "Running", + "variations": [ + { + "id": "1423767503", + "key": "A", + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "1423767503", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "3468206642", + "3988293898", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ], + "forcedVariations": {} + }, + { + "id": "1323241598", + "key": "audience_combinations_experiment", + "layerId": "1323241598", + "status": "Running", + "variations": [ + { + "id": "1423767504", + "key": "A", + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "1423767504", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "0" + ], + "audienceConditions": [ + "and", + [ + "or", + "3468206642", + "3988293898" + ], + [ + "or", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ] + ], + "forcedVariations": {} + }, + { + "id": "1323241599", + "key": "feat2_with_var_test", + "layerId": "1323241600", + "status": "Running", + "variations": [ + { + "variables": [ + { + "id": "11535264367", + "value": "150" + } + ], + "id": "1423767505", + "key": "variation_2", + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "1423767505", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "0" + ], + "audienceConditions": [ + "and", + [ + "or", + "3468206642", + "3988293898" + ], + [ + "or", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ] + ], + "forcedVariations": {} + } + ], + "audiences": [ + { + "id": "3468206642", + "name": "exactString", + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" + }, + { + "id": "3468206645", + "name": "notChrome", + "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]" + }, + { + "id": "3988293898", + "name": "$$dummySubstringString", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3988293899", + "name": "$$dummyExists", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206646", + "name": "$$dummyExactNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206647", + "name": "$$dummyGtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206644", + "name": "$$dummyLtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206643", + "name": "$$dummyExactBoolean", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "0", + "name": "$$dummy", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "$opt_dummy_audience", + "name": "dummy_audience", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + } + ], + "typedAudiences": [], + "groups": [], + "integrations": [ + { + "key": "odp", + "host": "https://api.zaius.com", + "publicKey": "fake-public-key" + } + ], + "attributes": [ + { + "key": "house", + "id": "594015" + }, + { + "key": "lasers", + "id": "594016" + }, + { + "key": "should_do_it", + "id": "594017" + }, + { + "key": "favorite_ice_cream", + "id": "594018" + } + ], + "botFiltering": false, + "accountId": "4879520872", + "events": [ + { + "key": "item_bought", + "id": "594089", + "experimentIds": [ + "11564051718", + "1323241597" + ] + }, + { + "key": "user_signed_up", + "id": "594090", + "experimentIds": [ + "1323241598", + "1323241599" + ] + } + ], + "revision": "3", + "sdkKey": "typedAudienceDatafile", + "environmentKey": "Production" } diff --git a/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json b/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json index 3fea39e72..c66ac3319 100644 --- a/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json +++ b/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json @@ -1,321 +1,445 @@ { - "version": "4", - "rollouts": [{ - "experiments": [{ - "status": "Running", - "key": "feat_no_vars_rule", - "layerId": "11551226731", - "trafficAllocation": [{ - "entityId": "11557362669", - "endOfRange": 10000 - }], - "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], - "variations": [{ - "variables": [], - "id": "11557362669", - "key": "11557362669", - "featureEnabled": true - }], - "forcedVariations": {}, - "id": "11488548027" - }], - "id": "11551226731" - }, - { - "experiments": [{ - "status": "Paused", - "key": "feat_with_var_rule", - "layerId": "11638870867", - "trafficAllocation": [{ - "entityId": "11475708558", - "endOfRange": 0 - }], - "audienceIds": [], - "variations": [{ - "variables": [], - "id": "11475708558", - "key": "11475708558", - "featureEnabled": false - }], - "forcedVariations": {}, - "id": "11630490911" - }], - "id": "11638870867" - }, - { - "experiments": [{ - "status": "Running", - "key": "11488548028", - "layerId": "11551226732", - "trafficAllocation": [{ - "entityId": "11557362670", - "endOfRange": 10000 - }], - "audienceIds": ["0"], - "audienceConditions": ["and", ["or", "3468206642", "3988293898"], - ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] - ], - "variations": [{ - "variables": [], - "id": "11557362670", - "key": "11557362670", - "featureEnabled": true - }], - "forcedVariations": {}, - "id": "11488548028" - }], - "id": "11551226732" - }, - { - "experiments": [{ - "status": "Paused", - "key": "11630490912", - "layerId": "11638870868", - "trafficAllocation": [{ - "entityId": "11475708559", - "endOfRange": 0 - }], - "audienceIds": [], - "variations": [{ - "variables": [], - "id": "11475708559", - "key": "11475708559", - "featureEnabled": false - }], - "forcedVariations": {}, - "id": "11630490912" - }], - "id": "11638870868" - } - ], - "anonymizeIP": false, - "projectId": "11624721371", - "variables": [], - "featureFlags": [{ - "experimentIds": [], - "rolloutId": "11551226731", - "variables": [], - "id": "11477755619", - "key": "feat_no_vars" - }, - { - "experimentIds": [ - "11564051718" - ], - "rolloutId": "11638870867", - "variables": [{ - "defaultValue": "x", - "type": "string", - "id": "11535264366", - "key": "x" - }], - "id": "11567102051", - "key": "feat_with_var" - }, - { - "experimentIds": [], - "rolloutId": "11551226732", - "variables": [], - "id": "11567102052", - "key": "feat2" - }, - { - "experimentIds": ["1323241599"], - "rolloutId": "11638870868", - "variables": [{ - "defaultValue": "10", - "type": "integer", - "id": "11535264367", - "key": "z" - }], - "id": "11567102053", - "key": "feat2_with_var" - } - ], - "experiments": [{ - "status": "Running", - "key": "feat_with_var_test", - "layerId": "11504144555", - "trafficAllocation": [{ - "entityId": "11617170975", - "endOfRange": 10000 - }], - "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], - "variations": [{ - "variables": [{ - "id": "11535264366", - "value": "xyz" - }], - "id": "11617170975", - "key": "variation_2", - "featureEnabled": true - }], - "forcedVariations": {}, - "id": "11564051718" - }, - { - "id": "1323241597", - "key": "typed_audience_experiment", - "layerId": "1630555627", - "status": "Running", - "variations": [{ - "id": "1423767503", - "key": "A", - "variables": [] - }], - "trafficAllocation": [{ - "entityId": "1423767503", - "endOfRange": 10000 - }], - "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], - "forcedVariations": {} - }, - { - "id": "1323241598", - "key": "audience_combinations_experiment", - "layerId": "1323241598", - "status": "Running", - "variations": [{ - "id": "1423767504", - "key": "A", - "variables": [] - }], - "trafficAllocation": [{ - "entityId": "1423767504", - "endOfRange": 10000 - }], - "audienceIds": ["0"], - "audienceConditions": ["and", ["or", "3468206642", "3988293898"], - ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] - ], - "forcedVariations": {} - }, - { - "id": "1323241599", - "key": "feat2_with_var_test", - "layerId": "1323241600", - "status": "Running", - "variations": [{ - "variables": [{ - "id": "11535264367", - "value": "150" - }], - "id": "1423767505", - "key": "variation_2", - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "1423767505", - "endOfRange": 10000 - }], - "audienceIds": ["0"], - "audienceConditions": ["and", ["or", "3468206642", "3988293898"], - ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] - ], - "forcedVariations": {} - } - ], - "audiences": [ - { - "id": "3468206642", - "name": "exactString", - "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" - }, - { - "id": "3468206645", - "name": "notChrome", - "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]" - }, - { - "id": "3988293898", - "name": "$$dummySubstringString", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3988293899", - "name": "$$dummyExists", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206646", - "name": "$$dummyExactNumber", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206647", - "name": "$$dummyGtNumber", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206644", - "name": "$$dummyLtNumber", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "3468206643", - "name": "$$dummyExactBoolean", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "0", - "name": "$$dummy", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - }, - { - "id": "$opt_dummy_audience", - "name": "dummy_audience", - "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" - } - ], - "typedAudiences": [], - "groups": [], - "integrations": [ - { - "key": "odp", - "host": "https://api.zaius.com", - "publicKey": "fake-public-key", - "k1": 10, - "k2": true, - "k3": "any-value" - } - ], - "attributes": [{ - "key": "house", - "id": "594015" - }, - { - "key": "lasers", - "id": "594016" - }, - { - "key": "should_do_it", - "id": "594017" - }, - { - "key": "favorite_ice_cream", - "id": "594018" - } - ], - "botFiltering": false, - "accountId": "4879520872", - "events": [{ - "key": "item_bought", - "id": "594089", - "experimentIds": [ - "11564051718", - "1323241597" - ] - }, - { - "key": "user_signed_up", - "id": "594090", - "experimentIds": [ - "1323241598", - "1323241599" - ] - } - ], - "revision": "3", - "sdkKey": "typedAudienceDatafile", - "environmentKey": "Production" + "version": "4", + "rollouts": [ + { + "experiments": [ + { + "status": "Running", + "key": "feat_no_vars_rule", + "layerId": "11551226731", + "trafficAllocation": [ + { + "entityId": "11557362669", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "3468206642", + "3988293898", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ], + "variations": [ + { + "variables": [], + "id": "11557362669", + "key": "11557362669", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "id": "11488548027" + } + ], + "id": "11551226731" + }, + { + "experiments": [ + { + "status": "Paused", + "key": "feat_with_var_rule", + "layerId": "11638870867", + "trafficAllocation": [ + { + "entityId": "11475708558", + "endOfRange": 0 + } + ], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "11475708558", + "key": "11475708558", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "id": "11630490911" + } + ], + "id": "11638870867" + }, + { + "experiments": [ + { + "status": "Running", + "key": "11488548028", + "layerId": "11551226732", + "trafficAllocation": [ + { + "entityId": "11557362670", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "0" + ], + "audienceConditions": [ + "and", + [ + "or", + "3468206642", + "3988293898" + ], + [ + "or", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ] + ], + "variations": [ + { + "variables": [], + "id": "11557362670", + "key": "11557362670", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "id": "11488548028" + } + ], + "id": "11551226732" + }, + { + "experiments": [ + { + "status": "Paused", + "key": "11630490912", + "layerId": "11638870868", + "trafficAllocation": [ + { + "entityId": "11475708559", + "endOfRange": 0 + } + ], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "11475708559", + "key": "11475708559", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "id": "11630490912" + } + ], + "id": "11638870868" + } + ], + "anonymizeIP": false, + "projectId": "11624721371", + "variables": [], + "featureFlags": [ + { + "experimentIds": [], + "rolloutId": "11551226731", + "variables": [], + "id": "11477755619", + "key": "feat_no_vars" + }, + { + "experimentIds": [ + "11564051718" + ], + "rolloutId": "11638870867", + "variables": [ + { + "defaultValue": "x", + "type": "string", + "id": "11535264366", + "key": "x" + } + ], + "id": "11567102051", + "key": "feat_with_var" + }, + { + "experimentIds": [], + "rolloutId": "11551226732", + "variables": [], + "id": "11567102052", + "key": "feat2" + }, + { + "experimentIds": [ + "1323241599" + ], + "rolloutId": "11638870868", + "variables": [ + { + "defaultValue": "10", + "type": "integer", + "id": "11535264367", + "key": "z" + } + ], + "id": "11567102053", + "key": "feat2_with_var" + } + ], + "experiments": [ + { + "status": "Running", + "key": "feat_with_var_test", + "layerId": "11504144555", + "trafficAllocation": [ + { + "entityId": "11617170975", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "3468206642", + "3988293898", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ], + "variations": [ + { + "variables": [ + { + "id": "11535264366", + "value": "xyz" + } + ], + "id": "11617170975", + "key": "variation_2", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "id": "11564051718" + }, + { + "id": "1323241597", + "key": "typed_audience_experiment", + "layerId": "1630555627", + "status": "Running", + "variations": [ + { + "id": "1423767503", + "key": "A", + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "1423767503", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "3468206642", + "3988293898", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ], + "forcedVariations": {} + }, + { + "id": "1323241598", + "key": "audience_combinations_experiment", + "layerId": "1323241598", + "status": "Running", + "variations": [ + { + "id": "1423767504", + "key": "A", + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "1423767504", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "0" + ], + "audienceConditions": [ + "and", + [ + "or", + "3468206642", + "3988293898" + ], + [ + "or", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ] + ], + "forcedVariations": {} + }, + { + "id": "1323241599", + "key": "feat2_with_var_test", + "layerId": "1323241600", + "status": "Running", + "variations": [ + { + "variables": [ + { + "id": "11535264367", + "value": "150" + } + ], + "id": "1423767505", + "key": "variation_2", + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "1423767505", + "endOfRange": 10000 + } + ], + "audienceIds": [ + "0" + ], + "audienceConditions": [ + "and", + [ + "or", + "3468206642", + "3988293898" + ], + [ + "or", + "3988293899", + "3468206646", + "3468206647", + "3468206644", + "3468206643" + ] + ], + "forcedVariations": {} + } + ], + "audiences": [ + { + "id": "3468206642", + "name": "exactString", + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" + }, + { + "id": "3468206645", + "name": "notChrome", + "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]" + }, + { + "id": "3988293898", + "name": "$$dummySubstringString", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3988293899", + "name": "$$dummyExists", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206646", + "name": "$$dummyExactNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206647", + "name": "$$dummyGtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206644", + "name": "$$dummyLtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206643", + "name": "$$dummyExactBoolean", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "0", + "name": "$$dummy", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "$opt_dummy_audience", + "name": "dummy_audience", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + } + ], + "typedAudiences": [], + "groups": [], + "integrations": [ + { + "key": "odp", + "host": "https://api.zaius.com", + "publicKey": "fake-public-key", + "k1": 10, + "k2": true, + "k3": "any-value" + } + ], + "attributes": [ + { + "key": "house", + "id": "594015" + }, + { + "key": "lasers", + "id": "594016" + }, + { + "key": "should_do_it", + "id": "594017" + }, + { + "key": "favorite_ice_cream", + "id": "594018" + } + ], + "botFiltering": false, + "accountId": "4879520872", + "events": [ + { + "key": "item_bought", + "id": "594089", + "experimentIds": [ + "11564051718", + "1323241597" + ] + }, + { + "key": "user_signed_up", + "id": "594090", + "experimentIds": [ + "1323241598", + "1323241599" + ] + } + ], + "revision": "3", + "sdkKey": "typedAudienceDatafile", + "environmentKey": "Production" } diff --git a/OptimizelySDK.Tests/InvalidEventDispatcher.cs b/OptimizelySDK.Tests/InvalidEventDispatcher.cs index db88a3a95..a30367e14 100644 --- a/OptimizelySDK.Tests/InvalidEventDispatcher.cs +++ b/OptimizelySDK.Tests/InvalidEventDispatcher.cs @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Event.Dispatcher; using OptimizelySDK.Logger; using System; namespace OptimizelySDK.Tests { - class InvalidEventDispatcher : IEventDispatcher + internal class InvalidEventDispatcher : IEventDispatcher { public ILogger Logger { get; set; } + public void DispatchEvent(Event.LogEvent logEvent) { throw new Exception("Invalid dispatch event"); } } -} \ No newline at end of file +} diff --git a/OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs b/OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs index 8b6d68066..ab4784872 100644 --- a/OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs +++ b/OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs @@ -51,7 +51,9 @@ public void Setup() public void TestAddAndRemoveNotificationListener() { // Verify that callback added successfully. - Assert.AreEqual(1, NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback)); + Assert.AreEqual(1, + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback)); Assert.AreEqual(1, NotificationCenter.NotificationsCount); // Verify that callback removed successfully. @@ -62,41 +64,53 @@ public void TestAddAndRemoveNotificationListener() Assert.AreEqual(false, NotificationCenter.RemoveNotification(1)); // Verify that callback added successfully and return right notification ID. - Assert.AreEqual(NotificationCenter.NotificationId, NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback)); + Assert.AreEqual(NotificationCenter.NotificationId, + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback)); Assert.AreEqual(1, NotificationCenter.NotificationsCount); } [Test] public void TestAddMultipleNotificationListeners() { - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback); - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestAnotherActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestAnotherActivateCallback); // Verify that multiple notifications will be added for same notification type. Assert.AreEqual(2, NotificationCenter.NotificationsCount); // Verify that notifications of other types will also gets added successfully. - NotificationCenter.AddNotification(NotificationTypeTrack, TestNotificationCallbacks.TestTrackCallback); + NotificationCenter.AddNotification(NotificationTypeTrack, + TestNotificationCallbacks.TestTrackCallback); Assert.AreEqual(3, NotificationCenter.NotificationsCount); // Verify that notifications of other types will also gets added successfully. - NotificationCenter.AddNotification(NotificationType.OptimizelyConfigUpdate, TestNotificationCallbacks.TestConfigUpdateCallback); + NotificationCenter.AddNotification(NotificationType.OptimizelyConfigUpdate, + TestNotificationCallbacks.TestConfigUpdateCallback); Assert.AreEqual(4, NotificationCenter.NotificationsCount); // Verify that notifications of other types will also gets added successfully. - NotificationCenter.AddNotification(NotificationType.LogEvent, TestNotificationCallbacks.TestLogEventCallback); + NotificationCenter.AddNotification(NotificationType.LogEvent, + TestNotificationCallbacks.TestLogEventCallback); Assert.AreEqual(5, NotificationCenter.NotificationsCount); } [Test] public void TestAddSameNotificationListenerMultipleTimes() { - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback); // Verify that adding same callback multiple times will gets failed. - Assert.AreEqual(-1, NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback)); + Assert.AreEqual(-1, + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback)); Assert.AreEqual(1, NotificationCenter.NotificationsCount); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "The notification callback already exists."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "The notification callback already exists."), + Times.Once); } [Test] @@ -107,7 +121,10 @@ public void TestAddInvalidNotificationListeners() TestNotificationCallbacks.TestActivateCallback)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $@"Invalid notification type provided for ""{NotificationTypeActivate}"" callback."), + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + $@"Invalid notification type provided for ""{NotificationTypeActivate + }"" callback."), Times.Once); // Verify that no notifion has been added. @@ -118,17 +135,21 @@ public void TestAddInvalidNotificationListeners() public void TestClearNotifications() { // Add decision notifications. - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback); - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestAnotherActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestAnotherActivateCallback); // Add track notification. - NotificationCenter.AddNotification(NotificationTypeTrack, TestNotificationCallbacks.TestTrackCallback); + NotificationCenter.AddNotification(NotificationTypeTrack, + TestNotificationCallbacks.TestTrackCallback); // Verify that callbacks added successfully. Assert.AreEqual(3, NotificationCenter.NotificationsCount); // Add config update callback. - NotificationCenter.AddNotification(NotificationType.OptimizelyConfigUpdate, TestNotificationCallbacks.TestConfigUpdateCallback); + NotificationCenter.AddNotification(NotificationType.OptimizelyConfigUpdate, + TestNotificationCallbacks.TestConfigUpdateCallback); // Verify that callbacks added successfully. Assert.AreEqual(4, NotificationCenter.NotificationsCount); @@ -150,11 +171,14 @@ public void TestClearNotifications() public void TestClearAllNotifications() { // Add decision notifications. - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestActivateCallback); - NotificationCenter.AddNotification(NotificationTypeActivate, TestNotificationCallbacks.TestAnotherActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + TestNotificationCallbacks.TestAnotherActivateCallback); // Add track notification. - NotificationCenter.AddNotification(NotificationTypeTrack, TestNotificationCallbacks.TestTrackCallback); + NotificationCenter.AddNotification(NotificationTypeTrack, + TestNotificationCallbacks.TestTrackCallback); // Verify that callbacks added successfully. Assert.AreEqual(3, NotificationCenter.NotificationsCount); @@ -172,69 +196,94 @@ public void TestClearAllNotifications() [Test] public void TestSendNotifications() { - var config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, new NoOpErrorHandler()); - var logEventMocker = new Mock("http://mockedurl", new Dictionary(), "POST", new Dictionary()); + var config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + new NoOpErrorHandler()); + var logEventMocker = new Mock("http://mockedurl", + new Dictionary(), "POST", new Dictionary()); // Mocking notification callbacks. var notificationCallbackMock = new Mock(); - notificationCallbackMock.Setup(nc => nc.TestActivateCallback(It.IsAny(), It.IsAny(), + notificationCallbackMock.Setup(nc => nc.TestActivateCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - notificationCallbackMock.Setup(nc => nc.TestAnotherActivateCallback(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + notificationCallbackMock.Setup(nc => nc.TestAnotherActivateCallback( + It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())); notificationCallbackMock.Setup(nc => nc.TestLogEventCallback(It.IsAny())); // Adding decision notifications. - NotificationCenter.AddNotification(NotificationTypeActivate, notificationCallbackMock.Object.TestActivateCallback); - NotificationCenter.AddNotification(NotificationTypeActivate, notificationCallbackMock.Object.TestAnotherActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + notificationCallbackMock.Object.TestActivateCallback); + NotificationCenter.AddNotification(NotificationTypeActivate, + notificationCallbackMock.Object.TestAnotherActivateCallback); // Adding track notifications. - NotificationCenter.AddNotification(NotificationTypeTrack, notificationCallbackMock.Object.TestTrackCallback); + NotificationCenter.AddNotification(NotificationTypeTrack, + notificationCallbackMock.Object.TestTrackCallback); // Fire decision type notifications. - NotificationCenter.SendNotifications(NotificationTypeActivate, config.GetExperimentFromKey("test_experiment"), - "testUser", new UserAttributes(), config.GetVariationFromId("test_experiment", "7722370027"), logEventMocker.Object); + NotificationCenter.SendNotifications(NotificationTypeActivate, + config.GetExperimentFromKey("test_experiment"), + "testUser", new UserAttributes(), + config.GetVariationFromId("test_experiment", "7722370027"), logEventMocker.Object); // Verify that only the registered notifications of decision type are called. - notificationCallbackMock.Verify(nc => nc.TestActivateCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + notificationCallbackMock.Verify(nc => nc.TestActivateCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); - notificationCallbackMock.Verify(nc => nc.TestAnotherActivateCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + notificationCallbackMock.Verify(nc => nc.TestAnotherActivateCallback( + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); - notificationCallbackMock.Verify(nc => nc.TestTrackCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + notificationCallbackMock.Verify(nc => nc.TestTrackCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); // Add logEvent Notification. - NotificationCenter.AddNotification(NotificationType.LogEvent, notificationCallbackMock.Object.TestLogEventCallback); + NotificationCenter.AddNotification(NotificationType.LogEvent, + notificationCallbackMock.Object.TestLogEventCallback); // Fire logEvent Notification. NotificationCenter.SendNotifications(NotificationType.LogEvent, logEventMocker.Object); // Verify that registered notifications of logEvent type are called. - notificationCallbackMock.Verify(nc => nc.TestLogEventCallback(It.IsAny()), Times.Once); + notificationCallbackMock.Verify(nc => nc.TestLogEventCallback(It.IsAny()), + Times.Once); // Verify that after clearing notifications, SendNotification should not call any notification // which were previously registered. NotificationCenter.ClearAllNotifications(); notificationCallbackMock.ResetCalls(); - NotificationCenter.SendNotifications(NotificationTypeActivate, config.GetExperimentFromKey("test_experiment"), - "testUser", new UserAttributes(), config.GetVariationFromId("test_experiment", "7722370027"), null); + NotificationCenter.SendNotifications(NotificationTypeActivate, + config.GetExperimentFromKey("test_experiment"), + "testUser", new UserAttributes(), + config.GetVariationFromId("test_experiment", "7722370027"), null); // Again verify notifications which were registered are not called. - notificationCallbackMock.Verify(nc => nc.TestActivateCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - - notificationCallbackMock.Verify(nc => nc.TestAnotherActivateCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - - notificationCallbackMock.Verify(nc => nc.TestTrackCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + notificationCallbackMock.Verify(nc => nc.TestActivateCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + + notificationCallbackMock.Verify(nc => nc.TestAnotherActivateCallback( + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + + notificationCallbackMock.Verify(nc => nc.TestTrackCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); } - } #region Test Notification callbacks class. @@ -244,31 +293,35 @@ public void TestSendNotifications() /// public class TestNotificationCallbacks { - public virtual void TestActivateCallback(Experiment experiment, string userId, UserAttributes userAttributes, - Variation variation, LogEvent logEvent) { - } - - public virtual void TestAnotherActivateCallback(Experiment experiment, string userId, UserAttributes userAttributes, - Variation variation, LogEvent logEvent) { - } - - public virtual void TestTrackCallback(string eventKey, string userId, UserAttributes userAttributes, - EventTags eventTags, LogEvent logEvent) { - } - - public virtual void TestAnotherTrackCallback(string eventKey, string userId, UserAttributes userAttributes, - EventTags eventTags, LogEvent logEvent) { - } - - public virtual void TestDecisionCallback(string type, string userId, UserAttributes userAttributes, - Dictionary decisionInfo) { - } - - public virtual void TestConfigUpdateCallback() { - } - - public virtual void TestLogEventCallback(LogEvent logEvent) { - } + public virtual void TestActivateCallback(Experiment experiment, string userId, + UserAttributes userAttributes, + Variation variation, LogEvent logEvent + ) { } + + public virtual void TestAnotherActivateCallback(Experiment experiment, string userId, + UserAttributes userAttributes, + Variation variation, LogEvent logEvent + ) { } + + public virtual void TestTrackCallback(string eventKey, string userId, + UserAttributes userAttributes, + EventTags eventTags, LogEvent logEvent + ) { } + + public virtual void TestAnotherTrackCallback(string eventKey, string userId, + UserAttributes userAttributes, + EventTags eventTags, LogEvent logEvent + ) { } + + public virtual void TestDecisionCallback(string type, string userId, + UserAttributes userAttributes, + Dictionary decisionInfo + ) { } + + public virtual void TestConfigUpdateCallback() { } + + public virtual void TestLogEventCallback(LogEvent logEvent) { } } + #endregion // Test Notification callbacks class. } diff --git a/OptimizelySDK.Tests/OdpTests/HttpClientTestUtil.cs b/OptimizelySDK.Tests/OdpTests/HttpClientTestUtil.cs index 8e2f0ac95..505e8b85e 100644 --- a/OptimizelySDK.Tests/OdpTests/HttpClientTestUtil.cs +++ b/OptimizelySDK.Tests/OdpTests/HttpClientTestUtil.cs @@ -47,12 +47,12 @@ public static HttpClient MakeHttpClient(HttpStatusCode statusCode, } var mockedHandler = new Mock(); - mockedHandler.Protected() - .Setup>( + mockedHandler.Protected(). + Setup>( "SendAsync", ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(response); + ItExpr.IsAny()). + ReturnsAsync(response); return new HttpClient(mockedHandler.Object); } @@ -63,12 +63,12 @@ public static HttpClient MakeHttpClient(HttpStatusCode statusCode, public static HttpClient MakeHttpClientWithTimeout() { var mockedHandler = new Mock(); - mockedHandler.Protected() - .Setup>( + mockedHandler.Protected(). + Setup>( "SendAsync", ItExpr.IsAny(), - ItExpr.IsAny()) - .Throws(); + ItExpr.IsAny()). + Throws(); return new HttpClient(mockedHandler.Object); } } diff --git a/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs b/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs index 27f8ee169..e45abbf03 100644 --- a/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs +++ b/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs @@ -142,7 +142,7 @@ public void ShouldReorderListOnSave() [Test] public void ShouldHandleWhenCacheIsDisabled() { - var cache = new LruCache>(maxSize: 0); + var cache = new LruCache>(0); cache.Save("user1", _segments1And2); cache.Save("user2", _segments3And4); @@ -172,7 +172,7 @@ public void ShouldHandleWhenItemsExpire() [Test] public void ShouldHandleWhenCacheReachesMaxSize() { - var cache = new LruCache>(maxSize: 2); + var cache = new LruCache>(2); cache.Save("user1", _segments1And2); cache.Save("user2", _segments3And4); diff --git a/OptimizelySDK.Tests/OdpTests/OdpEventApiManagerTest.cs b/OptimizelySDK.Tests/OdpTests/OdpEventApiManagerTest.cs index 2a987625b..cb6c91777 100644 --- a/OptimizelySDK.Tests/OdpTests/OdpEventApiManagerTest.cs +++ b/OptimizelySDK.Tests/OdpTests/OdpEventApiManagerTest.cs @@ -121,7 +121,8 @@ public void ShouldSuggestRetryFor500HttpResponse() } [Test] - public void ShouldSuggestRetryForNetworkTimeout() { + public void ShouldSuggestRetryForNetworkTimeout() + { var httpClient = HttpClientTestUtil.MakeHttpClientWithTimeout(); var manger = new OdpEventApiManager(_mockLogger.Object, _mockErrorHandler.Object, httpClient); @@ -129,6 +130,7 @@ public void ShouldSuggestRetryForNetworkTimeout() { var shouldRetry = manger.SendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, _odpEvents); - Assert.IsTrue(shouldRetry);} + Assert.IsTrue(shouldRetry); + } } } diff --git a/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs b/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs index 87ce608da..2a8b69caf 100644 --- a/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs +++ b/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs @@ -345,7 +345,7 @@ public void ShouldDispatchEventsInCorrectNumberOfBatches() Build(); eventManager.UpdateSettings(_odpConfig); - for (int i = 0; i < 25; i++) + for (var i = 0; i < 25; i++) { eventManager.SendEvent(MakeEvent(i)); } @@ -412,7 +412,7 @@ public void ShouldRetryFailedEvents() Build(); eventManager.UpdateSettings(_odpConfig); - for (int i = 0; i < 4; i++) // send 4 events in batches of 1 + for (var i = 0; i < 4; i++) // send 4 events in batches of 1 { eventManager.SendEvent(MakeEvent(i)); } @@ -436,7 +436,7 @@ public void ShouldFlushAllScheduledEventsBeforeStopping() Build(); eventManager.UpdateSettings(_odpConfig); - for (int i = 0; i < 25; i++) + for (var i = 0; i < 25; i++) { eventManager.SendEvent(MakeEvent(i)); } @@ -509,23 +509,26 @@ public void ShouldApplyUpdatedOdpConfigurationWhenAvailable() Assert.IsFalse(_odpConfig.Equals(eventManager.OdpConfigForTesting)); } - private static OdpEvent MakeEvent(int id) => - new OdpEvent($"test-type-{id}", $"test-action-{id}", new Dictionary - { - { - "an-identifier", $"value1-{id}" - }, - { - "another-identity", $"value2-{id}" - }, - }, new Dictionary - { + private static OdpEvent MakeEvent(int id) + { + return new OdpEvent($"test-type-{id}", $"test-action-{id}", + new Dictionary { - "data1", $"data1-value1-{id}" - }, + { + "an-identifier", $"value1-{id}" + }, + { + "another-identity", $"value2-{id}" + }, + }, new Dictionary { - "data2", id - }, - }); + { + "data1", $"data1-value1-{id}" + }, + { + "data2", id + }, + }); + } } } diff --git a/OptimizelySDK.Tests/OdpTests/OdpManagerTest.cs b/OptimizelySDK.Tests/OdpTests/OdpManagerTest.cs index a1adaafcb..80fe7ada9 100644 --- a/OptimizelySDK.Tests/OdpTests/OdpManagerTest.cs +++ b/OptimizelySDK.Tests/OdpTests/OdpManagerTest.cs @@ -87,7 +87,7 @@ public void ShouldInitializeWithCorrectDefaults() WithLogger(_mockLogger.Object). Build(); - var eventManager = (manager.EventManager as OdpEventManager); + var eventManager = manager.EventManager as OdpEventManager; var segmentCache = (manager.SegmentManager as OdpSegmentManager)?.SegmentsCacheForTesting as LruCache>; @@ -96,7 +96,8 @@ public void ShouldInitializeWithCorrectDefaults() Assert.AreEqual(Constants.DEFAULT_TIMEOUT_INTERVAL, eventManager.TimeoutIntervalForTesting); Assert.AreEqual(Constants.DEFAULT_MAX_CACHE_SIZE, segmentCache?.MaxSizeForTesting); - Assert.AreEqual(TimeSpan.FromSeconds(Constants.DEFAULT_CACHE_SECONDS), segmentCache.TimeoutForTesting); + Assert.AreEqual(TimeSpan.FromSeconds(Constants.DEFAULT_CACHE_SECONDS), + segmentCache.TimeoutForTesting); } [Test] diff --git a/OptimizelySDK.Tests/OdpTests/OdpSegmentManagerTest.cs b/OptimizelySDK.Tests/OdpTests/OdpSegmentManagerTest.cs index d1db2d6b3..35053a0a5 100644 --- a/OptimizelySDK.Tests/OdpTests/OdpSegmentManagerTest.cs +++ b/OptimizelySDK.Tests/OdpTests/OdpSegmentManagerTest.cs @@ -36,7 +36,8 @@ public class OdpSegmentManagerTest private static readonly string expectedCacheKey = $"fs_user_id-$-{FS_USER_ID}"; - private static readonly List segmentsToCheck = new List { "segment1", "segment2", }; + private static readonly List segmentsToCheck = new List + { "segment1", "segment2" }; private OdpConfig _odpConfig; private Mock _mockApiManager; @@ -60,10 +61,11 @@ public void Setup() public void ShouldFetchSegmentsOnCacheMiss() { var keyCollector = new List(); - _mockCache.Setup(c => c.Lookup(Capture.In(keyCollector))).Returns(default(List)); + _mockCache.Setup(c => c.Lookup(Capture.In(keyCollector))). + Returns(default(List)); _mockApiManager.Setup(a => a.FetchSegments(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns(segmentsToCheck.ToArray()); + It.IsAny(), It.IsAny(), It.IsAny>())). + Returns(segmentsToCheck.ToArray()); var manager = new OdpSegmentManager(_mockApiManager.Object, _mockCache.Object, _mockLogger.Object); manager.UpdateSettings(_odpConfig); @@ -119,8 +121,8 @@ public void ShouldHandleFetchSegmentsWithError() { // OdpSegmentApiManager.FetchSegments() return null on any error _mockApiManager.Setup(a => a.FetchSegments(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns(null as string[]); + It.IsAny(), It.IsAny(), It.IsAny>())). + Returns(null as string[]); var manager = new OdpSegmentManager(_mockApiManager.Object, _mockCache.Object, _mockLogger.Object); manager.UpdateSettings(_odpConfig); @@ -178,7 +180,8 @@ public void ShouldIgnoreCache() _mockLogger.Object); manager.UpdateSettings(_odpConfig); - manager.FetchQualifiedSegments(FS_USER_ID, new List { OdpSegmentOption.IGNORE_CACHE, }); + manager.FetchQualifiedSegments(FS_USER_ID, + new List { OdpSegmentOption.IGNORE_CACHE }); _mockCache.Verify(c => c.Reset(), Times.Never); _mockCache.Verify(c => c.Lookup(It.IsAny()), Times.Never); @@ -196,7 +199,8 @@ public void ShouldResetCache() _mockLogger.Object); manager.UpdateSettings(_odpConfig); - manager.FetchQualifiedSegments(FS_USER_ID, new List { OdpSegmentOption.RESET_CACHE, }); + manager.FetchQualifiedSegments(FS_USER_ID, + new List { OdpSegmentOption.RESET_CACHE }); _mockCache.Verify(c => c.Reset(), Times.Once); _mockCache.Verify(c => c.Lookup(It.IsAny()), Times.Once); diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 0689e6a2f..dbe647ddb 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -40,23 +40,25 @@ public void Setup() #region Test OptimizelyConfigService - private static Type[] ParameterTypes = { - typeof(ProjectConfig), - }; + private static Type[] ParameterTypes = + { + typeof(ProjectConfig), + }; private PrivateObject CreatePrivateOptimizelyConfigService(ProjectConfig projectConfig) { return new PrivateObject(typeof(OptimizelyConfigService), ParameterTypes, - new object[] - { - projectConfig - }); + new object[] + { + projectConfig, + }); } [Test] public void TestGetOptimizelyConfigServiceSerializedAudiences() { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, + new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); var optlyConfigService = CreatePrivateOptimizelyConfigService(datafileProjectConfig); var audienceConditions = new List> @@ -68,8 +70,16 @@ public void TestGetOptimizelyConfigServiceSerializedAudiences() new List() { "and", "3468206642" }, new List() { "3468206642" }, new List() { "3468206642", "3988293898" }, - new List() { "and", new JArray() { "or", "3468206642", "3988293898" }, "3468206646" }, - new List() { "and", new JArray() { "or", "3468206642", new JArray() { "and", "3988293898", "3468206646" } }, new JArray() { "and", "3988293899", new JArray() { "or", "3468206647", "3468206643" } } }, + new List() + { "and", new JArray() { "or", "3468206642", "3988293898" }, "3468206646" }, + new List() + { + "and", + new JArray() + { "or", "3468206642", new JArray() { "and", "3988293898", "3468206646" } }, + new JArray() + { "and", "3988293899", new JArray() { "or", "3468206647", "3468206643" } }, + }, new List() { "and", "and" }, new List() { "not", new JArray() { "and", "3468206642", "3988293898" } }, new List() { }, @@ -93,9 +103,10 @@ public void TestGetOptimizelyConfigServiceSerializedAudiences() "\"exactString\" OR \"999999999\"", }; - for (int testNo = 0; testNo < audienceConditions.Count; testNo++) + for (var testNo = 0; testNo < audienceConditions.Count; testNo++) { - var result = (string)optlyConfigService.Invoke("GetSerializedAudiences", audienceConditions[testNo], datafileProjectConfig.AudienceIdMap); + var result = (string)optlyConfigService.Invoke("GetSerializedAudiences", + audienceConditions[testNo], datafileProjectConfig.AudienceIdMap); Assert.AreEqual(result, expectedAudienceOutputs[testNo]); } } @@ -103,12 +114,12 @@ public void TestGetOptimizelyConfigServiceSerializedAudiences() [Test] public void TestAfterDisposeGetOptimizelyConfigIsNoLongerValid() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithPollingInterval(TimeSpan.FromMilliseconds(50000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithPollingInterval(TimeSpan.FromMilliseconds(50000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + Build(true); var optimizely = new Optimizely(httpManager); httpManager.Start(); @@ -128,20 +139,24 @@ public void TestAfterDisposeGetOptimizelyConfigIsNoLongerValid() [Test] public void TestGetOptimizelyConfigServiceNullConfig() { - OptimizelyConfig optimizelyConfig = new OptimizelyConfigService(null).GetOptimizelyConfig(); + var optimizelyConfig = new OptimizelyConfigService(null).GetOptimizelyConfig(); Assert.IsNull(optimizelyConfig); } [Test] public void TestGetOptimizelyConfigWithDuplicateExperimentKeys() { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.DuplicateExpKeysDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + var datafileProjectConfig = DatafileProjectConfig.Create( + TestData.DuplicateExpKeysDatafile, new NoOpLogger(), + new ErrorHandler.NoOpErrorHandler()); var optimizelyConfigService = new OptimizelyConfigService(datafileProjectConfig); var optimizelyConfig = optimizelyConfigService.GetOptimizelyConfig(); Assert.AreEqual(optimizelyConfig.ExperimentsMap.Count, 1); - var experimentMapFlag1 = optimizelyConfig.FeaturesMap["flag1"].ExperimentsMap; //9300000007569 - var experimentMapFlag2 = optimizelyConfig.FeaturesMap["flag2"].ExperimentsMap; // 9300000007573 + var experimentMapFlag1 = + optimizelyConfig.FeaturesMap["flag1"].ExperimentsMap; //9300000007569 + var experimentMapFlag2 = + optimizelyConfig.FeaturesMap["flag2"].ExperimentsMap; // 9300000007573 Assert.AreEqual(experimentMapFlag1["targeted_delivery"].Id, "9300000007569"); Assert.AreEqual(experimentMapFlag2["targeted_delivery"].Id, "9300000007573"); } @@ -149,14 +164,19 @@ public void TestGetOptimizelyConfigWithDuplicateExperimentKeys() [Test] public void TestGetOptimizelyConfigWithDuplicateRuleKeys() { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.DuplicateRuleKeysDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + var datafileProjectConfig = DatafileProjectConfig.Create( + TestData.DuplicateRuleKeysDatafile, new NoOpLogger(), + new ErrorHandler.NoOpErrorHandler()); var optimizelyConfigService = new OptimizelyConfigService(datafileProjectConfig); var optimizelyConfig = optimizelyConfigService.GetOptimizelyConfig(); Assert.AreEqual(optimizelyConfig.ExperimentsMap.Count, 0); - var rolloutFlag1 = optimizelyConfig.FeaturesMap["flag_1"].DeliveryRules[0]; // 9300000004977, - var rolloutFlag2 = optimizelyConfig.FeaturesMap["flag_2"].DeliveryRules[0]; // 9300000004979 - var rolloutFlag3 = optimizelyConfig.FeaturesMap["flag_3"].DeliveryRules[0]; // 9300000004981 + var rolloutFlag1 = + optimizelyConfig.FeaturesMap["flag_1"].DeliveryRules[0]; // 9300000004977, + var rolloutFlag2 = + optimizelyConfig.FeaturesMap["flag_2"].DeliveryRules[0]; // 9300000004979 + var rolloutFlag3 = + optimizelyConfig.FeaturesMap["flag_3"].DeliveryRules[0]; // 9300000004981 Assert.AreEqual(rolloutFlag1.Id, "9300000004977"); Assert.AreEqual(rolloutFlag1.Key, "targeted_delivery"); Assert.AreEqual(rolloutFlag2.Id, "9300000004979"); @@ -168,7 +188,9 @@ public void TestGetOptimizelyConfigWithDuplicateRuleKeys() [Test] public void TestGetOptimizelyConfigSDKAndEnvironmentKeyDefault() { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.DuplicateRuleKeysDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + var datafileProjectConfig = DatafileProjectConfig.Create( + TestData.DuplicateRuleKeysDatafile, new NoOpLogger(), + new ErrorHandler.NoOpErrorHandler()); var optimizelyConfigService = new OptimizelyConfigService(datafileProjectConfig); var optimizelyConfig = optimizelyConfigService.GetOptimizelyConfig(); @@ -179,376 +201,405 @@ public void TestGetOptimizelyConfigSDKAndEnvironmentKeyDefault() [Test] public void TestGetOptimizelyConfigService() { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); - IDictionary experimentsMap = new Dictionary - { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, + new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + IDictionary experimentsMap = + new Dictionary { - "feat_with_var_test", new OptimizelyExperiment ( - id: "11564051718", - key:"feat_with_var_test", - audiences: "", - variationsMap: new Dictionary - { + { + "feat_with_var_test", new OptimizelyExperiment( + "11564051718", + "feat_with_var_test", + "", + new Dictionary { - "variation_2", new OptimizelyVariation ( - id: "11617170975", - key: "variation_2", - featureEnabled: true, - variablesMap: new Dictionary - { + { + "variation_2", new OptimizelyVariation( + "11617170975", + "variation_2", + true, + new Dictionary { - "x" , new OptimizelyVariable ( - id: "11535264366", - key: "x", - type: "string", - value: "xyz") - } - }) + { + "x", new OptimizelyVariable( + "11535264366", + "x", + "string", + "xyz") + }, + }) + }, } - } - ) - }, - { - "typed_audience_experiment", new OptimizelyExperiment ( - id: "1323241597", - key:"typed_audience_experiment", - audiences: "", - variationsMap: new Dictionary - { + ) + }, + { + "typed_audience_experiment", new OptimizelyExperiment( + "1323241597", + "typed_audience_experiment", + "", + new Dictionary { - "A", new OptimizelyVariation ( - id: "1423767503", - key: "A", - featureEnabled: null, - variablesMap: new Dictionary ()) + { + "A", new OptimizelyVariation( + "1423767503", + "A", + null, + new Dictionary()) + }, } - } - ) - }, - { - "audience_combinations_experiment", new OptimizelyExperiment ( - id: "1323241598", - key:"audience_combinations_experiment", - audiences: "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", - variationsMap: new Dictionary - { + ) + }, + { + "audience_combinations_experiment", new OptimizelyExperiment( + "1323241598", + "audience_combinations_experiment", + "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", + new Dictionary { - "A", new OptimizelyVariation ( - id: "1423767504", - key: "A", - featureEnabled: null, - variablesMap: new Dictionary ()) + { + "A", new OptimizelyVariation( + "1423767504", + "A", + null, + new Dictionary()) + }, } - } - ) - }, - { - "feat2_with_var_test", new OptimizelyExperiment( - id: "1323241599", - key:"feat2_with_var_test", - audiences: "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", - variationsMap: new Dictionary - { + ) + }, + { + "feat2_with_var_test", new OptimizelyExperiment( + "1323241599", + "feat2_with_var_test", + "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", + new Dictionary { - "variation_2", new OptimizelyVariation ( - id: "1423767505", - key: "variation_2", - featureEnabled: true, - variablesMap: new Dictionary - { + { + "variation_2", new OptimizelyVariation( + "1423767505", + "variation_2", + true, + new Dictionary { - "z" , new OptimizelyVariable ( - id: "11535264367", - key: "z", - type: "integer", - value: "150") - } - }) + { + "z", new OptimizelyVariable( + "11535264367", + "z", + "integer", + "150") + }, + }) + }, } - } - ) - } - }; + ) + }, + }; var featuresMap = new Dictionary { { - "feat_no_vars", new OptimizelyFeature ( - id: "11477755619", - key: "feat_no_vars", - experimentRules: new List(), - deliveryRules: new List() { new OptimizelyExperiment( - id: "11488548027", - key:"feat_no_vars_rule", - audiences: "", - variationsMap: new Dictionary - { - { - "11557362669", new OptimizelyVariation ( - id: "11557362669", - key: "11557362669", - featureEnabled: true, - variablesMap: new Dictionary()) - } - } - ) }, - experimentsMap: new Dictionary(), - variablesMap: new Dictionary()) + "feat_no_vars", new OptimizelyFeature( + "11477755619", + "feat_no_vars", + new List(), + new List() + { + new OptimizelyExperiment( + "11488548027", + "feat_no_vars_rule", + "", + new Dictionary + { + { + "11557362669", new OptimizelyVariation( + "11557362669", + "11557362669", + true, + new Dictionary()) + }, + } + ), + }, + new Dictionary(), + new Dictionary()) }, { - "feat_with_var", new OptimizelyFeature ( - id: "11567102051", - key: "feat_with_var", - experimentRules: new List() { + "feat_with_var", new OptimizelyFeature( + "11567102051", + "feat_with_var", + new List() + { new OptimizelyExperiment( - id: "11564051718", - key:"feat_with_var_test", - audiences: "", - variationsMap: new Dictionary + "11564051718", + "feat_with_var_test", + "", + new Dictionary + { { - { - "variation_2", new OptimizelyVariation ( - id: "11617170975", - key: "variation_2", - featureEnabled: true, - variablesMap: new Dictionary + "variation_2", new OptimizelyVariation( + "11617170975", + "variation_2", + true, + new Dictionary + { { - { - "x" , new OptimizelyVariable ( - id: "11535264366", - key: "x", - type: "string", - value: "xyz") - } - }) - } - } - ) + "x", new OptimizelyVariable( + "11535264366", + "x", + "string", + "xyz") + }, + }) + }, + } + ), }, - deliveryRules: new List() { new OptimizelyExperiment( - id: "11630490911", - key:"feat_with_var_rule", - audiences: "", - variationsMap: new Dictionary + new List() + { + new OptimizelyExperiment( + "11630490911", + "feat_with_var_rule", + "", + new Dictionary + { + { + "11475708558", new OptimizelyVariation( + "11475708558", + "11475708558", + false, + new Dictionary() { { - "11475708558", new OptimizelyVariation ( - id: "11475708558", - key: "11475708558", - featureEnabled: false, - variablesMap: new Dictionary() - { - { "x" , new OptimizelyVariable("11535264366", "x", "string", "x") } - }) - } - } - ) }, - experimentsMap: new Dictionary + "x", + new OptimizelyVariable("11535264366", "x", + "string", "x") + }, + }) + }, + } + ), + }, + new Dictionary { { "feat_with_var_test", new OptimizelyExperiment( - id: "11564051718", - key:"feat_with_var_test", - audiences: "", - variationsMap: new Dictionary + "11564051718", + "feat_with_var_test", + "", + new Dictionary { { - "variation_2", new OptimizelyVariation ( - id: "11617170975", - key: "variation_2", - featureEnabled: true, - variablesMap: new Dictionary + "variation_2", new OptimizelyVariation( + "11617170975", + "variation_2", + true, + new Dictionary { { - "x" , new OptimizelyVariable ( - id: "11535264366", - key: "x", - type: "string", - value: "xyz") - } + "x", new OptimizelyVariable( + "11535264366", + "x", + "string", + "xyz") + }, }) - } + }, } ) - } + }, }, - variablesMap: new Dictionary + new Dictionary { { - "x", new OptimizelyVariable (id: "11535264366" , key: "x", type: "string", value: "x") - } + "x", new OptimizelyVariable("11535264366", "x", "string", "x") + }, }) }, { - "feat2", new OptimizelyFeature ( - id: "11567102052", - key: "feat2", - deliveryRules: new List() { new OptimizelyExperiment( - id: "11488548028", - key:"11488548028", - audiences: "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", - variationsMap: new Dictionary - { - { - "11557362670", new OptimizelyVariation ( - id: "11557362670", - key: "11557362670", - featureEnabled: true, - variablesMap: new Dictionary() - ) - } - } - ) }, + "feat2", new OptimizelyFeature( + "11567102052", + "feat2", + deliveryRules: new List() + { + new OptimizelyExperiment( + "11488548028", + "11488548028", + "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", + new Dictionary + { + { + "11557362670", new OptimizelyVariation( + "11557362670", + "11557362670", + true, + new Dictionary() + ) + }, + } + ), + }, experimentRules: new List(), experimentsMap: new Dictionary(), variablesMap: new Dictionary()) }, { - "feat2_with_var", new OptimizelyFeature ( - id: "11567102053", - key: "feat2_with_var", + "feat2_with_var", new OptimizelyFeature( + "11567102053", + "feat2_with_var", deliveryRules: new List() { new OptimizelyExperiment( - id: "11630490912", - key:"11630490912", - audiences: "", - variationsMap: new Dictionary + "11630490912", + "11630490912", + "", + new Dictionary + { + { + "11475708559", new OptimizelyVariation( + "11475708559", + "11475708559", + false, + new Dictionary() { { - "11475708559", new OptimizelyVariation ( - id: "11475708559", - key: "11475708559", - featureEnabled: false, - variablesMap: new Dictionary() - { - { - "z" , new OptimizelyVariable ( - id: "11535264367", - key: "z", - type: "integer", - value: "10") - } - }) - } - } - ) + "z", new OptimizelyVariable( + "11535264367", + "z", + "integer", + "10") + }, + }) + }, + } + ), }, experimentRules: new List() { - new OptimizelyExperiment ( - id: "1323241599", - key:"feat2_with_var_test", - audiences: "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", - variationsMap: new Dictionary + new OptimizelyExperiment( + "1323241599", + "feat2_with_var_test", + "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", + new Dictionary + { { - { - "variation_2", new OptimizelyVariation ( - id: "1423767505", - key: "variation_2", - featureEnabled: true, - variablesMap: new Dictionary + "variation_2", new OptimizelyVariation( + "1423767505", + "variation_2", + true, + new Dictionary + { { - { - "z" , new OptimizelyVariable ( - id: "11535264367", - key: "z", - type: "integer", - value: "150") - } - }) - } - } - ) + "z", new OptimizelyVariable( + "11535264367", + "z", + "integer", + "150") + }, + }) + }, + } + ), }, experimentsMap: new Dictionary { { - "feat2_with_var_test", new OptimizelyExperiment ( - id: "1323241599", - key:"feat2_with_var_test", - audiences: "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", - variationsMap: new Dictionary + "feat2_with_var_test", new OptimizelyExperiment( + "1323241599", + "feat2_with_var_test", + "(\"exactString\" OR \"substringString\") AND (\"exists\" OR \"exactNumber\" OR \"gtNumber\" OR \"ltNumber\" OR \"exactBoolean\")", + new Dictionary { { - "variation_2", new OptimizelyVariation ( - id: "1423767505", - key: "variation_2", - featureEnabled: true, - variablesMap: new Dictionary + "variation_2", new OptimizelyVariation( + "1423767505", + "variation_2", + true, + new Dictionary { { - "z" , new OptimizelyVariable ( - id: "11535264367", - key: "z", - type: "integer", - value: "150") - } + "z", new OptimizelyVariable( + "11535264367", + "z", + "integer", + "150") + }, }) - } + }, } ) - } + }, }, variablesMap: new Dictionary { { - "z", new OptimizelyVariable (id: "11535264367" , key: "z", type: "integer", value: "10") - } + "z", new OptimizelyVariable("11535264367", "z", "integer", "10") + }, }) - } + }, }; - OptimizelyConfig optimizelyConfig = new OptimizelyConfigService(datafileProjectConfig).GetOptimizelyConfig(); - OptimizelyConfig expectedOptimizelyConfig = new OptimizelyConfig(datafileProjectConfig.Revision, + var optimizelyConfig = + new OptimizelyConfigService(datafileProjectConfig).GetOptimizelyConfig(); + var expectedOptimizelyConfig = new OptimizelyConfig(datafileProjectConfig.Revision, datafileProjectConfig.SDKKey, datafileProjectConfig.EnvironmentKey, - attributes: new OptimizelyAttribute[] + new OptimizelyAttribute[] { new OptimizelyAttribute { - Id = "594015", Key = "house" + Id = "594015", Key = "house", }, new OptimizelyAttribute { - Id = "594016", Key = "lasers" + Id = "594016", Key = "lasers", }, new OptimizelyAttribute { - Id = "594017", Key = "should_do_it" + Id = "594017", Key = "should_do_it", }, new OptimizelyAttribute { - Id = "594018", Key = "favorite_ice_cream" - } + Id = "594018", Key = "favorite_ice_cream", + }, }, - audiences: new OptimizelyAudience[] + new OptimizelyAudience[] { - new OptimizelyAudience("0", "$$dummy", "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}"), - new OptimizelyAudience("3468206643", "exactBoolean", "[\"and\",[\"or\",[\"or\",{\"name\":\"should_do_it\",\"type\":\"custom_attribute\",\"match\":\"exact\",\"value\":true}]]]"), - new OptimizelyAudience("3468206646", "exactNumber", "[\"and\",[\"or\",[\"or\",{\"name\":\"lasers\",\"type\":\"custom_attribute\",\"match\":\"exact\",\"value\":45.5}]]]"), - new OptimizelyAudience("3468206642", "exactString", "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]"), - new OptimizelyAudience("3988293899", "exists", "[\"and\",[\"or\",[\"or\",{\"name\":\"favorite_ice_cream\",\"type\":\"custom_attribute\",\"match\":\"exists\"}]]]"), - new OptimizelyAudience("3468206647", "gtNumber", "[\"and\",[\"or\",[\"or\",{\"name\":\"lasers\",\"type\":\"custom_attribute\",\"match\":\"gt\",\"value\":70}]]]"), - new OptimizelyAudience("3468206644", "ltNumber", "[\"and\",[\"or\",[\"or\",{\"name\":\"lasers\",\"type\":\"custom_attribute\",\"match\":\"lt\",\"value\":1.0}]]]"), - new OptimizelyAudience("3468206645", "notChrome", "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]"), - new OptimizelyAudience("3468206648", "notExist", "[\"not\",{\"name\":\"input_value\",\"type\":\"custom_attribute\",\"match\":\"exists\"}]"), - new OptimizelyAudience("3988293898", "substringString", "[\"and\",[\"or\",[\"or\",{\"name\":\"house\",\"type\":\"custom_attribute\",\"match\":\"substring\",\"value\":\"Slytherin\"}]]]"), + new OptimizelyAudience("0", "$$dummy", + "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}"), + new OptimizelyAudience("3468206643", "exactBoolean", + "[\"and\",[\"or\",[\"or\",{\"name\":\"should_do_it\",\"type\":\"custom_attribute\",\"match\":\"exact\",\"value\":true}]]]"), + new OptimizelyAudience("3468206646", "exactNumber", + "[\"and\",[\"or\",[\"or\",{\"name\":\"lasers\",\"type\":\"custom_attribute\",\"match\":\"exact\",\"value\":45.5}]]]"), + new OptimizelyAudience("3468206642", "exactString", + "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]"), + new OptimizelyAudience("3988293899", "exists", + "[\"and\",[\"or\",[\"or\",{\"name\":\"favorite_ice_cream\",\"type\":\"custom_attribute\",\"match\":\"exists\"}]]]"), + new OptimizelyAudience("3468206647", "gtNumber", + "[\"and\",[\"or\",[\"or\",{\"name\":\"lasers\",\"type\":\"custom_attribute\",\"match\":\"gt\",\"value\":70}]]]"), + new OptimizelyAudience("3468206644", "ltNumber", + "[\"and\",[\"or\",[\"or\",{\"name\":\"lasers\",\"type\":\"custom_attribute\",\"match\":\"lt\",\"value\":1.0}]]]"), + new OptimizelyAudience("3468206645", "notChrome", + "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]"), + new OptimizelyAudience("3468206648", "notExist", + "[\"not\",{\"name\":\"input_value\",\"type\":\"custom_attribute\",\"match\":\"exists\"}]"), + new OptimizelyAudience("3988293898", "substringString", + "[\"and\",[\"or\",[\"or\",{\"name\":\"house\",\"type\":\"custom_attribute\",\"match\":\"substring\",\"value\":\"Slytherin\"}]]]"), }, - events: new OptimizelyEvent[] + new OptimizelyEvent[] { new OptimizelyEvent() { - Id = "594089", Key = "item_bought", ExperimentIds = new string[] { "11564051718", "1323241597" } + Id = "594089", Key = "item_bought", + ExperimentIds = new string[] { "11564051718", "1323241597" }, }, new OptimizelyEvent() { - Id = "594090", Key = "user_signed_up", ExperimentIds = new string[] { "1323241598", "1323241599" } - } + Id = "594090", Key = "user_signed_up", + ExperimentIds = new string[] { "1323241598", "1323241599" }, + }, }, - experimentsMap: experimentsMap, - featuresMap: featuresMap, - datafile: TestData.TypedAudienceDatafile); + experimentsMap, + featuresMap, + TestData.TypedAudienceDatafile); Assertions.AreEqual(expectedOptimizelyConfig, optimizelyConfig); } @@ -560,75 +611,91 @@ public void TestGetOptimizelyConfigService() [Test] public void TestOptimizelyConfigEntity() { - OptimizelyConfig expectedOptlyFeature = new OptimizelyConfig("123", + var expectedOptlyFeature = new OptimizelyConfig("123", "testSdkKey", "Development", - attributes: new OptimizelyAttribute[0], - audiences: new OptimizelyAudience[0], - events: new OptimizelyEvent[0], - experimentsMap: new Dictionary(), - featuresMap: new Dictionary() - ); + new OptimizelyAttribute[0], + new OptimizelyAudience[0], + new OptimizelyEvent[0], + new Dictionary(), + new Dictionary() + ); Assert.AreEqual(expectedOptlyFeature.Revision, "123"); Assert.AreEqual(expectedOptlyFeature.SDKKey, "testSdkKey"); Assert.AreEqual(expectedOptlyFeature.EnvironmentKey, "Development"); Assert.AreEqual(expectedOptlyFeature.Attributes, new Entity.Attribute[0]); Assert.AreEqual(expectedOptlyFeature.Audiences, new OptimizelyAudience[0]); Assert.AreEqual(expectedOptlyFeature.Events, new Entity.Event[0]); - Assert.AreEqual(expectedOptlyFeature.ExperimentsMap, new Dictionary()); - Assert.AreEqual(expectedOptlyFeature.FeaturesMap, new Dictionary()); + Assert.AreEqual(expectedOptlyFeature.ExperimentsMap, + new Dictionary()); + Assert.AreEqual(expectedOptlyFeature.FeaturesMap, + new Dictionary()); } [Test] public void TestOptimizelyFeatureEntity() { - OptimizelyFeature expectedOptlyFeature = new OptimizelyFeature("1", "featKey", + var expectedOptlyFeature = new OptimizelyFeature("1", "featKey", new List(), new List(), new Dictionary(), new Dictionary() - ); + ); Assert.AreEqual(expectedOptlyFeature.Id, "1"); Assert.AreEqual(expectedOptlyFeature.Key, "featKey"); Assert.AreEqual(expectedOptlyFeature.ExperimentRules, new List()); Assert.AreEqual(expectedOptlyFeature.DeliveryRules, new List()); Assert.AreEqual(expectedOptlyFeature.Key, "featKey"); - Assert.AreEqual(expectedOptlyFeature.ExperimentsMap, new Dictionary()); - Assert.AreEqual(expectedOptlyFeature.VariablesMap, new Dictionary()); + Assert.AreEqual(expectedOptlyFeature.ExperimentsMap, + new Dictionary()); + Assert.AreEqual(expectedOptlyFeature.VariablesMap, + new Dictionary()); } [Test] public void TestOptimizelyExperimentEntity() { - OptimizelyExperiment expectedOptlyExp = new OptimizelyExperiment("1", "exKey", + var expectedOptlyExp = new OptimizelyExperiment("1", "exKey", "", - new Dictionary { + new Dictionary + { { - "varKey", new OptimizelyVariation("1", "varKey", true, new Dictionary()) - } + "varKey", + new OptimizelyVariation("1", "varKey", true, + new Dictionary()) + }, }); Assert.AreEqual(expectedOptlyExp.Id, "1"); Assert.AreEqual(expectedOptlyExp.Key, "exKey"); Assert.AreEqual(expectedOptlyExp.Audiences, ""); - Assert.AreEqual(expectedOptlyExp.VariationsMap["varKey"], new OptimizelyVariation("1", "varKey", true, new Dictionary())); + Assert.AreEqual(expectedOptlyExp.VariationsMap["varKey"], + new OptimizelyVariation("1", "varKey", true, + new Dictionary())); } [Test] public void TestOptimizelyVariationEntity() { - OptimizelyVariation expectedOptlyVariation = new OptimizelyVariation("1", "varKey", true, new Dictionary { - { "variableKey", new OptimizelyVariable("varId", "variableKey", "integer", "2")} - }); + var expectedOptlyVariation = new OptimizelyVariation("1", "varKey", true, + new Dictionary + { + { + "variableKey", + new OptimizelyVariable("varId", "variableKey", "integer", "2") + }, + }); Assert.AreEqual(expectedOptlyVariation.Id, "1"); Assert.AreEqual(expectedOptlyVariation.Key, "varKey"); Assert.AreEqual(expectedOptlyVariation.FeatureEnabled, true); - Assert.AreEqual(expectedOptlyVariation.VariablesMap["variableKey"], new OptimizelyVariable("varId", "variableKey", "integer", "2")); + Assert.AreEqual(expectedOptlyVariation.VariablesMap["variableKey"], + new OptimizelyVariable("varId", "variableKey", "integer", "2")); } [Test] public void TestOptimizelyVariableEntity() { - OptimizelyVariable expectedOptlyVariable = new OptimizelyVariable("varId", "variableKey", "integer", "2"); + var expectedOptlyVariable = + new OptimizelyVariable("varId", "variableKey", "integer", "2"); Assert.AreEqual(expectedOptlyVariable.Id, "varId"); Assert.AreEqual(expectedOptlyVariable.Key, "variableKey"); Assert.AreEqual(expectedOptlyVariable.Type, "integer"); diff --git a/OptimizelySDK.Tests/OptimizelyDecisions/OptimizelyDecisionTest.cs b/OptimizelySDK.Tests/OptimizelyDecisions/OptimizelyDecisionTest.cs index 23c25b223..79aafcd9b 100644 --- a/OptimizelySDK.Tests/OptimizelyDecisions/OptimizelyDecisionTest.cs +++ b/OptimizelySDK.Tests/OptimizelyDecisions/OptimizelyDecisionTest.cs @@ -40,15 +40,18 @@ public void Initialize() LoggerMock = new Mock(); LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); } - + [Test] public void TestNewErrorDecision() { - var optimizelyDecision = OptimizelyDecision.NewErrorDecision("var_key", null, "some error message", ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyDecision = OptimizelyDecision.NewErrorDecision("var_key", null, + "some error message", ErrorHandlerMock.Object, LoggerMock.Object); Assert.IsNull(optimizelyDecision.VariationKey); Assert.AreEqual(optimizelyDecision.FlagKey, "var_key"); - Assert.AreEqual(optimizelyDecision.Variables.ToDictionary(), new Dictionary()); - Assert.AreEqual(optimizelyDecision.Reasons, new List() { "some error message" }); + Assert.AreEqual(optimizelyDecision.Variables.ToDictionary(), + new Dictionary()); + Assert.AreEqual(optimizelyDecision.Reasons, + new List() { "some error message" }); Assert.IsNull(optimizelyDecision.RuleKey); Assert.False(optimizelyDecision.Enabled); } @@ -56,16 +59,21 @@ public void TestNewErrorDecision() [Test] public void TestNewDecision() { - var variableMap = new Dictionary() { + var variableMap = new Dictionary() + { { "strField", "john doe" }, { "intField", 12 }, - { "objectField", new Dictionary () { - { "inner_field_int", 3 } + { + "objectField", new Dictionary() + { + { "inner_field_int", 3 }, } - } + }, }; - var optimizelyJSONUsingMap = new OptimizelyJSON(variableMap, ErrorHandlerMock.Object, LoggerMock.Object); - string expectedStringObj = "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; + var optimizelyJSONUsingMap = + new OptimizelyJSON(variableMap, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedStringObj = + "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; var optimizelyDecision = new OptimizelyDecision("var_key", true, @@ -86,25 +94,41 @@ public void TestNewDecision() public void TestNewDecisionReasonWithIncludeReasons() { var decisionReasons = new DecisionReasons(); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.INCLUDE_REASONS }; - decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, "invalid_key")); - - Assert.AreEqual(decisionReasons.ToReport(decideOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))[0], "No flag was found for key \"invalid_key\"."); - decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.VARIABLE_VALUE_INVALID, "invalid_key")); - Assert.AreEqual(decisionReasons.ToReport(decideOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))[1], "Variable value for key \"invalid_key\" is invalid or wrong type."); + var decideOptions = new OptimizelyDecideOption[] + { OptimizelyDecideOption.INCLUDE_REASONS }; + decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, + "invalid_key")); + + Assert.AreEqual( + decisionReasons.ToReport( + decideOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))[0], + "No flag was found for key \"invalid_key\"."); + decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.VARIABLE_VALUE_INVALID, + "invalid_key")); + Assert.AreEqual( + decisionReasons.ToReport( + decideOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))[1], + "Variable value for key \"invalid_key\" is invalid or wrong type."); decisionReasons.AddInfo("Some info message."); - Assert.AreEqual(decisionReasons.ToReport(decideOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))[2], "Some info message."); + Assert.AreEqual( + decisionReasons.ToReport( + decideOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))[2], + "Some info message."); } [Test] public void TestNewDecisionReasonWithoutIncludeReasons() { var decisionReasons = new DecisionReasons(); - decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, "invalid_key")); + decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, + "invalid_key")); - Assert.AreEqual(decisionReasons.ToReport()[0], "No flag was found for key \"invalid_key\"."); - decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.VARIABLE_VALUE_INVALID, "invalid_key")); - Assert.AreEqual(decisionReasons.ToReport()[1], "Variable value for key \"invalid_key\" is invalid or wrong type."); + Assert.AreEqual(decisionReasons.ToReport()[0], + "No flag was found for key \"invalid_key\"."); + decisionReasons.AddError(DecisionMessage.Reason(DecisionMessage.VARIABLE_VALUE_INVALID, + "invalid_key")); + Assert.AreEqual(decisionReasons.ToReport()[1], + "Variable value for key \"invalid_key\" is invalid or wrong type."); decisionReasons.AddInfo("Some info message."); Assert.AreEqual(decisionReasons.ToReport().Count, 2); } diff --git a/OptimizelySDK.Tests/OptimizelyFactoryTest.cs b/OptimizelySDK.Tests/OptimizelyFactoryTest.cs index 92d2a23b2..626eaeca7 100644 --- a/OptimizelySDK.Tests/OptimizelyFactoryTest.cs +++ b/OptimizelySDK.Tests/OptimizelyFactoryTest.cs @@ -26,12 +26,14 @@ using OptimizelySDK.Tests.EventTest; using OptimizelySDK.Tests.Utils; using System; + namespace OptimizelySDK.Tests { [TestFixture] public class OptimizelyFactoryTest { private Mock LoggerMock; + [SetUp] public void Initialize() { @@ -55,7 +57,7 @@ public void TestOptimizelyInstanceUsingConfigFile() AutoUpdate = true, DatafileAccessToken = "testingtoken123", BlockingTimeout = TimeSpan.FromSeconds(10), - PollingInterval = TimeSpan.FromSeconds(2) + PollingInterval = TimeSpan.FromSeconds(2), }; Assert.AreEqual(actualConfigManagerProps, expectedConfigManagerProps); @@ -78,7 +80,7 @@ public void TestProjectConfigManagerUsingSDKKey() LastModified = "", AutoUpdate = true, BlockingTimeout = TimeSpan.FromSeconds(30), - PollingInterval = TimeSpan.FromMilliseconds(2023) + PollingInterval = TimeSpan.FromMilliseconds(2023), }; Assert.AreEqual(actualConfigManagerProps, expectedConfigManagerProps); @@ -88,7 +90,8 @@ public void TestProjectConfigManagerUsingSDKKey() [Test] public void TestProjectConfigManagerWithDatafileAccessToken() { - var optimizely = OptimizelyFactory.NewDefaultInstance("my-sdk-key", null, "access-token"); + var optimizely = + OptimizelyFactory.NewDefaultInstance("my-sdk-key", null, "access-token"); // Check values are loaded from app.config or not. var projectConfigManager = optimizely.ProjectConfigManager as HttpProjectConfigManager; @@ -102,7 +105,7 @@ public void TestProjectConfigManagerWithDatafileAccessToken() DatafileAccessToken = "access-token", AutoUpdate = true, BlockingTimeout = TimeSpan.FromSeconds(30), - PollingInterval = TimeSpan.FromMilliseconds(2023) + PollingInterval = TimeSpan.FromMilliseconds(2023), }; Assert.AreEqual(actualConfigManagerProps, expectedConfigManagerProps); @@ -111,7 +114,8 @@ public void TestProjectConfigManagerWithDatafileAccessToken() } [Test] - public void TestOptimizelyInstanceUsingConfigNotUseFactoryClassBlockingTimeoutAndPollingInterval() + public void + TestOptimizelyInstanceUsingConfigNotUseFactoryClassBlockingTimeoutAndPollingInterval() { OptimizelyFactory.SetBlockingTimeOutPeriod(TimeSpan.FromSeconds(30)); OptimizelyFactory.SetPollingInterval(TimeSpan.FromMilliseconds(2023)); @@ -128,7 +132,7 @@ public void TestOptimizelyInstanceUsingConfigNotUseFactoryClassBlockingTimeoutAn AutoUpdate = true, DatafileAccessToken = "testingtoken123", BlockingTimeout = TimeSpan.FromMilliseconds(10000), - PollingInterval = TimeSpan.FromMilliseconds(2000) + PollingInterval = TimeSpan.FromMilliseconds(2000), }; Assert.AreEqual(actualConfigManagerProps, expectedConfigManagerProps); @@ -138,18 +142,20 @@ public void TestOptimizelyInstanceUsingConfigNotUseFactoryClassBlockingTimeoutAn [Test] public void TestProjectConfigManagerWithCustomProjectConfigManager() { - var projectConfigManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("10192104166") - .WithFormat("https://optimizely.com/json/{0}.json") - .WithPollingInterval(TimeSpan.FromMilliseconds(3000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(4500)) - .WithStartByDefault() - .WithAccessToken("access-token") - .Build(true); + var projectConfigManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("10192104166"). + WithFormat("https://optimizely.com/json/{0}.json"). + WithPollingInterval(TimeSpan.FromMilliseconds(3000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(4500)). + WithStartByDefault(). + WithAccessToken("access-token"). + Build(true); var optimizely = OptimizelyFactory.NewDefaultInstance(projectConfigManager); - var actualProjectConfigManager = optimizely.ProjectConfigManager as HttpProjectConfigManager; - var actualConfigManagerProps = new ProjectConfigManagerProps(actualProjectConfigManager); + var actualProjectConfigManager = + optimizely.ProjectConfigManager as HttpProjectConfigManager; + var actualConfigManagerProps = + new ProjectConfigManagerProps(actualProjectConfigManager); var expectedConfigManagerProps = new ProjectConfigManagerProps(projectConfigManager); Assert.AreEqual(actualConfigManagerProps, expectedConfigManagerProps); optimizely.Dispose(); @@ -160,13 +166,15 @@ public void TestEventProcessorWithDefaultEventBatching() { var optimizely = OptimizelyFactory.NewDefaultInstance(); - var batchEventProcessor = Reflection.GetFieldValue(optimizely, "EventProcessor"); + var batchEventProcessor = + Reflection.GetFieldValue(optimizely, + "EventProcessor"); var actualEventProcessorProps = new EventProcessorProps(batchEventProcessor); var expectedEventProcessorProps = new EventProcessorProps { BatchSize = 10, FlushInterval = TimeSpan.FromSeconds(2), - TimeoutInterval = TimeSpan.FromSeconds(10) + TimeoutInterval = TimeSpan.FromSeconds(10), }; Assert.AreEqual(actualEventProcessorProps, expectedEventProcessorProps); optimizely.Dispose(); @@ -180,13 +188,15 @@ public void TestEventProcessorWithEventBatchingBatchSizeAndInterval() var optimizely = OptimizelyFactory.NewDefaultInstance("sdk-Key"); - var batchEventProcessor = Reflection.GetFieldValue(optimizely, "EventProcessor"); + var batchEventProcessor = + Reflection.GetFieldValue(optimizely, + "EventProcessor"); var actualEventProcessorProps = new EventProcessorProps(batchEventProcessor); var expectedEventProcessorProps = new EventProcessorProps { BatchSize = 2, FlushInterval = TimeSpan.FromSeconds(4), - TimeoutInterval = TimeSpan.FromMinutes(5) + TimeoutInterval = TimeSpan.FromMinutes(5), }; Assert.AreEqual(actualEventProcessorProps, expectedEventProcessorProps); optimizely.Dispose(); @@ -197,21 +207,24 @@ public void TestEventProcessorWithBatchEventProcessorObj() { var eventDispatcher = new DefaultEventDispatcher(LoggerMock.Object); var notificationCenter = new NotificationCenter(); - var projectConfigManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("10192104166") - .Build(true); - - var batchEventProcessor = new BatchEventProcessor.Builder() - .WithLogger(LoggerMock.Object) - .WithMaxBatchSize(20) - .WithFlushInterval(TimeSpan.FromSeconds(3)) - .WithEventDispatcher(eventDispatcher) - .WithNotificationCenter(notificationCenter) - .Build(); - - var optimizely = OptimizelyFactory.NewDefaultInstance(projectConfigManager, notificationCenter, eventProcessor: batchEventProcessor); - - var actualbatchEventProcessor = Reflection.GetFieldValue(optimizely, "EventProcessor"); + var projectConfigManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("10192104166"). + Build(true); + + var batchEventProcessor = new BatchEventProcessor.Builder(). + WithLogger(LoggerMock.Object). + WithMaxBatchSize(20). + WithFlushInterval(TimeSpan.FromSeconds(3)). + WithEventDispatcher(eventDispatcher). + WithNotificationCenter(notificationCenter). + Build(); + + var optimizely = OptimizelyFactory.NewDefaultInstance(projectConfigManager, + notificationCenter, eventProcessor: batchEventProcessor); + + var actualbatchEventProcessor = + Reflection.GetFieldValue(optimizely, + "EventProcessor"); var actualEventProcessorProps = new EventProcessorProps(actualbatchEventProcessor); var expectedEventProcessorProps = new EventProcessorProps(batchEventProcessor); Assert.AreEqual(actualEventProcessorProps, expectedEventProcessorProps); @@ -222,11 +235,13 @@ public void TestEventProcessorWithBatchEventProcessorObj() public void TestGetFeatureVariableJSONEmptyDatafileTest() { var httpClientMock = new Mock(); - var task = TestHttpProjectConfigManagerUtil.MockSendAsync(httpClientMock, TestData.EmptyDatafile, TimeSpan.Zero, System.Net.HttpStatusCode.OK); + var task = TestHttpProjectConfigManagerUtil.MockSendAsync(httpClientMock, + TestData.EmptyDatafile, TimeSpan.Zero, System.Net.HttpStatusCode.OK); TestHttpProjectConfigManagerUtil.SetClientFieldValue(httpClientMock.Object); var optimizely = OptimizelyFactory.NewDefaultInstance("sdk-key"); - Assert.Null(optimizely.GetFeatureVariableJSON("no-feature-variable", "no-variable-key", "userId")); + Assert.Null(optimizely.GetFeatureVariableJSON("no-feature-variable", "no-variable-key", + "userId")); optimizely.Dispose(); } } diff --git a/OptimizelySDK.Tests/OptimizelyJSONTest.cs b/OptimizelySDK.Tests/OptimizelyJSONTest.cs index c07bb137d..549be1acd 100644 --- a/OptimizelySDK.Tests/OptimizelyJSONTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJSONTest.cs @@ -25,29 +25,30 @@ namespace OptimizelySDK.Tests { - class ParentJson + internal class ParentJson { public string strField { get; set; } public int intField { get; set; } public double doubleField { get; set; } public bool boolField { get; set; } public ObjectJson objectField { get; set; } - } - class ObjectJson + + internal class ObjectJson { public int inner_field_int { get; set; } public double inner_field_double { get; set; } - public string inner_field_string {get;set;} + public string inner_field_string { get; set; } public bool inner_field_boolean { get; set; } } - class Field4 + internal class Field4 { public long inner_field1 { get; set; } public InnerField2 inner_field2 { get; set; } } - class InnerField2 : List { } + + internal class InnerField2 : List { } [TestFixture] @@ -67,88 +68,114 @@ public void Initialize() LoggerMock = new Mock(); LoggerMock.Setup(i => i.Log(It.IsAny(), It.IsAny())); - Payload = "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3, 4.23, true]}, \"field5\": true, }"; - Map = new Dictionary() { + Payload = + "{ \"field1\": 1, \"field2\": 2.5, \"field3\": \"three\", \"field4\": {\"inner_field1\":3,\"inner_field2\":[\"1\",\"2\", 3, 4.23, true]}, \"field5\": true, }"; + Map = new Dictionary() + { { "strField", "john doe" }, { "intField", 12 }, { "doubleField", 2.23 }, - { "boolField", true}, - { "objectField", new Dictionary () { + { "boolField", true }, + { + "objectField", new Dictionary() + { { "inner_field_int", 3 }, { "inner_field_double", 13.21 }, { "inner_field_string", "john" }, - { "inner_field_boolean", true } + { "inner_field_boolean", true }, } - } + }, }; } [Test] public void TestOptimizelyJsonObjectIsValid() { - var optimizelyJSONUsingMap = new OptimizelyJSON(Map, ErrorHandlerMock.Object, LoggerMock.Object); - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingMap = + new OptimizelyJSON(Map, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); Assert.IsNotNull(optimizelyJSONUsingMap); Assert.IsNotNull(optimizelyJSONUsingString); } + [Test] public void TestToStringReturnValidString() { - var map = new Dictionary() { + var map = new Dictionary() + { { "strField", "john doe" }, { "intField", 12 }, - { "objectField", new Dictionary () { - { "inner_field_int", 3 } + { + "objectField", new Dictionary() + { + { "inner_field_int", 3 }, } - } + }, }; - var optimizelyJSONUsingMap = new OptimizelyJSON(map, ErrorHandlerMock.Object, LoggerMock.Object); - string str = optimizelyJSONUsingMap.ToString(); - string expectedStringObj = "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; + var optimizelyJSONUsingMap = + new OptimizelyJSON(map, ErrorHandlerMock.Object, LoggerMock.Object); + var str = optimizelyJSONUsingMap.ToString(); + var expectedStringObj = + "{\"strField\":\"john doe\",\"intField\":12,\"objectField\":{\"inner_field_int\":3}}"; Assert.AreEqual(expectedStringObj, str); } [Test] public void TestGettingErrorUponInvalidJsonString() { - var optimizelyJSONUsingString = new OptimizelyJSON("{\"invalid\":}", ErrorHandlerMock.Object, LoggerMock.Object); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), Times.Once); - ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); + var optimizelyJSONUsingString = new OptimizelyJSON("{\"invalid\":}", + ErrorHandlerMock.Object, LoggerMock.Object); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, "Provided string could not be converted to map."), + Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), + Times.Once); } [Test] public void TestOptimizelyJsonGetVariablesWhenSetUsingMap() { - var optimizelyJSONUsingMap = new OptimizelyJSON(Map, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingMap = + new OptimizelyJSON(Map, ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(optimizelyJSONUsingMap.GetValue("strField"), "john doe"); Assert.AreEqual(optimizelyJSONUsingMap.GetValue("intField"), 12); Assert.AreEqual(optimizelyJSONUsingMap.GetValue("doubleField"), 2.23); Assert.AreEqual(optimizelyJSONUsingMap.GetValue("boolField"), true); Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_int"), 3); - Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_double"), 13.21); - Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_string"), "john"); - Assert.AreEqual(optimizelyJSONUsingMap.GetValue("objectField.inner_field_boolean"), true); - Assert.IsTrue(optimizelyJSONUsingMap.GetValue>("objectField") is Dictionary); + Assert.AreEqual( + optimizelyJSONUsingMap.GetValue("objectField.inner_field_double"), 13.21); + Assert.AreEqual( + optimizelyJSONUsingMap.GetValue("objectField.inner_field_string"), "john"); + Assert.AreEqual( + optimizelyJSONUsingMap.GetValue("objectField.inner_field_boolean"), true); + Assert.IsTrue( + optimizelyJSONUsingMap.GetValue>("objectField") is + Dictionary); } [Test] public void TestOptimizelyJsonGetVariablesWhenSetUsingString() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(optimizelyJSONUsingString.GetValue("field1"), 1); Assert.AreEqual(optimizelyJSONUsingString.GetValue("field2"), 2.5); Assert.AreEqual(optimizelyJSONUsingString.GetValue("field3"), "three"); Assert.AreEqual(optimizelyJSONUsingString.GetValue("field4.inner_field1"), 3); - Assert.True(TestData.CompareObjects(optimizelyJSONUsingString.GetValue>("field4.inner_field2"), new List() { "1", "2", 3, 4.23, true })); + Assert.True(TestData.CompareObjects( + optimizelyJSONUsingString.GetValue>("field4.inner_field2"), + new List() { "1", "2", 3, 4.23, true })); } [Test] public void TestGetValueReturnsEntireDictWhenJsonPathIsEmptyAndTypeIsValid() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var actualDict = optimizelyJSONUsingString.ToDictionary(); var expectedValue = optimizelyJSONUsingString.GetValue>(""); Assert.NotNull(expectedValue); @@ -159,8 +186,10 @@ public void TestGetValueReturnsEntireDictWhenJsonPathIsEmptyAndTypeIsValid() public void TestGetValueReturnsDefaultValueWhenJsonIsInvalid() { var payload = "{ \"field1\" : {1:\"Csharp\", 2:\"Java\"} }"; - var optimizelyJSONUsingString = new OptimizelyJSON(payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = optimizelyJSONUsingString.GetValue>("field1"); + var optimizelyJSONUsingString = + new OptimizelyJSON(payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = + optimizelyJSONUsingString.GetValue>("field1"); // Even though above given JSON is not valid, newtonsoft is parsing it so Assert.IsNotNull(expectedValue); } @@ -169,15 +198,18 @@ public void TestGetValueReturnsDefaultValueWhenJsonIsInvalid() public void TestGetValueReturnsDefaultValueWhenTypeIsInvalid() { var payload = "{ \"field1\" : {\"1\":\"Csharp\",\"2\":\"Java\"} }"; - var optimizelyJSONUsingString = new OptimizelyJSON(payload, ErrorHandlerMock.Object, LoggerMock.Object); - var expectedValue = optimizelyJSONUsingString.GetValue>("field1"); + var optimizelyJSONUsingString = + new OptimizelyJSON(payload, ErrorHandlerMock.Object, LoggerMock.Object); + var expectedValue = + optimizelyJSONUsingString.GetValue>("field1"); Assert.IsNotNull(expectedValue); } [Test] public void TestGetValueReturnsNullWhenJsonPathIsEmptyAndTypeIsOfObject() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue(""); Assert.NotNull(expectedValue); } @@ -185,47 +217,61 @@ public void TestGetValueReturnsNullWhenJsonPathIsEmptyAndTypeIsOfObject() [Test] public void TestGetValueReturnsDefaultValueWhenJsonPathIsEmptyAndTypeIsNotValid() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue(""); Assert.IsNull(expectedValue); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for path could not be assigned to provided type."), Times.Once); - ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Value for path could not be assigned to provided type."), Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), + Times.Once); } [Test] public void TestGetValueReturnsDefaultValueWhenJsonPathIsInvalid() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue("field11"); Assert.IsNull(expectedValue); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); - ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), + Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), + Times.Once); } [Test] public void TestGetValueReturnsDefaultValueWhenJsonPath1IsInvalid() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue("field4."); Assert.IsNull(expectedValue); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); - ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), + Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), + Times.Once); } [Test] public void TestGetValueReturnsDefaultValueWhenJsonPath2IsInvalid() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue("field4..inner_field1"); Assert.IsNull(expectedValue); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), Times.Once); - ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), Times.Once); - } - + LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Value for JSON key not found."), + Times.Once); + ErrorHandlerMock.Verify(er => er.HandleError(It.IsAny()), + Times.Once); + } + [Test] public void TestGetValueObjectNotModifiedIfCalledTwice() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue("field4.inner_field1"); var expectedValue2 = optimizelyJSONUsingString.GetValue("field4.inner_field1"); @@ -235,20 +281,23 @@ public void TestGetValueObjectNotModifiedIfCalledTwice() [Test] public void TestGetValueReturnsUsingGivenClassType() { - var optimizelyJSONUsingString = new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJSONUsingString = + new OptimizelyJSON(Payload, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJSONUsingString.GetValue("field4"); - + Assert.AreEqual(expectedValue.inner_field1, 3); - Assert.AreEqual(expectedValue.inner_field2, new List() { "1", "2", 3, 4.23, true }); + Assert.AreEqual(expectedValue.inner_field2, + new List() { "1", "2", 3, 4.23, true }); } [Test] public void TestGetValueReturnsCastedObject() { - var optimizelyJson = new OptimizelyJSON(Map, ErrorHandlerMock.Object, LoggerMock.Object); + var optimizelyJson = + new OptimizelyJSON(Map, ErrorHandlerMock.Object, LoggerMock.Object); var expectedValue = optimizelyJson.ToDictionary(); var actualValue = optimizelyJson.GetValue(null); - + Assert.IsTrue(TestData.CompareObjects(actualValue, expectedValue)); } } diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index 36a800d7e..0471595dd 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -14,7 +14,7 @@ v4.5 512 - + 1.2.1 @@ -41,7 +41,7 @@ ..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll True - + ..\packages\Moq.4.7.1\lib\net45\Moq.dll True @@ -54,97 +54,97 @@ ..\packages\NUnit.2.6.4\lib\nunit.framework.dll True - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + + @@ -156,14 +156,14 @@ keypair.snk - - - + + + - + - +