diff --git a/.editorconfig b/.editorconfig index 20fe9082..f7323810 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,243 +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 -indent_size = 4 +# All files +[*] indent_style = space -tab_width = 4 -# New line preferences -end_of_line = crlf -insert_final_newline = false +# 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 = crlf -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 diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index ba05e637..85bae292 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -6,35 +6,38 @@ on: branches: [master] pull_request: branches: [master] - + env: RELEASE_BRANCH: "master" + WINDOWS_2019_SN_PATH: C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\sn.exe jobs: lint_code_base: runs-on: ubuntu-latest - name: Lint Code Base + name: Lint Codebase steps: - - name: Checkout Code + - name: Checkout code uses: actions/checkout@v3 with: # Full git history is needed to get a proper list of changed files fetch-depth: 0 - - name: Lint Code Base + - name: Lint codebase uses: github/super-linter@v4 env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: master VALIDATE_CSHARP: true - VALIDATE_MARKDOWN: true integration_tests: + name: Run Integration Tests uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@master secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} - + fullstack_production_suite: + name: Run Optimizely Feature Experimentation Compatibility Suite uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@master with: FULLSTACK_TEST_REPO: ProdTesting @@ -43,7 +46,8 @@ jobs: TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} unit_test: - runs-on: ubuntu-latest + name: Build and Run Unit Tests + runs-on: windows-2019 # required version for Framework 4.0 env: REPO_SLUG: ${{ github.repository }} BUILD_NUMBER: ${{ github.run_id }} @@ -52,35 +56,40 @@ jobs: EVENT_TYPE: ${{ github.event_name }} CURRENT_BRANCH: ${{ github.head_ref || github.ref_name }} steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.x - - name: Restore nuget packages - run: | - nuget restore OptimizelySDK.Travis.sln - nuget install ./OptimizelySDK.Tests/packages.config -OutputDirectory ./packages - nuget install NUnit.Runners -Version 2.6.4 -OutputDirectory ./testrunner - - name: script - run: | - ./install_mono.sh - xbuild /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=$(pwd)/keypair.snk /p:Configuration=Release ./OptimizelySDK.Travis.sln - mono ./testrunner/NUnit.Runners.2.6.4/tools/nunit-console.exe ./OptimizelySDK.Tests/bin/Release/OptimizelySDK.Tests.dll - - name: find and sign dll + - name: Checkout code + uses: actions/checkout@v3 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1 + - name: Setup NuGet + uses: NuGet/setup-nuget@v1 + - name: Restore NuGet packages + run: nuget restore ./OptimizelySDK.Travis.sln + - name: Build solution + run: msbuild /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=$(pwd)/keypair.snk /p:Configuration=Release ./OptimizelySDK.Travis.sln + - name: Install NUnit Console + run: nuget install NUnit.Console -Version 3.15.2 -DirectDownload -OutputDirectory . + - name: Run NUnit tests + # https://docs.nunit.org/articles/nunit/running-tests/Console-Command-Line.html + run: ./NUnit.ConsoleRunner.3.15.2\tools\nunit3-console.exe /timeout 10000 /process Separate ./OptimizelySDK.Tests/bin/Release/OptimizelySDK.Tests.dll + - name: Find and sign all DLLs id: unit_tests run: | - sudo find . -path './OptimizelySDK*bin/Release/OptimizelySDK*.dll' -not -regex '.*Tests.*' -print0 | while IFS= read -r -d '' file; do sn -R $file ./keypair.snk; done - - name: Check on success + Get-ChildItem -Recurse -Exclude '.*Tests.*' -Include 'OptimizelySDK*.dll' | + Where-Object { $_.DirectoryName -match '\\bin\\Release' } | + Foreach-Object { & $env:WINDOWS_2019_SN_PATH -R $_.FullName ./keypair.snk } + - name: Install AWS CLI, deploy to S3 on successful tests & for release if: steps.unit_tests.outcome == 'success' && env.CURRENT_BRANCH == env.RELEASE_BRANCH && env.EVENT_TYPE == 'push' env: AWS_ACCESS_KEY_ID: ${{ secrets.OFTA_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.OFTA_SECRET }} AWS_DEFAULT_REGION: ${{ secrets.OFTA_REGION }} run: | - find . -path './OptimizelySDK*bin/Release/OptimizelySDK*.dll' -not -regex '.*Tests.*' -print0 | while IFS= read -r -d '' file; do (aws s3 cp $file s3://optly-fs-travisci-artifacts/${{ env.REPO_SLUG }}/${{ env.BUILD_NUMBER }}/${{ env.RUN_NUMBER }}/${{ env.ATTEMPT_NUM }}/$(basename $file)-unsigned); done - + Install-Module -Name AWS.Tools.Installer -Force; + Install-AWSToolsModule AWS.Tools.S3 -Force -CleanUp; + Get-ChildItem -Recurse -Exclude '.*Tests.*' -include 'OptimizelySDK*.dll' | Where-Object { $_.DirectoryName -match '\\bin\\Release' } | Foreach-Object { aws s3 cp $_.FullName s3://optly-fs-travisci-artifacts/${{ env.REPO_SLUG }}/${{ env.BUILD_NUMBER }}/${{ env.RUN_NUMBER }}/${{ env.ATTEMPT_NUM }}/$(basename $file)-unsigned } + netStandard16: + name: Build For .NET Standard 1.6 runs-on: windows-2022 env: REPO_SLUG: ${{ github.repository }} @@ -90,19 +99,17 @@ jobs: EVENT_TYPE: ${{ github.event_name }} CURRENT_BRANCH: ${{ github.head_ref || github.ref_name }} steps: - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v2 with: dotnet-version: 3.1.x - name: Restore dependencies run: dotnet restore OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj - - name: Build + - name: Build and sign Standard 1.6 project id: netStandard16_build - run: | - # strongname signing is taken care of in build step - dotnet build OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=D:\a\csharp-sdk\csharp-sdk\keypair.snk -c Release - # TODO: no dotnet test yet for NetStandard16 + run: dotnet build OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=D:\a\csharp-sdk\csharp-sdk\keypair.snk -c Release - name: Check on success if: steps.netStandard16_build.outcome == 'success' && env.CURRENT_BRANCH == env.RELEASE_BRANCH && env.EVENT_TYPE == 'push' env: @@ -113,6 +120,7 @@ jobs: (aws s3 cp ./OptimizelySDK.NetStandard16/bin/Release/netstandard1.6/OptimizelySDK.NetStandard16.dll s3://optly-fs-travisci-artifacts/${{ env.REPO_SLUG }}/${{ env.BUILD_NUMBER }}/${{ env.RUN_NUMBER }}/${{ env.ATTEMPT_NUM }}/OptimizelySDK.NetStandard16.dll-unsigned) netStandard20: + name: Build For .NET Standard 2.0 runs-on: windows-2022 env: REPO_SLUG: ${{ github.repository }} @@ -122,19 +130,17 @@ jobs: EVENT_TYPE: ${{ github.event_name }} CURRENT_BRANCH: ${{ github.head_ref || github.ref_name }} steps: - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v2 with: dotnet-version: 3.1.x - name: Restore dependencies run: dotnet restore OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj - - name: Build + - name: Build and sign Standard 2.0 project id: netStandard20_build - run: | - # strongname signing is taken care of in build step - dotnet build OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=D:\a\csharp-sdk\csharp-sdk\keypair.snk -c Release - # TODO: no dotnet test yet for NetStandard20 + run: dotnet build OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=D:\a\csharp-sdk\csharp-sdk\keypair.snk -c Release - name: Check on success if: steps.netStandard20_build.outcome == 'success' && env.CURRENT_BRANCH == env.RELEASE_BRANCH && env.EVENT_TYPE == 'push' env: diff --git a/.github/workflows/ticket_reference_check.yml b/.github/workflows/ticket_reference_check.yml index d2829e0c..9c028cca 100644 --- a/.github/workflows/ticket_reference_check.yml +++ b/.github/workflows/ticket_reference_check.yml @@ -1,16 +1,14 @@ -name: Jira ticket reference check +name: Jira Ticket Reference Check on: pull_request: - types: [opened, edited, reopened, synchronize] + types: [opened, edited, reopened, synchronize] jobs: - jira_ticket_reference_check: runs-on: ubuntu-latest - steps: - name: Check for Jira ticket reference uses: optimizely/github-action-ticket-reference-checker-public@master with: - bodyRegex: 'OASIS-(?\d+)' + bodyRegex: 'FSSDK-(?\d+)' diff --git a/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj b/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj index 7da4aa12..a2e34dcf 100644 --- a/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj +++ b/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj @@ -64,8 +64,8 @@ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll True - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll True @@ -124,7 +124,7 @@ ..\packages\murmurhash-signed.1.0.2\lib\net45\MurmurHash.dll - ..\packages\NJsonSchema.8.30.6304.31883\lib\net45\NJsonSchema.dll + ..\packages\NJsonSchema.10.8.0\lib\net45\NJsonSchema.dll ..\packages\Optimizely.SDK.2.1.0\lib\net45\OptimizelySDK.dll diff --git a/OptimizelySDK.DemoApp/README.md b/OptimizelySDK.DemoApp/README.md index 1e3e6b06..ccf50c72 100644 --- a/OptimizelySDK.DemoApp/README.md +++ b/OptimizelySDK.DemoApp/README.md @@ -1,6 +1,6 @@ # C# SDK Demo App -This demo uses the C# SDK, a part of Optimizely's Full Stack solution. It will walk you through: +This demo uses the C# SDK, a part of Optimizely's Feature Experimentation and Optimizely Full Stack (legacy) solution. It will walk you through: 1. How to bucket users into experiment's variation(s) 2. How to track conversion events @@ -40,7 +40,7 @@ Using the instructions below, you can run the app locally and mimic bucketing we To better understand this experiment, we recommend that you select a few different visitors on the "Select Visitor" page and bucket them into variations and simulate a conversion event by clicking the "Buy Now" button on the "Shop" page. Within a few seconds, you should see the results populate on the Optimizely results page. -The crux of this Full Stack experiment is bucketing users into variations and exposing them to different sorting functions. The SDK’s `Activate()` function will bucket users into a variation based on a user ID (internal id, user cookie, etc…). +The crux of this Optimizely Feature Experimentation experiment is bucketing users into variations and exposing them to different sorting functions. The SDK’s `Decide()` function will bucket users into a variation based on a user ID (internal id, user cookie, etc…). To actually test which sorting algorithm influences increased sales, we need to track the number of clicks on the Buy Now button. We can leverage the SDK's `Track()` function for that, passing it the event key which, in our case, is "add_to_cart" and the user ID. diff --git a/OptimizelySDK.DemoApp/Web.config b/OptimizelySDK.DemoApp/Web.config index 126c7ed9..d485ed2a 100644 --- a/OptimizelySDK.DemoApp/Web.config +++ b/OptimizelySDK.DemoApp/Web.config @@ -34,7 +34,7 @@ - + diff --git a/OptimizelySDK.DemoApp/packages.config b/OptimizelySDK.DemoApp/packages.config index 003a2ba4..6ac654c9 100644 --- a/OptimizelySDK.DemoApp/packages.config +++ b/OptimizelySDK.DemoApp/packages.config @@ -16,7 +16,7 @@ - + diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 9d3d441d..1e34d303 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -32,8 +32,8 @@ 4 - - ..\packages\Newtonsoft.Json.9.0.1\lib\net35\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.13.0.2\lib\net35\Newtonsoft.Json.dll diff --git a/OptimizelySDK.Net35/packages.config b/OptimizelySDK.Net35/packages.config index 6655a9d2..fcf66c74 100644 --- a/OptimizelySDK.Net35/packages.config +++ b/OptimizelySDK.Net35/packages.config @@ -1,5 +1,5 @@  - + diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 3dfdfbc9..cd28fdba 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -32,8 +32,8 @@ 4 - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.13.0.2\lib\net40\Newtonsoft.Json.dll diff --git a/OptimizelySDK.Net40/packages.config b/OptimizelySDK.Net40/packages.config index 3c565723..719abd02 100644 --- a/OptimizelySDK.Net40/packages.config +++ b/OptimizelySDK.Net40/packages.config @@ -1,5 +1,5 @@  - + diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index 740e5d4d..d0262be3 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -155,8 +155,8 @@ - - + + diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index 0202fe84..952ea9de 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -324,8 +324,8 @@ - - + + diff --git a/OptimizelySDK.Package/OptimizelySDK.nuspec b/OptimizelySDK.Package/OptimizelySDK.nuspec index 891fba96..66121331 100644 --- a/OptimizelySDK.Package/OptimizelySDK.nuspec +++ b/OptimizelySDK.Package/OptimizelySDK.nuspec @@ -11,35 +11,35 @@ OptimizelySDK.png https://github.com/optimizely/csharp-sdk/blob/master/OptimizelySDK.png?raw=true false - C# SDK for Optimizely X Fullstack + C# SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts https://github.com/optimizely/csharp-sdk/blob/master/CHANGELOG.md Copyright 2017-2019 Optimizely - - + + - + - + - - + + - - + + diff --git a/OptimizelySDK.Tests/App.config b/OptimizelySDK.Tests/App.config index ba3d6873..3d6d5412 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 3f95e42b..9dfe2f7b 100644 --- a/OptimizelySDK.Tests/Assertions.cs +++ b/OptimizelySDK.Tests/Assertions.cs @@ -14,16 +14,16 @@ * limitations under the License. */ -using NUnit.Framework; -using OptimizelySDK.Entity; -using OptimizelySDK.OptimizelyDecisions; -using OptimizelySDK.OptlyConfig; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; +using NUnit.Framework; +using OptimizelySDK.Entity; +using OptimizelySDK.OptimizelyDecisions; +using OptimizelySDK.OptlyConfig; namespace OptimizelySDK.Tests { @@ -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,30 +387,38 @@ 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); } + [Obsolete] public static void AreEqual(OptimizelyFeature expected, OptimizelyFeature actual) { AreEquivalent(expected.DeliveryRules, actual.DeliveryRules); @@ -373,25 +433,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 +513,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 +534,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 +547,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 +565,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 +586,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 +604,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 +760,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 +792,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/BucketerTest.cs b/OptimizelySDK.Tests/BucketerTest.cs index 46ca90c5..c8c61864 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 ce8a2e57..d7763f21 100644 --- a/OptimizelySDK.Tests/ClientConfigHandlerTest.cs +++ b/OptimizelySDK.Tests/ClientConfigHandlerTest.cs @@ -16,19 +16,20 @@ #if !NETSTANDARD1_6 && !NET35 -using NUnit.Framework; using System.Configuration; +using NUnit.Framework; 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 5610ad09..c40b6551 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 9a3bbb87..08cb1049 100644 --- a/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs +++ b/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs @@ -14,18 +14,18 @@ * limitations under the License. */ -using Moq; -using NUnit.Framework; -using OptimizelySDK.Config; -using OptimizelySDK.Logger; -using OptimizelySDK.Tests.NotificationTests; -using OptimizelySDK.Tests.Utils; using System; using System.Diagnostics; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using OptimizelySDK.Config; +using OptimizelySDK.Logger; +using OptimizelySDK.Tests.NotificationTests; +using OptimizelySDK.Tests.Utils; namespace OptimizelySDK.Tests.DatafileManagement_Tests { @@ -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 e7877ca8..e5dbf85f 100644 --- a/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs +++ b/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs @@ -14,15 +14,14 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; +using System.Diagnostics; using Moq; using NUnit.Framework; using OptimizelySDK.Config; using OptimizelySDK.Logger; using OptimizelySDK.Tests.DatafileManagementTests; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; namespace OptimizelySDK.Tests.DatafileManagement_Tests { @@ -75,7 +74,11 @@ public void TestImmediatelyCalledScheduledRequestIfPreviousRequestDelayedInRespo { // period to call is one second // Giving response in 1200 milliseconds - var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(1500), true, LoggerMock.Object, new int[] { 1200, 500, 500 }); + var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(1), + TimeSpan.FromMilliseconds(1500), true, LoggerMock.Object, new int[] + { + 1200, 500, 500, + }); configManager.Start(); System.Threading.Tasks.Task.Delay(50).Wait(); @@ -97,7 +100,11 @@ public void TestTimedoutIfTakingMorethanBlockingTimeout() { // period to call is one second // Giving response in 1200 milliseconds - var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { 1300, 500, 500 }); + var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), + TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] + { + 1300, 500, 500, + }); configManager.Start(); var config = configManager.GetConfig(); @@ -110,8 +117,12 @@ public void TestTimedoutOnlyIfSchedulerStarted() { // period to call is 3 second // Giving response in 1200 milliseconds and timedout should be in 1000 miliseconds - var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { 1300, 500, 500 }); - Stopwatch sw = new Stopwatch(); + var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), + TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] + { + 1300, 500, 500, + }); + var sw = new Stopwatch(); sw.Start(); var config = configManager.GetConfig(); sw.Stop(); @@ -124,8 +135,12 @@ public void TestDontTimedoutIfSchedulerNotStarted() { // period to call is 3 second // Giving response in 1200 milliseconds and timedout should be in 1000 miliseconds - var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { 1300, 500, 500 }); - Stopwatch sw = new Stopwatch(); + var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), + TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] + { + 1300, 500, 500, + }); + var sw = new Stopwatch(); sw.Start(); var config = configManager.GetConfig(); sw.Stop(); @@ -136,10 +151,22 @@ public void TestDontTimedoutIfSchedulerNotStarted() [Test] public void TestReturnDatafileImmediatelyOnceGetValidDatafileRemotely() { - var projConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null); - var data = new List() { - new TestPollingData { PollingTime = 500, ChangeVersion = false, ConfigDatafile = projConfig}, - new TestPollingData { PollingTime = 500, ChangeVersion = false, ConfigDatafile = projConfig} + var projConfig = + DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null); + var data = new List() + { + new TestPollingData + { + PollingTime = 500, + ChangeVersion = false, + ConfigDatafile = projConfig, + }, + new TestPollingData + { + PollingTime = 500, + ChangeVersion = false, + ConfigDatafile = projConfig, + }, }; var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(5000), true, LoggerMock.Object, data.ToArray()); @@ -160,11 +187,28 @@ public void TestWaitUntilValidDatfileIsNotGiven() // then send the right datafile // see it should release blocking. // blocking timeout must be inifinity. - var projConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null); - var data = new List() { - new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null}, - new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null}, - new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = projConfig} + var projConfig = + DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null); + var data = new List() + { + new TestPollingData + { + PollingTime = 50, + ChangeVersion = false, + ConfigDatafile = null, + }, + new TestPollingData + { + PollingTime = 50, + ChangeVersion = false, + ConfigDatafile = null, + }, + new TestPollingData + { + PollingTime = 50, + ChangeVersion = false, + ConfigDatafile = projConfig, + }, }; @@ -181,10 +225,26 @@ public void TestWaitUntilValidDatfileIsNotGiven() [Test] public void TestWaitUntilValidDatafileIsNotGivenOrTimedout() { - var data = new List() { - new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null}, - new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null}, - new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null} + var data = new List() + { + new TestPollingData + { + PollingTime = 50, + ChangeVersion = false, + ConfigDatafile = null, + }, + new TestPollingData + { + PollingTime = 50, + ChangeVersion = false, + ConfigDatafile = null, + }, + new TestPollingData + { + PollingTime = 50, + ChangeVersion = false, + ConfigDatafile = null, + }, }; var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(2500), true, LoggerMock.Object, data.ToArray()); diff --git a/OptimizelySDK.Tests/ConfigTest/ProjectConfigProps.cs b/OptimizelySDK.Tests/ConfigTest/ProjectConfigProps.cs index 2b5e9ff5..e26414c4 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 b35621ae..9d3d27fb 100644 --- a/OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs +++ b/OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2019, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -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 f15d41ea..633847ae 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System.Collections.Generic; using Moq; using NUnit.Framework; using OptimizelySDK.Bucketing; @@ -24,7 +25,6 @@ using OptimizelySDK.Logger; using OptimizelySDK.OptimizelyDecisions; using OptimizelySDK.Utils; -using System.Collections.Generic; namespace OptimizelySDK.Tests { @@ -57,29 +57,39 @@ 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 +103,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 +259,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 +329,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 +494,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 +510,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 +526,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 +543,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 +609,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 +655,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 +675,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 +718,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 +755,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 +792,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 +817,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 +842,6 @@ public void TestGetVariationForFeatureRolloutWhenNoRuleInRollouts() [Test] public void TestGetVariationForFeatureRolloutWhenRolloutIsNotInDataFile() { - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); var invalidRolloutFeature = new FeatureFlag { @@ -689,89 +849,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 +986,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 +1042,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 +1109,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 +1194,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 +1290,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 +1365,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 +1436,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 +1474,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 07db4493..7138165a 100644 --- a/OptimizelySDK.Tests/DefaultErrorHandlerTest.cs +++ b/OptimizelySDK.Tests/DefaultErrorHandlerTest.cs @@ -14,15 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System; using System.Collections.Generic; using Moq; -using OptimizelySDK.Logger; -using OptimizelySDK.ErrorHandler; -using OptimizelySDK.Entity; using NUnit.Framework; using OptimizelySDK.Bucketing; +using OptimizelySDK.Entity; +using OptimizelySDK.ErrorHandler; using OptimizelySDK.Exceptions; +using OptimizelySDK.Logger; namespace OptimizelySDK.Tests { @@ -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 b44b3194..254e90ed 100644 --- a/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs +++ b/OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2019-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -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 5e0174a6..6a212f6a 100644 --- a/OptimizelySDK.Tests/EventTests/BatchEventProcessorTest.cs +++ b/OptimizelySDK.Tests/EventTests/BatchEventProcessorTest.cs @@ -1,4 +1,7 @@ -using Moq; +using System; +using System.Collections.Concurrent; +using System.Threading; +using Moq; using NUnit.Framework; using OptimizelySDK.Config; using OptimizelySDK.Entity; @@ -9,14 +12,11 @@ using OptimizelySDK.Logger; using OptimizelySDK.Notifications; using OptimizelySDK.Tests.NotificationTests; -using System; -using System.Collections.Concurrent; -using System.Threading; 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 cbefd066..95a9ead3 100644 --- a/OptimizelySDK.Tests/EventTests/CanonicalEvent.cs +++ b/OptimizelySDK.Tests/EventTests/CanonicalEvent.cs @@ -1,7 +1,7 @@ -using OptimizelySDK.Entity; -using System; +using System; using System.Collections.Generic; using System.Linq; +using OptimizelySDK.Entity; namespace OptimizelySDK.Tests.EventTests { @@ -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 6de01dd5..0c7b5806 100644 --- a/OptimizelySDK.Tests/EventTests/DefaultEventDispatcherTest.cs +++ b/OptimizelySDK.Tests/EventTests/DefaultEventDispatcherTest.cs @@ -1,13 +1,13 @@ -using OptimizelySDK.Entity; -using OptimizelySDK.Logger; -using Moq; -using OptimizelySDK.Event.Builder; -using OptimizelySDK.Event; +using System; using System.Collections.Generic; +using Moq; using Newtonsoft.Json.Linq; -using System; -using OptimizelySDK.Event.Dispatcher; using NUnit.Framework; +using OptimizelySDK.Entity; +using OptimizelySDK.Event; +using OptimizelySDK.Event.Builder; +using OptimizelySDK.Event.Dispatcher; +using OptimizelySDK.Logger; namespace OptimizelySDK.Tests.EventTests { @@ -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 da731223..c869d831 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2017-2019, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,16 +14,16 @@ * limitations under the License. */ -using OptimizelySDK.Entity; -using OptimizelySDK.Logger; -using OptimizelySDK.Event.Builder; -using OptimizelySDK.Event; -using System.Collections.Generic; using System; +using System.Collections.Generic; using NUnit.Framework; using OptimizelySDK.Bucketing; -using OptimizelySDK.Utils; using OptimizelySDK.Config; +using OptimizelySDK.Entity; +using OptimizelySDK.Event; +using OptimizelySDK.Event.Builder; +using OptimizelySDK.Logger; +using OptimizelySDK.Utils; namespace OptimizelySDK.Tests.EventTests { @@ -32,15 +32,17 @@ public class EventBuilderTest { private string TestUserId = string.Empty; private ProjectConfig Config; - + [Obsolete] private EventBuilder EventBuilder; [TestFixtureSetUp] + [Obsolete] 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 +54,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 +120,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 +139,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 +212,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 +238,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 +332,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 +359,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 +446,7 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload() "POST", new Dictionary { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -439,7 +464,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 +480,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 +536,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 +559,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 +622,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 +650,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 +714,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 +742,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 +814,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 +849,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 +914,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 +942,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 +1008,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 +1035,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 +1101,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 +1128,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 +1194,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 +1221,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 +1287,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 +1316,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 +1412,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 +1492,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 +1519,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 +1592,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 +1619,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 +1685,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 +1712,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 +1775,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 +1805,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 +1861,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 +1888,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 +1937,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 +1959,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 +1994,7 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); @@ -1897,71 +2010,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 +2087,7 @@ public void TestCreateConversionEventRemovesInvalidAttributesFromPayload() "POST", new Dictionary { - { "Content-Type", "application/json"} + { "Content-Type", "application/json" }, }); var userAttributes = new UserAttributes @@ -1990,10 +2107,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 10e5c3e4..d91f40f2 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 b17cff2c..3199a8ae 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 9919af19..33c013c1 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 0661c7ad..805e9241 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 1ef7e32b..ba6cc9c5 100644 --- a/OptimizelySDK.Tests/EventTests/LogEventTest.cs +++ b/OptimizelySDK.Tests/EventTests/LogEventTest.cs @@ -1,12 +1,11 @@ - -using Newtonsoft.Json.Linq; -using NUnit.Framework; -using OptimizelySDK.Event; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using OptimizelySDK.Event; namespace OptimizelySDK.Tests.EventTests { @@ -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 cfeab5d3..0eedba02 100644 --- a/OptimizelySDK.Tests/EventTests/TestEventDispatcher.cs +++ b/OptimizelySDK.Tests/EventTests/TestEventDispatcher.cs @@ -1,4 +1,8 @@ -using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OptimizelySDK.Config; using OptimizelySDK.Entity; @@ -6,10 +10,6 @@ using OptimizelySDK.Event.Dispatcher; using OptimizelySDK.Event.Entity; using OptimizelySDK.Logger; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; namespace OptimizelySDK.Tests.EventTests { @@ -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/TestForwardingEventDispatcher.cs b/OptimizelySDK.Tests/EventTests/TestForwardingEventDispatcher.cs index b31dd995..01eca1ee 100644 --- a/OptimizelySDK.Tests/EventTests/TestForwardingEventDispatcher.cs +++ b/OptimizelySDK.Tests/EventTests/TestForwardingEventDispatcher.cs @@ -1,12 +1,12 @@ -using NUnit.Framework; -using OptimizelySDK.Event; -using OptimizelySDK.Event.Dispatcher; -using OptimizelySDK.Logger; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using NUnit.Framework; +using OptimizelySDK.Event; +using OptimizelySDK.Event.Dispatcher; +using OptimizelySDK.Logger; namespace OptimizelySDK.Tests.EventTests { diff --git a/OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs b/OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs index 80acd1a6..ecf42fca 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 6cf2dfc8..d3e1fbb8 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/InvalidEventDispatcher.cs b/OptimizelySDK.Tests/InvalidEventDispatcher.cs index db88a3a9..be6ea26f 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 System; 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 8b6d6806..5683373e 100644 --- a/OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs +++ b/OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs @@ -14,6 +14,8 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; using Moq; using NUnit.Framework; using OptimizelySDK.Config; @@ -22,8 +24,6 @@ using OptimizelySDK.Event; using OptimizelySDK.Logger; using OptimizelySDK.Notifications; -using System; -using System.Collections.Generic; using NotificationType = OptimizelySDK.Notifications.NotificationCenter.NotificationType; namespace OptimizelySDK.Tests.NotificationTests @@ -48,10 +48,13 @@ public void Setup() } [Test] + [Obsolete] 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,44 +65,59 @@ 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] + [Obsolete] 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] + [Obsolete] 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] + [Obsolete] public void TestAddInvalidNotificationListeners() { // Verify that AddNotification gets failed on adding invalid notification listeners. @@ -107,7 +125,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. @@ -115,20 +136,25 @@ public void TestAddInvalidNotificationListeners() } [Test] + [Obsolete] 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); @@ -147,14 +173,18 @@ public void TestClearNotifications() } [Test] + [Obsolete] 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); @@ -170,71 +200,97 @@ public void TestClearAllNotifications() } [Test] + [Obsolete] 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 +300,40 @@ 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/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 007ee360..a2def275 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -1,5 +1,6 @@ + /* - * Copyright 2020-2021, Optimizely + * Copyright 2020-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +15,8 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; using Moq; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -22,8 +25,6 @@ using OptimizelySDK.Logger; using OptimizelySDK.OptlyConfig; using OptimizelySDK.Tests.UtilsTests; -using System; -using System.Collections.Generic; namespace OptimizelySDK.Tests.OptimizelyConfigTests { @@ -41,23 +42,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> @@ -69,8 +72,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() { }, @@ -94,9 +105,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]); } } @@ -104,12 +116,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(); @@ -129,20 +141,25 @@ public void TestAfterDisposeGetOptimizelyConfigIsNoLongerValid() [Test] public void TestGetOptimizelyConfigServiceNullConfig() { - OptimizelyConfig optimizelyConfig = new OptimizelyConfigService(null).GetOptimizelyConfig(); + var optimizelyConfig = new OptimizelyConfigService(null).GetOptimizelyConfig(); Assert.IsNull(optimizelyConfig); } [Test] + [Obsolete] 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"); } @@ -150,14 +167,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"); @@ -169,7 +191,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(); @@ -180,376 +204,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); } @@ -561,75 +614,92 @@ 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] + [Obsolete] 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 23c25b22..834a33e2 100644 --- a/OptimizelySDK.Tests/OptimizelyDecisions/OptimizelyDecisionTest.cs +++ b/OptimizelySDK.Tests/OptimizelyDecisions/OptimizelyDecisionTest.cs @@ -14,14 +14,14 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; +using System.Linq; using Moq; using NUnit.Framework; using OptimizelySDK.ErrorHandler; using OptimizelySDK.Logger; using OptimizelySDK.OptimizelyDecisions; -using System; -using System.Collections.Generic; -using System.Linq; namespace OptimizelySDK.Tests.OptimizelyDecisions { @@ -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 92d2a23b..e1cc7ebf 100644 --- a/OptimizelySDK.Tests/OptimizelyFactoryTest.cs +++ b/OptimizelySDK.Tests/OptimizelyFactoryTest.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System; using Moq; using NUnit.Framework; using OptimizelySDK.Config; @@ -25,13 +26,14 @@ using OptimizelySDK.Tests.ConfigTest; 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 c07bb137..e60b458b 100644 --- a/OptimizelySDK.Tests/OptimizelyJSONTest.cs +++ b/OptimizelySDK.Tests/OptimizelyJSONTest.cs @@ -15,39 +15,40 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; using Moq; using NUnit.Framework; using OptimizelySDK.ErrorHandler; using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; -using System; -using System.Collections.Generic; 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 2a776bcb..8ead9afd 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -46,9 +46,8 @@ ..\packages\Moq.4.7.1\lib\net45\Moq.dll True - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True + + ..\packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll ..\packages\NUnit.2.6.4\lib\nunit.framework.dll diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 05e50bc9..1097f9fe 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2017-2021, Optimizely + * Copyright 2017-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,27 +16,25 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Threading; using Moq; -using OptimizelySDK.Logger; -using OptimizelySDK.Event.Builder; -using OptimizelySDK.Event.Dispatcher; -using OptimizelySDK.ErrorHandler; -using OptimizelySDK.Exceptions; -using OptimizelySDK.Event; -using OptimizelySDK.Entity; using NUnit.Framework; -using OptimizelySDK.Tests.UtilsTests; using OptimizelySDK.Bucketing; -using OptimizelySDK.Notifications; -using OptimizelySDK.Tests.NotificationTests; -using OptimizelySDK.Utils; using OptimizelySDK.Config; +using OptimizelySDK.Entity; +using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Event; +using OptimizelySDK.Event.Dispatcher; using OptimizelySDK.Event.Entity; -using OptimizelySDK.OptlyConfig; -using System.Globalization; -using System.Threading; -using OptimizelySDK.Tests.Utils; +using OptimizelySDK.Exceptions; +using OptimizelySDK.Logger; +using OptimizelySDK.Notifications; using OptimizelySDK.OptimizelyDecisions; +using OptimizelySDK.Tests.NotificationTests; +using OptimizelySDK.Tests.Utils; +using OptimizelySDK.Tests.UtilsTests; +using OptimizelySDK.Utils; namespace OptimizelySDK.Tests { @@ -84,14 +82,16 @@ public void Initialize() EventProcessorMock.Setup(b => b.Process(It.IsAny())); DecisionReasons = new DecisionReasons(); var config = DatafileProjectConfig.Create( - content: TestData.Datafile, - logger: LoggerMock.Object, - errorHandler: new NoOpErrorHandler()); + TestData.Datafile, + LoggerMock.Object, + new NoOpErrorHandler()); ConfigManager = new FallbackProjectConfigManager(config); Config = ConfigManager.GetConfig(); EventDispatcherMock = new Mock(); - Optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); - OptimizelyWithTypedAudiences = new Optimizely(TestData.TypedAudienceDatafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); + Optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, + LoggerMock.Object, ErrorHandlerMock.Object); + OptimizelyWithTypedAudiences = new Optimizely(TestData.TypedAudienceDatafile, + EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); Helper = new OptimizelyHelper { @@ -102,12 +102,14 @@ public void Initialize() SkipJsonValidation = false, }; - OptimizelyMock = new Mock(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object, null, false, null, null) + OptimizelyMock = new Mock(TestData.Datafile, EventDispatcherMock.Object, + LoggerMock.Object, ErrorHandlerMock.Object, null, false, null, null) { - CallBase = true + CallBase = true, }; - DecisionServiceMock = new Mock(new Bucketer(LoggerMock.Object), ErrorHandlerMock.Object, + DecisionServiceMock = new Mock(new Bucketer(LoggerMock.Object), + ErrorHandlerMock.Object, null, LoggerMock.Object); NotificationCenter = new NotificationCenter(LoggerMock.Object); @@ -131,37 +133,55 @@ public void Cleanup() private class OptimizelyHelper { - private static Type[] ParameterTypes = { - typeof(string), - typeof(IEventDispatcher), - typeof(ILogger), - typeof(IErrorHandler), - typeof(bool), - typeof(EventProcessor) - }; - - public static Dictionary SingleParameter = new Dictionary + private static Type[] ParameterTypes = { - { "param1", "val1" } + typeof(string), typeof(IEventDispatcher), typeof(ILogger), typeof(IErrorHandler), + typeof(bool), typeof(EventProcessor), }; + public static Dictionary SingleParameter = + new Dictionary + { + { + "param1", "val1" + }, + }; + public static UserAttributes UserAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; // NullUserAttributes extends copy of UserAttributes with key-value // pairs containing null values which should not be sent to OPTIMIZELY.COM . public static UserAttributes NullUserAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" }, - { "null_value", null}, - { "wont_be_sent", null}, - { "bad_food", null} + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, + { + "null_value", null + }, + { + "wont_be_sent", null + }, + { + "bad_food", null + }, }; public string Datafile { get; set; } @@ -179,14 +199,8 @@ public PrivateObject CreatePrivateOptimizely() return new PrivateObject(typeof(Optimizely), ParameterTypes, new object[] { - Datafile, - EventDispatcher, - Logger, - ErrorHandler, - UserProfileService, - SkipJsonValidation, - EventProcessor, - DefaultDecideOptions + Datafile, EventDispatcher, Logger, ErrorHandler, UserProfileService, + SkipJsonValidation, EventProcessor, DefaultDecideOptions, }); } } @@ -199,10 +213,14 @@ public PrivateObject CreatePrivateOptimizely() public void TestCreateUserContext() { var attribute = new UserAttributes + { { - { "device_type", "iPhone" }, - { "location", "San Francisco" } - }; + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, + }; var optlyUserContext = Optimizely.CreateUserContext(TestUserId, attribute); Assert.AreEqual(TestUserId, optlyUserContext.GetUserId()); Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely()); @@ -222,17 +240,25 @@ public void TestCreateUserContextWithoutAttributes() public void TestCreateUserContextMultipleAttribute() { var attribute1 = new UserAttributes + { { - { "device_type", "iPhone" }, - { "location", "San Francisco" } - }; + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, + }; var optlyUserContext1 = Optimizely.CreateUserContext("userId1", attribute1); var attribute2 = new UserAttributes + { { - { "device_type2", "Samsung" }, - { "location2", "California" } - }; + "device_type2", "Samsung" + }, + { + "location2", "California" + }, + }; var optlyUserContext2 = Optimizely.CreateUserContext("userId2", attribute2); Assert.AreEqual("userId1", optlyUserContext1.GetUserId()); @@ -251,8 +277,12 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsFalseAndFeature() var variables = Optimizely.GetAllFeatureVariables(featureKey, TestUserId); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; Config.SendFlagDecisions = false; var fallbackConfigManager = new FallbackProjectConfigManager(Config); @@ -262,26 +292,49 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsFalseAndFeature() LoggerMock.Object, ErrorHandlerMock.Object, null, - new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object), + new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, + LoggerMock.Object, ErrorHandlerMock.Object), null); // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny>())); - optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny>())); + optimizely.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); var optimizelyUserContext = optimizely.CreateUserContext(TestUserId, userAttributes); optimizelyUserContext.Decide(featureKey); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "flagKey", featureKey }, - { "enabled", false }, - { "variables", variables.ToDictionary() }, - { "variationKey", "group_exp_2_var_1" }, - { "ruleKey", "group_experiment_2" }, - { "reasons", new OptimizelyDecideOption[0] }, - { "decisionEventDispatched", true } - }))), Times.Once); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FLAG, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "flagKey", featureKey + }, + { + "enabled", false + }, + { + "variables", variables.ToDictionary() + }, + { + "variationKey", "group_exp_2_var_1" + }, + { + "ruleKey", "group_experiment_2" + }, + { + "reasons", new OptimizelyDecideOption[0] + }, + { + "decisionEventDispatched", true + }, + }))), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] @@ -291,8 +344,12 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsTrueAndFeature() var variables = Optimizely.GetAllFeatureVariables(featureKey, TestUserId); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; var fallbackConfigManager = new FallbackProjectConfigManager(Config); var optimizely = new Optimizely(fallbackConfigManager, @@ -301,26 +358,49 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsTrueAndFeature() LoggerMock.Object, ErrorHandlerMock.Object, null, - new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object), + new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, + LoggerMock.Object, ErrorHandlerMock.Object), null); // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny>())); - optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny>())); + optimizely.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); var optimizelyUserContext = optimizely.CreateUserContext(TestUserId, userAttributes); optimizelyUserContext.Decide(featureKey); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "flagKey", featureKey }, - { "enabled", false }, - { "variables", variables.ToDictionary() }, - { "variationKey", "group_exp_2_var_1" }, - { "ruleKey", "group_experiment_2" }, - { "reasons", new OptimizelyDecideOption[0] }, - { "decisionEventDispatched", true } - }))), Times.Once); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FLAG, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "flagKey", featureKey + }, + { + "enabled", false + }, + { + "variables", variables.ToDictionary() + }, + { + "variationKey", "group_exp_2_var_1" + }, + { + "ruleKey", "group_experiment_2" + }, + { + "reasons", new OptimizelyDecideOption[0] + }, + { + "decisionEventDispatched", true + }, + }))), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] @@ -331,8 +411,12 @@ public void TestDecisionNotificationNotSentWhenSendFlagDecisionsFalseAndRollout( var variables = Optimizely.GetAllFeatureVariables(featureKey, TestUserId); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; var experiment = Config.GetRolloutFromId("166660").Experiments[1]; var ruleKey = experiment.Key; @@ -345,26 +429,49 @@ public void TestDecisionNotificationNotSentWhenSendFlagDecisionsFalseAndRollout( LoggerMock.Object, ErrorHandlerMock.Object, null, - new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object), + new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, + LoggerMock.Object, ErrorHandlerMock.Object), null); // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny>())); - optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny>())); + optimizely.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); var optimizelyUserContext = optimizely.CreateUserContext(TestUserId, userAttributes); optimizelyUserContext.Decide(featureKey); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "flagKey", featureKey }, - { "enabled", true }, - { "variables", variables.ToDictionary() }, - { "variationKey", variation.Key }, - { "ruleKey", ruleKey }, - { "reasons", new OptimizelyDecideOption[0] }, - { "decisionEventDispatched", false } - }))), Times.Once); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Never); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FLAG, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "flagKey", featureKey + }, + { + "enabled", true + }, + { + "variables", variables.ToDictionary() + }, + { + "variationKey", variation.Key + }, + { + "ruleKey", ruleKey + }, + { + "reasons", new OptimizelyDecideOption[0] + }, + { + "decisionEventDispatched", false + }, + }))), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Never); } [Test] @@ -375,8 +482,12 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsTrueAndRollout() var variables = Optimizely.GetAllFeatureVariables(featureKey, TestUserId); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; var experiment = Config.GetRolloutFromId("166660").Experiments[1]; var ruleKey = experiment.Key; @@ -389,26 +500,49 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsTrueAndRollout() LoggerMock.Object, ErrorHandlerMock.Object, null, - new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, LoggerMock.Object, ErrorHandlerMock.Object), + new ForwardingEventProcessor(EventDispatcherMock.Object, NotificationCenter, + LoggerMock.Object, ErrorHandlerMock.Object), null); // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny>())); - optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny>())); + optimizely.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); var optimizelyUserContext = optimizely.CreateUserContext(TestUserId, userAttributes); optimizelyUserContext.Decide(featureKey); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "flagKey", featureKey }, - { "enabled", true }, - { "variables", variables.ToDictionary() }, - { "variationKey", variation.Key }, - { "ruleKey", ruleKey }, - { "reasons", new OptimizelyDecideOption[0] }, - { "decisionEventDispatched", true } - }))), Times.Once); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FLAG, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "flagKey", featureKey + }, + { + "enabled", true + }, + { + "variables", variables.ToDictionary() + }, + { + "variationKey", variation.Key + }, + { + "ruleKey", ruleKey + }, + { + "reasons", new OptimizelyDecideOption[0] + }, + { + "decisionEventDispatched", true + }, + }))), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] @@ -416,21 +550,31 @@ public void TestChangeAttributeDoesNotEffectValues() { var userId = "testUserId"; var attribute = new UserAttributes + { { - { "device_type", "iPhone" }, - { "location", "San Francisco" } - }; + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, + }; var optlyUserContext = Optimizely.CreateUserContext(userId, attribute); Assert.AreEqual(TestUserId, optlyUserContext.GetUserId()); Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely()); Assert.AreEqual(attribute, optlyUserContext.GetAttributes()); attribute = new UserAttributes + { { - { "device_type", "iPhone" }, - { "level", "low" }, - { "location", "San Francisco" } - }; + "device_type", "iPhone" + }, + { + "level", "low" + }, + { + "location", "San Francisco" + }, + }; Assert.AreEqual("testUserId", optlyUserContext.GetUserId()); Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely()); Assert.AreNotEqual(attribute, optlyUserContext.GetAttributes()); @@ -443,7 +587,7 @@ public void TestChangeAttributeDoesNotEffectValues() [Test] public void TestInvalidInstanceLogMessages() { - string datafile = "{\"name\":\"optimizely\"}"; + var datafile = "{\"name\":\"optimizely\"}"; var optimizely = new Optimizely(datafile, null, LoggerMock.Object); Assert.IsNull(optimizely.GetVariation(string.Empty, string.Empty)); @@ -451,123 +595,111 @@ public void TestInvalidInstanceLogMessages() optimizely.Track(string.Empty, string.Empty); Assert.IsFalse(optimizely.IsFeatureEnabled(string.Empty, string.Empty)); Assert.AreEqual(optimizely.GetEnabledFeatures(string.Empty).Count, 0); - Assert.IsNull(optimizely.GetFeatureVariableBoolean(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableString(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableDouble(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableInteger(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableJSON(string.Empty, string.Empty, string.Empty)); - - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Provided 'datafile' has invalid schema."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetVariation'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Activate'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Track'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'IsFeatureEnabled'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetEnabledFeatures'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableBoolean'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableString'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableDouble'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableInteger'."), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetFeatureVariableJSON'."), Times.Once); + Assert.IsNull( + optimizely.GetFeatureVariableBoolean(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableString(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableDouble(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableInteger(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableJSON(string.Empty, string.Empty, string.Empty)); + + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, "Provided 'datafile' has invalid schema."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetVariation'."), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Activate'."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Track'."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'IsFeatureEnabled'."), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetEnabledFeatures'."), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetFeatureVariableBoolean'."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetFeatureVariableString'."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetFeatureVariableDouble'."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetFeatureVariableInteger'."), + Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetFeatureVariableJSON'."), Times.Once); } [Test] public void TestValidateInputsInvalidFileJsonValidationNotSkipped() { - string datafile = "{\"name\":\"optimizely\"}"; - Optimizely optimizely = new Optimizely(datafile); + var datafile = "{\"name\":\"optimizely\"}"; + var optimizely = new Optimizely(datafile); Assert.IsFalse(optimizely.IsValid); } [Test] public void TestValidateInputsInvalidFileJsonValidationSkipped() { - string datafile = "{\"name\":\"optimizely\"}"; - Optimizely optimizely = new Optimizely(datafile, null, null, null, skipJsonValidation: true); + var datafile = "{\"name\":\"optimizely\"}"; + var optimizely = + new Optimizely(datafile, null, null, null, skipJsonValidation: true); Assert.IsFalse(optimizely.IsValid); } [Test] public void TestErrorHandlingWithNullDatafile() { - var optimizelyNullDatafile = new Optimizely(null, null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Unable to parse null datafile."), Times.Once); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Unable to parse null datafile.")), Times.Once); + var optimizelyNullDatafile = new Optimizely(null, null, LoggerMock.Object, + ErrorHandlerMock.Object, null, true); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Unable to parse null datafile."), + Times.Once); + ErrorHandlerMock.Verify( + e => e.HandleError(It.Is(ex => + ex.Message == "Unable to parse null datafile.")), Times.Once); } [Test] public void TestErrorHandlingWithEmptyDatafile() { - var optimizelyEmptyDatafile = new Optimizely("", null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Unable to parse empty datafile."), Times.Once); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Unable to parse empty datafile.")), Times.Once); + var optimizelyEmptyDatafile = new Optimizely("", null, LoggerMock.Object, + ErrorHandlerMock.Object, null, true); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Unable to parse empty datafile."), + Times.Once); + ErrorHandlerMock.Verify( + e => e.HandleError(It.Is(ex => + ex.Message == "Unable to parse empty datafile.")), Times.Once); } [Test] public void TestErrorHandlingWithUnsupportedConfigVersion() { - var optimizelyUnsupportedVersion = new Optimizely(TestData.UnsupportedVersionDatafile, null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $"This version of the C# SDK does not support the given datafile version: 5"), Times.Once); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == $"This version of the C# SDK does not support the given datafile version: 5")), Times.Once); - } - - [Test] - public void TestValidatePreconditionsExperimentNotRunning() - { - var optly = Helper.CreatePrivateOptimizely(); - - optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("paused_experiment"), - TestUserId, ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsFalse(result); - } - - [Test] - public void TestValidatePreconditionsExperimentRunning() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - TestUserId, - ConfigManager.GetConfig(), - new UserAttributes - { - { "device_type", "iPhone" }, - { "location", "San Francisco" } - } - ); - Assert.IsTrue(result); - } - - [Test] - public void TestValidatePreconditionsUserInForcedVariationNotInExperiment() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - "user1", ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsTrue(result); - } - - [Test] - public void TestValidatePreconditionsUserInForcedVariationInExperiment() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - "user1", ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsTrue(result); - } - - [Test] - public void TestValidatePreconditionsUserNotInForcedVariationNotInExperiment() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - TestUserId, ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsFalse(result); + var optimizelyUnsupportedVersion = new Optimizely(TestData.UnsupportedVersionDatafile, + null, LoggerMock.Object, ErrorHandlerMock.Object, null, true); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + $"This version of the C# SDK does not support the given datafile version: 5"), + Times.Once); + ErrorHandlerMock.Verify( + e => e.HandleError(It.Is(ex => + ex.Message == + $"This version of the C# SDK does not support the given datafile version: 5")), + Times.Once); } [Test] @@ -575,16 +707,31 @@ public void TestValidatePreconditionsUserNotInForcedVariationInExperiment() { var attributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; var variation = Optimizely.GetVariation("test_experiment", "test_user", attributes); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"test_user\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"test_user\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [test_user] is in variation [control] of experiment [test_experiment]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "This decision will not be saved since the UserProfileService is null."), + Times.Once); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -593,8 +740,8 @@ public void TestValidatePreconditionsUserNotInForcedVariationInExperiment() public void TestActivateInvalidOptimizelyObject() { var optly = new Optimizely("Random datafile", null, LoggerMock.Object); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided 'datafile' has invalid schema."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided 'datafile' has invalid schema."), + Times.Once); } //attributes can not be invalid beacuse now it is strongly typed. @@ -620,11 +767,19 @@ public void TestActivateUserInNoVariation() { var optly = Helper.CreatePrivateOptimizely(); - var result = optly.Invoke("Activate", "test_experiment", "not_in_variation_user", OptimizelyHelper.UserAttributes); + var result = optly.Invoke("Activate", "test_experiment", "not_in_variation_user", + OptimizelyHelper.UserAttributes); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [8495] to user [not_in_variation_user] with bucketing ID [not_in_variation_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [not_in_variation_user] is in no variation."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Not activating user not_in_variation_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [8495] to user [not_in_variation_user] with bucketing ID [not_in_variation_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "User [not_in_variation_user] is in no variation."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Not activating user not_in_variation_user."), + Times.Once); Assert.IsNull(result); } @@ -634,22 +789,41 @@ public void TestActivateNoAudienceNoAttributes() { var parameters = new Dictionary { - { "param1", "val1" }, - { "param2", "val2" } + { + "param1", "val1" + }, + { + "param2", "val2" + }, }; var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("EventProcessor", EventProcessorMock.Object); - var variation = (Variation)optly.Invoke("Activate", "group_experiment_1", "user_1", null); + var variation = + (Variation)optly.Invoke("Activate", "group_experiment_1", "user_1", null); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [1922] to user [user_1] with bucketing ID [user_1]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in experiment [group_experiment_1] of group [7722400015]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [1922] to user [user_1] with bucketing ID [user_1]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [user_1] is in experiment [group_experiment_1] of group [7722400015]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [user_1] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Activating user user_1 in experiment group_experiment_1."), Times.Once); Assert.IsTrue(TestData.CompareObjects(GroupVariation, variation)); } @@ -665,8 +839,12 @@ public void TestActivateAudienceNoAttributes() var variationkey = optly.Invoke("Activate", "test_experiment", "test_user", null); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User \"test_user\" does not meet conditions to be in experiment \"test_experiment\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Not activating user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User \"test_user\" does not meet conditions to be in experiment \"test_experiment\"."), + Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Not activating user test_user."), + Times.Once); Assert.IsNull(variationkey); } @@ -677,15 +855,27 @@ public void TestActivateWithAttributes() var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("EventProcessor", EventProcessorMock.Object); - var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", OptimizelyHelper.UserAttributes); + var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", + OptimizelyHelper.UserAttributes); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"test_user\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null.")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"test_user\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [test_user] is in variation [control] of experiment [test_experiment]."), + Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + "This decision will not be saved since the UserProfileService is null.")); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Activating user test_user in experiment test_experiment."), Times.Once); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -696,15 +886,26 @@ public void TestActivateWithNullAttributes() var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("EventProcessor", EventProcessorMock.Object); - var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", OptimizelyHelper.NullUserAttributes); + var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", + OptimizelyHelper.NullUserAttributes); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); //"User "test_user" is not in the forced variation map." - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"test_user\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"test_user\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [test_user] is in variation [control] of experiment [test_experiment]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Activating user test_user in experiment test_experiment."), Times.Once); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -716,9 +917,11 @@ public void TestActivateExperimentNotRunning() var variationkey = optly.Invoke("Activate", "paused_experiment", "test_user", null); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Experiment \"paused_experiment\" is not running."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Not activating user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Experiment \"paused_experiment\" is not running."), + Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Not activating user test_user."), + Times.Once); Assert.IsNull(variationkey); } @@ -728,23 +931,42 @@ public void TestActivateWithTypedAttributes() { var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" }, - {"boolean_key", true }, - {"integer_key", 15 }, - {"double_key", 3.14 } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, + { + "boolean_key", true + }, + { + "integer_key", 15 + }, + { + "double_key", 3.14 + }, }; var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("EventProcessor", EventProcessorMock.Object); - var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", userAttributes); + var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", + userAttributes); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [test_user] is in variation [control] of experiment [test_experiment]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Activating user test_user in experiment test_experiment."), Times.Once); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -758,8 +980,9 @@ public void TestGetVariationInvalidOptimizelyObject() { var optly = new Optimizely("Random datafile", null, LoggerMock.Object); var variationkey = optly.Activate("some_experiment", "some_user"); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Activate'."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Activate'."), + Times.Once); //Assert.IsNull(variationkey); } @@ -784,15 +1007,28 @@ public void TestGetVariationAudienceMatch() { var attributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; var variation = Optimizely.GetVariation("test_experiment", "test_user", attributes); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [test_user] is in variation [control] of experiment [test_experiment]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "This decision will not be saved since the UserProfileService is null."), + Times.Once); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -801,7 +1037,10 @@ public void TestGetVariationAudienceMatch() public void TestGetVariationAudienceNoMatch() { var variation = Optimizely.Activate("test_experiment", "test_user"); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User \"test_user\" does not meet conditions to be in experiment \"test_experiment\"."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User \"test_user\" does not meet conditions to be in experiment \"test_experiment\"."), + Times.Once); Assert.IsNull(variation); } @@ -809,7 +1048,9 @@ public void TestGetVariationAudienceNoMatch() public void TestGetVariationExperimentNotRunning() { var variation = Optimizely.Activate("paused_experiment", "test_user"); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Experiment \"paused_experiment\" is not running."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Experiment \"paused_experiment\" is not running."), + Times.Once); Assert.IsNull(variation); } @@ -818,8 +1059,9 @@ public void TestTrackInvalidOptimizelyObject() { var optly = new Optimizely("Random datafile", null, LoggerMock.Object); optly.Track("some_event", "some_user"); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Track'."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'Track'."), + Times.Once); } #endregion Test GetVariation @@ -831,12 +1073,16 @@ public void TestTrackInvalidAttributes() { var attributes = new UserAttributes { - { "abc", "43" } + { + "abc", "43" + }, }; Optimizely.Track("purchase", TestUserId, attributes); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"Attribute key ""abc"" is not in datafile."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, @"Attribute key ""abc"" is not in datafile."), + Times.Once); } [Test] @@ -846,9 +1092,12 @@ public void TestTrackNoAttributesNoEventValue() optly.SetFieldOrProperty("EventProcessor", EventProcessorMock.Object); optly.Invoke("Track", "purchase", "test_user", null, null); - EventProcessorMock.Verify(processor => processor.Process(It.IsAny()), Times.Once); + EventProcessorMock.Verify(processor => processor.Process(It.IsAny()), + Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), + Times.Once); } [Test] @@ -860,7 +1109,9 @@ public void TestTrackWithAttributesNoEventValue() optly.Invoke("Track", "purchase", "test_user", OptimizelyHelper.UserAttributes, null); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), + Times.Once); } [Test] @@ -868,8 +1119,12 @@ public void TestTrackUnknownEventKey() { Optimizely.Track("unknown_event", "test_user"); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Event key \"unknown_event\" is not in datafile."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Not tracking user test_user for event unknown_event."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Event key \"unknown_event\" is not in datafile."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Not tracking user test_user for event unknown_event."), + Times.Once); } [Test] @@ -880,11 +1135,15 @@ public void TestTrackNoAttributesWithEventValue() optly.Invoke("Track", "purchase", "test_user", null, new EventTags { - { "revenue", 42 } + { + "revenue", 42 + }, }); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), + Times.Once); } [Test] @@ -892,8 +1151,12 @@ public void TestTrackWithAttributesWithEventValue() { var attributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, }; var optly = Helper.CreatePrivateOptimizely(); @@ -901,12 +1164,16 @@ public void TestTrackWithAttributesWithEventValue() optly.Invoke("Track", "purchase", "test_user", attributes, new EventTags { - { "revenue", 42 } + { + "revenue", 42 + }, }); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), + Times.Once); } [Test] @@ -915,16 +1182,23 @@ public void TestTrackWithNullAttributesWithNullEventValue() var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("EventProcessor", EventProcessorMock.Object); - optly.Invoke("Track", "purchase", "test_user", OptimizelyHelper.NullUserAttributes, new EventTags - { - { "revenue", 42 }, - { "wont_send_null", null} - }); + optly.Invoke("Track", "purchase", "test_user", OptimizelyHelper.NullUserAttributes, + new EventTags + { + { + "revenue", 42 + }, + { + "wont_send_null", null + }, + }); EventProcessorMock.Verify(ep => ep.Process(It.IsAny()), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "[EventTags] Null value for key wont_send_null removed and will not be sent to results.")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user.")); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, + "[EventTags] Null value for key wont_send_null removed and will not be sent to results.")); + LoggerMock.Verify(l => + l.Log(LogLevel.INFO, "Tracking event purchase for user test_user.")); } #endregion Test Track @@ -937,16 +1211,28 @@ public void TestInvalidDispatchImpressionEvent() var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("EventDispatcher", new InvalidEventDispatcher()); - var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", OptimizelyHelper.UserAttributes); + var variation = (Variation)optly.Invoke("Activate", "test_experiment", "test_user", + OptimizelyHelper.UserAttributes); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [test_user] is in variation [control] of experiment [test_experiment]."), + Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Activating user test_user in experiment test_experiment."), Times.Once); // Need to see how error handler can be verified. - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, It.IsAny()), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Attribute key \"company\" is not in datafile."), + Times.Once); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -959,9 +1245,12 @@ public void TestInvalidDispatchConversionEvent() optly.Invoke("Track", "purchase", "test_user", null, null); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), + Times.Once); } #endregion Test Invalid Dispatch @@ -977,7 +1266,9 @@ public void TestTrackNoAttributesWithInvalidEventValue() optly.Invoke("Track", "purchase", "test_user", null, new Dictionary { - {"revenue", 4200 } + { + "revenue", 4200 + }, }); } @@ -990,17 +1281,21 @@ public void TestTrackNoAttributesWithDeprecatedEventValue() optly.SetFieldOrProperty("EventDispatcher", new ValidEventDispatcher()); optly.Invoke("Track", "purchase", "test_user", null, new Dictionary { - {"revenue", 42 } + { + "revenue", 42 + }, }); } [Test] public void TestForcedVariationPreceedsWhitelistedVariation() { - var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); - var projectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, ErrorHandlerMock.Object); - Variation expectedVariation1 = projectConfig.GetVariationFromKey("etag3", "vtag5"); - Variation expectedVariation2 = projectConfig.GetVariationFromKey("etag3", "vtag6"); + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, + LoggerMock.Object, ErrorHandlerMock.Object); + var projectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + ErrorHandlerMock.Object); + var expectedVariation1 = projectConfig.GetVariationFromKey("etag3", "vtag5"); + var expectedVariation2 = projectConfig.GetVariationFromKey("etag3", "vtag6"); //Check whitelisted experiment var variation = optimizely.GetVariation("etag3", "testUser1"); @@ -1019,13 +1314,28 @@ public void TestForcedVariationPreceedsWhitelistedVariation() Assert.IsTrue(TestData.CompareObjects(expectedVariation1, variation)); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(7)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User \"testUser1\" is forced in variation \"vtag5\"."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Set variation \"281\" for experiment \"224\" and user \"testUser1\" in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Variation \"vtag6\" is mapped to experiment \"etag3\" and user \"testUser1\" in the forced variation map"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Variation mapped to experiment \"etag3\" has been removed for user \"testUser1\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "No experiment \"etag3\" mapped to user \"testUser1\" in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUser1\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "User \"testUser1\" is forced in variation \"vtag5\"."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Set variation \"281\" for experiment \"224\" and user \"testUser1\" in the forced variation map."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Variation \"vtag6\" is mapped to experiment \"etag3\" and user \"testUser1\" in the forced variation map"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Variation mapped to experiment \"etag3\" has been removed for user \"testUser1\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "No experiment \"etag3\" mapped to user \"testUser1\" in the forced variation map."), + Times.Once); } [Test] @@ -1037,17 +1347,24 @@ public void TestForcedVariationPreceedsUserProfile() var variationKey = "vtag2"; var fbVariationKey = "vtag1"; - UserProfile userProfile = new UserProfile(userId, new Dictionary - { - { experimentKey, new Bucketing.Decision(variationKey)} - }); + var userProfile = new UserProfile(userId, + new Dictionary + { + { + experimentKey, new Bucketing.Decision(variationKey) + }, + }); userProfileServiceMock.Setup(_ => _.Lookup(userId)).Returns(userProfile.ToMap()); - var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); - var projectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, ErrorHandlerMock.Object); - Variation expectedFbVariation = projectConfig.GetVariationFromKey(experimentKey, fbVariationKey); - Variation expectedVariation = projectConfig.GetVariationFromKey(experimentKey, variationKey); + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, + LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); + var projectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + ErrorHandlerMock.Object); + var expectedFbVariation = + projectConfig.GetVariationFromKey(experimentKey, fbVariationKey); + var expectedVariation = + projectConfig.GetVariationFromKey(experimentKey, variationKey); var variationUserProfile = optimizely.GetVariation(experimentKey, userId); Assert.IsTrue(TestData.CompareObjects(expectedVariation, variationUserProfile)); @@ -1064,16 +1381,43 @@ public void TestForcedVariationPreceedsUserProfile() variationUserProfile = optimizely.GetVariation(experimentKey, userId); Assert.IsTrue(TestData.CompareObjects(expectedVariation, variationUserProfile)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser3\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "No previously activated variation of experiment \"etag1\" for user \"testUser3\" found in user profile."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [4969] to user [testUser3] with bucketing ID [testUser3]."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [testUser3] is in variation [vtag2] of experiment [etag1]."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Saved variation \"277\" of experiment \"223\" for user \"testUser3\"."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Set variation \"276\" for experiment \"223\" and user \"testUser3\" in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Variation mapped to experiment \"etag1\" has been removed for user \"testUser3\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "No experiment \"etag1\" mapped to user \"testUser3\" in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser3\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser3\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUser3\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "No previously activated variation of experiment \"etag1\" for user \"testUser3\" found in user profile."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [4969] to user [testUser3] with bucketing ID [testUser3]."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [testUser3] is in variation [vtag2] of experiment [etag1]."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Saved variation \"277\" of experiment \"223\" for user \"testUser3\"."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Set variation \"276\" for experiment \"223\" and user \"testUser3\" in the forced variation map."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Variation mapped to experiment \"etag1\" has been removed for user \"testUser3\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "No experiment \"etag1\" mapped to user \"testUser3\" in the forced variation map."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUser3\" is not in the forced variation map."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUser3\" is not in the forced variation map."), Times.Once); } // check that a null variation key clears the forced variation @@ -1085,23 +1429,36 @@ public void TestSetForcedVariationNullVariation() var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; Optimizely.Activate(experimentKey, TestUserId, userAttributes); // set variation - Assert.IsTrue(Optimizely.SetForcedVariation(experimentKey, TestUserId, expectedForcedVariationKey), "Set forced variation to variation failed."); + Assert.IsTrue( + Optimizely.SetForcedVariation(experimentKey, TestUserId, + expectedForcedVariationKey), "Set forced variation to variation failed."); - var actualForcedVariation = Optimizely.GetVariation(experimentKey, TestUserId, userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualForcedVariation), string.Format(@"Forced variation key should be variation, but got ""{0}"".", actualForcedVariation?.Key)); + var actualForcedVariation = + Optimizely.GetVariation(experimentKey, TestUserId, userAttributes); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualForcedVariation), + string.Format(@"Forced variation key should be variation, but got ""{0}"".", + actualForcedVariation?.Key)); // clear variation and check that the user gets bucketed normally - Assert.IsTrue(Optimizely.SetForcedVariation(experimentKey, TestUserId, null), "Clear forced variation failed."); + Assert.IsTrue(Optimizely.SetForcedVariation(experimentKey, TestUserId, null), + "Clear forced variation failed."); - var actualVariation = Optimizely.GetVariation("test_experiment", "test_user", userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation), string.Format(@"Variation key should be control, but got ""{0}"".", actualVariation?.Key)); + var actualVariation = + Optimizely.GetVariation("test_experiment", "test_user", userAttributes); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation), + string.Format(@"Variation key should be control, but got ""{0}"".", + actualVariation?.Key)); } // check that the forced variation is set correctly @@ -1113,35 +1470,51 @@ public void TestSetForcedVariation() var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; Optimizely.Activate(experimentKey, TestUserId, userAttributes); // test invalid experiment -. normal bucketing should occur - Assert.IsFalse(Optimizely.SetForcedVariation("bad_experiment", TestUserId, "bad_control"), "Set variation to 'variation' should have failed because of invalid experiment."); + Assert.IsFalse( + Optimizely.SetForcedVariation("bad_experiment", TestUserId, "bad_control"), + "Set variation to 'variation' should have failed because of invalid experiment."); var variation = Optimizely.GetVariation(experimentKey, TestUserId, userAttributes); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); // test invalid variation -. normal bucketing should occur - Assert.IsFalse(Optimizely.SetForcedVariation("test_experiment", TestUserId, "bad_variation"), "Set variation to 'bad_variation' should have failed."); + Assert.IsFalse( + Optimizely.SetForcedVariation("test_experiment", TestUserId, "bad_variation"), + "Set variation to 'bad_variation' should have failed."); variation = Optimizely.GetVariation("test_experiment", "test_user", userAttributes); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); // test valid variation -. the user should be bucketed to the specified forced variation - Assert.IsTrue(Optimizely.SetForcedVariation(experimentKey, TestUserId, expectedForcedVariationKey), "Set variation to 'variation' failed."); + Assert.IsTrue( + Optimizely.SetForcedVariation(experimentKey, TestUserId, + expectedForcedVariationKey), "Set variation to 'variation' failed."); - var actualForcedVariation = Optimizely.GetVariation(experimentKey, TestUserId, userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualForcedVariation)); + var actualForcedVariation = + Optimizely.GetVariation(experimentKey, TestUserId, userAttributes); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, + actualForcedVariation)); // make sure another setForcedVariation call sets a new forced variation correctly - Assert.IsTrue(Optimizely.SetForcedVariation(experimentKey, "test_user2", expectedForcedVariationKey), "Set variation to 'variation' failed."); - actualForcedVariation = Optimizely.GetVariation(experimentKey, "test_user2", userAttributes); + Assert.IsTrue( + Optimizely.SetForcedVariation(experimentKey, "test_user2", + expectedForcedVariationKey), "Set variation to 'variation' failed."); + actualForcedVariation = + Optimizely.GetVariation(experimentKey, "test_user2", userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualForcedVariation)); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, + actualForcedVariation)); } [Test] @@ -1157,7 +1530,8 @@ public void TestSetForcedVariationWithInvalidExperimentKey() var userId = "test_user"; var variation = "variation"; - Assert.False(Optimizely.SetForcedVariation("test_experiment_not_in_datafile", userId, variation)); + Assert.False(Optimizely.SetForcedVariation("test_experiment_not_in_datafile", userId, + variation)); Assert.False(Optimizely.SetForcedVariation("", userId, variation)); Assert.False(Optimizely.SetForcedVariation(null, userId, variation)); } @@ -1168,7 +1542,8 @@ public void TestSetForcedVariationWithInvalidVariationKey() var userId = "test_user"; var experimentKey = "test_experiment"; - Assert.False(Optimizely.SetForcedVariation(experimentKey, userId, "variation_not_in_datafile")); + Assert.False(Optimizely.SetForcedVariation(experimentKey, userId, + "variation_not_in_datafile")); Assert.False(Optimizely.SetForcedVariation(experimentKey, userId, "")); } @@ -1177,20 +1552,35 @@ public void TestSetForcedVariationWithInvalidVariationKey() public void TestGetForcedVariation() { var experimentKey = "test_experiment"; - var expectedForcedVariation = new Variation { Key = "variation", Id = "7721010009" }; - var expectedForcedVariation2 = new Variation { Key = "variation", Id = "7721010509" }; + var expectedForcedVariation = new Variation + { + Key = "variation", + Id = "7721010009", + }; + var expectedForcedVariation2 = new Variation + { + Key = "variation", + Id = "7721010509", + }; var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; Optimizely.Activate(experimentKey, TestUserId, userAttributes); - Assert.IsTrue(Optimizely.SetForcedVariation(experimentKey, TestUserId, expectedForcedVariation.Key), "Set variation to 'variation' failed."); + Assert.IsTrue( + Optimizely.SetForcedVariation(experimentKey, TestUserId, + expectedForcedVariation.Key), "Set variation to 'variation' failed."); // call getForcedVariation with valid experiment key and valid user ID - var actualForcedVariation = Optimizely.GetForcedVariation("test_experiment", TestUserId); + var actualForcedVariation = + Optimizely.GetForcedVariation("test_experiment", TestUserId); Assert.IsTrue(TestData.CompareObjects(expectedForcedVariation, actualForcedVariation)); // call getForcedVariation with invalid experiment and valid userID @@ -1198,14 +1588,18 @@ public void TestGetForcedVariation() Assert.Null(actualForcedVariation); // call getForcedVariation with valid experiment and invalid userID - actualForcedVariation = Optimizely.GetForcedVariation("test_experiment", "invalid_user"); + actualForcedVariation = + Optimizely.GetForcedVariation("test_experiment", "invalid_user"); Assert.Null(actualForcedVariation); // call getForcedVariation with an experiment that"s not running - Assert.IsTrue(Optimizely.SetForcedVariation("paused_experiment", "test_user2", "variation"), "Set variation to 'variation' failed."); + Assert.IsTrue( + Optimizely.SetForcedVariation("paused_experiment", "test_user2", "variation"), + "Set variation to 'variation' failed."); - actualForcedVariation = Optimizely.GetForcedVariation("paused_experiment", "test_user2"); + actualForcedVariation = + Optimizely.GetForcedVariation("paused_experiment", "test_user2"); Assert.IsTrue(TestData.CompareObjects(expectedForcedVariation2, actualForcedVariation)); @@ -1248,16 +1642,26 @@ public void TestGetVariationAudienceMatchAfterSetForcedVariation() var attributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; - Assert.True(Optimizely.SetForcedVariation(experimentKey, userId, variationKey), "Set variation for paused experiment should have failed."); + Assert.True(Optimizely.SetForcedVariation(experimentKey, userId, variationKey), + "Set variation for paused experiment should have failed."); var variation = Optimizely.GetVariation(experimentKey, userId, attributes); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - 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(@"Variation ""{0}"" is mapped to experiment ""{1}"" and user ""{2}"" in the forced variation map", variationKey, experimentKey, userId))); + 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( + @"Variation ""{0}"" is mapped to experiment ""{1}"" and user ""{2}"" in the forced variation map", + variationKey, experimentKey, userId))); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation)); } @@ -1273,16 +1677,24 @@ public void TestGetVariationExperimentNotRunningAfterSetForceVariation() var attributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; - Assert.True(Optimizely.SetForcedVariation(experimentKey, userId, variationKey), "Set variation for paused experiment should have failed."); + Assert.True(Optimizely.SetForcedVariation(experimentKey, userId, variationKey), + "Set variation for paused experiment should have failed."); var variation = Optimizely.GetVariation(experimentKey, userId, attributes); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - 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.INFO, string.Format("Experiment \"{0}\" is not running.", 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(LogLevel.INFO, + string.Format("Experiment \"{0}\" is not running.", experimentKey))); Assert.Null(variation); } @@ -1296,12 +1708,18 @@ public void TestGetVariationWhitelistedUserAfterSetForcedVariation() var variationKey = "variation"; var variationId = "7721010009"; - Assert.True(Optimizely.SetForcedVariation(experimentKey, userId, variationKey), "Set variation for paused experiment should have passed."); + Assert.True(Optimizely.SetForcedVariation(experimentKey, userId, variationKey), + "Set variation for paused experiment should have passed."); var variation = Optimizely.GetVariation(experimentKey, userId); - LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Exactly(2)); - 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(@"Variation ""{0}"" is mapped to experiment ""{1}"" and user ""{2}"" in the forced variation map", variationKey, experimentKey, userId))); + 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( + @"Variation ""{0}"" is mapped to experiment ""{1}"" and user ""{2}"" in the forced variation map", + variationKey, experimentKey, userId))); Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, variation)); } @@ -1316,32 +1734,63 @@ public void TestActivateNoAudienceNoAttributesAfterSetForcedVariation() var variationId = "7722370027"; var parameters = new Dictionary { - { "param1", "val1" }, - { "param2", "val2" } + { + "param1", "val1" + }, + { + "param2", "val2" + }, }; - Experiment experiment = new Experiment(); + var experiment = new Experiment(); experiment.Key = "group_experiment_1"; var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); // Set forced variation - Assert.True((bool)optly.Invoke("SetForcedVariation", experimentKey, userId, variationKey), "Set variation for paused experiment should have failed."); + Assert.True( + (bool)optly.Invoke("SetForcedVariation", experimentKey, userId, variationKey), + "Set variation for paused experiment should have failed."); // Activate - var variation = (Variation)optly.Invoke("Activate", "group_experiment_1", "user_1", null); - - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); - - 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)), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"user_1\" is not in the forced variation map."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [1922] to user [user_1] with bucketing ID [user_1]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in experiment [group_experiment_1] of group [7722400015]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once); + var variation = + (Variation)optly.Invoke("Activate", "group_experiment_1", "user_1", null); + + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); + + 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)), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, "User \"user_1\" is not in the forced variation map."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [1922] to user [user_1] with bucketing ID [user_1]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [user_1] is in experiment [group_experiment_1] of group [7722400015]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "User [user_1] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "This decision will not be saved since the UserProfileService is null."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Activating user user_1 in experiment group_experiment_1."), Times.Once); Assert.IsTrue(TestData.CompareObjects(GroupVariation, variation)); } @@ -1356,60 +1805,99 @@ public void TestTrackNoAttributesNoEventValueAfterSetForcedVariation() var variationId = "7722370027"; var parameters = new Dictionary { - { "param1", "val1" } + { + "param1", "val1" + }, }; var optly = Helper.CreatePrivateOptimizely(); // Set forced variation - Assert.True((bool)optly.Invoke("SetForcedVariation", experimentKey, userId, variationKey), "Set variation for paused experiment should have failed."); + Assert.True( + (bool)optly.Invoke("SetForcedVariation", experimentKey, userId, variationKey), + "Set variation for paused experiment should have failed."); // Track optly.Invoke("Track", "purchase", "test_user", null, null); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); - 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)), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), Times.Once); + 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)), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "Tracking event purchase for user test_user."), + Times.Once); } [Test] public void TestGetVariationBucketingIdAttribute() { - var testBucketingIdControl = "testBucketingIdControl!"; // generates bucketing number 3741 + var testBucketingIdControl = + "testBucketingIdControl!"; // generates bucketing number 3741 var testBucketingIdVariation = "123456789"; // generates bucketing number 4567 var userId = "test_user"; var experimentKey = "test_experiment"; var userAttributes = 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 + }, }; // confirm that a valid variation is bucketed without the bucketing ID var actualVariation = Optimizely.GetVariation(experimentKey, userId, userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation), string.Format("Invalid variation key \"{0}\" for getVariation.", actualVariation?.Key)); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation), + string.Format("Invalid variation key \"{0}\" for getVariation.", + actualVariation?.Key)); // confirm that invalid audience returns null actualVariation = Optimizely.GetVariation(experimentKey, userId); - Assert.Null(actualVariation, string.Format("Invalid variation key \"{0}\" for getVariation with bucketing ID \"{1}\".", actualVariation?.Key, testBucketingIdControl)); + Assert.Null(actualVariation, + string.Format( + "Invalid variation key \"{0}\" for getVariation with bucketing ID \"{1}\".", + actualVariation?.Key, testBucketingIdControl)); // confirm that a valid variation is bucketed with the bucketing ID - actualVariation = Optimizely.GetVariation(experimentKey, userId, userAttributesWithBucketingId); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualVariation), string.Format("Invalid variation key \"{0}\" for getVariation with bucketing ID \"{1}\".", actualVariation?.Key, testBucketingIdVariation)); + actualVariation = + Optimizely.GetVariation(experimentKey, userId, userAttributesWithBucketingId); + Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualVariation), + string.Format( + "Invalid variation key \"{0}\" for getVariation with bucketing ID \"{1}\".", + actualVariation?.Key, testBucketingIdVariation)); // confirm that invalid experiment with the bucketing ID returns null - actualVariation = Optimizely.GetVariation("invalidExperimentKey", userId, userAttributesWithBucketingId); - Assert.Null(actualVariation, string.Format("Invalid variation key \"{0}\" for getVariation with bucketing ID \"{1}\".", actualVariation?.Key, testBucketingIdControl)); + actualVariation = Optimizely.GetVariation("invalidExperimentKey", userId, + userAttributesWithBucketingId); + Assert.Null(actualVariation, + string.Format( + "Invalid variation key \"{0}\" for getVariation with bucketing ID \"{1}\".", + actualVariation?.Key, testBucketingIdControl)); } #endregion Test Misc @@ -1426,21 +1914,35 @@ public void TestGetFeatureVariableBooleanReturnsCorrectValue() var variableKeyNull = "varNull"; var featureVariableType = "boolean"; - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyTrue, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(true); - Assert.AreEqual(true, OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyTrue, TestUserId, null)); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyFalse, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(false); - Assert.AreEqual(false, OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyFalse, TestUserId, null)); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNonBoolean, It.IsAny(), - It.IsAny(), featureVariableType)).Returns("non_boolean_value"); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyNonBoolean, TestUserId, null)); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(null); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyNull, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyTrue, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(true); + Assert.AreEqual(true, + OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyTrue, + TestUserId, null)); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyFalse, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(false); + Assert.AreEqual(false, + OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyFalse, + TestUserId, null)); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyNonBoolean, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns("non_boolean_value"); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, + variableKeyNonBoolean, TestUserId, null)); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableBoolean(featureKey, variableKeyNull, + TestUserId, null)); } [Test] @@ -1451,12 +1953,15 @@ public void TestGetFeatureVariableDoubleFRCulture() var optimizely = new Optimizely(fallbackConfigManager); - var doubleValue = optimizely.GetFeatureVariableDouble("double_single_variable_feature", "double_variable", "testUser1"); + var doubleValue = optimizely.GetFeatureVariableDouble("double_single_variable_feature", + "double_variable", "testUser1"); Assert.AreEqual(doubleValue, 14.99); SetCulture("fr-FR"); - var doubleValueFR = optimizely.GetFeatureVariableDouble("double_single_variable_feature", "double_variable", "testUser1"); + var doubleValueFR = + optimizely.GetFeatureVariableDouble("double_single_variable_feature", + "double_variable", "testUser1"); Assert.AreEqual(doubleValueFR, 14.99); } @@ -1468,12 +1973,16 @@ public void TestGetFeatureVariableIntegerFRCulture() var optimizely = new Optimizely(fallbackConfigManager); - var integerValue = optimizely.GetFeatureVariableInteger("integer_single_variable_feature", "integer_variable", "testUser1"); + var integerValue = + optimizely.GetFeatureVariableInteger("integer_single_variable_feature", + "integer_variable", "testUser1"); Assert.AreEqual(integerValue, 13); SetCulture("fr-FR"); - var integerValueFR = optimizely.GetFeatureVariableInteger("integer_single_variable_feature", "integer_variable", "testUser1"); + var integerValueFR = + optimizely.GetFeatureVariableInteger("integer_single_variable_feature", + "integer_variable", "testUser1"); Assert.AreEqual(integerValueFR, 13); } @@ -1485,12 +1994,16 @@ public void TestGetFeatureVariableBooleanFRCulture() var optimizely = new Optimizely(fallbackConfigManager); - var booleanValue = optimizely.GetFeatureVariableBoolean("boolean_single_variable_feature", "boolean_variable", "testUser1"); + var booleanValue = + optimizely.GetFeatureVariableBoolean("boolean_single_variable_feature", + "boolean_variable", "testUser1"); Assert.AreEqual(booleanValue, false); SetCulture("fr-FR"); - var booleanValueFR = optimizely.GetFeatureVariableBoolean("boolean_single_variable_feature", "boolean_variable", "testUser1"); + var booleanValueFR = + optimizely.GetFeatureVariableBoolean("boolean_single_variable_feature", + "boolean_variable", "testUser1"); Assert.AreEqual(booleanValueFR, false); } @@ -1502,12 +2015,15 @@ public void TestGetFeatureVariableStringFRCulture() var optimizely = new Optimizely(fallbackConfigManager); - var stringValue = optimizely.GetFeatureVariableString("string_single_variable_feature", "string_variable", "testUser1"); + var stringValue = optimizely.GetFeatureVariableString("string_single_variable_feature", + "string_variable", "testUser1"); Assert.AreEqual(stringValue, "cta_1"); SetCulture("fr-FR"); - var stringValueFR = optimizely.GetFeatureVariableString("string_single_variable_feature", "string_variable", "testUser1"); + var stringValueFR = + optimizely.GetFeatureVariableString("string_single_variable_feature", + "string_variable", "testUser1"); Assert.AreEqual(stringValueFR, "cta_1"); } @@ -1518,27 +2034,39 @@ public void TestGetFeatureVariableJSONFRCulture() var expectedDict = new Dictionary() { - { "int_var", 1 }, - { "boolean_key", false} + { + "int_var", 1 + }, + { + "boolean_key", false + }, }; SetCulture("en-US"); var optimizely = new Optimizely(fallbackConfigManager); - var optimizelyJsonValue = optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", "testUser1"); + var optimizelyJsonValue = + optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", + "testUser1"); - Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.ToDictionary(), expectedDict)); + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.ToDictionary(), + expectedDict)); Assert.AreEqual(optimizelyJsonValue.GetValue("int_var"), 1); Assert.AreEqual(optimizelyJsonValue.GetValue("boolean_key"), false); - Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.GetValue(""), expectedDict)); + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.GetValue(""), + expectedDict)); SetCulture("fr-FR"); - var optimizelyJsonValueFR = optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", "testUser1"); + var optimizelyJsonValueFR = + optimizely.GetFeatureVariableJSON("string_single_variable_feature", "json_var", + "testUser1"); - Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.ToDictionary(), expectedDict)); + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.ToDictionary(), + expectedDict)); Assert.AreEqual(optimizelyJsonValueFR.GetValue("int_var"), 1); Assert.AreEqual(optimizelyJsonValueFR.GetValue("boolean_key"), false); - Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.GetValue(""), expectedDict)); + Assert.IsTrue(TestData.CompareObjects(optimizelyJsonValue.GetValue(""), + expectedDict)); } [Test] @@ -1551,21 +2079,35 @@ public void TestGetFeatureVariableDoubleReturnsCorrectValue() var variableKeyNull = "varNull"; var featureVariableType = "double"; - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyDouble, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(100.54); - Assert.AreEqual(100.54, OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyDouble, TestUserId, null)); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyInt, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(100); - Assert.AreEqual(100, OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyInt, TestUserId, null)); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNonDouble, It.IsAny(), - It.IsAny(), featureVariableType)).Returns("non_double_value"); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyNonDouble, TestUserId, null)); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(null); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyNull, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyDouble, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(100.54); + Assert.AreEqual(100.54, + OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyDouble, + TestUserId, null)); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyInt, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(100); + Assert.AreEqual(100, + OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyInt, + TestUserId, null)); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyNonDouble, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns("non_double_value"); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, + variableKeyNonDouble, TestUserId, null)); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableDouble(featureKey, variableKeyNull, + TestUserId, null)); } [Test] @@ -1577,17 +2119,27 @@ public void TestGetFeatureVariableIntegerReturnsCorrectValue() var variableKeyNull = "varNull"; var featureVariableType = "integer"; - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyInt, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(100); - Assert.AreEqual(100, OptimizelyMock.Object.GetFeatureVariableInteger(featureKey, variableKeyInt, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyInt, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(100); + Assert.AreEqual(100, + OptimizelyMock.Object.GetFeatureVariableInteger(featureKey, variableKeyInt, + TestUserId, null)); - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableNonInt, It.IsAny(), - It.IsAny(), featureVariableType)).Returns("non_integer_value"); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableInteger(featureKey, variableNonInt, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableNonInt, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns("non_integer_value"); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableInteger(featureKey, variableNonInt, + TestUserId, null)); - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(null); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableInteger(featureKey, variableKeyNull, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableInteger(featureKey, variableKeyNull, + TestUserId, null)); } [Test] @@ -1599,17 +2151,28 @@ public void TestGetFeatureVariableStringReturnsCorrectValue() var variableKeyNull = "varNull"; var featureVariableType = "string"; - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyString, It.IsAny(), - It.IsAny(), featureVariableType)).Returns("Test String"); - Assert.AreEqual("Test String", OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyString, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyString, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns("Test String"); + Assert.AreEqual("Test String", + OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyString, + TestUserId, null)); - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyIntString, It.IsAny(), - It.IsAny(), featureVariableType)).Returns("123"); - Assert.AreEqual("123", OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyIntString, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyIntString, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns("123"); + Assert.AreEqual("123", + OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyIntString, + TestUserId, null)); - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(null); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyNull, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), + variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableString(featureKey, variableKeyNull, + TestUserId, null)); } [Test] @@ -1622,21 +2185,42 @@ public void TestGetFeatureVariableJSONReturnsCorrectValue() var variableKeyNull = "varNull"; var featureVariableType = "json"; - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyString, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON("{\"string\": \"Test String\"}", ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.AreEqual("Test String", OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null).GetValue("string")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyIntString, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON("{ \"integer\": 123 }", ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.AreEqual(123, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null).GetValue("integer")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyDouble, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON("{ \"double\": 123.28 }", ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.AreEqual(123.28, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null).GetValue("double")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(null); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, TestUserId, null)); + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyString, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON("{\"string\": \"Test String\"}", ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.AreEqual("Test String", + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null). + GetValue("string")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyIntString, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON("{ \"integer\": 123 }", ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.AreEqual(123, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null). + GetValue("integer")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyDouble, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON("{ \"double\": 123.28 }", ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.AreEqual(123.28, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null). + GetValue("double")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, + TestUserId, null)); } [Test] @@ -1650,40 +2234,100 @@ public void TestGetFeatureVariableJSONReturnsCorrectValueWhenInitializedUsingDic var variableKeyNull = "varNull"; var featureVariableType = "json"; - var expectedStringDict = new Dictionary() { { "string", "Test String" } }; - var expectedIntegerDict = new Dictionary() { { "integer", 123 } }; - var expectedDoubleDict = new Dictionary() { { "double", 123.28 } }; - var expectedBooleanDict = new Dictionary() { { "boolean", true } }; - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyString, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON(expectedStringDict, ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.IsTrue(TestData.CompareObjects(expectedStringDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null).ToDictionary())); - Assert.AreEqual("Test String", OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null).GetValue("string")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyIntString, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON(expectedIntegerDict, ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.IsTrue(TestData.CompareObjects(expectedIntegerDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null).ToDictionary())); - Assert.AreEqual(123, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null).GetValue("integer")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyDouble, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON(expectedDoubleDict, ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.IsTrue(TestData.CompareObjects(expectedDoubleDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null).ToDictionary())); - Assert.AreEqual(123.28, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null).GetValue("double")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyBoolean, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(new OptimizelyJSON(expectedBooleanDict, ErrorHandlerMock.Object, LoggerMock.Object)); - Assert.IsTrue(TestData.CompareObjects(expectedBooleanDict, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyBoolean, TestUserId, null).ToDictionary())); - Assert.AreEqual(true, OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyBoolean, TestUserId, null).GetValue("boolean")); - - OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType(It.IsAny(), variableKeyNull, It.IsAny(), - It.IsAny(), featureVariableType)).Returns(null); - Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, TestUserId, null)); - } - - #region Feature Toggle Tests - - [Test] - public void TestGetFeatureVariableDoubleReturnsRightValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() + var expectedStringDict = new Dictionary() + { + { + "string", "Test String" + }, + }; + var expectedIntegerDict = new Dictionary() + { + { + "integer", 123 + }, + }; + var expectedDoubleDict = new Dictionary() + { + { + "double", 123.28 + }, + }; + var expectedBooleanDict = new Dictionary() + { + { + "boolean", true + }, + }; + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyString, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON(expectedStringDict, ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedStringDict, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null). + ToDictionary())); + Assert.AreEqual("Test String", + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyString, TestUserId, null). + GetValue("string")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyIntString, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON(expectedIntegerDict, ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedIntegerDict, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null). + ToDictionary())); + Assert.AreEqual(123, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyIntString, TestUserId, null). + GetValue("integer")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyDouble, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON(expectedDoubleDict, ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedDoubleDict, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null). + ToDictionary())); + Assert.AreEqual(123.28, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyDouble, TestUserId, null). + GetValue("double")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyBoolean, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(new OptimizelyJSON(expectedBooleanDict, ErrorHandlerMock.Object, + LoggerMock.Object)); + Assert.IsTrue(TestData.CompareObjects(expectedBooleanDict, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyBoolean, TestUserId, null). + ToDictionary())); + Assert.AreEqual(true, + OptimizelyMock.Object. + GetFeatureVariableJSON(featureKey, variableKeyBoolean, TestUserId, null). + GetValue("boolean")); + + OptimizelyMock.Setup(om => om.GetFeatureVariableValueForType( + It.IsAny(), variableKeyNull, It.IsAny(), + It.IsAny(), featureVariableType)). + Returns(null); + Assert.Null(OptimizelyMock.Object.GetFeatureVariableJSON(featureKey, variableKeyNull, + TestUserId, null)); + } + + #region Feature Toggle Tests + + [Test] + public void + TestGetFeatureVariableDoubleReturnsRightValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1691,103 +2335,158 @@ public void TestGetFeatureVariableDoubleReturnsRightValueWhenUserBuckedIntoFeatu var expectedValue = 42.42; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); var variation = Config.GetVariationFromKey("test_experiment_double_feature", "control"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); + var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""{variableValue}"" for variable ""{variableKey + }"" of feature flag ""{featureKey}"".")); } [Test] - public void TestGetFeatureVariableIntegerReturnsRightValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() + public void + TestGetFeatureVariableIntegerReturnsRightValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() { var featureKey = "integer_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "integer_variable"; var expectedValue = 13; var experiment = Config.GetExperimentFromKey("test_experiment_integer_feature"); - var variation = Config.GetVariationFromKey("test_experiment_integer_feature", "variation"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var variation = + Config.GetVariationFromKey("test_experiment_integer_feature", "variation"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""{variableValue}"" for variable ""{variableKey + }"" of feature flag ""{featureKey}"".")); } [Test] - public void TestGetFeatureVariableDoubleReturnsDefaultValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() + public void + TestGetFeatureVariableDoubleReturnsDefaultValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "double_variable"; var expectedValue = 14.99; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Config.GetVariationFromKey("test_experiment_double_feature", "variation"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + var variation = + Config.GetVariationFromKey("test_experiment_double_feature", "variation"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); + var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId + }. Returning the default variable value ""{variableValue}"".")); } [Test] - public void TestGetFeatureVariableIntegerReturnsDefaultValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() + public void + TestGetFeatureVariableIntegerReturnsDefaultValueWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() { var featureKey = "integer_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "integer_variable"; var expectedValue = 7; var experiment = Config.GetExperimentFromKey("test_experiment_integer_feature"); - var variation = Config.GetVariationFromKey("test_experiment_integer_feature", "control"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var variation = + Config.GetVariationFromKey("test_experiment_integer_feature", "control"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId + }. Returning the default variable value ""{variableValue}"".")); } [Test] - public void TestGetFeatureVariableBooleanReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() + public void + TestGetFeatureVariableBooleanReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() { var featureKey = "boolean_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1795,21 +2494,31 @@ public void TestGetFeatureVariableBooleanReturnsRightValueWhenUserBuckedIntoRoll var expectedValue = true; var experiment = Config.GetRolloutFromId("166660").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177771"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); + var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""true"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""true"" for variable ""{variableKey}"" of feature flag ""{ + featureKey}"".")); } [Test] - public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRolloutAndVariationIsToggleOn() + public void + TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRolloutAndVariationIsToggleOn() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1818,30 +2527,46 @@ public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRollout var expectedIntValue = 4; var experiment = Config.GetRolloutFromId("166661").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177775"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (OptimizelyJSON)optly.Invoke("GetFeatureVariableJSON", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (OptimizelyJSON)optly.Invoke("GetFeatureVariableJSON", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedIntValue, variableValue.GetValue("int_var")); Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""{variableValue}"" for variable ""{variableKey + }"" of feature flag ""{featureKey}"".")); } [Test] - public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRolloutAndVariationIsToggleOnTypeIsJson() + public void + TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRolloutAndVariationIsToggleOnTypeIsJson() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1850,30 +2575,46 @@ public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRollout var expectedIntValue = 5; var experiment = Config.GetRolloutFromId("166661").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177775"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (OptimizelyJSON)optly.Invoke("GetFeatureVariableJSON", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (OptimizelyJSON)optly.Invoke("GetFeatureVariableJSON", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedIntValue, variableValue.GetValue("int_var")); Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""{variableValue}"" for variable ""{variableKey + }"" of feature flag ""{featureKey}"".")); } [Test] - public void TestGetFeatureVariableStringReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() + public void + TestGetFeatureVariableStringReturnsRightValueWhenUserBuckedIntoRolloutAndVariationIsToggleOn() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1881,29 +2622,45 @@ public void TestGetFeatureVariableStringReturnsRightValueWhenUserBuckedIntoRollo var expectedValue = "cta_4"; var experiment = Config.GetRolloutFromId("166661").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177775"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""{variableValue}"" for variable ""{variableKey + }"" of feature flag ""{featureKey}"".")); } [Test] - public void TestGetFeatureVariableBooleanReturnsDefaultValueWhenUserBuckedIntoRolloutAndVariationIsToggleOff() + public void + TestGetFeatureVariableBooleanReturnsDefaultValueWhenUserBuckedIntoRolloutAndVariationIsToggleOff() { var featureKey = "boolean_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1911,20 +2668,30 @@ public void TestGetFeatureVariableBooleanReturnsDefaultValueWhenUserBuckedIntoRo var expectedValue = true; var experiment = Config.GetRolloutFromId("166660").Experiments[3]; var variation = Config.GetVariationFromKey(experiment.Key, "177782"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); + var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""true"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId + }. Returning the default variable value ""true"".")); } [Test] - public void TestGetFeatureVariableStringReturnsDefaultValueWhenUserBuckedIntoRolloutAndVariationIsToggleOff() + public void + TestGetFeatureVariableStringReturnsDefaultValueWhenUserBuckedIntoRolloutAndVariationIsToggleOff() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -1932,45 +2699,70 @@ public void TestGetFeatureVariableStringReturnsDefaultValueWhenUserBuckedIntoRol var expectedValue = "wingardium leviosa"; var experiment = Config.GetRolloutFromId("166661").Experiments[2]; var variation = Config.GetVariationFromKey(experiment.Key, "177784"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId + }. Returning the default variable value ""{variableValue}"".")); } [Test] - public void TestGetFeatureVariableDoubleReturnsDefaultValueWhenUserNotBuckedIntoBothFeatureExperimentAndRollout() + public void + TestGetFeatureVariableDoubleReturnsDefaultValueWhenUserNotBuckedIntoBothFeatureExperimentAndRollout() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey("double_single_variable_feature"); var variableKey = "double_variable"; var expectedValue = 14.99; - var decision = Result.NewResult(new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); + var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"User ""{TestUserId}"" is not in any variation for feature flag ""{featureKey}"", returning default value ""{variableValue}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"User ""{TestUserId}"" is not in any variation for feature flag ""{featureKey + }"", returning default value ""{variableValue}"".")); } #endregion Feature Toggle Tests @@ -1988,36 +2780,53 @@ public void TestGetFeatureVariableValueForTypeGivenNullOrEmptyArguments() var variableType = "boolean"; // Passing null and empty feature key. - Assert.IsNull(Optimizely.GetFeatureVariableValueForType(null, variableKey, TestUserId, null, variableType)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("", variableKey, TestUserId, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType(null, variableKey, + TestUserId, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType("", variableKey, + TestUserId, null, variableType)); // Passing null and empty variable key. - Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, null, TestUserId, null, variableType)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, "", TestUserId, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, null, + TestUserId, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, "", + TestUserId, null, variableType)); // Passing null and empty user Id. - Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, variableKey, null, null, variableType)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, variableKey, "", null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, variableKey, + null, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, variableKey, + "", null, variableType)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Feature Key is in invalid format."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Variable Key is in invalid format."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Exactly(1)); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Feature Key is in invalid format."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Variable Key is in invalid format."), + Times.Exactly(2)); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Exactly(1)); } // Should return null and log error message when feature key or variable key does not get found. [Test] public void TestGetFeatureVariableValueForTypeGivenFeatureKeyOrVariableKeyNotFound() { - var featureKey = "this_feature_should_never_be_found_in_the_datafile_unless_the_datafile_creator_got_insane"; - var variableKey = "this_variable_should_never_be_found_in_the_datafile_unless_the_datafile_creator_got_insane"; + var featureKey = + "this_feature_should_never_be_found_in_the_datafile_unless_the_datafile_creator_got_insane"; + var variableKey = + "this_variable_should_never_be_found_in_the_datafile_unless_the_datafile_creator_got_insane"; var variableType = "boolean"; - Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, variableKey, TestUserId, null, variableType)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("double_single_variable_feature", variableKey, TestUserId, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType(featureKey, variableKey, + TestUserId, null, variableType)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType( + "double_single_variable_feature", variableKey, TestUserId, null, variableType)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $@"Feature key ""{featureKey}"" is not in datafile.")); + LoggerMock.Verify(l => + l.Log(LogLevel.ERROR, $@"Feature key ""{featureKey}"" is not in datafile.")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"No feature variable was found for key ""{variableKey}"" in feature flag ""double_single_variable_feature"".")); + $@"No feature variable was found for key ""{variableKey + }"" in feature flag ""double_single_variable_feature"".")); } // Should return null and log error message when variable type is invalid. @@ -2029,30 +2838,46 @@ public void TestGetFeatureVariableValueForTypeGivenInvalidVariableType() var variableTypeDouble = "double"; var variableTypeString = "string"; - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("double_single_variable_feature", "double_variable", TestUserId, null, variableTypeBool)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("boolean_single_variable_feature", "boolean_variable", TestUserId, null, variableTypeDouble)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("integer_single_variable_feature", "integer_variable", TestUserId, null, variableTypeString)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("string_single_variable_feature", "string_variable", TestUserId, null, variableTypeInt)); - Assert.IsNull(Optimizely.GetFeatureVariableValueForType("string_single_variable_feature", "json_var", TestUserId, null, variableTypeInt)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType( + "double_single_variable_feature", "double_variable", TestUserId, null, + variableTypeBool)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType( + "boolean_single_variable_feature", "boolean_variable", TestUserId, null, + variableTypeDouble)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType( + "integer_single_variable_feature", "integer_variable", TestUserId, null, + variableTypeString)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType( + "string_single_variable_feature", "string_variable", TestUserId, null, + variableTypeInt)); + Assert.IsNull(Optimizely.GetFeatureVariableValueForType( + "string_single_variable_feature", "json_var", TestUserId, null, variableTypeInt)); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""double"", but you requested it as type ""{variableTypeBool}"".")); + $@"Variable is of type ""double"", but you requested it as type ""{variableTypeBool + }"".")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""boolean"", but you requested it as type ""{variableTypeDouble}"".")); + $@"Variable is of type ""boolean"", but you requested it as type ""{ + variableTypeDouble}"".")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""integer"", but you requested it as type ""{variableTypeString}"".")); + $@"Variable is of type ""integer"", but you requested it as type ""{ + variableTypeString}"".")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""string"", but you requested it as type ""{variableTypeInt}"".")); + $@"Variable is of type ""string"", but you requested it as type ""{variableTypeInt + }"".")); } [Test] public void TestUnsupportedVariableType() { - var featureVariableStringRandomType = Optimizely.GetFeatureVariableString("", "any_key", TestUserId); + var featureVariableStringRandomType = + Optimizely.GetFeatureVariableString("", "any_key", TestUserId); Assert.IsNull(featureVariableStringRandomType); // This is to test that only json subtype is parsing and all other will subtype will be stringify - var featureVariableStringRegexSubType = Optimizely.GetFeatureVariableString("unsupported_variabletype", "string_regex_key", TestUserId); + var featureVariableStringRegexSubType = + Optimizely.GetFeatureVariableString("unsupported_variabletype", "string_regex_key", + TestUserId); Assert.AreEqual(featureVariableStringRegexSubType, "^\\d+(\\.\\d+)?"); } @@ -2063,60 +2888,87 @@ public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsNotEnabledForUse var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey("double_single_variable_feature"); var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Config.GetVariationFromKey("test_experiment_double_feature", "variation"); + var variation = + Config.GetVariationFromKey("test_experiment_double_feature", "variation"); var variableKey = "double_variable"; var variableType = "double"; var expectedValue = 14.99; - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", new Type[] { typeof(double?) }, featureKey, variableKey, TestUserId, null, variableType); + var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", + new Type[] + { + typeof(double?), + }, featureKey, variableKey, TestUserId, null, variableType); Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId + }. Returning the default variable value ""{variableValue}"".")); } // Should return default value and log message when feature is enabled for the user // but variable usage does not get found for the variation. [Test] - public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsEnabledForUserAndVaribaleNotInVariation() + public void + TestGetFeatureVariableValueForTypeGivenFeatureFlagIsEnabledForUserAndVaribaleNotInVariation() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey("double_single_variable_feature"); var experiment = Config.GetExperimentFromKey("test_experiment_integer_feature"); - var differentVariation = Config.GetVariationFromKey("test_experiment_integer_feature", "control"); - var expectedDecision = Result.NewResult(new FeatureDecision(experiment, differentVariation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var differentVariation = + Config.GetVariationFromKey("test_experiment_integer_feature", "control"); + var expectedDecision = Result.NewResult( + new FeatureDecision(experiment, differentVariation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); var variableKey = "double_variable"; var variableType = "double"; var expectedValue = 14.99; // Mock GetVariationForFeature method to return variation of different feature. - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(expectedDecision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(expectedDecision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", new Type[] { typeof(double?) }, featureKey, variableKey, TestUserId, null, variableType); + var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", + new Type[] + { + typeof(double?), + }, featureKey, variableKey, TestUserId, null, variableType); Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Variable ""{variableKey}"" is not used in variation ""control"", returning default value ""{expectedValue}"".")); + $@"Variable ""{variableKey + }"" is not used in variation ""control"", returning default value ""{expectedValue + }"".")); } // Should return variable value from variation and log message when feature is enabled for the user // and variable usage has been found for the variation. [Test] - public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsEnabledForUserAndVaribaleIsInVariation() + public void + TestGetFeatureVariableValueForTypeGivenFeatureFlagIsEnabledForUserAndVaribaleIsInVariation() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey("double_single_variable_feature"); @@ -2125,19 +2977,31 @@ public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsEnabledForUserAn var expectedValue = 42.42; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); var variation = Config.GetVariationFromKey("test_experiment_double_feature", "control"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", new Type[] { typeof(double?) }, featureKey, variableKey, TestUserId, null, variableType); + var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", + new Type[] + { + typeof(double?), + }, featureKey, variableKey, TestUserId, null, variableType); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, + $@"Got variable value ""{variableValue}"" for variable ""{variableKey + }"" of feature flag ""{featureKey}"".")); } // Verify that GetFeatureVariableValueForType returns correct variable value for rollout rule. @@ -2150,10 +3014,16 @@ public void TestGetFeatureVariableValueForTypeWithRolloutRule() //experimentid - 177772 var experiment = Config.Rollouts[0].Experiments[1]; var variation = Config.GetVariationFromId(experiment.Key, "177773"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var expectedVariableValue = false; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); @@ -2161,7 +3031,8 @@ public void TestGetFeatureVariableValueForTypeWithRolloutRule() optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); // Calling GetFeatureVariableBoolean to get GetFeatureVariableValueForType returned value casted in bool. - var actualVariableValue = (bool?)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); + var actualVariableValue = (bool?)optly.Invoke("GetFeatureVariableBoolean", featureKey, + variableKey, TestUserId, null); // Verify that variable value 'false' has been returned from GetFeatureVariableValueForType as it is the value // stored in rollout rule '177772'. @@ -2184,8 +3055,11 @@ public void TestIsFeatureEnabledGivenNullOrEmptyArguments() Assert.IsFalse(Optimizely.IsFeatureEnabled(null, TestUserId, null)); Assert.IsFalse(Optimizely.IsFeatureEnabled("", TestUserId, null)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Feature Key is in invalid format."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Exactly(1)); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Feature Key is in invalid format."), + Times.Exactly(2)); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Exactly(1)); } // Should return false and log error message when feature flag key is not found in the datafile. @@ -2195,14 +3069,16 @@ public void TestIsFeatureEnabledGivenFeatureFlagNotFound() var featureKey = "feature_not_found"; Assert.IsFalse(Optimizely.IsFeatureEnabled(featureKey, TestUserId, null)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $@"Feature key ""{featureKey}"" is not in datafile.")); + LoggerMock.Verify(l => + l.Log(LogLevel.ERROR, $@"Feature key ""{featureKey}"" is not in datafile.")); } // Should return false and log error message when arguments are null or empty. [Test] public void TestIsFeatureEnabledGivenFeatureFlagContainsInvalidExperiment() { - var tempConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, new NoOpErrorHandler()); + var tempConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, + new NoOpErrorHandler()); var tempConfigManager = new FallbackProjectConfigManager(tempConfig); var featureFlag = tempConfig.GetFeatureFlagFromKey("multi_variate_feature"); @@ -2210,12 +3086,17 @@ public void TestIsFeatureEnabledGivenFeatureFlagContainsInvalidExperiment() optly.SetFieldOrProperty("ProjectConfigManager", tempConfigManager); // Set such an experiment to the list of experiment ids, that does not belong to the feature. - featureFlag.ExperimentIds = new List { "4209211" }; + featureFlag.ExperimentIds = new List + { + "4209211", + }; // Should return false when the experiment in feature flag does not get found in the datafile. - Assert.False((bool)optly.Invoke("IsFeatureEnabled", "multi_variate_feature", TestUserId, null)); + Assert.False((bool)optly.Invoke("IsFeatureEnabled", "multi_variate_feature", TestUserId, + null)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"Experiment ID ""4209211"" is not in datafile.")); + LoggerMock.Verify(l => + l.Log(LogLevel.ERROR, @"Experiment ID ""4209211"" is not in datafile.")); } // Should return false and log message when feature is not enabled for the user. @@ -2225,16 +3106,23 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsNotEnabledForUser() var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey("double_single_variable_feature"); var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Config.GetVariationFromKey("test_experiment_double_feature", "variation"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + var variation = + Config.GetVariationFromKey("test_experiment_double_feature", "variation"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.False(result); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @@ -2251,20 +3139,27 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsNotBeingExperi var experiment = rollout.Experiments[0]; var variation = experiment.Variations[0]; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.True(result); // SendImpressionEvent() does not get called. LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey}""."), Times.Once); + $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey + }""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is enabled for user ""{TestUserId}"".")); @@ -2279,20 +3174,27 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsBeingExperimen var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); var variation = Config.GetVariationFromKey("test_experiment_double_feature", "control"); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.True(result); // SendImpressionEvent() gets called. LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey}""."), Times.Never); + $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey + }""."), Times.Never); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is enabled for user ""{TestUserId}"".")); @@ -2305,26 +3207,35 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsNotEnabledAndUserIsBeingExperi { var featureKey = "double_single_variable_feature"; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Config.GetVariationFromKey("test_experiment_double_feature", "variation"); + var variation = + Config.GetVariationFromKey("test_experiment_double_feature", "variation"); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.False(result); // SendImpressionEvent() gets called. LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey}""."), Times.Never); + $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey + }""."), Times.Never); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is not enabled for user ""{TestUserId}"".")); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny())); + EventDispatcherMock.Verify(dispatcher => + dispatcher.DispatchEvent(It.IsAny())); } // Verify that IsFeatureEnabled returns true if a variation does not get found in the feature @@ -2335,19 +3246,44 @@ public void TestIsFeatureEnabledGivenVariationNotFoundInFeatureExperimentButInRo var featureKey = "boolean_single_variable_feature"; var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, }; Assert.True(Optimizely.IsFeatureEnabled(featureKey, TestUserId, userAttributes)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The feature flag \"boolean_single_variable_feature\" is not used in any experiments."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUserId\" does not meet the conditions for targeting rule \"1\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUserId\" does not meet the conditions for targeting rule \"2\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [8408] to user [testUserId] with bucketing ID [testUserId]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"testUserId\" is bucketed into a rollout for feature flag \"boolean_single_variable_feature\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"testUserId\" is not being experimented on feature \"boolean_single_variable_feature\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Feature flag \"boolean_single_variable_feature\" is enabled for user \"testUserId\"."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "The feature flag \"boolean_single_variable_feature\" is not used in any experiments."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUserId\" does not meet the conditions for targeting rule \"1\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "User \"testUserId\" does not meet the conditions for targeting rule \"2\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "Assigned bucket [8408] to user [testUserId] with bucketing ID [testUserId]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "The user \"testUserId\" is bucketed into a rollout for feature flag \"boolean_single_variable_feature\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "The user \"testUserId\" is not being experimented on feature \"boolean_single_variable_feature\"."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "Feature flag \"boolean_single_variable_feature\" is enabled for user \"testUserId\"."), + Times.Once); } public void TestIsFeatureEnabledWithFeatureEnabledPropertyGivenFeatureExperiment() @@ -2355,21 +3291,35 @@ public void TestIsFeatureEnabledWithFeatureEnabledPropertyGivenFeatureExperiment var userId = "testUserId2"; var featureKey = "double_single_variable_feature"; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var featureEnabledTrue = Config.GetVariationFromKey("test_experiment_double_feature", "control"); - var featureEnabledFalse = Config.GetVariationFromKey("test_experiment_double_feature", "variation"); + var featureEnabledTrue = + Config.GetVariationFromKey("test_experiment_double_feature", "control"); + var featureEnabledFalse = + Config.GetVariationFromKey("test_experiment_double_feature", "variation"); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decisionTrue = Result.NewResult(new FeatureDecision(experiment, featureEnabledTrue, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - var decisionFalse = Result.NewResult(new FeatureDecision(experiment, featureEnabledFalse, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decisionTrue); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decisionFalse); + var decisionTrue = Result.NewResult( + new FeatureDecision(experiment, featureEnabledTrue, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decisionFalse = Result.NewResult( + new FeatureDecision(experiment, featureEnabledFalse, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decisionTrue); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decisionFalse); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); // Verify that IsFeatureEnabled returns true when feature experiment variation's 'featureEnabled' property is true. - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.True(result); // Verify that IsFeatureEnabled returns false when feature experiment variation's 'featureEnabled' property is false. @@ -2385,12 +3335,16 @@ public void TestIsFeatureEnabledWithFeatureEnabledPropertyGivenRolloutRule() // Verify that IsFeatureEnabled returns true when user is bucketed into the rollout rule's variation. Assert.True(Optimizely.IsFeatureEnabled("boolean_single_variable_feature", TestUserId)); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(null); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(null); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); // Verify that IsFeatureEnabled returns false when user does not get bucketed into the rollout rule's variation. - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.False(result); } @@ -2409,45 +3363,67 @@ public void TestActivateListenerWithAttributes() { var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; TestActivateListener(userAttributes); } + [Obsolete] public void TestActivateListener(UserAttributes userAttributes) { var experimentKey = "group_experiment_1"; var variationKey = "group_exp_1_var_1"; var featureKey = "boolean_feature"; var experiment = Config.GetExperimentFromKey(experimentKey); - var variation = Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), DecisionReasons); + var variation = + Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), + DecisionReasons); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); // Mocking objects. - 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(), + NotificationCallbackMock.Setup(nc => nc.TestAnotherActivateCallback( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - NotificationCallbackMock.Setup(nc => nc.TestTrackCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestTrackCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - Mock mockUserContext = new Mock(OptimizelyMock.Object, TestUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + var mockUserContext = + new Mock(OptimizelyMock.Object, TestUserId, userAttributes, + ErrorHandlerMock.Object, LoggerMock.Object); mockUserContext.Setup(ouc => ouc.GetUserId()).Returns(TestUserId); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), It.IsAny())).Returns(variation); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), It.IsAny())).Returns(decision); + DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, + It.IsAny(), It.IsAny())). + Returns(variation); + DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, + It.IsAny(), It.IsAny())). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; // Adding notification listeners. var notificationType = NotificationCenter.NotificationType.Activate; - optStronglyTyped.NotificationCenter.AddNotification(notificationType, NotificationCallbackMock.Object.TestActivateCallback); - optStronglyTyped.NotificationCenter.AddNotification(notificationType, NotificationCallbackMock.Object.TestAnotherActivateCallback); + optStronglyTyped.NotificationCenter.AddNotification(notificationType, + NotificationCallbackMock.Object.TestActivateCallback); + optStronglyTyped.NotificationCenter.AddNotification(notificationType, + NotificationCallbackMock.Object.TestAnotherActivateCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); @@ -2456,10 +3432,15 @@ public void TestActivateListener(UserAttributes userAttributes) optStronglyTyped.IsFeatureEnabled(featureKey, TestUserId, userAttributes); // Verify that all the registered callbacks are called once for both Activate and IsFeatureEnabled. - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Exactly(2)); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Exactly(2)); - NotificationCallbackMock.Verify(nc => nc.TestActivateCallback(experiment, TestUserId, userAttributes, variation.ResultObject, It.IsAny()), Times.Exactly(2)); - NotificationCallbackMock.Verify(nc => nc.TestAnotherActivateCallback(experiment, TestUserId, userAttributes, variation.ResultObject, It.IsAny()), Times.Exactly(2)); + NotificationCallbackMock.Verify( + nc => nc.TestActivateCallback(experiment, TestUserId, userAttributes, + variation.ResultObject, It.IsAny()), Times.Exactly(2)); + NotificationCallbackMock.Verify( + nc => nc.TestAnotherActivateCallback(experiment, TestUserId, userAttributes, + variation.ResultObject, It.IsAny()), Times.Exactly(2)); } [Test] @@ -2473,9 +3454,15 @@ public void TestTrackListenerWithAttributesWithoutEventTags() { var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; TestTrackListener(userAttributes, null); @@ -2486,14 +3473,22 @@ public void TestTrackListenerWithAttributesAndEventTags() { var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; var eventTags = new EventTags { - { "revenue", 42 } + { + "revenue", 42 + }, }; TestTrackListener(userAttributes, eventTags); @@ -2505,28 +3500,39 @@ public void TestTrackListener(UserAttributes userAttributes, EventTags eventTags var variationKey = "control"; var eventKey = "purchase"; var experiment = Config.GetExperimentFromKey(experimentKey); - var variation = Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), DecisionReasons); - var logEvent = new LogEvent("https://logx.optimizely.com/v1/events", OptimizelyHelper.SingleParameter, - "POST", new Dictionary { }); + var variation = + Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), + DecisionReasons); + var logEvent = new LogEvent("https://logx.optimizely.com/v1/events", + OptimizelyHelper.SingleParameter, + "POST", new Dictionary()); var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestTrackCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestTrackCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - NotificationCallbackMock.Setup(nc => nc.TestAnotherTrackCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestAnotherTrackCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - Mock mockUserContext = new Mock(Optimizely, TestUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var mockUserContext = + new Mock(Optimizely, TestUserId, new UserAttributes(), + ErrorHandlerMock.Object, LoggerMock.Object); mockUserContext.Setup(ouc => ouc.GetUserId()).Returns(TestUserId); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)).Returns(variation); + DecisionServiceMock. + Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)). + Returns(variation); // Adding notification listeners. var notificationType = NotificationCenter.NotificationType.Track; - optStronglyTyped.NotificationCenter.AddNotification(notificationType, NotificationCallbackMock.Object.TestTrackCallback); - optStronglyTyped.NotificationCenter.AddNotification(notificationType, NotificationCallbackMock.Object.TestAnotherTrackCallback); + optStronglyTyped.NotificationCenter.AddNotification(notificationType, + NotificationCallbackMock.Object.TestTrackCallback); + optStronglyTyped.NotificationCenter.AddNotification(notificationType, + NotificationCallbackMock.Object.TestAnotherTrackCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); @@ -2534,9 +3540,14 @@ public void TestTrackListener(UserAttributes userAttributes, EventTags eventTags optly.Invoke("Track", eventKey, TestUserId, userAttributes, eventTags); // Verify that all the registered callbacks for Track are called. - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestTrackCallback(eventKey, TestUserId, userAttributes, eventTags, It.IsAny()), Times.Exactly(1)); - NotificationCallbackMock.Verify(nc => nc.TestAnotherTrackCallback(eventKey, TestUserId, userAttributes, eventTags, It.IsAny()), Times.Exactly(1)); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestTrackCallback(eventKey, TestUserId, userAttributes, eventTags, + It.IsAny()), Times.Exactly(1)); + NotificationCallbackMock.Verify( + nc => nc.TestAnotherTrackCallback(eventKey, TestUserId, userAttributes, eventTags, + It.IsAny()), Times.Exactly(1)); } #region Decision Listener @@ -2547,72 +3558,111 @@ public void TestActivateSendsDecisionNotificationWithActualVariationKey() var experimentKey = "test_experiment"; var variationKey = "variation"; var experiment = Config.GetExperimentFromKey(experimentKey); - var variation = Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), DecisionReasons); + var variation = + Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)).Returns(variation); + DecisionServiceMock. + Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)). + Returns(variation); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.Invoke("Activate", experimentKey, TestUserId, userAttributes); var decisionInfo = new Dictionary { - { "experimentKey", experimentKey }, - { "variationKey", variationKey }, + { + "experimentKey", experimentKey + }, + { + "variationKey", variationKey + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, userAttributes, decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, + userAttributes, decisionInfo), Times.Once); } [Test] - public void TestActivateSendsDecisionNotificationWithVariationKeyAndTypeFeatureTestForFeatureExperiment() + public void + TestActivateSendsDecisionNotificationWithVariationKeyAndTypeFeatureTestForFeatureExperiment() { var experimentKey = "group_experiment_1"; var variationKey = "group_exp_1_var_1"; var experiment = Config.GetExperimentFromKey(experimentKey); - var variation = Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), DecisionReasons); + var variation = + Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)).Returns(variation); + DecisionServiceMock. + Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)). + Returns(variation); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.Invoke("Activate", experimentKey, TestUserId, userAttributes); var decisionInfo = new Dictionary { - { "experimentKey", experimentKey }, - { "variationKey", variationKey }, + { + "experimentKey", experimentKey + }, + { + "variationKey", variationKey + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_TEST, TestUserId, userAttributes, decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_TEST, TestUserId, + userAttributes, decisionInfo), Times.Once); } [Test] @@ -2624,22 +3674,34 @@ public void TestActivateSendsDecisionNotificationWithNullVariationKey() var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), It.IsAny(), null)).Returns(Result.NullResult(null)); + DecisionServiceMock. + Setup(ds => ds.GetVariation(experiment, It.IsAny(), + It.IsAny(), null)). + Returns(Result.NullResult(null)); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.Invoke("Activate", experimentKey, TestUserId, null); var decisionInfo = new Dictionary { - { "experimentKey", experimentKey }, - { "variationKey", null }, + { + "experimentKey", experimentKey + }, + { + "variationKey", null + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, new UserAttributes(), decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, + new UserAttributes(), decisionInfo), Times.Once); } [Test] @@ -2648,12 +3710,20 @@ public void TestGetVariationSendsDecisionNotificationWithActualVariationKey() var experimentKey = "test_experiment"; var variationKey = "variation"; var experiment = Config.GetExperimentFromKey(experimentKey); - var variation = Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), DecisionReasons); + var variation = + Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; var optly = Helper.CreatePrivateOptimizely(); @@ -2662,65 +3732,100 @@ public void TestGetVariationSendsDecisionNotificationWithActualVariationKey() var optStronglyTyped = optly.GetObject() as Optimizely; // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - Mock mockUserContext = new Mock(optStronglyTyped, TestUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var mockUserContext = + new Mock(optStronglyTyped, TestUserId, new UserAttributes(), + ErrorHandlerMock.Object, LoggerMock.Object); mockUserContext.Setup(ouc => ouc.GetUserId()).Returns(TestUserId); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)).Returns(variation); + DecisionServiceMock. + Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)). + Returns(variation); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.Invoke("GetVariation", experimentKey, TestUserId, userAttributes); var decisionInfo = new Dictionary { - { "experimentKey", experimentKey }, - { "variationKey", variationKey }, + { + "experimentKey", experimentKey + }, + { + "variationKey", variationKey + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, userAttributes, decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, + userAttributes, decisionInfo), Times.Once); } [Test] - public void TestGetVariationSendsDecisionNotificationWithVariationKeyAndTypeFeatureTestForFeatureExperiment() + public void + TestGetVariationSendsDecisionNotificationWithVariationKeyAndTypeFeatureTestForFeatureExperiment() { var experimentKey = "group_experiment_1"; var variationKey = "group_exp_1_var_1"; var experiment = Config.GetExperimentFromKey(experimentKey); - var variation = Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), DecisionReasons); + var variation = + Result.NewResult(Config.GetVariationFromKey(experimentKey, variationKey), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; // Mocking objects. - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - Mock mockUserContext = new Mock(optStronglyTyped, TestUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var mockUserContext = + new Mock(optStronglyTyped, TestUserId, new UserAttributes(), + ErrorHandlerMock.Object, LoggerMock.Object); mockUserContext.Setup(ouc => ouc.GetUserId()).Returns(TestUserId); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)).Returns(variation); + DecisionServiceMock. + Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config)). + Returns(variation); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); optly.Invoke("GetVariation", experimentKey, TestUserId, userAttributes); var decisionInfo = new Dictionary { - { "experimentKey", experimentKey }, - { "variationKey", variationKey }, + { + "experimentKey", experimentKey + }, + { + "variationKey", variationKey + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_TEST, TestUserId, userAttributes, decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_TEST, TestUserId, + userAttributes, decisionInfo), Times.Once); } [Test] @@ -2732,317 +3837,593 @@ public void TestGetVariationSendsDecisionNotificationWithNullVariationKey() var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - DecisionServiceMock.Setup(ds => ds.GetVariation(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Result.NullResult(null)); + DecisionServiceMock. + Setup(ds => ds.GetVariation(It.IsAny(), + It.IsAny(), It.IsAny())). + Returns(Result.NullResult(null)); //DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, TestUserId, Config, null)).Returns(Result.NullResult(null)); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.Invoke("GetVariation", experimentKey, TestUserId, null); var decisionInfo = new Dictionary { - { "experimentKey", experimentKey }, - { "variationKey", null }, + { + "experimentKey", experimentKey + }, + { + "variationKey", null + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, new UserAttributes(), decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.AB_TEST, TestUserId, + new UserAttributes(), decisionInfo), Times.Once); } - public void TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledTrueForFeatureExperiment() + public void + TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledTrueForFeatureExperiment() { var featureKey = "double_single_variable_feature"; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Result.NewResult(Config.GetVariationFromKey("test_experiment_double_feature", "control"), DecisionReasons); + var variation = Result.NewResult( + Config.GetVariationFromKey("test_experiment_double_feature", "control"), + DecisionReasons); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), ConfigManager.GetConfig(), null)).Returns(variation); + DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, + It.IsAny(), ConfigManager.GetConfig(), null)). + Returns(variation); var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.True(result); var decisionInfo = new Dictionary - { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceExperimentKey", "test_experiment_double_feature" }, - { "sourceVariationKey", "control" }, + { + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceExperimentKey", "test_experiment_double_feature" + }, + { + "sourceVariationKey", "control" + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, new UserAttributes(), decisionInfo), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, + new UserAttributes(), decisionInfo), Times.Once); } [Test] - public void TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledFalseForFeatureExperiment() + public void + TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledFalseForFeatureExperiment() { var featureKey = "double_single_variable_feature"; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Result.NewResult(Config.GetVariationFromKey("test_experiment_double_feature", "variation"), DecisionReasons); + var variation = Result.NewResult( + Config.GetVariationFromKey("test_experiment_double_feature", "variation"), + DecisionReasons); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation.ResultObject, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, It.IsAny(), Config, null)).Returns(variation); + DecisionServiceMock. + Setup(ds => + ds.GetVariation(experiment, It.IsAny(), Config, null)). + Returns(variation); var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.False(result); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary { - { "experimentKey", "test_experiment_double_feature" }, - { "variationKey", "variation" }, + { + "experimentKey", "test_experiment_double_feature" + }, + { + "variationKey", "variation" + }, } }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, + new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledTrueForFeatureRollout() + public void + TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledTrueForFeatureRollout() { var featureKey = "boolean_single_variable_feature"; var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, }; var experiment = Config.GetRolloutFromId("166660").Experiments[0]; var variation = Config.GetVariationFromKey("177770", "177771"); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, userAttributes); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, + userAttributes); Assert.True(result); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, + userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledFalseForFeatureRollout() + public void + TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledFalseForFeatureRollout() { var featureKey = "boolean_single_variable_feature"; var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, }; var experiment = Config.GetRolloutFromId("166660").Experiments[3]; var variation = Config.GetVariationFromKey("188880", "188881"); var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, userAttributes); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, + userAttributes); Assert.False(result); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, + userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledFalseWhenUserIsNotBucketed() + public void + TestIsFeatureEnabledSendsDecisionNotificationWithFeatureEnabledFalseWhenUserIsNotBucketed() { var featureKey = "boolean_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); var optStronglyTyped = optly.GetObject() as Optimizely; - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - bool result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); + var result = (bool)optly.Invoke("IsFeatureEnabled", featureKey, TestUserId, null); Assert.False(result); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, + new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetEnabledFeaturesSendDecisionNotificationForBothEnabledAndDisabledFeatures() + public void + TestGetEnabledFeaturesSendDecisionNotificationForBothEnabledAndDisabledFeatures() { string[] enabledFeatures = { - "double_single_variable_feature", - "boolean_single_variable_feature", - "string_single_variable_feature" + "double_single_variable_feature", "boolean_single_variable_feature", + "string_single_variable_feature", }; var userAttributes = new UserAttributes - { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny>())); - OptimizelyMock.Object.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny>())); + OptimizelyMock.Object.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var actualFeaturesList = OptimizelyMock.Object.GetEnabledFeatures(TestUserId, userAttributes); + var actualFeaturesList = + OptimizelyMock.Object.GetEnabledFeatures(TestUserId, userAttributes); CollectionAssert.AreEquivalent(enabledFeatures, actualFeaturesList); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "boolean_feature" }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary { - { "experimentKey", "group_experiment_2" }, - { "variationKey", "group_exp_2_var_1" }, - } - }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "double_single_variable_feature" }, - { "featureEnabled", true }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", "boolean_feature" + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary + { + { + "experimentKey", "group_experiment_2" + }, + { + "variationKey", "group_exp_2_var_1" + }, + } + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary { - { "experimentKey", "test_experiment_double_feature" }, - { "variationKey", "control" } - } - }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "integer_single_variable_feature" }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", "double_single_variable_feature" + }, + { + "featureEnabled", true + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary + { + { + "experimentKey", "test_experiment_double_feature" + }, + { + "variationKey", "control" + }, + } + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary { - { "experimentKey", "test_experiment_integer_feature" }, - { "variationKey", "control" }, - } - }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "boolean_single_variable_feature" }, - { "featureEnabled", true }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "string_single_variable_feature" }, - { "featureEnabled", true }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", "integer_single_variable_feature" + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary + { + { + "experimentKey", "test_experiment_integer_feature" + }, + { + "variationKey", "control" + }, + } + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary { - { "experimentKey", "test_experiment_with_feature_rollout" }, - { "variationKey", "variation" }, - } - }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "multi_variate_feature" }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "mutex_group_feature" }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", "boolean_single_variable_feature" + }, + { + "featureEnabled", true + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary { - { "experimentKey", "group_experiment_2" }, - { "variationKey", "group_exp_2_var_1" }, - } - }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "empty_feature" }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, - }))), Times.Once); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, new Dictionary { - { "featureKey", "no_rollout_experiment_feature" }, - { "featureEnabled", false }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, - }))), Times.Once); + { + "featureKey", "string_single_variable_feature" + }, + { + "featureEnabled", true + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary + { + { + "experimentKey", "test_experiment_with_feature_rollout" + }, + { + "variationKey", "variation" + }, + } + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "featureKey", "multi_variate_feature" + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "featureKey", "mutex_group_feature" + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary + { + { + "experimentKey", "group_experiment_2" + }, + { + "variationKey", "group_exp_2_var_1" + }, + } + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "featureKey", "empty_feature" + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, + }))), Times.Once); + NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback( + DecisionNotificationTypes.FEATURE, TestUserId, userAttributes, + It.Is>(info => TestData.CompareObjects(info, + new Dictionary + { + { + "featureKey", "no_rollout_experiment_feature" + }, + { + "featureEnabled", false + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, + }))), Times.Once); } [Test] - public void TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() + public void + TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -3050,10 +4431,17 @@ public void TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatu var expectedValue = 42.42; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); var variation = Config.GetVariationFromKey("test_experiment_double_feature", "control"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3062,52 +4450,94 @@ public void TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatu optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); + var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_DOUBLETYPE }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_DOUBLETYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary { - { "experimentKey", "test_experiment_double_feature" }, - { "variationKey", "control" }, + { + "experimentKey", "test_experiment_double_feature" + }, + { + "variationKey", "control" + }, } }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableJsonSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() + public void + TestGetFeatureVariableJsonSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "json_var"; var expectedDict = new Dictionary() { - { "int_var", 4 }, - { "string_var", "cta_4"} + { + "int_var", 4 + }, + { + "string_var", "cta_4" + }, }; var experiment = Config.GetRolloutFromId("166661").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177775"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3116,43 +4546,79 @@ public void TestGetFeatureVariableJsonSendsNotificationWhenUserBuckedIntoFeature var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (OptimizelyJSON)optly.Invoke("GetFeatureVariableJSON", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (OptimizelyJSON)optly.Invoke("GetFeatureVariableJSON", featureKey, + variableKey, TestUserId, userAttributes); Assert.IsTrue(TestData.CompareObjects(expectedDict, variableValue.ToDictionary())); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "variableKey", variableKey }, - { "variableValue", expectedDict }, - { "variableType", FEATUREVARIABLE_JSONTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, - }; - - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); - } - - [Test] - public void TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedDict + }, + { + "variableType", FEATUREVARIABLE_JSONTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, + }; + + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); + } + + [Test] + public void + TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOn() { var featureKey = "integer_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "integer_variable"; var expectedValue = 13; var experiment = Config.GetExperimentFromKey("test_experiment_integer_feature"); - var variation = Config.GetVariationFromKey("test_experiment_integer_feature", "variation"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var variation = + Config.GetVariationFromKey("test_experiment_integer_feature", "variation"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3161,42 +4627,75 @@ public void TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeat var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_INTEGERTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_INTEGERTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary { - { "experimentKey", "test_experiment_integer_feature" }, - { "variationKey", "variation" }, + { + "experimentKey", "test_experiment_integer_feature" + }, + { + "variationKey", "variation" + }, } }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() + public void + TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "double_variable"; var expectedValue = 14.99; var experiment = Config.GetExperimentFromKey("test_experiment_double_feature"); - var variation = Config.GetVariationFromKey("test_experiment_double_feature", "variation"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + var variation = + Config.GetVariationFromKey("test_experiment_double_feature", "variation"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3204,48 +4703,87 @@ public void TestGetFeatureVariableDoubleSendsNotificationWhenUserBuckedIntoFeatu optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); + var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_DOUBLETYPE }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_DOUBLETYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary { - { "experimentKey", "test_experiment_double_feature" }, - { "variationKey", "variation" }, + { + "experimentKey", "test_experiment_double_feature" + }, + { + "variationKey", "variation" + }, } }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() + public void + TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeatureExperimentAndVariationIsToggleOff() { var featureKey = "integer_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); var variableKey = "integer_variable"; var expectedValue = 7; var experiment = Config.GetExperimentFromKey("test_experiment_integer_feature"); - var variation = Config.GetVariationFromKey("test_experiment_integer_feature", "control"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); + var variation = + Config.GetVariationFromKey("test_experiment_integer_feature", "control"); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, + FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3253,31 +4791,56 @@ public void TestGetFeatureVariableIntegerSendsNotificationWhenUserBuckedIntoFeat var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_INTEGERTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST }, - { "sourceInfo", new Dictionary + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_INTEGERTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_FEATURE_TEST + }, + { + "sourceInfo", new Dictionary { - { "experimentKey", "test_experiment_integer_feature" }, - { "variationKey", "control" }, + { + "experimentKey", "test_experiment_integer_feature" + }, + { + "variationKey", "control" + }, } }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOn() + public void + TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOn() { var featureKey = "boolean_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -3285,10 +4848,17 @@ public void TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRoll var expectedValue = true; var experiment = Config.GetRolloutFromId("166660").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177771"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3296,26 +4866,48 @@ public void TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRoll var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); + var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_BOOLEANTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_BOOLEANTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOn() + public void + TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOn() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -3323,16 +4915,29 @@ public void TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRollo var expectedValue = "cta_4"; var experiment = Config.GetRolloutFromId("166661").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177775"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3341,26 +4946,48 @@ public void TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRollo var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_STRINGTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_STRINGTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOff() + public void + TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOff() { var featureKey = "boolean_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -3368,10 +4995,17 @@ public void TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRoll var expectedValue = true; var experiment = Config.GetRolloutFromId("166660").Experiments[3]; var variation = Config.GetVariationFromKey(experiment.Key, "177782"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3379,26 +5013,48 @@ public void TestGetFeatureVariableBooleanSendsNotificationWhenUserBuckedIntoRoll optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); + var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_BOOLEANTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary()}, + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_BOOLEANTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOff() + public void + TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRolloutAndVariationIsToggleOff() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); @@ -3406,16 +5062,29 @@ public void TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRollo var expectedValue = "wingardium leviosa"; var experiment = Config.GetRolloutFromId("166661").Experiments[2]; var variation = Config.GetVariationFromKey(experiment.Key, "177784"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3423,35 +5092,64 @@ public void TestGetFeatureVariableStringSendsNotificationWhenUserBuckedIntoRollo var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, variableKey, TestUserId, userAttributes); + var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, + variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_STRINGTYPE }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_STRINGTYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetFeatureVariableDoubleSendsNotificationWhenUserNotBuckedIntoBothFeatureExperimentAndRollout() + public void + TestGetFeatureVariableDoubleSendsNotificationWhenUserNotBuckedIntoBothFeatureExperimentAndRollout() { var featureKey = "double_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey("double_single_variable_feature"); var variableKey = "double_variable"; var expectedValue = 14.99; - var decision = Result.NewResult(new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + var decision = Result.NewResult( + new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); + + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3459,56 +5157,104 @@ public void TestGetFeatureVariableDoubleSendsNotificationWhenUserNotBuckedIntoBo optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); + var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, + variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", false }, - { "variableKey", variableKey }, - { "variableValue", expectedValue }, - { "variableType", FEATUREVARIABLE_DOUBLETYPE }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", false + }, + { + "variableKey", variableKey + }, + { + "variableValue", expectedValue + }, + { + "variableType", FEATUREVARIABLE_DOUBLETYPE + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, + TestUserId, new UserAttributes(), + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } [Test] - public void TestGetAllFeatureVariablesSendsNotificationWhenUserBucketIntoRolloutAndVariationIsToggleOn() + public void + TestGetAllFeatureVariablesSendsNotificationWhenUserBucketIntoRolloutAndVariationIsToggleOn() { var featureKey = "string_single_variable_feature"; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var expectedValue = new Dictionary() { - { "string_variable", "cta_4" }, - { "json_var", new Dictionary() + var expectedValue = new Dictionary() + { + { + "string_variable", "cta_4" + }, + { + "json_var", new Dictionary() { - { "int_var", 4 }, - { "string_var", "cta_4" } + { + "int_var", 4 + }, + { + "string_var", "cta_4" + }, } }, - { "true_json_var", new Dictionary() + { + "true_json_var", new Dictionary() { - { "int_var", 5 }, - { "string_var", "cta_5" } + { + "int_var", 5 + }, + { + "string_var", "cta_5" + }, } - } + }, }; var experiment = Config.GetRolloutFromId("166661").Experiments[0]; var variation = Config.GetVariationFromKey(experiment.Key, "177775"); - var decision = Result.NewResult(new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); - NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), It.IsAny(), It.IsAny(), + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); + NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())); var optly = Helper.CreatePrivateOptimizely(); @@ -3517,20 +5263,37 @@ public void TestGetAllFeatureVariablesSendsNotificationWhenUserBucketIntoRollout var optStronglyTyped = optly.GetObject() as Optimizely; optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); - optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback); + optStronglyTyped.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.Decision, + NotificationCallbackMock.Object.TestDecisionCallback); - var variableValues = (OptimizelyJSON)optly.Invoke("GetAllFeatureVariables", featureKey, TestUserId, userAttributes); + var variableValues = (OptimizelyJSON)optly.Invoke("GetAllFeatureVariables", featureKey, + TestUserId, userAttributes); Assert.IsTrue(TestData.CompareObjects(variableValues.ToDictionary(), expectedValue)); var decisionInfo = new Dictionary { - { "featureKey", featureKey }, - { "featureEnabled", true }, - { "variableValues", expectedValue }, - { "source", FeatureDecision.DECISION_SOURCE_ROLLOUT }, - { "sourceInfo", new Dictionary() }, + { + "featureKey", featureKey + }, + { + "featureEnabled", true + }, + { + "variableValues", expectedValue + }, + { + "source", FeatureDecision.DECISION_SOURCE_ROLLOUT + }, + { + "sourceInfo", new Dictionary() + }, }; - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.ALL_FEATURE_VARIABLE, TestUserId, userAttributes, It.Is>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.ALL_FEATURE_VARIABLE, + TestUserId, userAttributes, + It.Is>(info => + TestData.CompareObjects(info, decisionInfo))), Times.Once); } #endregion Decision Listener @@ -3542,23 +5305,34 @@ public void TestGetAllFeatureVariablesReturnsNullScenarios() { var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; - var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, + LoggerMock.Object, ErrorHandlerMock.Object); // Null Feature flag key var result = optimizely.GetAllFeatureVariables(null, TestUserId, userAttributes); Assert.Null(result); - LoggerMock.Verify(log => log.Log(LogLevel.WARN, "The featureKey parameter must be nonnull."), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.WARN, "The featureKey parameter must be nonnull."), + Times.Once); // Null User ID - var result2 = optimizely.GetAllFeatureVariables("string_single_variable_feature", null, userAttributes); + var result2 = optimizely.GetAllFeatureVariables("string_single_variable_feature", null, + userAttributes); Assert.Null(result2); - LoggerMock.Verify(log => log.Log(LogLevel.WARN, "The userId parameter must be nonnull."), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.WARN, "The userId parameter must be nonnull."), Times.Once); // Invalid featureKey var featureKey = "InvalidFeatureKey"; @@ -3566,16 +5340,22 @@ public void TestGetAllFeatureVariablesReturnsNullScenarios() var result3 = optimizely.GetAllFeatureVariables(featureKey, TestUserId, userAttributes); Assert.Null(result3); - LoggerMock.Verify(log => log.Log(LogLevel.INFO, "No feature flag was found for key \"" + featureKey + "\"."), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.INFO, + "No feature flag was found for key \"" + featureKey + "\"."), Times.Once); // Null Optimizely config var invalidOptly = new Optimizely("Random datafile", null, LoggerMock.Object); - var result4 = invalidOptly.GetAllFeatureVariables("validFeatureKey", TestUserId, userAttributes); + var result4 = + invalidOptly.GetAllFeatureVariables("validFeatureKey", TestUserId, userAttributes); Assert.Null(result4); - LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Optimizely instance is not valid, failing getAllFeatureVariableValues call. type"), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.ERROR, + "Optimizely instance is not valid, failing getAllFeatureVariableValues call. type"), + Times.Once); } [Test] @@ -3586,45 +5366,72 @@ public void TestGetAllFeatureVariablesRollout() var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "company", "Optimizely" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "company", "Optimizely" + }, + { + "location", "San Francisco" + }, }; var featureFlag = Config.GetFeatureFlagFromKey(featureKey); - var decision = Result.NewResult(new FeatureDecision(experiment, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); + var decision = Result.NewResult( + new FeatureDecision(experiment, null, FeatureDecision.DECISION_SOURCE_ROLLOUT), + DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, It.IsAny(), Config)).Returns(decision); + DecisionServiceMock. + Setup(ds => + ds.GetVariationForFeature(featureFlag, It.IsAny(), + Config)). + Returns(decision); var optly = Helper.CreatePrivateOptimizely(); optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object); optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - var result = (OptimizelyJSON)optly.Invoke("GetAllFeatureVariables", featureKey, TestUserId, userAttributes); + var result = (OptimizelyJSON)optly.Invoke("GetAllFeatureVariables", featureKey, + TestUserId, userAttributes); Assert.NotNull(result); - LoggerMock.Verify(log => log.Log(LogLevel.INFO, "Feature \"" + featureKey + "\" is not enabled for user \"" + TestUserId + "\""), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.INFO, + "Feature \"" + featureKey + "\" is not enabled for user \"" + TestUserId + + "\""), Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.INFO, "User \"" + TestUserId + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " + - "The default values are being returned."), Times.Once); + LoggerMock.Verify(log => log.Log(LogLevel.INFO, + "User \"" + TestUserId + + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " + + "The default values are being returned."), Times.Once); } [Test] public void TestGetAllFeatureVariablesSourceFeatureTest() { var featureKey = "double_single_variable_feature"; - var expectedValue = new Dictionary() { - { "double_variable", 42.42} + var expectedValue = new Dictionary() + { + { + "double_variable", 42.42 + }, }; - var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, + LoggerMock.Object, ErrorHandlerMock.Object); var variableValues = optimizely.GetAllFeatureVariables(featureKey, TestUserId, null); Assert.IsTrue(TestData.CompareObjects(variableValues.ToDictionary(), expectedValue)); - LoggerMock.Verify(log => log.Log(LogLevel.INFO, "Feature \"" + featureKey + "\" is enabled for user \"" + TestUserId + "\""), Times.Once); + LoggerMock.Verify( + log => log.Log(LogLevel.INFO, + "Feature \"" + featureKey + "\" is enabled for user \"" + TestUserId + "\""), + Times.Once); - LoggerMock.Verify(log => log.Log(LogLevel.INFO, "User \"" + TestUserId + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " + - "The default values are being returned."), Times.Never); + LoggerMock.Verify(log => log.Log(LogLevel.INFO, + "User \"" + TestUserId + + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " + + "The default values are being returned."), Times.Never); } #endregion Test GetAllFeatureVariables @@ -3635,23 +5442,26 @@ public void TestGetAllFeatureVariablesSourceFeatureTest() public void TestDFMNotificationWhenProjectConfigIsUpdated() { var httpClientMock = new Mock(); - var t = TestHttpProjectConfigManagerUtil.MockSendAsync(httpClientMock, TestData.Datafile, TimeSpan.FromMilliseconds(300)); + var t = TestHttpProjectConfigManagerUtil.MockSendAsync(httpClientMock, + TestData.Datafile, TimeSpan.FromMilliseconds(300)); TestHttpProjectConfigManagerUtil.SetClientFieldValue(httpClientMock.Object); - NotificationCenter notificationCenter = new NotificationCenter(); + var notificationCenter = new NotificationCenter(); NotificationCallbackMock.Setup(notification => notification.TestConfigUpdateCallback()); - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) - .WithStartByDefault(false) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .WithNotificationCenter(notificationCenter) - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(1000)). + WithStartByDefault(false). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + WithNotificationCenter(notificationCenter). + Build(true); var optimizely = new Optimizely(httpManager, notificationCenter); - optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.OptimizelyConfigUpdate, NotificationCallbackMock.Object.TestConfigUpdateCallback); + optimizely.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.OptimizelyConfigUpdate, + NotificationCallbackMock.Object.TestConfigUpdateCallback); httpManager.Start(); // wait till 10 seconds max, to avoid stale state in worst case. @@ -3668,16 +5478,18 @@ public void TestDFMWhenDatafileProvidedDoesNotNotifyWithoutStart() var httpClientMock = new Mock(); TestHttpProjectConfigManagerUtil.SetClientFieldValue(httpClientMock.Object); - 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(); var optimizely = new Optimizely(httpManager); - optimizely.NotificationCenter.AddNotification(NotificationCenter.NotificationType.OptimizelyConfigUpdate, NotificationCallbackMock.Object.TestConfigUpdateCallback); + optimizely.NotificationCenter.AddNotification( + NotificationCenter.NotificationType.OptimizelyConfigUpdate, + NotificationCallbackMock.Object.TestConfigUpdateCallback); // added 10 secs max wait to avoid stale state. httpManager.OnReady().Wait(10000); @@ -3699,7 +5511,9 @@ public void TestGetEnabledFeaturesWithInvalidDatafile() Assert.IsEmpty(optly.GetEnabledFeatures("some_user", null)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Datafile has invalid format. Failing 'GetEnabledFeatures'."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + "Datafile has invalid format. Failing 'GetEnabledFeatures'."), Times.Once); } [Test] @@ -3707,11 +5521,18 @@ public void TestGetEnabledFeaturesWithNoFeatureEnabledForUser() { var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; - OptimizelyMock.Setup(om => om.IsFeatureEnabled(It.IsAny(), TestUserId, It.IsAny())).Returns(false); + OptimizelyMock.Setup(om => + om.IsFeatureEnabled(It.IsAny(), TestUserId, + It.IsAny())). + Returns(false); Assert.IsEmpty(OptimizelyMock.Object.GetEnabledFeatures(TestUserId, userAttributes)); } @@ -3720,35 +5541,40 @@ public void TestGetEnabledFeaturesWithSomeFeaturesEnabledForUser() { string[] enabledFeatures = { - "boolean_feature", - "double_single_variable_feature", - "string_single_variable_feature", - "multi_variate_feature", - "empty_feature" + "boolean_feature", "double_single_variable_feature", + "string_single_variable_feature", "multi_variate_feature", "empty_feature", }; string[] notEnabledFeatures = { - "integer_single_variable_feature", - "boolean_single_variable_feature", - "mutex_group_feature", - "no_rollout_experiment_feature" + "integer_single_variable_feature", "boolean_single_variable_feature", + "mutex_group_feature", "no_rollout_experiment_feature", }; var userAttributes = new UserAttributes { - { "device_type", "iPhone" }, - { "location", "San Francisco" } + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, }; - OptimizelyMock.Setup(om => om.IsFeatureEnabled(It.IsIn(enabledFeatures), TestUserId, - It.IsAny())).Returns(true); - OptimizelyMock.Setup(om => om.IsFeatureEnabled(It.IsIn(notEnabledFeatures), TestUserId, - It.IsAny())).Returns(false); + OptimizelyMock.Setup(om => om.IsFeatureEnabled(It.IsIn(enabledFeatures), + TestUserId, + It.IsAny())). + Returns(true); + OptimizelyMock.Setup(om => om.IsFeatureEnabled(It.IsIn(notEnabledFeatures), + TestUserId, + It.IsAny())). + Returns(false); - var actualFeaturesList = OptimizelyMock.Object.GetEnabledFeatures(TestUserId, userAttributes); + var actualFeaturesList = + OptimizelyMock.Object.GetEnabledFeatures(TestUserId, userAttributes); // Verify that the returned feature list contains only enabledFeatures. CollectionAssert.AreEquivalent(enabledFeatures, actualFeaturesList); - Array.ForEach(notEnabledFeatures, nef => CollectionAssert.DoesNotContain(actualFeaturesList, nef)); + Array.ForEach(notEnabledFeatures, + nef => CollectionAssert.DoesNotContain(actualFeaturesList, nef)); } #endregion Test GetEnabledFeatures @@ -3760,10 +5586,20 @@ public void TestValidateStringInputsWithValidValues() { var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.EXPERIMENT_KEY, "test_experiment" } }); + var result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.EXPERIMENT_KEY, "test_experiment" + }, + }); Assert.True(result); - result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.EVENT_KEY, "buy_now_event" } }); + result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.EVENT_KEY, "buy_now_event" + }, + }); Assert.True(result); } @@ -3772,10 +5608,20 @@ public void TestValidateStringInputsWithInvalidValues() { var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.EXPERIMENT_KEY, "" } }); + var result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.EXPERIMENT_KEY, "" + }, + }); Assert.False(result); - result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.EVENT_KEY, null } }); + result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.EVENT_KEY, null + }, + }); Assert.False(result); } @@ -3784,13 +5630,28 @@ public void TestValidateStringInputsWithUserId() { var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.USER_ID, "testUser" } }); + var result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.USER_ID, "testUser" + }, + }); Assert.True(result); - result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.USER_ID, "" } }); + result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.USER_ID, "" + }, + }); Assert.True(result); - result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary { { Optimizely.USER_ID, null } }); + result = (bool)optly.Invoke("ValidateStringInputs", new Dictionary + { + { + Optimizely.USER_ID, null + }, + }); Assert.False(result); } @@ -3799,13 +5660,19 @@ public void TestActivateValidateInputValues() { // Verify that ValidateStringInputs does not log error for valid values. var variation = Optimizely.Activate("test_experiment", "test_user"); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Never); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), Times.Never); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Never); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), + Times.Never); // Verify that ValidateStringInputs logs error for invalid values. variation = Optimizely.Activate("", null); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), + Times.Once); } [Test] @@ -3813,13 +5680,19 @@ public void TestGetVariationValidateInputValues() { // Verify that ValidateStringInputs does not log error for valid values. var variation = Optimizely.GetVariation("test_experiment", "test_user"); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Never); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), Times.Never); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Never); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), + Times.Never); // Verify that ValidateStringInputs logs error for invalid values. variation = Optimizely.GetVariation("", null); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Experiment Key is in invalid format."), + Times.Once); } [Test] @@ -3827,13 +5700,18 @@ public void TestTrackValidateInputValues() { // Verify that ValidateStringInputs does not log error for valid values. Optimizely.Track("purchase", "test_user"); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Never); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Event Key is in invalid format."), Times.Never); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Never); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Event Key is in invalid format."), + Times.Never); // Verify that ValidateStringInputs logs error for invalid values. Optimizely.Track("", null); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided Event Key is in invalid format."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided User Id is in invalid format."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, "Provided Event Key is in invalid format."), Times.Once); } #endregion Test ValidateStringInputs @@ -3843,33 +5721,45 @@ public void TestTrackValidateInputValues() [Test] public void TestActivateWithTypedAudiences() { - var variation = OptimizelyWithTypedAudiences.Activate("typed_audience_experiment", "user1", new UserAttributes - { - { "house", "Gryffindor" } - }); + var variation = OptimizelyWithTypedAudiences.Activate("typed_audience_experiment", + "user1", new UserAttributes + { + { + "house", "Gryffindor" + }, + }); Assert.AreEqual("A", variation.Key); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); - variation = OptimizelyWithTypedAudiences.Activate("typed_audience_experiment", "user1", new UserAttributes - { - { "lasers", 45.5 } - }); + variation = OptimizelyWithTypedAudiences.Activate("typed_audience_experiment", "user1", + new UserAttributes + { + { + "lasers", 45.5 + }, + }); Assert.AreEqual("A", variation.Key); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Exactly(2)); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Exactly(2)); } [Test] public void TestActivateExcludeUserFromExperimentWithTypedAudiences() { - var variation = OptimizelyWithTypedAudiences.Activate("typed_audience_experiment", "user1", new UserAttributes - { - { "house", "Hufflepuff" } - }); + var variation = OptimizelyWithTypedAudiences.Activate("typed_audience_experiment", + "user1", new UserAttributes + { + { + "house", "Hufflepuff" + }, + }); Assert.Null(variation); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Never); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Never); } [Test] @@ -3877,37 +5767,50 @@ public void TestTrackWithTypedAudiences() { OptimizelyWithTypedAudiences.Track("item_bought", "user1", new UserAttributes { - { "house", "Welcome to Slytherin!" } + { + "house", "Welcome to Slytherin!" + }, }); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] - public void TestTrackDoesNotExcludeUserFromExperimentWhenAttributesMismatchWithTypedAudiences() + public void + TestTrackDoesNotExcludeUserFromExperimentWhenAttributesMismatchWithTypedAudiences() { OptimizelyWithTypedAudiences.Track("item_bought", "user1", new UserAttributes { - { "house", "Hufflepuff" } + { + "house", "Hufflepuff" + }, }); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] public void TestIsFeatureEnabledWithTypedAudiences() { - var featureEnabled = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat_no_vars", "user1", new UserAttributes - { - { "favorite_ice_cream", "chocolate" } - }); + var featureEnabled = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat_no_vars", + "user1", new UserAttributes + { + { + "favorite_ice_cream", "chocolate" + }, + }); Assert.True(featureEnabled); - featureEnabled = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat_no_vars", "user1", new UserAttributes - { - { "lasers", 45.5 } - }); + featureEnabled = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat_no_vars", "user1", + new UserAttributes + { + { + "lasers", 45.5 + }, + }); Assert.True(featureEnabled); } @@ -3915,24 +5818,31 @@ public void TestIsFeatureEnabledWithTypedAudiences() [Test] public void TestIsFeatureEnabledExcludeUserFromExperimentWithTypedAudiences() { - var featureEnabled = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat", "user1", new UserAttributes { }); + var featureEnabled = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat", "user1", + new UserAttributes()); Assert.False(featureEnabled); } [Test] public void TestGetFeatureVariableStringReturnVariableValueWithTypedAudiences() { - var variableValue = OptimizelyWithTypedAudiences.GetFeatureVariableString("feat_with_var", "x", "user1", new UserAttributes - { - { "lasers", 71 } - }); + var variableValue = OptimizelyWithTypedAudiences.GetFeatureVariableString( + "feat_with_var", "x", "user1", new UserAttributes + { + { + "lasers", 71 + }, + }); Assert.AreEqual(variableValue, "xyz"); - variableValue = OptimizelyWithTypedAudiences.GetFeatureVariableString("feat_with_var", "x", "user1", new UserAttributes - { - { "should_do_it", true } - }); + variableValue = OptimizelyWithTypedAudiences.GetFeatureVariableString("feat_with_var", + "x", "user1", new UserAttributes + { + { + "should_do_it", true + }, + }); Assert.AreEqual(variableValue, "xyz"); } @@ -3940,10 +5850,13 @@ public void TestGetFeatureVariableStringReturnVariableValueWithTypedAudiences() [Test] public void TestGetFeatureVariableStringReturnDefaultVariableValueWithTypedAudiences() { - var variableValue = OptimizelyWithTypedAudiences.GetFeatureVariableString("feat_with_var", "x", "user1", new UserAttributes - { - { "lasers", 50 } - }); + var variableValue = OptimizelyWithTypedAudiences.GetFeatureVariableString( + "feat_with_var", "x", "user1", new UserAttributes + { + { + "lasers", 50 + }, + }); Assert.AreEqual(variableValue, "x"); } @@ -3957,15 +5870,22 @@ public void TestActivateIncludesUserInExperimentWithComplexAudienceConditions() { var userAttributes = new UserAttributes { - { "house", "Welcome to Slytherin!" }, - { "lasers", 45.5 } + { + "house", "Welcome to Slytherin!" + }, + { + "lasers", 45.5 + }, }; // Should be included via substring match string audience with id '3988293898' and exact match number audience with id '3468206646' - var variation = OptimizelyWithTypedAudiences.Activate("audience_combinations_experiment", "user1", userAttributes); + var variation = + OptimizelyWithTypedAudiences.Activate("audience_combinations_experiment", "user1", + userAttributes); Assert.AreEqual("A", variation.Key); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] @@ -3973,15 +5893,22 @@ public void TestActivateExcludesUserFromExperimentWithComplexAudienceConditions( { var userAttributes = new UserAttributes { - { "house", "Hufflepuff" }, - { "lasers", 45.5 } + { + "house", "Hufflepuff" + }, + { + "lasers", 45.5 + }, }; // Should be excluded as substring audience with id '3988293898' does not match, so the overall conditions fail. - var variation = OptimizelyWithTypedAudiences.Activate("audience_combinations_experiment", "user1", userAttributes); + var variation = + OptimizelyWithTypedAudiences.Activate("audience_combinations_experiment", "user1", + userAttributes); Assert.Null(variation); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Never); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Never); } [Test] @@ -3989,29 +5916,40 @@ public void TestTrackIncludesUserInExperimentWithComplexAudienceConditions() { var userAttributes = new UserAttributes { - { "house", "Gryffindor" }, - { "should_do_it", true } + { + "house", "Gryffindor" + }, + { + "should_do_it", true + }, }; // Should be included via exact match string audience with id '3468206642' and exact match boolean audience with id '3468206646' OptimizelyWithTypedAudiences.Track("user_signed_up", "user1", userAttributes); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] - public void TestTrackDoesNotExcludesUserFromExperimentWhenAttributesMismatchWithAudienceConditions() + public void + TestTrackDoesNotExcludesUserFromExperimentWhenAttributesMismatchWithAudienceConditions() { var userAttributes = new UserAttributes { - { "house", "Gryffindor" }, - { "should_do_it", false } + { + "house", "Gryffindor" + }, + { + "should_do_it", false + }, }; // Should be excluded as exact match boolean audience with id '3468206643' does not match so the overall conditions fail. OptimizelyWithTypedAudiences.Track("user_signed_up", "user1", userAttributes); - EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); + EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), + Times.Once); } [Test] @@ -4019,12 +5957,17 @@ public void TestIsFeatureEnabledIncludesUserInRolloutWithComplexAudienceConditio { var userAttributes = new UserAttributes { - { "house", "Welcome to Slytherin!" }, - { "favorite_ice_cream", "walls" } + { + "house", "Welcome to Slytherin!" + }, + { + "favorite_ice_cream", "walls" + }, }; // Should be included via substring match string audience with id '3988293898' and exists audience with id '3988293899' - var result = OptimizelyWithTypedAudiences.IsFeatureEnabled("feat2", "user1", userAttributes); + var result = + OptimizelyWithTypedAudiences.IsFeatureEnabled("feat2", "user1", userAttributes); Assert.True(result); } @@ -4033,13 +5976,19 @@ public void TestIsFeatureEnabledExcludesUserFromRolloutWithComplexAudienceCondit { var userAttributes = new UserAttributes { - { "house", "Ravenclaw" }, - { "lasers", 45.5 } + { + "house", "Ravenclaw" + }, + { + "lasers", 45.5 + }, }; // Should be excluded - substring match string audience with id '3988293898' does not match, // and no audience in the other branch of the 'and' matches either - var result = OptimizelyWithTypedAudiences.IsFeatureEnabled("audience_combinations_experiment", "user1", userAttributes); + var result = + OptimizelyWithTypedAudiences.IsFeatureEnabled("audience_combinations_experiment", + "user1", userAttributes); Assert.False(result); } @@ -4048,22 +5997,30 @@ public void TestGetFeatureVariableIntegerReturnsVariableValueWithComplexAudience { var userAttributes = new UserAttributes { - { "house", "Gryffindor" }, - { "lasers", 700 } + { + "house", "Gryffindor" + }, + { + "lasers", 700 + }, }; // Should be included via substring match string audience with id '3988293898' and exists audience with id '3988293899' - var value = OptimizelyWithTypedAudiences.GetFeatureVariableInteger("feat2_with_var", "z", "user1", userAttributes); + var value = + OptimizelyWithTypedAudiences.GetFeatureVariableInteger("feat2_with_var", "z", + "user1", userAttributes); Assert.AreEqual(150, value); } [Test] public void TestGetFeatureVariableIntegerReturnsDefaultValueWithComplexAudienceConditions() { - var userAttributes = new UserAttributes { }; + var userAttributes = new UserAttributes(); // Should be excluded - no audiences match with no attributes. - var value = OptimizelyWithTypedAudiences.GetFeatureVariableInteger("feat2_with_var", "z", "user1", userAttributes); + var value = + OptimizelyWithTypedAudiences.GetFeatureVariableInteger("feat2_with_var", "z", + "user1", userAttributes); Assert.AreEqual(10, value); } @@ -4074,12 +6031,12 @@ public void TestGetFeatureVariableIntegerReturnsDefaultValueWithComplexAudienceC [Test] public void TestOptimizelyDisposeAlsoDisposedConfigManager() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(5000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(5000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + Build(); var optimizely = new Optimizely(httpManager); optimizely.Dispose(); @@ -4090,12 +6047,12 @@ public void TestOptimizelyDisposeAlsoDisposedConfigManager() [Test] public void TestDisposeInvalidateObject() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(5000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .Build(); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(5000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + Build(); var optimizely = new Optimizely(httpManager); optimizely.Dispose(); @@ -4105,21 +6062,36 @@ public void TestDisposeInvalidateObject() [Test] public void TestAfterDisposeAPIsNoLongerValid() { - var httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") - .WithDatafile(TestData.Datafile) - .WithLogger(LoggerMock.Object) - .WithPollingInterval(TimeSpan.FromMilliseconds(50000)) - .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) - .Build(true); + var httpManager = new HttpProjectConfigManager.Builder(). + WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z"). + WithDatafile(TestData.Datafile). + WithLogger(LoggerMock.Object). + WithPollingInterval(TimeSpan.FromMilliseconds(50000)). + WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)). + Build(true); var optimizely = new Optimizely(httpManager); httpManager.Start(); - var activate = optimizely.Activate("test_experiment", TestUserId, new UserAttributes() { - { "device_type", "iPhone" }, { "location", "San Francisco" } }); + var activate = optimizely.Activate("test_experiment", TestUserId, new UserAttributes() + { + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, + }); Assert.NotNull(activate); optimizely.Dispose(); - var activateAfterDispose = optimizely.Activate("test_experiment", TestUserId, new UserAttributes() { - { "device_type", "iPhone" }, { "location", "San Francisco" } }); + var activateAfterDispose = optimizely.Activate("test_experiment", TestUserId, + new UserAttributes() + { + { + "device_type", "iPhone" + }, + { + "location", "San Francisco" + }, + }); Assert.Null(activateAfterDispose); httpManager.Dispose(); } @@ -4148,10 +6120,14 @@ public void TestAfterDisposeAPIsShouldNotCrash() optimizely.Track(string.Empty, string.Empty); Assert.IsFalse(optimizely.IsFeatureEnabled(string.Empty, string.Empty)); Assert.AreEqual(optimizely.GetEnabledFeatures(string.Empty).Count, 0); - Assert.IsNull(optimizely.GetFeatureVariableBoolean(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableString(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableDouble(string.Empty, string.Empty, string.Empty)); - Assert.IsNull(optimizely.GetFeatureVariableInteger(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableBoolean(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableString(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableDouble(string.Empty, string.Empty, string.Empty)); + Assert.IsNull( + optimizely.GetFeatureVariableInteger(string.Empty, string.Empty, string.Empty)); } #endregion Disposable Optimizely diff --git a/OptimizelySDK.Tests/OptimizelyUserContextTest.cs b/OptimizelySDK.Tests/OptimizelyUserContextTest.cs index 5e9ddad9..00368f72 100644 --- a/OptimizelySDK.Tests/OptimizelyUserContextTest.cs +++ b/OptimizelySDK.Tests/OptimizelyUserContextTest.cs @@ -15,6 +15,9 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; +using System.Threading; using Castle.Core.Internal; using Moq; using NUnit.Framework; @@ -29,15 +32,13 @@ using OptimizelySDK.OptimizelyDecisions; using OptimizelySDK.Tests.NotificationTests; using OptimizelySDK.Utils; -using System; -using System.Collections.Generic; namespace OptimizelySDK.Tests { [TestFixture] public class OptimizelyUserContextTest { - const string UserID = "testUserID"; + private const string UserID = "testUserID"; private Optimizely Optimizely; private Mock LoggerMock; private Mock ErrorHandlerMock; @@ -62,8 +63,14 @@ public void SetUp() [Test] public void OptimizelyUserContextWithAttributes() { - var attributes = new UserAttributes() { { "house", "GRYFFINDOR" } }; - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, attributes, ErrorHandlerMock.Object, LoggerMock.Object); + var attributes = new UserAttributes() + { + { + "house", "GRYFFINDOR" + }, + }; + var user = new OptimizelyUserContext(Optimizely, UserID, attributes, + ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(user.GetOptimizely(), Optimizely); Assert.AreEqual(user.GetUserId(), UserID); @@ -73,7 +80,8 @@ public void OptimizelyUserContextWithAttributes() [Test] public void OptimizelyUserContextNoAttributes() { - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, null, ErrorHandlerMock.Object, LoggerMock.Object); + var user = new OptimizelyUserContext(Optimizely, UserID, null, + ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(user.GetOptimizely(), Optimizely); Assert.AreEqual(user.GetUserId(), UserID); @@ -83,8 +91,14 @@ public void OptimizelyUserContextNoAttributes() [Test] public void SetAttribute() { - var attributes = new UserAttributes() { { "house", "GRYFFINDOR" } }; - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, attributes, ErrorHandlerMock.Object, LoggerMock.Object); + var attributes = new UserAttributes() + { + { + "house", "GRYFFINDOR" + }, + }; + var user = new OptimizelyUserContext(Optimizely, UserID, attributes, + ErrorHandlerMock.Object, LoggerMock.Object); user.SetAttribute("k1", "v1"); user.SetAttribute("k2", true); @@ -104,7 +118,8 @@ public void SetAttribute() [Test] public void SetAttributeNoAttribute() { - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, null, ErrorHandlerMock.Object, LoggerMock.Object); + var user = new OptimizelyUserContext(Optimizely, UserID, null, + ErrorHandlerMock.Object, LoggerMock.Object); user.SetAttribute("k1", "v1"); user.SetAttribute("k2", true); @@ -119,8 +134,14 @@ public void SetAttributeNoAttribute() [Test] public void SetAttributeOverride() { - var attributes = new UserAttributes() { { "house", "GRYFFINDOR" } }; - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, attributes, ErrorHandlerMock.Object, LoggerMock.Object); + var attributes = new UserAttributes() + { + { + "house", "GRYFFINDOR" + }, + }; + var user = new OptimizelyUserContext(Optimizely, UserID, attributes, + ErrorHandlerMock.Object, LoggerMock.Object); user.SetAttribute("k1", "v1"); user.SetAttribute("house", "v2"); @@ -133,8 +154,14 @@ public void SetAttributeOverride() [Test] public void SetAttributeNullValue() { - var attributes = new UserAttributes() { { "k1", null } }; - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, attributes, ErrorHandlerMock.Object, LoggerMock.Object); + var attributes = new UserAttributes() + { + { + "k1", null + }, + }; + var user = new OptimizelyUserContext(Optimizely, UserID, attributes, + ErrorHandlerMock.Object, LoggerMock.Object); var newAttributes = user.GetAttributes(); Assert.AreEqual(newAttributes["k1"], null); @@ -151,7 +178,8 @@ public void SetAttributeNullValue() [Test] public void SetAttributeToOverrideAttribute() { - OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, null, ErrorHandlerMock.Object, LoggerMock.Object); + var user = new OptimizelyUserContext(Optimizely, UserID, null, + ErrorHandlerMock.Object, LoggerMock.Object); Assert.AreEqual(user.GetOptimizely(), Optimizely); Assert.AreEqual(user.GetUserId(), UserID); @@ -362,7 +390,8 @@ public void DecideInvalidFlagKey() [Test] public void DecideWhenConfigIsNull() { - Optimizely optimizely = new Optimizely(TestData.UnsupportedVersionDatafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); + var optimizely = new Optimizely(TestData.UnsupportedVersionDatafile, + EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); var flagKey = "multi_variate_feature"; var decisionExpected = OptimizelyDecision.NewErrorDecision( @@ -385,7 +414,10 @@ public void DecideWhenConfigIsNull() public void DecideForKeysWithOneFlag() { var flagKey = "multi_variate_feature"; - var flagKeys = new string[] { flagKey }; + var flagKeys = new string[] + { + flagKey, + }; var variablesExpected = Optimizely.GetAllFeatureVariables(flagKey, UserID); @@ -397,14 +429,14 @@ public void DecideForKeysWithOneFlag() Assert.True(decisions.Count == 1); var decision = decisions[flagKey]; - OptimizelyDecision expDecision = new OptimizelyDecision( - "Gred", - false, - variablesExpected, - "test_experiment_multivariate", - flagKey, - user, - new string[0]); + var expDecision = new OptimizelyDecision( + "Gred", + false, + variablesExpected, + "test_experiment_multivariate", + flagKey, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decision, expDecision)); } @@ -413,7 +445,10 @@ public void DecideAllTwoFlag() { var flagKey1 = "multi_variate_feature"; var flagKey2 = "string_single_variable_feature"; - var flagKeys = new string[] { flagKey1, flagKey2 }; + var flagKeys = new string[] + { + flagKey1, flagKey2, + }; var variablesExpected1 = Optimizely.GetAllFeatureVariables(flagKey1, UserID); var variablesExpected2 = Optimizely.GetAllFeatureVariables(flagKey2, UserID); @@ -430,30 +465,34 @@ public void DecideAllTwoFlag() var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + { + "browser_type", "chrome" + }, }; Assert.True(decisions.Count == 2); - NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, UserID, userAttributes, It.IsAny>()), - Times.Exactly(2)); - OptimizelyDecision expDecision1 = new OptimizelyDecision( - "Gred", - false, - variablesExpected1, - "test_experiment_multivariate", - flagKey1, - user, - new string[0]); + NotificationCallbackMock.Verify( + nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, UserID, + userAttributes, It.IsAny>()), + Times.Exactly(2)); + var expDecision1 = new OptimizelyDecision( + "Gred", + false, + variablesExpected1, + "test_experiment_multivariate", + flagKey1, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey1], expDecision1)); - OptimizelyDecision expDecision2 = new OptimizelyDecision( - "control", - true, - variablesExpected2, - "test_experiment_with_feature_rollout", - flagKey2, - user, - new string[0]); + var expDecision2 = new OptimizelyDecision( + "control", + true, + variablesExpected2, + "test_experiment_with_feature_rollout", + flagKey2, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey2], expDecision2)); } @@ -494,111 +533,113 @@ public void DecideAllAllFlags() var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + { + "browser_type", "chrome" + }, }; Assert.True(decisions.Count == 10); NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FLAG, UserID, userAttributes, It.IsAny>()), Times.Exactly(10)); - OptimizelyDecision expDecision1 = new OptimizelyDecision( - null, - false, - variablesExpected1, - null, - flagKey1, - user, - new string[0]); + var expDecision1 = new OptimizelyDecision( + null, + false, + variablesExpected1, + null, + flagKey1, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey1], expDecision1)); - OptimizelyDecision expDecision2 = new OptimizelyDecision( - "variation", - false, - variablesExpected2, - "test_experiment_double_feature", - flagKey2, - user, - new string[0]); + var expDecision2 = new OptimizelyDecision( + "variation", + false, + variablesExpected2, + "test_experiment_double_feature", + flagKey2, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey2], expDecision2)); - OptimizelyDecision expDecision3 = new OptimizelyDecision( - "control", - false, - variablesExpected3, - "test_experiment_integer_feature", - flagKey3, - user, - new string[0]); + var expDecision3 = new OptimizelyDecision( + "control", + false, + variablesExpected3, + "test_experiment_integer_feature", + flagKey3, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey3], expDecision3)); - OptimizelyDecision expDecision4 = new OptimizelyDecision( - "188881", - false, - variablesExpected4, - "188880", - flagKey4, - user, - new string[0]); + var expDecision4 = new OptimizelyDecision( + "188881", + false, + variablesExpected4, + "188880", + flagKey4, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey4], expDecision4)); - OptimizelyDecision expDecision5 = new OptimizelyDecision( - "control", - true, - variablesExpected5, - "test_experiment_with_feature_rollout", - flagKey5, - user, - new string[0]); + var expDecision5 = new OptimizelyDecision( + "control", + true, + variablesExpected5, + "test_experiment_with_feature_rollout", + flagKey5, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey5], expDecision5)); - OptimizelyDecision expDecision6 = new OptimizelyDecision( - "Gred", - false, - variablesExpected6, - "test_experiment_multivariate", - flagKey6, - user, - new string[0]); + var expDecision6 = new OptimizelyDecision( + "Gred", + false, + variablesExpected6, + "test_experiment_multivariate", + flagKey6, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey6], expDecision6)); - OptimizelyDecision expDecision7 = new OptimizelyDecision( - null, - false, - variablesExpected7, - null, - flagKey7, - user, - new string[0]); + var expDecision7 = new OptimizelyDecision( + null, + false, + variablesExpected7, + null, + flagKey7, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey7], expDecision7)); - OptimizelyDecision expDecision8 = new OptimizelyDecision( - null, - false, - variablesExpected8, - null, - flagKey8, - user, - new string[0]); + var expDecision8 = new OptimizelyDecision( + null, + false, + variablesExpected8, + null, + flagKey8, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey8], expDecision8)); - OptimizelyDecision expDecision9 = new OptimizelyDecision( - null, - false, - variablesExpected9, - null, - flagKey9, - user, - new string[0]); + var expDecision9 = new OptimizelyDecision( + null, + false, + variablesExpected9, + null, + flagKey9, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey9], expDecision9)); - OptimizelyDecision expDecision10 = new OptimizelyDecision( - null, - false, - variablesExpected10, - null, - flagKey10, - user, - new string[0]); + var expDecision10 = new OptimizelyDecision( + null, + false, + variablesExpected10, + null, + flagKey10, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey10], expDecision10)); } @@ -609,7 +650,10 @@ public void DecideAllEnabledFlagsOnlyDecideOptions() var variablesExpected1 = Optimizely.GetAllFeatureVariables(flagKey1, UserID); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.ENABLED_FLAGS_ONLY }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.ENABLED_FLAGS_ONLY, + }; var user = Optimizely.CreateUserContext(UserID); user.SetAttribute("browser_type", "chrome"); @@ -617,14 +661,14 @@ public void DecideAllEnabledFlagsOnlyDecideOptions() Assert.True(decisions.Count == 1); - OptimizelyDecision expDecision1 = new OptimizelyDecision( - "control", - true, - variablesExpected1, - "test_experiment_with_feature_rollout", - flagKey1, - user, - new string[0]); + var expDecision1 = new OptimizelyDecision( + "control", + true, + variablesExpected1, + "test_experiment_with_feature_rollout", + flagKey1, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey1], expDecision1)); } @@ -632,7 +676,10 @@ public void DecideAllEnabledFlagsOnlyDecideOptions() public void DecideAllEnabledFlagsDefaultDecideOptions() { var flagKey1 = "string_single_variable_feature"; - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.ENABLED_FLAGS_ONLY }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.ENABLED_FLAGS_ONLY, + }; var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, @@ -649,14 +696,14 @@ public void DecideAllEnabledFlagsDefaultDecideOptions() Assert.True(decisions.Count == 1); - OptimizelyDecision expDecision1 = new OptimizelyDecision( - "control", - true, - variablesExpected1, - "test_experiment_with_feature_rollout", - flagKey1, - user, - new string[0]); + var expDecision1 = new OptimizelyDecision( + "control", + true, + variablesExpected1, + "test_experiment_with_feature_rollout", + flagKey1, + user, + new string[0]); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey1], expDecision1)); } @@ -664,7 +711,10 @@ public void DecideAllEnabledFlagsDefaultDecideOptions() public void DecideAllEnabledFlagsDefaultDecideOptionsPlusApiOptions() { var flagKey1 = "string_single_variable_feature"; - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.ENABLED_FLAGS_ONLY }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.ENABLED_FLAGS_ONLY, + }; var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, @@ -674,20 +724,24 @@ public void DecideAllEnabledFlagsDefaultDecideOptionsPlusApiOptions() var user = optimizely.CreateUserContext(UserID); user.SetAttribute("browser_type", "chrome"); - decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.EXCLUDE_VARIABLES }; + decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.EXCLUDE_VARIABLES, + }; var decisions = user.DecideAll(decideOptions); Assert.True(decisions.Count == 1); var expectedOptlyJson = new Dictionary(); - OptimizelyDecision expDecision1 = new OptimizelyDecision( - "control", - true, - new OptimizelyJSON(expectedOptlyJson, ErrorHandlerMock.Object, LoggerMock.Object), - "test_experiment_with_feature_rollout", - flagKey1, - user, - new string[] { }); + var expDecision1 = new OptimizelyDecision( + "control", + true, + new OptimizelyJSON(expectedOptlyJson, ErrorHandlerMock.Object, LoggerMock.Object), + "test_experiment_with_feature_rollout", + flagKey1, + user, + new string[] + { }); Assert.IsTrue(TestData.CompareObjects(decisions[flagKey1], expDecision1)); } @@ -698,7 +752,10 @@ public void DecideExcludeVariablesDecideOptions() var variablesExpected = new Dictionary(); var user = Optimizely.CreateUserContext(UserID); user.SetAttribute("browser_type", "chrome"); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.EXCLUDE_VARIABLES }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.EXCLUDE_VARIABLES, + }; var decision = user.Decide(flagKey, decideOptions); @@ -723,7 +780,10 @@ public void DecideIncludeReasonsDecideOptions() Assert.True(decision.Reasons.Length == 1); Assert.AreEqual(decision.Reasons[0], DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, flagKey)); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.INCLUDE_REASONS }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.INCLUDE_REASONS, + }; decision = user.Decide(flagKey, decideOptions); Assert.True(decision.Reasons.Length == 1); @@ -757,7 +817,10 @@ public void TestDoNotSendEventDecide() var user = optimizely.CreateUserContext(UserID); user.SetAttribute("browser_type", "chrome"); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.DISABLE_DECISION_EVENT }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + }; var decision = user.Decide(flagKey, decideOptions); EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Never); @@ -778,7 +841,10 @@ public void TestDefaultDecideOptions() { var flagKey = "multi_variate_feature"; var variablesExpected = Optimizely.GetAllFeatureVariables(flagKey, UserID); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.DISABLE_DECISION_EVENT }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + }; var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, @@ -826,7 +892,9 @@ public void TestDecisionNotification() var userAttributes = new UserAttributes { - { "browser_type", "chrome" } + { + "browser_type", "chrome" + }, }; // Mocking objects. @@ -855,7 +923,9 @@ public void TestDecideOptionsByPassUPS() var userProfile = new UserProfile(userId, new Dictionary { - { experimentId, new Decision(fbVariationId)} + { + experimentId, new Decision(fbVariationId) + }, }); userProfileServiceMock.Setup(_ => _.Lookup(userId)).Returns(userProfile.ToMap()); @@ -869,7 +939,10 @@ public void TestDecideOptionsByPassUPS() var variationUserProfile = user.Decide(flagKey); Assert.AreEqual(fbVariationKey, variationUserProfile.VariationKey); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE, + }; variationUserProfile = user.Decide(flagKey, decideOptions); Assert.AreEqual(variationKey, variationUserProfile.VariationKey); } @@ -886,7 +959,10 @@ public void TestDecideOptionsByPassUPSNeverCallsSaveVariation() var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); var user = optimizely.CreateUserContext(userId); - var decideOptions = new OptimizelyDecideOption[] { OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE }; + var decideOptions = new OptimizelyDecideOption[] + { + OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE, + }; var variationUserProfile = user.Decide(flagKey, decideOptions); userProfileServiceMock.Verify(l => l.Save(It.IsAny>()), Times.Never); @@ -903,8 +979,12 @@ public void TestTrackEventWithAudienceConditions() var OptimizelyWithTypedAudiences = new Optimizely(TestData.TypedAudienceDatafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object); var userAttributes = new UserAttributes { - { "house", "Gryffindor" }, - { "should_do_it", false } + { + "house", "Gryffindor" + }, + { + "should_do_it", false + }, }; var user = OptimizelyWithTypedAudiences.CreateUserContext(UserID, userAttributes); @@ -925,8 +1005,12 @@ public void TrackEventEmptyAttributesWithEventTags() // Should be excluded as exact match boolean audience with id '3468206643' does not match so the overall conditions fail. user.TrackEvent("user_signed_up", new EventTags { - { "revenue", 42 }, - { "wont_send_null", null} + { + "revenue", 42 + }, + { + "wont_send_null", null + }, }); EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index 7cf69c54..9012b9ea 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -14,6 +14,9 @@ * limitations under the License. */ +using System; +using System.Collections.Generic; +using System.Linq; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -24,9 +27,6 @@ using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; using OptimizelySDK.Utils; -using System; -using System.Collections.Generic; -using System.Linq; namespace OptimizelySDK.Tests { @@ -49,7 +49,12 @@ public void Setup() public static Dictionary CreateDictionary(string name, object entityObject) { - return new Dictionary() { { name, entityObject } }; + return new Dictionary() + { + { + name, entityObject + }, + }; } [Test] @@ -80,18 +85,46 @@ public void TestInit() // Check Experiment Key Map var experimentKeyMap = new Dictionary() { - {"test_experiment",Config.GetExperimentFromKey("test_experiment") }, - { "paused_experiment",Config.GetExperimentFromKey("paused_experiment") }, - { "test_experiment_multivariate",Config.GetExperimentFromKey("test_experiment_multivariate") }, - { "test_experiment_with_feature_rollout",Config.GetExperimentFromKey("test_experiment_with_feature_rollout") }, - { "test_experiment_double_feature",Config.GetExperimentFromKey("test_experiment_double_feature") }, - { "test_experiment_integer_feature",Config.GetExperimentFromKey("test_experiment_integer_feature") }, - { "group_experiment_1",Config.GetExperimentFromKey("group_experiment_1") }, - {"group_experiment_2",Config.GetExperimentFromKey("group_experiment_2") }, - {"etag1",Config.GetExperimentFromKey("etag1") }, - {"etag2",Config.GetExperimentFromKey("etag2") }, - {"etag3",Config.GetExperimentFromKey("etag3") }, - {"etag4",Config.GetExperimentFromKey("etag4") } + { + "test_experiment", Config.GetExperimentFromKey("test_experiment") + }, + { + "paused_experiment", Config.GetExperimentFromKey("paused_experiment") + }, + { + "test_experiment_multivariate", + Config.GetExperimentFromKey("test_experiment_multivariate") + }, + { + "test_experiment_with_feature_rollout", + Config.GetExperimentFromKey("test_experiment_with_feature_rollout") + }, + { + "test_experiment_double_feature", + Config.GetExperimentFromKey("test_experiment_double_feature") + }, + { + "test_experiment_integer_feature", + Config.GetExperimentFromKey("test_experiment_integer_feature") + }, + { + "group_experiment_1", Config.GetExperimentFromKey("group_experiment_1") + }, + { + "group_experiment_2", Config.GetExperimentFromKey("group_experiment_2") + }, + { + "etag1", Config.GetExperimentFromKey("etag1") + }, + { + "etag2", Config.GetExperimentFromKey("etag2") + }, + { + "etag3", Config.GetExperimentFromKey("etag3") + }, + { + "etag4", Config.GetExperimentFromKey("etag4") + }, }; Assert.IsTrue(TestData.CompareObjects(experimentKeyMap, Config.ExperimentKeyMap)); @@ -100,164 +133,318 @@ public void TestInit() var experimentIdMap = new Dictionary() { - {"7716830082",Config.GetExperimentFromId("7716830082") }, - {"7716830585",Config.GetExperimentFromId("7716830585") }, - {"122230",Config.GetExperimentFromId("122230") }, - {"122235",Config.GetExperimentFromId("122235") }, - {"122238",Config.GetExperimentFromId("122238") }, - {"122241",Config.GetExperimentFromId("122241") }, - { "7723330021",Config.GetExperimentFromId("7723330021") }, - { "7718750065",Config.GetExperimentFromId("7718750065") }, - { "223",Config.GetExperimentFromId("223") }, - { "118",Config.GetExperimentFromId("118") }, - { "224",Config.GetExperimentFromId("224") }, - { "119",Config.GetExperimentFromId("119") } + { + "7716830082", Config.GetExperimentFromId("7716830082") + }, + { + "7716830585", Config.GetExperimentFromId("7716830585") + }, + { + "122230", Config.GetExperimentFromId("122230") + }, + { + "122235", Config.GetExperimentFromId("122235") + }, + { + "122238", Config.GetExperimentFromId("122238") + }, + { + "122241", Config.GetExperimentFromId("122241") + }, + { + "7723330021", Config.GetExperimentFromId("7723330021") + }, + { + "7718750065", Config.GetExperimentFromId("7718750065") + }, + { + "223", Config.GetExperimentFromId("223") + }, + { + "118", Config.GetExperimentFromId("118") + }, + { + "224", Config.GetExperimentFromId("224") + }, + { + "119", Config.GetExperimentFromId("119") + }, }; Assert.IsTrue(TestData.CompareObjects(experimentIdMap, Config.ExperimentIdMap)); // Check Event key Map - var eventKeyMap = new Dictionary { { "purchase", Config.GetEvent("purchase") } }; + var eventKeyMap = new Dictionary + { + { + "purchase", Config.GetEvent("purchase") + }, + }; Assert.IsTrue(TestData.CompareObjects(eventKeyMap, Config.EventKeyMap)); // Check Attribute Key Map var attributeKeyMap = new Dictionary { - { "device_type", Config.GetAttribute("device_type") }, - { "location", Config.GetAttribute("location")}, - { "browser_type", Config.GetAttribute("browser_type")}, - { "boolean_key", Config.GetAttribute("boolean_key")}, - { "integer_key", Config.GetAttribute("integer_key")}, - { "double_key", Config.GetAttribute("double_key")} + { + "device_type", Config.GetAttribute("device_type") + }, + { + "location", Config.GetAttribute("location") + }, + { + "browser_type", Config.GetAttribute("browser_type") + }, + { + "boolean_key", Config.GetAttribute("boolean_key") + }, + { + "integer_key", Config.GetAttribute("integer_key") + }, + { + "double_key", Config.GetAttribute("double_key") + }, }; Assert.IsTrue(TestData.CompareObjects(attributeKeyMap, Config.AttributeKeyMap)); // Check Audience ID Map var audienceIdMap = new Dictionary { - { "7718080042", Config.GetAudience("7718080042") }, - { "11154", Config.GetAudience("11154") }, - { "100", Config.GetAudience("100") } + { + "7718080042", Config.GetAudience("7718080042") + }, + { + "11154", Config.GetAudience("11154") + }, + { + "100", Config.GetAudience("100") + }, }; Assert.IsTrue(TestData.CompareObjects(audienceIdMap, Config.AudienceIdMap)); // Check Variation Key Map var expectedVariationKeyMap = new Dictionary { - { "test_experiment", new Dictionary - { - { "control", Config.GetVariationFromKey("test_experiment", "control") }, - { "variation", Config.GetVariationFromKey("test_experiment", "variation")} - } - }, - { "paused_experiment", new Dictionary - { - { "control", Config.GetVariationFromKey("paused_experiment", "control") }, - { "variation", Config.GetVariationFromKey("paused_experiment", "variation") } - } - }, - { "group_experiment_1", new Dictionary - { - {"group_exp_1_var_1", Config.GetVariationFromKey("group_experiment_1", "group_exp_1_var_1") }, - { "group_exp_1_var_2", Config.GetVariationFromKey("group_experiment_1", "group_exp_1_var_2") } - } - }, - { "group_experiment_2", new Dictionary - { - {"group_exp_2_var_1", Config.GetVariationFromKey("group_experiment_2", "group_exp_2_var_1") }, - { "group_exp_2_var_2", Config.GetVariationFromKey("group_experiment_2", "group_exp_2_var_2") } - } - }, - { "test_experiment_multivariate", new Dictionary - { - {"Fred", Config.GetVariationFromKey("test_experiment_multivariate", "Fred") }, - { "Feorge", Config.GetVariationFromKey("test_experiment_multivariate", "Feorge") }, - { "Gred", Config.GetVariationFromKey("test_experiment_multivariate", "Gred") }, - { "George", Config.GetVariationFromKey("test_experiment_multivariate", "George") } - } - }, - { "test_experiment_with_feature_rollout", new Dictionary - { - {"control", Config.GetVariationFromKey("test_experiment_with_feature_rollout", "control") }, - { "variation", Config.GetVariationFromKey("test_experiment_with_feature_rollout", "variation") } - } - }, - { "test_experiment_double_feature", new Dictionary - { - {"control", Config.GetVariationFromKey("test_experiment_double_feature", "control") }, - { "variation", Config.GetVariationFromKey("test_experiment_double_feature", "variation") } - } - }, - { "test_experiment_integer_feature", new Dictionary - { - {"control", Config.GetVariationFromKey("test_experiment_integer_feature", "control") }, - { "variation", Config.GetVariationFromKey("test_experiment_integer_feature", "variation") } - } - }, - { "177770", new Dictionary - { - {"177771", Config.GetVariationFromKey("177770", "177771") } - } - }, - { "177772", new Dictionary - { - {"177773", Config.GetVariationFromKey("177772", "177773") } - } - }, - { "177776", new Dictionary - { - {"177778", Config.GetVariationFromKey("177776", "177778") } - } - }, - { "177774", new Dictionary - { - {"177775", Config.GetVariationFromKey("177774", "177775") } - } - }, - { "177779", new Dictionary - { - {"177780", Config.GetVariationFromKey("177779", "177780") } - } - }, - { "177781", new Dictionary - { - {"177782", Config.GetVariationFromKey("177781", "177782") } - } - }, - { "177783", new Dictionary - { - {"177784", Config.GetVariationFromKey("177783", "177784") } - } - }, - { "188880", new Dictionary - { - {"188881", Config.GetVariationFromKey("188880", "188881") } - } - }, - { "etag1", new Dictionary - { - {"vtag1", Config.GetVariationFromKey("etag1", "vtag1") }, - {"vtag2", Config.GetVariationFromKey("etag1", "vtag2") } - } - }, - { "etag2", new Dictionary - { - {"vtag3", Config.GetVariationFromKey("etag2", "vtag3") }, - {"vtag4", Config.GetVariationFromKey("etag2", "vtag4") } - } - }, - { "etag3", new Dictionary - { - {"vtag5", Config.GetVariationFromKey("etag3", "vtag5") }, - {"vtag6", Config.GetVariationFromKey("etag3", "vtag6") } - } - }, - { "etag4", new Dictionary - { - {"vtag7", Config.GetVariationFromKey("etag4", "vtag7") }, - {"vtag8", Config.GetVariationFromKey("etag4", "vtag8") } - } - } + { + "test_experiment", new Dictionary + { + { + "control", Config.GetVariationFromKey("test_experiment", "control") + }, + { + "variation", Config.GetVariationFromKey("test_experiment", "variation") + }, + } + }, + { + "paused_experiment", new Dictionary + { + { + "control", Config.GetVariationFromKey("paused_experiment", "control") + }, + { + "variation", + Config.GetVariationFromKey("paused_experiment", "variation") + }, + } + }, + { + "group_experiment_1", new Dictionary + { + { + "group_exp_1_var_1", + Config.GetVariationFromKey("group_experiment_1", "group_exp_1_var_1") + }, + { + "group_exp_1_var_2", + Config.GetVariationFromKey("group_experiment_1", "group_exp_1_var_2") + }, + } + }, + { + "group_experiment_2", new Dictionary + { + { + "group_exp_2_var_1", + Config.GetVariationFromKey("group_experiment_2", "group_exp_2_var_1") + }, + { + "group_exp_2_var_2", + Config.GetVariationFromKey("group_experiment_2", "group_exp_2_var_2") + }, + } + }, + { + "test_experiment_multivariate", new Dictionary + { + { + "Fred", + Config.GetVariationFromKey("test_experiment_multivariate", "Fred") + }, + { + "Feorge", + Config.GetVariationFromKey("test_experiment_multivariate", "Feorge") + }, + { + "Gred", + Config.GetVariationFromKey("test_experiment_multivariate", "Gred") + }, + { + "George", + Config.GetVariationFromKey("test_experiment_multivariate", "George") + }, + } + }, + { + "test_experiment_with_feature_rollout", new Dictionary + { + { + "control", Config.GetVariationFromKey( + "test_experiment_with_feature_rollout", + "control") + }, + { + "variation", Config.GetVariationFromKey( + "test_experiment_with_feature_rollout", + "variation") + }, + } + }, + { + "test_experiment_double_feature", new Dictionary + { + { + "control", + Config.GetVariationFromKey("test_experiment_double_feature", "control") + }, + { + "variation", Config.GetVariationFromKey( + "test_experiment_double_feature", + "variation") + }, + } + }, + { + "test_experiment_integer_feature", new Dictionary + { + { + "control", + Config.GetVariationFromKey("test_experiment_integer_feature", "control") + }, + { + "variation", Config.GetVariationFromKey( + "test_experiment_integer_feature", + "variation") + }, + } + }, + { + "177770", new Dictionary + { + { + "177771", Config.GetVariationFromKey("177770", "177771") + }, + } + }, + { + "177772", new Dictionary + { + { + "177773", Config.GetVariationFromKey("177772", "177773") + }, + } + }, + { + "177776", new Dictionary + { + { + "177778", Config.GetVariationFromKey("177776", "177778") + }, + } + }, + { + "177774", new Dictionary + { + { + "177775", Config.GetVariationFromKey("177774", "177775") + }, + } + }, + { + "177779", new Dictionary + { + { + "177780", Config.GetVariationFromKey("177779", "177780") + }, + } + }, + { + "177781", new Dictionary + { + { + "177782", Config.GetVariationFromKey("177781", "177782") + }, + } + }, + { + "177783", new Dictionary + { + { + "177784", Config.GetVariationFromKey("177783", "177784") + }, + } + }, + { + "188880", new Dictionary + { + { + "188881", Config.GetVariationFromKey("188880", "188881") + }, + } + }, + { + "etag1", new Dictionary + { + { + "vtag1", Config.GetVariationFromKey("etag1", "vtag1") + }, + { + "vtag2", Config.GetVariationFromKey("etag1", "vtag2") + }, + } + }, + { + "etag2", new Dictionary + { + { + "vtag3", Config.GetVariationFromKey("etag2", "vtag3") + }, + { + "vtag4", Config.GetVariationFromKey("etag2", "vtag4") + }, + } + }, + { + "etag3", new Dictionary + { + { + "vtag5", Config.GetVariationFromKey("etag3", "vtag5") + }, + { + "vtag6", Config.GetVariationFromKey("etag3", "vtag6") + }, + } + }, + { + "etag4", new Dictionary + { + { + "vtag7", Config.GetVariationFromKey("etag4", "vtag7") + }, + { + "vtag8", Config.GetVariationFromKey("etag4", "vtag8") + }, + } + }, }; Assert.IsTrue(TestData.CompareObjects(expectedVariationKeyMap, Config.VariationKeyMap)); @@ -265,120 +452,226 @@ public void TestInit() // Check Variation ID Map var expectedVariationIdMap = new Dictionary { - { "test_experiment", new Dictionary - { - {"7722370027", Config.GetVariationFromId("test_experiment", "7722370027") }, - { "7721010009", Config.GetVariationFromId("test_experiment", "7721010009") } - } - }, - { "paused_experiment", new Dictionary - { - {"7722370427", Config.GetVariationFromId("paused_experiment", "7722370427") }, - { "7721010509", Config.GetVariationFromId("paused_experiment", "7721010509") } - } - }, - { "test_experiment_multivariate", new Dictionary - { - { "122231", Config.GetVariationFromId("test_experiment_multivariate", "122231") }, - { "122232", Config.GetVariationFromId("test_experiment_multivariate", "122232") }, - { "122233", Config.GetVariationFromId("test_experiment_multivariate", "122233") }, - { "122234", Config.GetVariationFromId("test_experiment_multivariate", "122234") } - } - }, - { "test_experiment_with_feature_rollout", new Dictionary - { - { "122236", Config.GetVariationFromId("test_experiment_with_feature_rollout", "122236") }, - { "122237", Config.GetVariationFromId("test_experiment_with_feature_rollout", "122237") } - } - }, - { "test_experiment_double_feature", new Dictionary - { - { "122239", Config.GetVariationFromId("test_experiment_double_feature", "122239") }, - { "122240", Config.GetVariationFromId("test_experiment_double_feature", "122240") } - } - }, - { "test_experiment_integer_feature", new Dictionary - { - { "122242", Config.GetVariationFromId("test_experiment_integer_feature", "122242") }, - { "122243", Config.GetVariationFromId("test_experiment_integer_feature", "122243") } - } - }, - { "group_experiment_1", new Dictionary - { - {"7722260071", Config.GetVariationFromId("group_experiment_1", "7722260071") }, - { "7722360022", Config.GetVariationFromId("group_experiment_1", "7722360022")} - } - }, - { "group_experiment_2", new Dictionary - { - {"7713030086", Config.GetVariationFromId("group_experiment_2", "7713030086") }, - { "7725250007", Config.GetVariationFromId("group_experiment_2", "7725250007")} - } - }, - { "177770", new Dictionary - { - {"177771", Config.GetVariationFromId("177770", "177771") } - } - }, - { "177772", new Dictionary - { - {"177773", Config.GetVariationFromId("177772", "177773") } - } - }, - { "177776", new Dictionary - { - {"177778", Config.GetVariationFromId("177776", "177778") } - } - }, - { "177774", new Dictionary - { - {"177775", Config.GetVariationFromId("177774", "177775") } - } - }, - { "177779", new Dictionary - { - {"177780", Config.GetVariationFromId("177779", "177780") } - } - }, - { "177781", new Dictionary - { - {"177782", Config.GetVariationFromId("177781", "177782") } - } - }, - { "177783", new Dictionary - { - {"177784", Config.GetVariationFromId("177783", "177784") } - } - }, - { "188880", new Dictionary - { - {"188881", Config.GetVariationFromId("188880", "188881") } - } - }, - { "etag1", new Dictionary - { - {"276", Config.GetVariationFromId("etag1", "276") }, - {"277", Config.GetVariationFromId("etag1", "277") } - } - }, - { "etag2", new Dictionary - { - {"278", Config.GetVariationFromId("etag2", "278") }, - {"279", Config.GetVariationFromId("etag2", "279") } - } - }, - { "etag3", new Dictionary - { - {"280", Config.GetVariationFromId("etag3", "280") }, - {"281", Config.GetVariationFromId("etag3", "281") } - } - }, - { "etag4", new Dictionary - { - {"282", Config.GetVariationFromId("etag4", "282") }, - {"283", Config.GetVariationFromId("etag4", "283") } - } - } + { + "test_experiment", new Dictionary + { + { + "7722370027", Config.GetVariationFromId("test_experiment", "7722370027") + }, + { + "7721010009", Config.GetVariationFromId("test_experiment", "7721010009") + }, + } + }, + { + "paused_experiment", new Dictionary + { + { + "7722370427", + Config.GetVariationFromId("paused_experiment", "7722370427") + }, + { + "7721010509", + Config.GetVariationFromId("paused_experiment", "7721010509") + }, + } + }, + { + "test_experiment_multivariate", new Dictionary + { + { + "122231", + Config.GetVariationFromId("test_experiment_multivariate", "122231") + }, + { + "122232", + Config.GetVariationFromId("test_experiment_multivariate", "122232") + }, + { + "122233", + Config.GetVariationFromId("test_experiment_multivariate", "122233") + }, + { + "122234", + Config.GetVariationFromId("test_experiment_multivariate", "122234") + }, + } + }, + { + "test_experiment_with_feature_rollout", new Dictionary + { + { + "122236", Config.GetVariationFromId( + "test_experiment_with_feature_rollout", + "122236") + }, + { + "122237", Config.GetVariationFromId( + "test_experiment_with_feature_rollout", + "122237") + }, + } + }, + { + "test_experiment_double_feature", new Dictionary + { + { + "122239", + Config.GetVariationFromId("test_experiment_double_feature", "122239") + }, + { + "122240", + Config.GetVariationFromId("test_experiment_double_feature", "122240") + }, + } + }, + { + "test_experiment_integer_feature", new Dictionary + { + { + "122242", + Config.GetVariationFromId("test_experiment_integer_feature", "122242") + }, + { + "122243", + Config.GetVariationFromId("test_experiment_integer_feature", "122243") + }, + } + }, + { + "group_experiment_1", new Dictionary + { + { + "7722260071", + Config.GetVariationFromId("group_experiment_1", "7722260071") + }, + { + "7722360022", + Config.GetVariationFromId("group_experiment_1", "7722360022") + }, + } + }, + { + "group_experiment_2", new Dictionary + { + { + "7713030086", + Config.GetVariationFromId("group_experiment_2", "7713030086") + }, + { + "7725250007", + Config.GetVariationFromId("group_experiment_2", "7725250007") + }, + } + }, + { + "177770", new Dictionary + { + { + "177771", Config.GetVariationFromId("177770", "177771") + }, + } + }, + { + "177772", new Dictionary + { + { + "177773", Config.GetVariationFromId("177772", "177773") + }, + } + }, + { + "177776", new Dictionary + { + { + "177778", Config.GetVariationFromId("177776", "177778") + }, + } + }, + { + "177774", new Dictionary + { + { + "177775", Config.GetVariationFromId("177774", "177775") + }, + } + }, + { + "177779", new Dictionary + { + { + "177780", Config.GetVariationFromId("177779", "177780") + }, + } + }, + { + "177781", new Dictionary + { + { + "177782", Config.GetVariationFromId("177781", "177782") + }, + } + }, + { + "177783", new Dictionary + { + { + "177784", Config.GetVariationFromId("177783", "177784") + }, + } + }, + { + "188880", new Dictionary + { + { + "188881", Config.GetVariationFromId("188880", "188881") + }, + } + }, + { + "etag1", new Dictionary + { + { + "276", Config.GetVariationFromId("etag1", "276") + }, + { + "277", Config.GetVariationFromId("etag1", "277") + }, + } + }, + { + "etag2", new Dictionary + { + { + "278", Config.GetVariationFromId("etag2", "278") + }, + { + "279", Config.GetVariationFromId("etag2", "279") + }, + } + }, + { + "etag3", new Dictionary + { + { + "280", Config.GetVariationFromId("etag3", "280") + }, + { + "281", Config.GetVariationFromId("etag3", "281") + }, + } + }, + { + "etag4", new Dictionary + { + { + "282", Config.GetVariationFromId("etag4", "282") + }, + { + "283", Config.GetVariationFromId("etag4", "283") + }, + } + }, }; Assert.IsTrue(TestData.CompareObjects(expectedVariationIdMap, Config.VariationIdMap)); @@ -386,28 +679,69 @@ public void TestInit() // Check Variation returns correct variable usage var featureVariableUsageInstance = new List { - new FeatureVariableUsage{Id="155560", Value="F"}, - new FeatureVariableUsage{Id="155561", Value="red"}, + new FeatureVariableUsage + { + Id = "155560", + Value = "F", + }, + new FeatureVariableUsage + { + Id = "155561", + Value = "red", + }, }; - var expectedVariationUsage = new Variation { Id = "122231", Key = "Fred", FeatureVariableUsageInstances = featureVariableUsageInstance, FeatureEnabled = true }; - var actualVariationUsage = Config.GetVariationFromKey("test_experiment_multivariate", "Fred"); + var expectedVariationUsage = new Variation + { + Id = "122231", + Key = "Fred", + FeatureVariableUsageInstances = featureVariableUsageInstance, + FeatureEnabled = true, + }; + var actualVariationUsage = + Config.GetVariationFromKey("test_experiment_multivariate", "Fred"); Assertions.AreEqual(expectedVariationUsage, actualVariationUsage); // Check Feature Key map. var expectedFeatureKeyMap = new Dictionary { - { "boolean_feature", Config.GetFeatureFlagFromKey("boolean_feature") }, - { "double_single_variable_feature", Config.GetFeatureFlagFromKey("double_single_variable_feature") }, - { "integer_single_variable_feature", Config.GetFeatureFlagFromKey("integer_single_variable_feature") }, - { "boolean_single_variable_feature", Config.GetFeatureFlagFromKey("boolean_single_variable_feature") }, - { "string_single_variable_feature", Config.GetFeatureFlagFromKey("string_single_variable_feature") }, - { "multi_variate_feature", Config.GetFeatureFlagFromKey("multi_variate_feature") }, - { "mutex_group_feature", Config.GetFeatureFlagFromKey("mutex_group_feature") }, - { "empty_feature", Config.GetFeatureFlagFromKey("empty_feature") }, - { "no_rollout_experiment_feature", Config.GetFeatureFlagFromKey("no_rollout_experiment_feature") }, - { "unsupported_variabletype", Config.GetFeatureFlagFromKey("unsupported_variabletype") } + { + "boolean_feature", Config.GetFeatureFlagFromKey("boolean_feature") + }, + { + "double_single_variable_feature", + Config.GetFeatureFlagFromKey("double_single_variable_feature") + }, + { + "integer_single_variable_feature", + Config.GetFeatureFlagFromKey("integer_single_variable_feature") + }, + { + "boolean_single_variable_feature", + Config.GetFeatureFlagFromKey("boolean_single_variable_feature") + }, + { + "string_single_variable_feature", + Config.GetFeatureFlagFromKey("string_single_variable_feature") + }, + { + "multi_variate_feature", Config.GetFeatureFlagFromKey("multi_variate_feature") + }, + { + "mutex_group_feature", Config.GetFeatureFlagFromKey("mutex_group_feature") + }, + { + "empty_feature", Config.GetFeatureFlagFromKey("empty_feature") + }, + { + "no_rollout_experiment_feature", + Config.GetFeatureFlagFromKey("no_rollout_experiment_feature") + }, + { + "unsupported_variabletype", + Config.GetFeatureFlagFromKey("unsupported_variabletype") + }, }; Assertions.AreEquivalent(expectedFeatureKeyMap, Config.FeatureKeyMap); @@ -415,8 +749,12 @@ public void TestInit() // Check Feature Key map. var expectedRolloutIdMap = new Dictionary { - { "166660", Config.GetRolloutFromId("166660") }, - { "166661", Config.GetRolloutFromId("166661") } + { + "166660", Config.GetRolloutFromId("166660") + }, + { + "166661", Config.GetRolloutFromId("166661") + }, }; Assert.IsTrue(TestData.CompareObjects(expectedRolloutIdMap, Config.RolloutIdMap)); @@ -434,7 +772,14 @@ public void TestFlagVariations() FeatureEnabled = true, Id = "7722260071", Key = "group_exp_1_var_1", - FeatureVariableUsageInstances = new List { new FeatureVariableUsage { Id = "155563", Value= "groupie_1_v1" } } + FeatureVariableUsageInstances = new List + { + new FeatureVariableUsage + { + Id = "155563", + Value = "groupie_1_v1", + }, + }, } }, { "group_exp_1_var_2", new Variation @@ -442,7 +787,14 @@ public void TestFlagVariations() FeatureEnabled = true, Id = "7722360022", Key = "group_exp_1_var_2", - FeatureVariableUsageInstances = new List { new FeatureVariableUsage { Id = "155563", Value= "groupie_1_v2" } } + FeatureVariableUsageInstances = new List + { + new FeatureVariableUsage + { + Id = "155563", + Value = "groupie_1_v2", + }, + }, } }, { "group_exp_2_var_1", new Variation @@ -450,7 +802,14 @@ public void TestFlagVariations() FeatureEnabled = false, Id = "7713030086", Key = "group_exp_2_var_1", - FeatureVariableUsageInstances = new List { new FeatureVariableUsage { Id = "155563", Value= "groupie_2_v1" } } + FeatureVariableUsageInstances = new List + { + new FeatureVariableUsage + { + Id = "155563", + Value = "groupie_2_v1", + }, + }, } }, { "group_exp_2_var_2", new Variation @@ -458,9 +817,16 @@ public void TestFlagVariations() FeatureEnabled = false, Id = "7725250007", Key = "group_exp_2_var_2", - FeatureVariableUsageInstances = new List { new FeatureVariableUsage { Id = "155563", Value= "groupie_2_v2" } } + FeatureVariableUsageInstances = new List + { + new FeatureVariableUsage + { + Id = "155563", + Value = "groupie_2_v2", + }, + }, } - } + }, }; var filteredActualFlagVariations = allVariations["boolean_feature"]; TestData.CompareObjects(expectedVariationDict, filteredActualFlagVariations); @@ -505,7 +871,7 @@ public void TestGetGroupInvalidId() It.Is(ex => ex.Message == "Provided group is not in datafile.")), Times.Once, "Failed"); - Assert.IsTrue(TestData.CompareObjects(group, new Entity.Group())); + Assert.IsTrue(TestData.CompareObjects(group, new Group())); } [Test] @@ -526,7 +892,7 @@ public void TestGetExperimentInvalidKey() ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided experiment is not in datafile."))); - Assert.IsTrue(TestData.CompareObjects(new Entity.Experiment(), experiment)); + Assert.IsTrue(TestData.CompareObjects(new Experiment(), experiment)); } [Test] @@ -547,7 +913,7 @@ public void TestGetExperimentInvalidId() ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided experiment is not in datafile."))); - Assert.IsTrue(TestData.CompareObjects(new Entity.Experiment(), experiment)); + Assert.IsTrue(TestData.CompareObjects(new Experiment(), experiment)); } [Test] @@ -557,7 +923,10 @@ public void TestGetEventValidKey() Assert.AreEqual("purchase", ev.Key); Assert.AreEqual("7718020063", ev.Id); - Assert.IsTrue(TestData.CompareObjects(new object[] { "7716830082", "7723330021", "7718750065", "7716830585" }, ev.ExperimentIds)); + Assert.IsTrue(TestData.CompareObjects(new object[] + { + "7716830082", "7723330021", "7718750065", "7716830585", + }, ev.ExperimentIds)); } [Test] @@ -590,8 +959,10 @@ public void TestGetAudienceInvalidKey() LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"Audience ID ""invalid_id"" is not in datafile.")); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided audience is not in datafile."))); - Assert.IsTrue(TestData.CompareObjects(new Entity.Audience(), audience)); + ErrorHandlerMock.Verify(e => + e.HandleError(It.Is(ex => + ex.Message == "Provided audience is not in datafile."))); + Assert.IsTrue(TestData.CompareObjects(new Audience(), audience)); } [Test] @@ -642,7 +1013,7 @@ public void TestGetVariationFromKeyValidEKInvalidVK() ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided variation is not in datafile."))); - Assert.AreEqual(new Entity.Variation(), variation); + Assert.AreEqual(new Variation(), variation); } [Test] @@ -653,8 +1024,10 @@ public void TestGetVariationFromKeyInvalidEK() LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"No variation key ""control"" defined in datafile for experiment ""invalid_experiment"".")); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided variation is not in datafile."))); - Assert.AreEqual(new Entity.Variation(), variation); + ErrorHandlerMock.Verify(e => + e.HandleError(It.Is(ex => + ex.Message == "Provided variation is not in datafile."))); + Assert.AreEqual(new Variation(), variation); } [Test] @@ -673,8 +1046,10 @@ public void TestGetVariationFromIdValidEKInvalidVId() LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"No variation ID ""invalid_id"" defined in datafile for experiment ""test_experiment"".")); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided variation is not in datafile."))); - Assert.AreEqual(new Entity.Variation(), variation); + ErrorHandlerMock.Verify(e => + e.HandleError(It.Is(ex => + ex.Message == "Provided variation is not in datafile."))); + Assert.AreEqual(new Variation(), variation); } [Test] @@ -685,14 +1060,17 @@ public void TestGetVariationFromIdInvalidEK() LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"No variation ID ""7722370027"" defined in datafile for experiment ""invalid_experiment"".")); - ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == "Provided variation is not in datafile."))); - Assert.AreEqual(new Entity.Variation(), variation); + ErrorHandlerMock.Verify(e => + e.HandleError(It.Is(ex => + ex.Message == "Provided variation is not in datafile."))); + Assert.AreEqual(new Variation(), variation); } [Test] public void TempProjectConfigTest() { - ProjectConfig config = DatafileProjectConfig.Create(TestData.Datafile, new Mock().Object, new DefaultErrorHandler()); + var config = DatafileProjectConfig.Create(TestData.Datafile, + new Mock().Object, new DefaultErrorHandler()); Assert.IsNotNull(config); Assert.AreEqual("1592310167", config.AccountId); } @@ -701,7 +1079,8 @@ public void TempProjectConfigTest() [Test] public void TestProjectConfigDatafileIsSame() { - ProjectConfig config = DatafileProjectConfig.Create(TestData.Datafile, new Mock().Object, new DefaultErrorHandler()); + var config = DatafileProjectConfig.Create(TestData.Datafile, + new Mock().Object, new DefaultErrorHandler()); Assert.AreEqual(config.ToDatafile(), TestData.Datafile); } @@ -725,8 +1104,8 @@ public void TestBotFilteringValues() Assert.True(Config.BotFiltering.GetValueOrDefault()); // Remove botFilering node and verify returned value in null. - JObject projConfig = JObject.Parse(TestData.Datafile); - if (projConfig.TryGetValue("botFiltering", out JToken token)) + var projConfig = JObject.Parse(TestData.Datafile); + if (projConfig.TryGetValue("botFiltering", out var token)) { projConfig.Property("botFiltering").Remove(); var configWithoutBotFilter = DatafileProjectConfig.Create(JsonConvert.SerializeObject(projConfig), @@ -747,11 +1126,15 @@ public void TestGetAttributeIdWithReservedPrefix() Assert.AreEqual(Config.GetAttributeId("$opt_reserved_prefix_attribute"), "$opt_reserved_prefix_attribute"); // Create config file copy with additional resered prefix attribute. - string reservedPrefixAttrKey = "$opt_user_defined_attribute"; - JObject projConfig = JObject.Parse(TestData.Datafile); + var reservedPrefixAttrKey = "$opt_user_defined_attribute"; + var projConfig = JObject.Parse(TestData.Datafile); var attributes = (JArray)projConfig["attributes"]; - var reservedAttr = new Entity.Attribute { Id = "7723348204", Key = reservedPrefixAttrKey }; + var reservedAttr = new Entity.Attribute + { + Id = "7723348204", + Key = reservedPrefixAttrKey, + }; attributes.Add((JObject)JToken.FromObject(reservedAttr)); // Verify that attribute Id is returned and warning is logged for attribute key with reserved prefix that exists in datafile. @@ -801,7 +1184,11 @@ public void TestExperimentAudiencesRetrivedFromTypedAudiencesFirstThenFromAudien var typedConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, null, null); var experiment = typedConfig.GetExperimentFromKey("feat_with_var_test"); - var expectedAudienceIds = new string[] { "3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643" }; + var expectedAudienceIds = new string[] + { + "3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", + "3468206643", + }; Assert.That(expectedAudienceIds, Is.EquivalentTo(experiment.AudienceIds)); } diff --git a/OptimizelySDK.Tests/TestBucketer.cs b/OptimizelySDK.Tests/TestBucketer.cs index d12b6dfb..ec282ec2 100644 --- a/OptimizelySDK.Tests/TestBucketer.cs +++ b/OptimizelySDK.Tests/TestBucketer.cs @@ -14,14 +14,12 @@ * limitations under the License. */ - namespace OptimizelySDK.Tests +namespace OptimizelySDK.Tests { internal class TestBucketer : Bucketing.Bucketer { public TestBucketer(Logger.ILogger logger) - : base(logger) - { - } + : base(logger) { } public void SetBucketValues(int[] bucketValues) { @@ -36,7 +34,10 @@ public void SetBucketValues(int[] bucketValues) public override int GenerateBucketValue(string bucketingId) { if (BucketValues == null || BucketValues.Length == 0) + { return 0; + } + Ndx %= BucketValues.Length; return BucketValues[Ndx++]; } diff --git a/OptimizelySDK.Tests/TestSetup.cs b/OptimizelySDK.Tests/TestSetup.cs index 5f77494c..57008460 100644 --- a/OptimizelySDK.Tests/TestSetup.cs +++ b/OptimizelySDK.Tests/TestSetup.cs @@ -1,6 +1,6 @@ -using NUnit.Framework; -using System.Globalization; +using System.Globalization; using System.Threading; +using NUnit.Framework; namespace OptimizelySDK.Tests { diff --git a/OptimizelySDK.Tests/Utils/Reflection.cs b/OptimizelySDK.Tests/Utils/Reflection.cs index c42fe4ee..2870cc78 100644 --- a/OptimizelySDK.Tests/Utils/Reflection.cs +++ b/OptimizelySDK.Tests/Utils/Reflection.cs @@ -24,7 +24,8 @@ public static T GetFieldValue(U obj, string fieldName) return (T)fieldInfo.GetValue(obj); } } - return (T)default(T); + + return (T)default; } /// @@ -34,7 +35,9 @@ public static T GetFieldValue(U obj, string fieldName) /// Object from which you want to get variable /// Name of the field /// - public static T GetFieldValue(U obj, string fieldName, IEnumerable fieldsInfo) + public static T GetFieldValue(U obj, string fieldName, + IEnumerable fieldsInfo + ) { foreach (var fieldInfo in fieldsInfo) { @@ -43,7 +46,8 @@ public static T GetFieldValue(U obj, string fieldName, IEnumerable(U obj, string propertyName) return (T)popertyInfo.GetValue(obj); } } - return (T)default(T); + + return (T)default; } /// @@ -75,10 +80,13 @@ public static T GetPropertyValue(U obj, string propertyName) public static IEnumerable GetAllProperties(Type type) { if (type == null) + { return Enumerable.Empty(); + } + var flags = BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Static | BindingFlags.Instance | - BindingFlags.DeclaredOnly; + BindingFlags.Static | BindingFlags.Instance | + BindingFlags.DeclaredOnly; return type.GetProperties(flags).Concat(GetAllProperties(type.BaseType)); } @@ -90,10 +98,13 @@ public static IEnumerable GetAllProperties(Type type) public static IEnumerable GetAllFields(Type t) { if (t == null) + { return Enumerable.Empty(); + } + var flags = BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Static | BindingFlags.Instance | - BindingFlags.DeclaredOnly; + BindingFlags.Static | BindingFlags.Instance | + BindingFlags.DeclaredOnly; return t.GetFields(flags).Concat(GetAllFields(t.BaseType)); } } diff --git a/OptimizelySDK.Tests/Utils/TestData.cs b/OptimizelySDK.Tests/Utils/TestData.cs index 172c7056..fe596a68 100644 --- a/OptimizelySDK.Tests/Utils/TestData.cs +++ b/OptimizelySDK.Tests/Utils/TestData.cs @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -using Newtonsoft.Json.Linq; + using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using Newtonsoft.Json.Linq; namespace OptimizelySDK.Tests { @@ -32,70 +33,45 @@ public class TestData private static string duplicateExpKeysDatafile = null; private static string duplicateRuleKeysDatafile = null; - public static string Datafile - { - get - { - return cachedDataFile ?? (cachedDataFile = LoadJsonData()); - } - } + public static string Datafile => cachedDataFile ?? (cachedDataFile = LoadJsonData()); - public static string DuplicateExpKeysDatafile { - get { - return duplicateExpKeysDatafile ?? (duplicateExpKeysDatafile = LoadJsonData("similar_exp_keys.json")); - } - } + public static string DuplicateExpKeysDatafile => + duplicateExpKeysDatafile ?? + (duplicateExpKeysDatafile = LoadJsonData("similar_exp_keys.json")); - public static string DuplicateRuleKeysDatafile { - get { - return duplicateRuleKeysDatafile ?? (duplicateRuleKeysDatafile = LoadJsonData("similar_rule_keys_bucketing.json")); - } - } + public static string DuplicateRuleKeysDatafile => + duplicateRuleKeysDatafile ?? (duplicateRuleKeysDatafile = + LoadJsonData("similar_rule_keys_bucketing.json")); - public static string SimpleABExperimentsDatafile - { - get - { - return simpleABExperimentsDatafile ?? (simpleABExperimentsDatafile = LoadJsonData("simple_ab_experiments.json")); - } - } + public static string SimpleABExperimentsDatafile => + simpleABExperimentsDatafile ?? (simpleABExperimentsDatafile = + LoadJsonData("simple_ab_experiments.json")); - public static string UnsupportedVersionDatafile - { - get - { - return unsupportedVersionDatafile ?? (unsupportedVersionDatafile = LoadJsonData("unsupported_version_datafile.json")); - } - } + public static string UnsupportedVersionDatafile => + unsupportedVersionDatafile ?? (unsupportedVersionDatafile = + LoadJsonData("unsupported_version_datafile.json")); - public static string EmptyRolloutDatafile { - get { - return emptyRolloutDatafile ?? (emptyRolloutDatafile = LoadJsonData("EmptyRolloutRule.json")); - } - } + public static string EmptyRolloutDatafile => + emptyRolloutDatafile ?? + (emptyRolloutDatafile = LoadJsonData("EmptyRolloutRule.json")); - public static string EmptyDatafile { - get { - return emptyDatafile ?? (emptyDatafile = LoadJsonData("emptydatafile.json")); - } - } + public static string EmptyDatafile => + emptyDatafile ?? (emptyDatafile = LoadJsonData("emptydatafile.json")); - public static string TypedAudienceDatafile - { - get - { - return typedAudienceDatafile ?? (typedAudienceDatafile = LoadJsonData("typed_audience_datafile.json")); - } - } + public static string TypedAudienceDatafile => + typedAudienceDatafile ?? (typedAudienceDatafile = + LoadJsonData("typed_audience_datafile.json")); private static string LoadJsonData(string fileName = "TestData.json") { var assembly = Assembly.GetExecutingAssembly(); var resourceName = string.Format("OptimizelySDK.Tests.{0}", fileName); - 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 reader.ReadToEnd(); + } } public static bool CompareObjects(object o1, object o2) diff --git a/OptimizelySDK.Tests/Utils/TestHttpProjectConfigManagerUtil.cs b/OptimizelySDK.Tests/Utils/TestHttpProjectConfigManagerUtil.cs index 19e854c8..c4a0609e 100644 --- a/OptimizelySDK.Tests/Utils/TestHttpProjectConfigManagerUtil.cs +++ b/OptimizelySDK.Tests/Utils/TestHttpProjectConfigManagerUtil.cs @@ -1,5 +1,4 @@ - -/* +/* * Copyright 2019-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,21 +28,31 @@ namespace OptimizelySDK.Tests.Utils /// public static class TestHttpProjectConfigManagerUtil { - public static Task MockSendAsync(Mock HttpClientMock, string datafile = null, TimeSpan? delay=null, HttpStatusCode statusCode = HttpStatusCode.OK) + public static Task MockSendAsync(Mock HttpClientMock, + string datafile = null, TimeSpan? delay = null, + HttpStatusCode statusCode = HttpStatusCode.OK + ) { - var t = new System.Threading.Tasks.TaskCompletionSource(); + var t = new TaskCompletionSource(); - HttpClientMock.Setup(_ => _.SendAsync(It.IsAny())) - .Returns(() => { - if (delay != null) { + HttpClientMock.Setup(_ => _.SendAsync(It.IsAny())). + Returns(() => + { + if (delay != null) + { // This delay mocks the networking delay. And help to see the behavior when get a datafile with some delay. Task.Delay(delay.Value).Wait(); } - - return System.Threading.Tasks.Task.FromResult(new HttpResponseMessage { StatusCode = statusCode, Content = new StringContent(datafile ?? string.Empty) }); - }) - .Callback(() - => { + + return Task.FromResult(new HttpResponseMessage + { + StatusCode = statusCode, + Content = new StringContent(datafile ?? string.Empty), + }); + }). + Callback(() + => + { t.SetResult(true); }); @@ -61,6 +70,6 @@ public static void SetClientFieldValue(object value) System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); field.SetValue(new object(), value); - } + } } } diff --git a/OptimizelySDK.Tests/UtilsTests/ConditionParserTest.cs b/OptimizelySDK.Tests/UtilsTests/ConditionParserTest.cs index 952c0b3a..3bc522c6 100644 --- a/OptimizelySDK.Tests/UtilsTests/ConditionParserTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ConditionParserTest.cs @@ -24,18 +24,21 @@ namespace OptimizelySDK.Tests.UtilsTests [TestFixture] public class ConditionParserTest { - JToken Conditions; - JToken BaseCondition; - JToken AudienceConditions; - JToken NoOpAudienceConditions; + private JToken Conditions; + private JToken BaseCondition; + private JToken AudienceConditions; + private JToken NoOpAudienceConditions; [TestFixtureSetUp] public void Initialize() { - string conditionStr = @"[""and"", [""or"", [""or"", {""name"": ""device_type"", ""type"": ""custom_attribute"", ""value"": ""iPhone"", ""match"": ""substring""}]]]"; - string baseConditionStr = @"{""name"": ""browser_type"", ""type"": ""custom_attribute"", ""value"": ""Chrome"", ""match"": ""exact""}"; - string audienceConditionStr = @"[""or"", [""or"", ""3468206642"", ""3988293898""], [""or"", ""3988293899"", ""3468206646"", ""3468206647""]]"; - string noOpAudienceConditionStr = @"[""3468206642"", ""3988293898""]"; + var conditionStr = + @"[""and"", [""or"", [""or"", {""name"": ""device_type"", ""type"": ""custom_attribute"", ""value"": ""iPhone"", ""match"": ""substring""}]]]"; + var baseConditionStr = + @"{""name"": ""browser_type"", ""type"": ""custom_attribute"", ""value"": ""Chrome"", ""match"": ""exact""}"; + var audienceConditionStr = + @"[""or"", [""or"", ""3468206642"", ""3988293898""], [""or"", ""3988293899"", ""3468206646"", ""3468206647""]]"; + var noOpAudienceConditionStr = @"[""3468206642"", ""3988293898""]"; Conditions = JToken.Parse(conditionStr); BaseCondition = JToken.Parse(baseConditionStr); @@ -80,7 +83,7 @@ public void TestParseAudienceConditionsParsesAudienceConditionsWithNoOperator() [Test] public void TestParseConditionsAssignsNullConditionIfNoConditionIsProvidedInNotOperator() { - JToken emptyNotCondition = JToken.Parse(@"[""not""]"); + var emptyNotCondition = JToken.Parse(@"[""not""]"); var condition = ConditionParser.ParseConditions(emptyNotCondition); Assert.NotNull(condition); Assert.IsInstanceOf(typeof(NotCondition), condition); diff --git a/OptimizelySDK.Tests/UtilsTests/EventTagUtilsTest.cs b/OptimizelySDK.Tests/UtilsTests/EventTagUtilsTest.cs index 6cb01bfe..9f41b264 100644 --- a/OptimizelySDK.Tests/UtilsTests/EventTagUtilsTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/EventTagUtilsTest.cs @@ -14,16 +14,16 @@ * limitations under the License. */ +using System.Collections.Generic; using Moq; using NUnit.Framework; using OptimizelySDK.Logger; using OptimizelySDK.Utils; -using System.Collections.Generic; namespace OptimizelySDK.Tests.UtilsTests { [TestFixture] - class EventTagUtilsTest + internal class EventTagUtilsTest { private Mock LoggerMock; private ILogger Logger; @@ -43,28 +43,34 @@ public void TestGetRevenueValue() var expectedValue = 42; var expectedValue2 = 100; var expectedValueString = 123; - var validTag = new Dictionary() { - { "revenue", 42 } + var validTag = new Dictionary() + { + { "revenue", 42 }, }; - var validTag2 = new Dictionary() { - { "revenue", 100 } + var validTag2 = new Dictionary() + { + { "revenue", 100 }, }; - var validTagStringValue = new Dictionary() { - { "revenue", "123" } + var validTagStringValue = new Dictionary() + { + { "revenue", "123" }, }; - var invalidTag = new Dictionary() { - { "abc", 42 } + var invalidTag = new Dictionary() + { + { "abc", 42 }, }; - var nullValue = new Dictionary() { - { "revenue", null } + var nullValue = new Dictionary() + { + { "revenue", null }, }; - var invalidValue = new Dictionary() { - { "revenue", 42.5 } + var invalidValue = new Dictionary() + { + { "revenue", 42.5 }, }; var invalidTagNonRevenue = new Dictionary() { - {"non-revenue", 123 } + { "non-revenue", 123 }, }; // Invalid data. @@ -75,50 +81,72 @@ public void TestGetRevenueValue() Assert.Null(EventTagUtils.GetRevenueValue(invalidTagNonRevenue, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Event tags is undefined."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "The revenue key is not defined in the event tags."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "The revenue key value is not defined in event tags."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Revenue value is not an integer or couldn't be parsed as an integer."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, "The revenue key is not defined in the event tags."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, "The revenue key value is not defined in event tags."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + "Revenue value is not an integer or couldn't be parsed as an integer."), + Times.Once); // Valid data. Assert.AreEqual(EventTagUtils.GetRevenueValue(validTag, Logger), expectedValue); Assert.AreEqual(EventTagUtils.GetRevenueValue(validTag2, Logger), expectedValue2); - Assert.AreEqual(EventTagUtils.GetRevenueValue(validTagStringValue, Logger), expectedValueString); - - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The revenue value {expectedValue} will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The revenue value {expectedValue2} will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The revenue value {expectedValueString} will be sent to results."), Times.Once); + Assert.AreEqual(EventTagUtils.GetRevenueValue(validTagStringValue, Logger), + expectedValueString); + + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The revenue value {expectedValue} will be sent to results."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The revenue value {expectedValue2} will be sent to results."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The revenue value {expectedValueString} will be sent to results."), + Times.Once); } [Test] public void TestGetEventValue() { - int expectedValue = 42; - float expectedValue2 = 42.5F; - double expectedValue3 = 42.52; + var expectedValue = 42; + var expectedValue2 = 42.5F; + var expectedValue3 = 42.52; - var validTag = new Dictionary() { - { "value", 42 } + var validTag = new Dictionary() + { + { "value", 42 }, }; - var validTag2 = new Dictionary() { - { "value", 42.5 } + var validTag2 = new Dictionary() + { + { "value", 42.5 }, }; - var validTag3 = new Dictionary() { - { "value", 42.52 } + var validTag3 = new Dictionary() + { + { "value", 42.52 }, }; - var invalidTag = new Dictionary() { - { "abc", 42 } + var invalidTag = new Dictionary() + { + { "abc", 42 }, }; - var nullValue = new Dictionary() { - { "value", null } + var nullValue = new Dictionary() + { + { "value", null }, }; - var validTagStr = new Dictionary() { - { "value", "42" } + var validTagStr = new Dictionary() + { + { "value", "42" }, }; - var validTagStr1 = new Dictionary() { - { "value", "42.3" } + var validTagStr1 = new Dictionary() + { + { "value", "42.3" }, }; // Invalid data. @@ -127,20 +155,36 @@ public void TestGetEventValue() Assert.Null(EventTagUtils.GetNumericValue(nullValue, Logger)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Event tags is undefined."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "The numeric metric key is not in event tags."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "The numeric metric key value is not defined in event tags."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, "The numeric metric key is not in event tags."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + "The numeric metric key value is not defined in event tags."), Times.Once); // Valid data. Assert.AreEqual(42, EventTagUtils.GetNumericValue(validTagStr, Logger)); Assert.AreEqual("42.3", EventTagUtils.GetNumericValue(validTagStr1, Logger).ToString()); Assert.AreEqual(EventTagUtils.GetNumericValue(validTag, Logger), expectedValue); Assert.AreEqual(EventTagUtils.GetNumericValue(validTag2, Logger), expectedValue2); - Assert.AreEqual(EventTagUtils.GetNumericValue(validTag3, Logger).ToString(), expectedValue3.ToString()); - - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The numeric metric value 42.3 will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The numeric metric value {expectedValue} will be sent to results."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The numeric metric value {expectedValue2} will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The numeric metric value {expectedValue3} will be sent to results."), Times.Once); + Assert.AreEqual(EventTagUtils.GetNumericValue(validTag3, Logger).ToString(), + expectedValue3.ToString()); + + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "The numeric metric value 42.3 will be sent to results."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The numeric metric value {expectedValue} will be sent to results."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The numeric metric value {expectedValue2} will be sent to results."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The numeric metric value {expectedValue3} will be sent to results."), + Times.Once); } [Test] @@ -159,15 +203,20 @@ public void TestGetNumericMetricInvalidArgs() //Assert.IsNull(EventTagUtils.GetEventValue(False)); } - + [Test] public void TestGetNumericMetricNoValueTag() { // Test that numeric value is not returned when there's no numeric event tag. - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", 42 } }, Logger)); + Assert.IsNull( + EventTagUtils.GetNumericValue(new Dictionary { }, Logger)); + Assert.IsNull( + EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", 42 } }, Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "The numeric metric key is not in event tags."), Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, "The numeric metric key is not in event tags."), + Times.Exactly(2)); //Errors for all, because it accepts only dictionary// //Assert.IsNull(EventTagUtils.GetEventValue(new object[] { })); @@ -176,71 +225,126 @@ public void TestGetNumericMetricNoValueTag() [Test] public void TestGetNumericMetricInvalidValueTag() { - // Test that numeric value is not returned when revenue event tag has invalid data type. - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", null } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", 0.5} }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", 12345} }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", "65536" } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", true } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", false } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", new object[] { 1, 2, 3 } } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "non-value", new object[] { 'a', 'b', 'c' } } }, Logger)); - - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "The numeric metric key is not in event tags."), Times.Exactly(8)); + Assert.IsNull( + EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", null } }, Logger)); + Assert.IsNull( + EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", 0.5 } }, Logger)); + Assert.IsNull(EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", 12345 } }, Logger)); + Assert.IsNull(EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", "65536" } }, Logger)); + Assert.IsNull( + EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", true } }, Logger)); + Assert.IsNull(EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", false } }, Logger)); + Assert.IsNull(EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", new object[] { 1, 2, 3 } } }, + Logger)); + Assert.IsNull(EventTagUtils.GetNumericValue( + new Dictionary { { "non-value", new object[] { 'a', 'b', 'c' } } }, + Logger)); + + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, "The numeric metric key is not in event tags."), + Times.Exactly(8)); } [Test] - public void TestGetNumericMetricValueTag() { - // An integer should be cast to a float - Assert.AreEqual(12345.0, EventTagUtils.GetNumericValue(new Dictionary { { "value", 12345 } }, Logger)); + Assert.AreEqual(12345.0, + EventTagUtils.GetNumericValue(new Dictionary { { "value", 12345 } }, + Logger)); // A string should be cast to a float - Assert.AreEqual(12345.0, EventTagUtils.GetNumericValue(new Dictionary { { "value", "12345" } }, Logger)); - + Assert.AreEqual(12345.0, + EventTagUtils.GetNumericValue( + new Dictionary { { "value", "12345" } }, Logger)); + // Valid float values - float someFloat = 1.2345F; + var someFloat = 1.2345F; - Assert.AreEqual(someFloat, EventTagUtils.GetNumericValue(new Dictionary { { "value", someFloat } }, Logger)); + Assert.AreEqual(someFloat, + EventTagUtils.GetNumericValue( + new Dictionary { { "value", someFloat } }, Logger)); - float maxFloat = float.MaxValue; - Assert.AreEqual(maxFloat, EventTagUtils.GetNumericValue(new Dictionary { { "value", maxFloat } }, Logger)); + var maxFloat = float.MaxValue; + Assert.AreEqual(maxFloat, + EventTagUtils.GetNumericValue( + new Dictionary { { "value", maxFloat } }, Logger)); - float minFloat = float.MinValue; - Assert.AreEqual(minFloat, EventTagUtils.GetNumericValue(new Dictionary { { "value", minFloat } }, Logger)); + var minFloat = float.MinValue; + Assert.AreEqual(minFloat, + EventTagUtils.GetNumericValue( + new Dictionary { { "value", minFloat } }, Logger)); // Invalid values - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "value", false } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "value", null } }, Logger)); - - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "value", true } }, Logger)); - Assert.IsNull(EventTagUtils.GetNumericValue(new Dictionary { { "value", new int[] { } } }, Logger)); - - var numericValueArray = EventTagUtils.GetNumericValue(new Dictionary { { "value", new object[] { }} }, Logger); - Assert.IsNull(numericValueArray, string.Format("Array numeric value is {0}", numericValueArray)); - - - var numericValueNone = EventTagUtils.GetNumericValue(new Dictionary { { "value", null } }, Logger); - Assert.IsNull(numericValueNone, string.Format("None numeric value is {0}", numericValueNone)); - - - var numericValueOverflow = EventTagUtils.GetNumericValue(new Dictionary { { "value", float.MaxValue * 10 } }, Logger); - Assert.IsNull(numericValueOverflow, string.Format("Max numeric value is {0}", float.MaxValue * 10 )); - - Assert.AreEqual(0.0, EventTagUtils.GetNumericValue(new Dictionary { { "value", 0.0 } }, Logger)); - - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The numeric metric value 12345 will be sent to results."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The numeric metric value {maxFloat} will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The numeric metric value {minFloat} will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $"Provided numeric value {float.PositiveInfinity} is in an invalid format."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The numeric metric value 0 will be sent to results."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided numeric value is boolean which is an invalid format."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Numeric metric value is not in integer, float, or string form."), Times.Exactly(2)); + Assert.IsNull( + EventTagUtils.GetNumericValue(new Dictionary { { "value", false } }, + Logger)); + Assert.IsNull( + EventTagUtils.GetNumericValue(new Dictionary { { "value", null } }, + Logger)); + + Assert.IsNull( + EventTagUtils.GetNumericValue(new Dictionary { { "value", true } }, + Logger)); + Assert.IsNull(EventTagUtils.GetNumericValue( + new Dictionary { { "value", new int[] { } } }, Logger)); + + var numericValueArray = EventTagUtils.GetNumericValue( + new Dictionary { { "value", new object[] { } } }, Logger); + Assert.IsNull(numericValueArray, + string.Format("Array numeric value is {0}", numericValueArray)); + + + var numericValueNone = + EventTagUtils.GetNumericValue(new Dictionary { { "value", null } }, + Logger); + Assert.IsNull(numericValueNone, + string.Format("None numeric value is {0}", numericValueNone)); + + + var numericValueOverflow = EventTagUtils.GetNumericValue( + new Dictionary { { "value", float.MaxValue * 10 } }, Logger); + Assert.IsNull(numericValueOverflow, + string.Format("Max numeric value is {0}", float.MaxValue * 10)); + + Assert.AreEqual(0.0, + EventTagUtils.GetNumericValue(new Dictionary { { "value", 0.0 } }, + Logger)); + + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + "The numeric metric value 12345 will be sent to results."), Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The numeric metric value {maxFloat} will be sent to results."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + $"The numeric metric value {minFloat} will be sent to results."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + $"Provided numeric value {float.PositiveInfinity} is in an invalid format."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, "The numeric metric value 0 will be sent to results."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + "Provided numeric value is boolean which is an invalid format."), + Times.Exactly(2)); + LoggerMock.Verify( + l => l.Log(LogLevel.ERROR, + "Numeric metric value is not in integer, float, or string form."), + Times.Exactly(2)); /* Value is converted into 1234F */ //var numericValueInvalidLiteral = EventTagUtils.GetEventValue(new Dictionary { { "value", "1,234" } }); @@ -250,6 +354,5 @@ public void TestGetNumericMetricValueTag() // float - inf is not possible. // float -inf is not possible. } - } } diff --git a/OptimizelySDK.Tests/UtilsTests/ExceptionExtensionsTest.cs b/OptimizelySDK.Tests/UtilsTests/ExceptionExtensionsTest.cs index 9eb1020b..8ba46fc1 100644 --- a/OptimizelySDK.Tests/UtilsTests/ExceptionExtensionsTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ExceptionExtensionsTest.cs @@ -14,9 +14,9 @@ * limitations under the License. */ +using System; using NUnit.Framework; using OptimizelySDK.Utils; -using System; namespace OptimizelySDK.Tests.UtilsTests { @@ -25,9 +25,11 @@ public class ExceptionExtensionsTest [Test] public void TestGetAllMessagesReturnsAllInnerExceptionMessages() { - var exception = new Exception("Outer exception.", new Exception("Inner exception.", new Exception("Second level inner exception."))); - var expectedMessage = "Outer exception.\nInner exception.\nSecond level inner exception."; - + var exception = new Exception("Outer exception.", + new Exception("Inner exception.", new Exception("Second level inner exception."))); + var expectedMessage = + "Outer exception.\nInner exception.\nSecond level inner exception."; + Assert.AreEqual(expectedMessage, exception.GetAllMessages()); } } diff --git a/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs b/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs index 805409e7..d5a5dc47 100644 --- a/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs @@ -49,25 +49,43 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueWithNoAudience() experiment.AudienceIds = new string[] { }; experiment.AudienceConditions = null; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, null, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + new UserAttributes(), "experiment", experiment.Key, Logger). + ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": []."), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Evaluating audiences for experiment ""feat_with_var_test"": []."), + Times.Once); } [Test] - public void TestDoesUserMeetAudienceConditionsReturnsTrueWhenAudienceUsedInExperimentNoAttributesProvided() + public void + TestDoesUserMeetAudienceConditionsReturnsTrueWhenAudienceUsedInExperimentNoAttributesProvided() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206648" }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, null , "experiment", experiment.Key, Logger).ResultObject);; - var boolResult = ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, new UserAttributes { }, "experiment", experiment.Key, Logger); + var boolResult = ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + new UserAttributes { }, "experiment", experiment.Key, Logger); Assert.True(boolResult.ResultObject); - Assert.AreEqual(boolResult.DecisionReasons.ToReport(true)[0], "Audiences for experiment \"feat_with_var_test\" collectively evaluated to TRUE"); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206648"" with conditions: [""not"",{""name"":""input_value"",""type"":""custom_attribute"",""match"":""exists""}]"), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $@"Audience ""3468206648"" evaluated to TRUE"), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Exactly(2)); + Assert.AreEqual(boolResult.DecisionReasons.ToReport(true)[0], + "Audiences for experiment \"feat_with_var_test\" collectively evaluated to TRUE"); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206648"" with conditions: [""not"",{""name"":""input_value"",""type"":""custom_attribute"",""match"":""exists""}]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, $@"Audience ""3468206648"" evaluated to TRUE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), + Times.Once); } [Test] @@ -78,25 +96,63 @@ public void TestDoesUserMeetAudienceConditionsReturnsFalseIfNoAudienceInORCondit { { "house", "Ravenclaw" }, { "lasers", 50 }, - { "should_do_it", false } + { "should_do_it", false }, }; - Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); - - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3988293899"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206646"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206647"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206643"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to FALSE"), Times.Once); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + userAttributes, "experiment", experiment.Key, Logger). + ResultObject); + + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3988293899"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206646"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206647"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206644"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206643"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to FALSE"), + Times.Once); } [Test] @@ -107,15 +163,28 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAnyAudienceInORCondit { { "house", "Gryffindor" }, { "lasers", 50 }, - { "should_do_it", false } + { "should_do_it", false }, }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + userAttributes, "experiment", experiment.Key, Logger). + ResultObject); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to TRUE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), + Times.Once); } [Test] @@ -125,65 +194,115 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAllAudiencesInANDCond var userAttributes = new UserAttributes { { "house", "Gryffindor" }, - { "favorite_ice_cream", "walls" } + { "favorite_ice_cream", "walls" }, }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + userAttributes, "experiment", experiment.Key, Logger). + ResultObject); } [Test] - public void TestDoesUserMeetAudienceConditionsReturnsFalseIfAnyAudienceInANDConditionDoesNotPass() + public void + TestDoesUserMeetAudienceConditionsReturnsFalseIfAnyAudienceInANDConditionDoesNotPass() { var experiment = Config.GetExperimentFromKey("audience_combinations_experiment"); var userAttributes = new UserAttributes { { "house", "Gryffindor" }, - { "lasers", 50 } + { "lasers", 50 }, }; - Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); - - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""audience_combinations_experiment"": [""and"",[""or"",""3468206642"",""3988293898""],[""or"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]]."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3988293899"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206646"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206647"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""should_do_it"",""value"":true} evaluated to UNKNOWN because no value was passed for user attribute ""should_do_it""."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206643"" evaluated to UNKNOWN"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""audience_combinations_experiment"" collectively evaluated to FALSE"), Times.Once); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + userAttributes, "experiment", experiment.Key, Logger). + ResultObject); + + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Evaluating audiences for experiment ""audience_combinations_experiment"": [""and"",[""or"",""3468206642"",""3988293898""],[""or"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]]."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to TRUE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3988293899"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206646"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206647"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206644"" evaluated to FALSE"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, + @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""should_do_it"",""value"":true} evaluated to UNKNOWN because no value was passed for user attribute ""should_do_it""."), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.DEBUG, @"Audience ""3468206643"" evaluated to UNKNOWN"), + Times.Once); + LoggerMock.Verify( + l => l.Log(LogLevel.INFO, + @"Audiences for experiment ""audience_combinations_experiment"" collectively evaluated to FALSE"), + Times.Once); } [Test] - public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAudienceInNOTConditionDoesNotPass() + public void + TestDoesUserMeetAudienceConditionsReturnsTrueIfAudienceInNOTConditionDoesNotPass() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206645" }; var userAttributes = new UserAttributes { - { "browser_type", "Safari" } + { "browser_type", "Safari" }, }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + userAttributes, "experiment", experiment.Key, Logger). + ResultObject); } [Test] - public void TestDoesUserMeetAudienceConditionsReturnsFalseIfAudienceInNOTConditionGetsPassed() + public void + TestDoesUserMeetAudienceConditionsReturnsFalseIfAudienceInNOTConditionGetsPassed() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206645" }; var userAttributes = new UserAttributes { - { "browser_type", "Chrome" } + { "browser_type", "Chrome" }, }; - Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, + userAttributes, "experiment", experiment.Key, Logger). + ResultObject); } #endregion // DoesUserMeetAudienceConditions Tests diff --git a/OptimizelySDK.Tests/UtilsTests/PrivateObject.cs b/OptimizelySDK.Tests/UtilsTests/PrivateObject.cs index c0a474d2..d32b6b6e 100644 --- a/OptimizelySDK.Tests/UtilsTests/PrivateObject.cs +++ b/OptimizelySDK.Tests/UtilsTests/PrivateObject.cs @@ -29,6 +29,7 @@ public PrivateObject(Type privateObject, Type[] parameterTypes, object[] paramet instanceType = privateObject; createdInstance = Activator.CreateInstance(instanceType, parameterValues); } + private PrivateObject(Type privateObject, object[] parameterValues) { instanceType = privateObject; @@ -37,26 +38,34 @@ private PrivateObject(Type privateObject, object[] parameterValues) public void SetFieldOrProperty(string propertyName, object value) { - instanceType.InvokeMember(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.SetField, + instanceType.InvokeMember(propertyName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty | + BindingFlags.NonPublic | BindingFlags.SetField, Type.DefaultBinder, createdInstance, new object[] { value }); } public Type GetFieldOrProperyType(string propertyName) { - return instanceType.GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance).FieldType; + return instanceType. + GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance). + FieldType; } public object Invoke(string name, params object[] args) { - return instanceType.InvokeMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.NonPublic, + return instanceType.InvokeMember(name, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | + BindingFlags.NonPublic, Type.DefaultBinder, createdInstance, args); } public object InvokeGeneric(string name, Type[] genericTypes, params object[] args) { - MethodInfo method = instanceType.GetMethod(name); - MethodInfo genericMethod = method.MakeGenericMethod(genericTypes); - return genericMethod.Invoke(createdInstance, BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.NonPublic, + var method = instanceType.GetMethod(name); + var genericMethod = method.MakeGenericMethod(genericTypes); + return genericMethod.Invoke(createdInstance, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | + BindingFlags.NonPublic, Type.DefaultBinder, args, null); } diff --git a/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs b/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs index 33305648..55135018 100644 --- a/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs @@ -43,7 +43,10 @@ public void TestValidateJsonSchemaValidFileWithAdditionalProperty() { Assert.IsTrue(Validator.ValidateJSONSchema(TestData.Datafile)); - var testDataJSON = Newtonsoft.Json.JsonConvert.DeserializeObject>(TestData.Datafile); + var testDataJSON = + Newtonsoft.Json.JsonConvert. + DeserializeObject>( + TestData.Datafile); Assert.IsTrue(testDataJSON.ContainsKey("tempproperty")); } @@ -81,15 +84,26 @@ public void TestValidateJsonSchemaNoJsonContent() [Test] public void TestDoesUserMeetAudienceConditionsNoAudienceUsedInExperiment() { - Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes(), "experiment", "paused_experiment", Logger).ResultObject); + Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, + Config.GetExperimentFromKey("paused_experiment"), + new UserAttributes(), "experiment", "paused_experiment", + Logger). + ResultObject); } [Test] public void TestDoesUserMeetAudienceConditionsAudienceUsedInExperimentNoAttributesProvided() { - Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes(), "experiment", "test_experiment", Logger).ResultObject); - Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), null, "experiment", "test_experiment", Logger).ResultObject); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, + Config.GetExperimentFromKey("test_experiment"), + new UserAttributes(), "experiment", "test_experiment", Logger). + ResultObject); + + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, + Config.GetExperimentFromKey("test_experiment"), null, "experiment", + "test_experiment", Logger). + ResultObject); } [Test] @@ -97,34 +111,38 @@ public void TestUserInExperimentAudienceMatch() { var userAttributes = new UserAttributes { - {"device_type", "iPhone" }, - {"location", "San Francisco" } + { "device_type", "iPhone" }, + { "location", "San Francisco" }, }; - Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes, "experiment", "test_experiment", Logger).ResultObject); + + Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, + Config.GetExperimentFromKey("test_experiment"), userAttributes, + "experiment", "test_experiment", Logger). + ResultObject); } [Test] public void TestDoesUserMeetAudienceConditionsAudienceNoMatch() { - var userAttributes = new UserAttributes - { - {"device_type", "Android" }, - {"location", "San Francisco" } - }; - - Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), null, "experiment", "test_experiment", Logger).ResultObject); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, + Config.GetExperimentFromKey("test_experiment"), + new UserAttributes(), "experiment", "test_experiment", Logger). + ResultObject); } [Test] public void TestAreEventTagsValidValidEventTags() { - Assert.IsTrue(Validator.AreEventTagsValid(new System.Collections.Generic.Dictionary())); - Assert.IsTrue(Validator.AreEventTagsValid(new System.Collections.Generic.Dictionary - { - {"location", "San Francisco" }, - {"browser", "Firefox" }, - {"revenue", 0 } - })); + Assert.IsTrue( + Validator.AreEventTagsValid( + new System.Collections.Generic.Dictionary())); + Assert.IsTrue(Validator.AreEventTagsValid( + new System.Collections.Generic.Dictionary + { + { "location", "San Francisco" }, + { "browser", "Firefox" }, + { "revenue", 0 }, + })); } [Test] @@ -132,10 +150,11 @@ public void TestAreEventTagsValidInvalidTags() { // Some of the tests cases are not applicable because C# is strongly typed. - Assert.IsFalse(Validator.AreEventTagsValid(new System.Collections.Generic.Dictionary - { - {"43", "23"} - })); + Assert.IsFalse(Validator.AreEventTagsValid( + new System.Collections.Generic.Dictionary + { + { "43", "23" }, + })); } [Test] @@ -146,11 +165,13 @@ public void TestIsUserAttributeValidWithValidValues() { "device_type", "Android" }, { "is_firefox", true }, { "num_users", 15 }, - { "pi_value", 3.14 } + { "pi_value", 3.14 }, }; foreach (var attribute in userAttributes) + { Assert.True(Validator.IsUserAttributeValid(attribute)); + } } [Test] @@ -159,11 +180,13 @@ public void TestIsUserAttributeValidWithInvalidValues() var invalidUserAttributes = new UserAttributes { { "objects", new object() }, - { "arrays", new string[] { "a", "b", "c" } } + { "arrays", new string[] { "a", "b", "c" } }, }; foreach (var attribute in invalidUserAttributes) + { Assert.False(Validator.IsUserAttributeValid(attribute)); + } } [Test] @@ -173,11 +196,13 @@ public void TestIsUserAttributeValidWithEmptyKeyOrValue() { { "", "Android" }, { "integer", 0 }, - { "string", string.Empty } + { "string", string.Empty }, }; foreach (var attribute in validUserAttributes) - Assert.True(Validator.IsUserAttributeValid(attribute)); + { + Assert.True(Validator.IsUserAttributeValid(attribute)); + } } } -} \ No newline at end of file +} diff --git a/OptimizelySDK.Tests/ValidEventDispatcher.cs b/OptimizelySDK.Tests/ValidEventDispatcher.cs index 7959f43d..2a91cdc7 100644 --- a/OptimizelySDK.Tests/ValidEventDispatcher.cs +++ b/OptimizelySDK.Tests/ValidEventDispatcher.cs @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using OptimizelySDK.Event.Dispatcher; using OptimizelySDK.Logger; namespace OptimizelySDK.Tests { - class ValidEventDispatcher : IEventDispatcher + internal class ValidEventDispatcher : IEventDispatcher { public ILogger Logger { get; set; } - public void DispatchEvent(Event.LogEvent logEvent) - { - } + public void DispatchEvent(Event.LogEvent logEvent) { } } -} \ No newline at end of file +} diff --git a/OptimizelySDK.Tests/packages.config b/OptimizelySDK.Tests/packages.config index bb3cbf9f..166ddc9d 100644 --- a/OptimizelySDK.Tests/packages.config +++ b/OptimizelySDK.Tests/packages.config @@ -1,10 +1,10 @@  - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/OptimizelySDK.Tests/similar_exp_keys.json b/OptimizelySDK.Tests/similar_exp_keys.json index 5428060e..bf750074 100644 --- a/OptimizelySDK.Tests/similar_exp_keys.json +++ b/OptimizelySDK.Tests/similar_exp_keys.json @@ -46,14 +46,18 @@ "variables": [], "featureFlags": [ { - "experimentIds": ["9300000007569"], + "experimentIds": [ + "9300000007569" + ], "rolloutId": "", "variables": [], "id": "3045", "key": "flag1" }, { - "experimentIds": ["9300000007573"], + "experimentIds": [ + "9300000007573" + ], "rolloutId": "", "variables": [], "id": "3046", @@ -63,8 +67,13 @@ "experiments": [ { "status": "Running", - "audienceConditions": ["or", "20415611520"], - "audienceIds": ["20415611520"], + "audienceConditions": [ + "or", + "20415611520" + ], + "audienceIds": [ + "20415611520" + ], "variations": [ { "variables": [], @@ -76,13 +85,23 @@ "forcedVariations": {}, "key": "targeted_delivery", "layerId": "9300000007569", - "trafficAllocation": [{ "entityId": "8045", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "8045", + "endOfRange": 10000 + } + ], "id": "9300000007569" }, { "status": "Running", - "audienceConditions": ["or", "20406066925"], - "audienceIds": ["20406066925"], + "audienceConditions": [ + "or", + "20406066925" + ], + "audienceIds": [ + "20406066925" + ], "variations": [ { "variables": [], @@ -94,7 +113,12 @@ "forcedVariations": {}, "key": "targeted_delivery", "layerId": "9300000007573", - "trafficAllocation": [{ "entityId": "8048", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "8048", + "endOfRange": 10000 + } + ], "id": "9300000007573" } ], @@ -116,7 +140,12 @@ } ], "groups": [], - "attributes": [{ "id": "20408641883", "key": "hiddenLiveEnabled" }], + "attributes": [ + { + "id": "20408641883", + "key": "hiddenLiveEnabled" + } + ], "botFiltering": false, "accountId": "17882702980", "events": [], diff --git a/OptimizelySDK.Tests/similar_rule_keys_bucketing.json b/OptimizelySDK.Tests/similar_rule_keys_bucketing.json index b4a3898e..68dab680 100644 --- a/OptimizelySDK.Tests/similar_rule_keys_bucketing.json +++ b/OptimizelySDK.Tests/similar_rule_keys_bucketing.json @@ -18,7 +18,12 @@ "forcedVariations": {}, "key": "targeted_delivery", "layerId": "9300000004981", - "trafficAllocation": [{ "entityId": "5452", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "5452", + "endOfRange": 10000 + } + ], "id": "9300000004981" }, { @@ -36,7 +41,12 @@ "forcedVariations": {}, "key": "default-rollout-2029-20301771717", "layerId": "default-layer-rollout-2029-20301771717", - "trafficAllocation": [{ "entityId": "5451", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "5451", + "endOfRange": 10000 + } + ], "id": "default-rollout-2029-20301771717" } ], @@ -59,7 +69,12 @@ "forcedVariations": {}, "key": "targeted_delivery", "layerId": "9300000004979", - "trafficAllocation": [{ "entityId": "5450", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "5450", + "endOfRange": 10000 + } + ], "id": "9300000004979" }, { @@ -77,7 +92,12 @@ "forcedVariations": {}, "key": "default-rollout-2028-20301771717", "layerId": "default-layer-rollout-2028-20301771717", - "trafficAllocation": [{ "entityId": "5449", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "5449", + "endOfRange": 10000 + } + ], "id": "default-rollout-2028-20301771717" } ], @@ -100,7 +120,12 @@ "forcedVariations": {}, "key": "targeted_delivery", "layerId": "9300000004977", - "trafficAllocation": [{ "entityId": "5448", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "5448", + "endOfRange": 10000 + } + ], "id": "9300000004977" }, { @@ -118,7 +143,12 @@ "forcedVariations": {}, "key": "default-rollout-2027-20301771717", "layerId": "default-layer-rollout-2027-20301771717", - "trafficAllocation": [{ "entityId": "5447", "endOfRange": 10000 }], + "trafficAllocation": [ + { + "entityId": "5447", + "endOfRange": 10000 + } + ], "id": "default-rollout-2027-20301771717" } ], diff --git a/OptimizelySDK.Tests/typed_audience_datafile.json b/OptimizelySDK.Tests/typed_audience_datafile.json index 7c6fee4b..986f909c 100644 --- a/OptimizelySDK.Tests/typed_audience_datafile.json +++ b/OptimizelySDK.Tests/typed_audience_datafile.json @@ -1,379 +1,561 @@ { - "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": [{ - "id": "3988293898", - "name": "substringString", - "conditions": ["and", ["or", ["or", { - "name": "house", - "type": "custom_attribute", - "match": "substring", - "value": "Slytherin" - }]]] - }, - { - "id": "3988293899", - "name": "exists", - "conditions": ["and", ["or", ["or", { - "name": "favorite_ice_cream", - "type": "custom_attribute", - "match": "exists" - }]]] - }, - { - "id": "3468206646", - "name": "exactNumber", - "conditions": ["and", ["or", ["or", { - "name": "lasers", - "type": "custom_attribute", - "match": "exact", - "value": 45.5 - }]]] - }, - { - "id": "3468206647", - "name": "gtNumber", - "conditions": ["and", ["or", ["or", { - "name": "lasers", - "type": "custom_attribute", - "match": "gt", - "value": 70 - }]]] - }, - { - "id": "3468206644", - "name": "ltNumber", - "conditions": ["and", ["or", ["or", { - "name": "lasers", - "type": "custom_attribute", - "match": "lt", - "value": 1.0 - }]]] - }, - { - "id": "3468206643", - "name": "exactBoolean", - "conditions": ["and", ["or", ["or", { - "name": "should_do_it", - "type": "custom_attribute", - "match": "exact", - "value": true - }]]] - }, - { - "id": "3468206648", - "name": "notExist", - "conditions": ["not", { - "name": "input_value", - "type": "custom_attribute", - "match": "exists" - }] - } - ], - "groups": [], - "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": [ + { + "id": "3988293898", + "name": "substringString", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "name": "house", + "type": "custom_attribute", + "match": "substring", + "value": "Slytherin" + } + ] + ] + ] + }, + { + "id": "3988293899", + "name": "exists", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "name": "favorite_ice_cream", + "type": "custom_attribute", + "match": "exists" + } + ] + ] + ] + }, + { + "id": "3468206646", + "name": "exactNumber", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "name": "lasers", + "type": "custom_attribute", + "match": "exact", + "value": 45.5 + } + ] + ] + ] + }, + { + "id": "3468206647", + "name": "gtNumber", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "name": "lasers", + "type": "custom_attribute", + "match": "gt", + "value": 70 + } + ] + ] + ] + }, + { + "id": "3468206644", + "name": "ltNumber", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "name": "lasers", + "type": "custom_attribute", + "match": "lt", + "value": 1.0 + } + ] + ] + ] + }, + { + "id": "3468206643", + "name": "exactBoolean", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "name": "should_do_it", + "type": "custom_attribute", + "match": "exact", + "value": true + } + ] + ] + ] + }, + { + "id": "3468206648", + "name": "notExist", + "conditions": [ + "not", + { + "name": "input_value", + "type": "custom_attribute", + "match": "exists" + } + ] + } + ], + "groups": [], + "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/unsupported_version_datafile.json b/OptimizelySDK.Tests/unsupported_version_datafile.json index 4830b21f..08fcaa0a 100644 --- a/OptimizelySDK.Tests/unsupported_version_datafile.json +++ b/OptimizelySDK.Tests/unsupported_version_datafile.json @@ -1,42 +1,49 @@ { - "version": "5", - "rollouts": [], - "projectId": "10431130345", - "variables": [], - "featureFlags": [], - "experiments": [{ - "status": "Running", - "key": "ab_running_exp_untargeted", - "layerId": "10417730432", - "trafficAllocation": [{ - "entityId": "10418551353", - "endOfRange": 10000 - }], - "audienceIds": [], - "variations": [{ - "variables": [], - "id": "10418551353", - "key": "all_traffic_variation" - }, - { - "variables": [], - "id": "10418510624", - "key": "no_traffic_variation" - } - ], - "forcedVariations": {}, - "id": "10420810910" - }], - "audiences": [], - "groups": [], - "attributes": [], - "accountId": "10367498574", - "events": [{ - "experimentIds": [ - "10420810910" - ], - "id": "10404198134", - "key": "winning" - }], - "revision": "1337" -} \ No newline at end of file + "version": "5", + "rollouts": [], + "projectId": "10431130345", + "variables": [], + "featureFlags": [], + "experiments": [ + { + "status": "Running", + "key": "ab_running_exp_untargeted", + "layerId": "10417730432", + "trafficAllocation": [ + { + "entityId": "10418551353", + "endOfRange": 10000 + } + ], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "10418551353", + "key": "all_traffic_variation" + }, + { + "variables": [], + "id": "10418510624", + "key": "no_traffic_variation" + } + ], + "forcedVariations": {}, + "id": "10420810910" + } + ], + "audiences": [], + "groups": [], + "attributes": [], + "accountId": "10367498574", + "events": [ + { + "experimentIds": [ + "10420810910" + ], + "id": "10404198134", + "key": "winning" + } + ], + "revision": "1337" +} diff --git a/OptimizelySDK/AudienceConditions/AndCondition.cs b/OptimizelySDK/AudienceConditions/AndCondition.cs index 55bf75fd..c413b0cb 100644 --- a/OptimizelySDK/AudienceConditions/AndCondition.cs +++ b/OptimizelySDK/AudienceConditions/AndCondition.cs @@ -40,13 +40,19 @@ public class AndCondition : ICondition { var result = condition.Evaluate(config, attributes, logger); if (result == null) + { foundNull = true; + } else if (result == false) + { return false; + } } if (foundNull) + { return null; + } return true; } diff --git a/OptimizelySDK/AudienceConditions/OrCondition.cs b/OptimizelySDK/AudienceConditions/OrCondition.cs index ee42ceb4..b15ee2c0 100644 --- a/OptimizelySDK/AudienceConditions/OrCondition.cs +++ b/OptimizelySDK/AudienceConditions/OrCondition.cs @@ -38,13 +38,19 @@ public class OrCondition : ICondition { var result = condition.Evaluate(config, attributes, 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 e191dc51..8b039486 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 0b0eaa25..33df35a3 100644 --- a/OptimizelySDK/Bucketing/Bucketer.cs +++ b/OptimizelySDK/Bucketing/Bucketer.cs @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -using OptimizelySDK.Entity; -using OptimizelySDK.Logger; -using OptimizelySDK.OptimizelyDecisions; + using System; using System.Collections.Generic; using System.Text; +using OptimizelySDK.Entity; +using OptimizelySDK.Logger; +using OptimizelySDK.OptimizelyDecisions; namespace OptimizelySDK.Bucketing { @@ -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 30985663..703adce8 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 bf01bf60..20b9619f 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -54,7 +54,8 @@ public class DecisionService #if NET35 private Dictionary> ForcedVariationMap; #else - private System.Collections.Concurrent.ConcurrentDictionary> ForcedVariationMap; + private System.Collections.Concurrent.ConcurrentDictionary> ForcedVariationMap; #endif /// @@ -64,7 +65,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; @@ -73,7 +76,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 } @@ -86,7 +91,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[] { }); } @@ -102,12 +108,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); @@ -129,38 +138,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, filteredAttributes, LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger); + var doesUserMeetAudienceConditionsResult = + ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, filteredAttributes, + LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger); reasons += doesUserMeetAudienceConditionsResult.DecisionReasons; if (doesUserMeetAudienceConditionsResult.ResultObject) { @@ -168,23 +190,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); } @@ -196,46 +228,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); } @@ -248,7 +294,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) @@ -261,32 +309,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; } @@ -303,20 +364,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); } @@ -327,39 +399,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) @@ -374,11 +458,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)) @@ -396,12 +484,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)); } } @@ -416,9 +507,11 @@ public void SaveVariation(Experiment experiment, Variation variation, UserProfil /// Decision log messages. /// 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(); @@ -430,19 +523,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); } @@ -466,7 +564,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 @@ -480,35 +580,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, attributes, LOGGING_KEY_TYPE_RULE, rule.Key, Logger); + var doesUserMeetAudienceConditionsResult = + ExperimentUtils.DoesUserMeetAudienceConditions(config, rule, attributes, + 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); @@ -523,11 +636,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(); @@ -539,7 +654,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); } @@ -549,34 +666,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); } @@ -588,9 +714,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[] { }); } /// @@ -607,12 +736,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) @@ -626,12 +757,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); + ; } /// @@ -643,19 +780,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")); } } @@ -669,23 +810,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 8cebb594..3f7512de 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 409437d9..f5e81623 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 0916b750..582d57a6 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 c9d27786..62bde77f 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 93a0aceb..247b6a76 100644 --- a/OptimizelySDK/Config/DatafileProjectConfig.cs +++ b/OptimizelySDK/Config/DatafileProjectConfig.cs @@ -40,7 +40,7 @@ public enum OPTLYSDKVersion { V2 = 2, V3 = 3, - V4 = 4 + V4 = 4, } /// @@ -59,7 +59,7 @@ public enum OPTLYSDKVersion public string AccountId { get; set; } /// - /// Project ID of the Full Stack project. + /// Project ID of the Optimizely Feature Experimentation project. /// public string ProjectId { get; set; } @@ -104,7 +104,7 @@ public enum OPTLYSDKVersion private static List SupportedVersions = new List { OPTLYSDKVersion.V2, OPTLYSDKVersion.V3, - OPTLYSDKVersion.V4 + OPTLYSDKVersion.V4, }; //========================= Mappings =========================== @@ -114,14 +114,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 @@ -129,7 +129,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 @@ -137,7 +137,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 @@ -145,7 +146,8 @@ private 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 @@ -153,7 +155,8 @@ private Dictionary> _VariationKeyMapByExpe 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 @@ -161,42 +164,42 @@ private Dictionary> _VariationIdMapByExper 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 @@ -207,8 +210,11 @@ private Dictionary> _VariationIdMap /// /// Associated dictionary of flags to variations key value. /// - private Dictionary> _FlagVariationMap = new Dictionary>(); - public Dictionary> FlagVariationMap { get { return _FlagVariationMap; } } + private Dictionary> _FlagVariationMap = + new Dictionary>(); + + public Dictionary> FlagVariationMap => + _FlagVariationMap; //========================= Interfaces =========================== @@ -282,23 +288,36 @@ private void Initialize() Rollouts = Rollouts ?? new Rollout[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); - _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); + _GroupIdMap = ConfigParser.GenerateMap(Groups, + g => g.Id.ToString(), true); + _ExperimentIdMap = ConfigParser.GenerateMap(Experiments, + e => e.Id, true); + _EventKeyMap = + 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) + var experimentsInGroup = + ConfigParser.GenerateMap(group.Experiments, e => e.Id, + true); + foreach (var experiment in experimentsInGroup.Values) { experiment.GroupId = group.Id; experiment.GroupPolicy = group.Policy; @@ -307,10 +326,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(); @@ -321,7 +342,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; @@ -370,8 +391,11 @@ private void Initialize() ExperimentFeatureMap[experimentId].Add(feature.Id); } else - { - ExperimentFeatureMap[experimentId] = new List { feature.Id }; + { + ExperimentFeatureMap[experimentId] = new List + { + feature.Id, + }; } } if (RolloutIdMap.TryGetValue(feature.RolloutId, out var rolloutRules)) { @@ -395,7 +419,7 @@ private void Initialize() /// ProjectConfig instance created from datafile string 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); @@ -408,16 +432,25 @@ public static ProjectConfig Create(string content, ILogger logger, IErrorHandler 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}"); + 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; } @@ -432,11 +465,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.")); + ErrorHandler.HandleError( + new InvalidGroupException("Provided group is not in datafile.")); return new Group(); } @@ -448,11 +484,15 @@ 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("Provided experiment is not in datafile.")); + ErrorHandler.HandleError( + new InvalidExperimentException( + "Provided experiment is not in datafile.")); return new Experiment(); } @@ -464,11 +504,15 @@ 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("Provided experiment is not in datafile.")); + ErrorHandler.HandleError( + new InvalidExperimentException( + "Provided experiment is not in datafile.")); return new Experiment(); } @@ -480,11 +524,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.")); + ErrorHandler.HandleError( + new InvalidEventException("Provided event is not in datafile.")); return new Entity.Event(); } @@ -496,11 +543,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.")); + ErrorHandler.HandleError( + new InvalidAudienceException("Provided audience is not in datafile.")); return new Audience(); } @@ -512,11 +562,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.")); + ErrorHandler.HandleError( + new InvalidAttributeException("Provided attribute is not in datafile.")); return new Attribute(); } @@ -531,11 +584,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}"" defined in datafile for experiment ""{experimentKey}""."; + 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.")); + ErrorHandler.HandleError( + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -550,11 +607,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}"" defined in datafile for experiment ""{experimentId}""."; + 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.")); + ErrorHandler.HandleError( + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -569,11 +630,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}"" defined in datafile for experiment ""{experimentKey}""."; + 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.")); + ErrorHandler.HandleError( + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -588,11 +653,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}"" defined in datafile for experiment ""{experimentId}""."; + 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.")); + ErrorHandler.HandleError( + new InvalidVariationException("Provided variation is not in datafile.")); return new Variation(); } @@ -604,11 +673,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.")); + ErrorHandler.HandleError( + new InvalidFeatureException("Provided feature is not in datafile.")); return new FeatureFlag(); } @@ -646,11 +718,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.")); + ErrorHandler.HandleError( + new InvalidRolloutException("Provided rollout is not in datafile.")); return new Rollout(); } @@ -667,7 +742,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."); + { + 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/HttpProjectConfigManager.cs b/OptimizelySDK/Config/HttpProjectConfigManager.cs index 8b909755..c15b5fdb 100644 --- a/OptimizelySDK/Config/HttpProjectConfigManager.cs +++ b/OptimizelySDK/Config/HttpProjectConfigManager.cs @@ -21,6 +21,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Logger; +using OptimizelySDK.Notifications; namespace OptimizelySDK.Config { @@ -66,8 +69,10 @@ public HttpClient(System.Net.Http.HttpClient httpClient) : this() public static System.Net.Http.HttpClientHandler GetHttpClientHandler() { - var handler = new System.Net.Http.HttpClientHandler() { - AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate + var handler = new System.Net.Http.HttpClientHandler() + { + AutomaticDecompression = System.Net.DecompressionMethods.GZip | + System.Net.DecompressionMethods.Deflate, }; return handler; @@ -96,7 +101,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)) { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", DatafileAccessToken); @@ -113,11 +120,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(); @@ -131,21 +142,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; } } @@ -160,7 +177,9 @@ protected override ProjectConfig Poll() var datafile = GetRemoteDatafileResponse(); if (datafile == null) + { return null; + } return DatafileProjectConfig.Create(datafile, Logger, ErrorHandler); } @@ -213,7 +232,7 @@ public Builder WithSdkKey(string sdkKey) #if !NET40 && !NET35 public Builder WithAccessToken(string accessToken) { - this.DatafileAccessToken = accessToken; + DatafileAccessToken = accessToken; return this; } @@ -298,10 +317,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)) { @@ -358,12 +381,16 @@ 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 93d28474..d4c55959 100644 --- a/OptimizelySDK/Config/PollingProjectConfigManager.cs +++ b/OptimizelySDK/Config/PollingProjectConfigManager.cs @@ -16,11 +16,11 @@ using System; using System.Threading; -using OptimizelySDK.Logger; -using OptimizelySDK.Utils; using System.Threading.Tasks; using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Logger; using OptimizelySDK.OptlyConfig; +using OptimizelySDK.Utils; namespace OptimizelySDK.Config { @@ -90,7 +90,11 @@ public void Start() /// public void Stop() { - if (Disposed) return; + if (Disposed) + { + return; + } + // don't call now and onwards. SchedulerService.Change(-1, -1); @@ -105,13 +109,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. @@ -140,12 +147,17 @@ public ProjectConfig GetConfig() public bool SetConfig(ProjectConfig projectConfig) { if (projectConfig == null) + { return false; - - var previousVersion = CurrentProjectConfig == null ? "null" : CurrentProjectConfig.Revision; + } + + var previousVersion = + CurrentProjectConfig == null ? "null" : CurrentProjectConfig.Revision; if (projectConfig.Revision == previousVersion) + { return false; - + } + CurrentProjectConfig = projectConfig; SetOptimizelyConfig(CurrentProjectConfig); @@ -181,7 +193,10 @@ public OptimizelyConfig GetOptimizelyConfig() public virtual void Dispose() { - if (Disposed) return; + if (Disposed) + { + return; + } SchedulerService.Change(-1, -1); SchedulerService.Dispose(); @@ -200,12 +215,18 @@ public virtual void Run() var config = Poll(); // during in-flight, if PollingProjectConfigManagerStopped, then don't need to set. - if(IsStarted) + if (IsStarted) + { SetConfig(config); - - } catch (Exception exception) { - Logger.Log(LogLevel.ERROR, "Unable to get project config. Error: " + exception.GetAllMessages()); - } finally { + } + } + catch (Exception exception) + { + Logger.Log(LogLevel.ERROR, + "Unable to get project config. Error: " + exception.GetAllMessages()); + } + finally + { Interlocked.Exchange(ref resourceInUse, 0); // trigger now, due because of delayed latency response diff --git a/OptimizelySDK/Entity/Attribute.cs b/OptimizelySDK/Entity/Attribute.cs index df351727..231c0978 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 186535ef..79e115d5 100644 --- a/OptimizelySDK/Entity/Audience.cs +++ b/OptimizelySDK/Entity/Audience.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OptimizelySDK.AudienceConditions; @@ -48,7 +49,9 @@ public ICondition ConditionList get { if (Conditions == null) + { return null; + } if (_decodedConditions == null) { @@ -77,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 6acefe9d..a339356a 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 e10b46d2..2543b74f 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 774aa05d..7422f4f9 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 e291fa5b..e1eee5f2 100644 --- a/OptimizelySDK/Entity/Experiment.cs +++ b/OptimizelySDK/Entity/Experiment.cs @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using System.Collections.Generic; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OptimizelySDK.Utils; using OptimizelySDK.AudienceConditions; -using Newtonsoft.Json; +using OptimizelySDK.Utils; 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 f93c5c46..e768cc5a 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 2067f17e..dc703c50 100644 --- a/OptimizelySDK/Entity/FeatureFlag.cs +++ b/OptimizelySDK/Entity/FeatureFlag.cs @@ -14,8 +14,8 @@ * limitations under the License. */ -using OptimizelySDK.Utils; using System.Collections.Generic; +using OptimizelySDK.Utils; namespace OptimizelySDK.Entity { @@ -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 33f69a72..d8429573 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 388699eb..ee4d7b26 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 58214f24..071c5c91 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 f9d4e305..61a2b7d3 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 3074ec77..66f7b890 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/Result.cs b/OptimizelySDK/Entity/Result.cs index 17a63133..47820826 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 95c16e68..20663520 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 c11e1009..d39581cc 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 2083794e..62e81cc9 100644 --- a/OptimizelySDK/Entity/Variation.cs +++ b/OptimizelySDK/Entity/Variation.cs @@ -14,8 +14,8 @@ * limitations under the License. */ -using OptimizelySDK.Utils; using System.Collections.Generic; +using OptimizelySDK.Utils; namespace OptimizelySDK.Entity { @@ -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 bbfe6146..43d91f3a 100644 --- a/OptimizelySDK/ErrorHandler/DefaultErrorHandler.cs +++ b/OptimizelySDK/ErrorHandler/DefaultErrorHandler.cs @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -using OptimizelySDK.Logger; + using System; +using OptimizelySDK.Logger; namespace OptimizelySDK.ErrorHandler { @@ -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 93efda07..d34111d0 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 1f5b7e9d..f58f3598 100644 --- a/OptimizelySDK/ErrorHandler/NoOpErrorHandler.cs +++ b/OptimizelySDK/ErrorHandler/NoOpErrorHandler.cs @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -using OptimizelySDK.Logger; + using System; +using OptimizelySDK.Logger; 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 51526454..5f714dc6 100644 --- a/OptimizelySDK/Event/BatchEventProcessor.cs +++ b/OptimizelySDK/Event/BatchEventProcessor.cs @@ -17,14 +17,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using OptimizelySDK.Event.Entity; +using System.Linq; using System.Threading; -using OptimizelySDK.Utils; -using OptimizelySDK.Logger; using OptimizelySDK.ErrorHandler; -using System.Linq; using OptimizelySDK.Event.Dispatcher; +using OptimizelySDK.Event.Entity; +using OptimizelySDK.Logger; using OptimizelySDK.Notifications; +using OptimizelySDK.Utils; namespace OptimizelySDK.Event { @@ -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 fd08d327..1d73ff94 100644 --- a/OptimizelySDK/Event/Builder/EventBuilder.cs +++ b/OptimizelySDK/Event/Builder/EventBuilder.cs @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; +using System.Collections.Generic; +using System.Linq; using OptimizelySDK.Bucketing; using OptimizelySDK.Entity; using OptimizelySDK.Logger; using OptimizelySDK.Utils; -using System; -using System.Collections.Generic; -using System.Linq; namespace OptimizelySDK.Event.Builder { @@ -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 f3b47688..dd1894eb 100644 --- a/OptimizelySDK/Event/Builder/Params.cs +++ b/OptimizelySDK/Event/Builder/Params.cs @@ -13,31 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -using OptimizelySDK.Event.Entity; + using System; +using OptimizelySDK.Event.Entity; 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 d875c5cd..20d7197a 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 82915815..dd7b9e0f 100644 --- a/OptimizelySDK/Event/Dispatcher/HttpClientEventDispatcher45.cs +++ b/OptimizelySDK/Event/Dispatcher/HttpClientEventDispatcher45.cs @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #if !NET35 && !NET40 -using OptimizelySDK.Logger; -using OptimizelySDK.Utils; using System; using System.Net.Http; using System.Threading.Tasks; +using OptimizelySDK.Logger; +using OptimizelySDK.Utils; namespace OptimizelySDK.Event.Dispatcher { @@ -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 fb875e34..06d9ef60 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 f5e09de0..f1c282c9 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 2042b148..78e17301 100644 --- a/OptimizelySDK/Event/Entity/ConversionEvent.cs +++ b/OptimizelySDK/Event/Entity/ConversionEvent.cs @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; using OptimizelySDK.Entity; using OptimizelySDK.Utils; -using System; namespace OptimizelySDK.Event.Entity { @@ -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 08657a33..7d309f1f 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 9f4b4428..88b0a27c 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 ed5f9f16..0fe1fec2 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 7385817c..44b77644 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 5d525b2d..12949ea6 100644 --- a/OptimizelySDK/Event/Entity/ImpressionEvent.cs +++ b/OptimizelySDK/Event/Entity/ImpressionEvent.cs @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; using OptimizelySDK.Entity; using OptimizelySDK.Utils; -using System; namespace OptimizelySDK.Event.Entity { @@ -117,6 +118,5 @@ public ImpressionEvent Build() return impressionEvent; } } - } } diff --git a/OptimizelySDK/Event/Entity/Snapshot.cs b/OptimizelySDK/Event/Entity/Snapshot.cs index 525059dc..7ad6001e 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 4fe13611..a620ca41 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 784b84c0..dd1568cd 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 c60e8ac1..c6799aa1 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 69151477..0f881469 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 e0981651..841b650f 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 67da6450..4dbb5b41 100644 --- a/OptimizelySDK/Event/ForwardingEventProcessor.cs +++ b/OptimizelySDK/Event/ForwardingEventProcessor.cs @@ -14,12 +14,12 @@ * limitations under the License. */ +using System; using OptimizelySDK.ErrorHandler; using OptimizelySDK.Event.Dispatcher; using OptimizelySDK.Event.Entity; using OptimizelySDK.Logger; using OptimizelySDK.Notifications; -using System; namespace OptimizelySDK.Event { @@ -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 4a9b76fb..b7c71697 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 c601b033..f073237a 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 1cad8c44..ad9cf4d5 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 7097a121..1b842fbc 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 a7d250a9..6c8cb4c7 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 13c8828f..2a3a4597 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 a8f6b45f..9e81b92d 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 cce0ba65..cdc63aca 100644 --- a/OptimizelySDK/Notifications/NotificationCenter.cs +++ b/OptimizelySDK/Notifications/NotificationCenter.cs @@ -14,12 +14,12 @@ * limitations under the License. */ -using OptimizelySDK.Entity; -using OptimizelySDK.Event; -using OptimizelySDK.Logger; using System; using System.Collections.Generic; using System.Linq; +using OptimizelySDK.Entity; +using OptimizelySDK.Event; +using OptimizelySDK.Logger; namespace OptimizelySDK.Notifications { @@ -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/Optimizely.cs b/OptimizelySDK/Optimizely.cs index cef5a68c..62d35dbb 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -44,7 +44,7 @@ namespace OptimizelySDK public class Optimizely : IOptimizely, IDisposable { private Bucketer Bucketer; - + [Obsolete] private EventBuilder EventBuilder; private IEventDispatcher EventDispatcher; @@ -69,11 +69,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 { @@ -81,25 +77,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, version.Build); + 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"; @@ -111,7 +102,7 @@ public static String SDK_TYPE public bool Disposed { get; private set; } /// - /// Optimizely constructor for managing Full Stack .NET projects. + /// Optimizely constructor for managing Optimizely Feature Experimentation .NET projects. /// /// string JSON string representing the project /// EventDispatcherInterface @@ -129,7 +120,8 @@ public Optimizely(string datafile, OptimizelyDecideOption[] defaultDecideOptions = null) { try { - InitializeComponents(eventDispatcher, logger, errorHandler, userProfileService, null, eventProcessor, defaultDecideOptions); + InitializeComponents(eventDispatcher, logger, errorHandler, userProfileService, + null, eventProcessor, defaultDecideOptions); if (ValidateInputs(datafile, skipJsonValidation)) { var config = DatafileProjectConfig.Create(datafile, Logger, ErrorHandler); @@ -138,12 +130,17 @@ public Optimizely(string datafile, Logger.Log(LogLevel.ERROR, "Provided 'datafile' has invalid schema."); } } - catch (Exception ex) { - string error = String.Empty; + catch (Exception ex) + { + 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); @@ -170,9 +167,11 @@ public Optimizely(ProjectConfigManager configManager, { ProjectConfigManager = configManager; - InitializeComponents(eventDispatcher, logger, errorHandler, userProfileService, notificationCenter, eventProcessor, defaultDecideOptions); + InitializeComponents(eventDispatcher, logger, errorHandler, userProfileService, + notificationCenter, eventProcessor, defaultDecideOptions); } + [Obsolete] private void InitializeComponents(IEventDispatcher eventDispatcher = null, ILogger logger = null, IErrorHandler errorHandler = null, @@ -239,13 +238,12 @@ public Variation Activate(string experimentKey, string userId, UserAttributes us } var inputValues = new Dictionary - { - { USER_ID, userId }, - { EXPERIMENT_KEY, experimentKey } - }; + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; if (!ValidateStringInputs(inputValues)) + { return null; + } var experiment = config.GetExperimentFromKey(experimentKey); @@ -297,13 +295,12 @@ public void Track(string eventKey, string userId, UserAttributes userAttributes } var inputValues = new Dictionary - { - { USER_ID, userId }, - { EVENT_KEY, eventKey } - }; + { { USER_ID, userId }, { EVENT_KEY, eventKey } }; if (!ValidateStringInputs(inputValues)) + { return; + } var eevent = config.GetEvent(eventKey); @@ -360,29 +357,35 @@ private Variation GetVariation(string experimentKey, string userId, ProjectConfi } var inputValues = new Dictionary - { - { USER_ID, userId }, - { EXPERIMENT_KEY, experimentKey } - }; + { { 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 = CreateUserContext(userId, userAttributes); - var variation = DecisionService.GetVariation(experiment, userContext, config)?.ResultObject; + var userContext = CreateUserContextCopy(userId, userAttributes); + 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; - NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, decisionNotificationType, userId, + var decisionNotificationType = config.IsFeatureExperiment(experiment.Id) ? + DecisionNotificationTypes.FEATURE_TEST : + DecisionNotificationTypes.AB_TEST; + NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, + decisionNotificationType, userId, userAttributes, decisionInfo); return variation; } @@ -405,11 +408,9 @@ public bool SetForcedVariation(string experimentKey, string userId, string varia } var inputValues = new Dictionary - { - { USER_ID, userId }, - { EXPERIMENT_KEY, experimentKey } - }; - return ValidateStringInputs(inputValues) && DecisionService.SetForcedVariation(experimentKey, userId, variationKey, config); + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; + return ValidateStringInputs(inputValues) && + DecisionService.SetForcedVariation(experimentKey, userId, variationKey, config); } /// @@ -427,13 +428,12 @@ public Variation GetForcedVariation(string experimentKey, string userId) } var inputValues = new Dictionary - { - { USER_ID, userId }, - { EXPERIMENT_KEY, experimentKey } - }; + { { USER_ID, userId }, { EXPERIMENT_KEY, experimentKey } }; if (!ValidateStringInputs(inputValues)) + { return null; + } return DecisionService.GetForcedVariation(experimentKey, userId, config).ResultObject; } @@ -447,8 +447,10 @@ public Variation GetForcedVariation(string experimentKey, string userId) /// The feature key /// The user ID /// The user's attributes. - /// True if feature is enabled, false or null otherwise - public virtual bool IsFeatureEnabled(string featureKey, string userId, UserAttributes userAttributes = null) + /// True if feature is enabled, otherwise false + public virtual bool IsFeatureEnabled(string featureKey, string userId, + UserAttributes userAttributes = null + ) { var config = ProjectConfigManager?.GetConfig(); @@ -460,24 +462,30 @@ public virtual bool IsFeatureEnabled(string featureKey, string userId, UserAttri } var inputValues = new Dictionary - { - { USER_ID, userId }, - { FEATURE_KEY, featureKey } - }; + { { 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, CreateUserContext(userId, userAttributes), config).ResultObject; + var decision = DecisionService.GetVariationForFeature(featureFlag, + CreateUserContextCopy(userId, userAttributes), + config). + ResultObject; var variation = decision?.Variation; var decisionSource = decision?.Source ?? FeatureDecision.DECISION_SOURCE_ROLLOUT; @@ -500,9 +508,15 @@ public virtual bool IsFeatureEnabled(string featureKey, string userId, UserAttri } if (featureEnabled == true) - Logger.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is enabled for user ""{userId}""."); + { + 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}""."); + { + Logger.Log(LogLevel.INFO, + $@"Feature flag ""{featureKey}"" is not enabled for user ""{userId}""."); + } var decisionInfo = new Dictionary { @@ -534,41 +548,50 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var var config = ProjectConfigManager?.GetConfig(); if (config == null) { - Logger.Log(LogLevel.ERROR, $@"Datafile has invalid format. Failing '{FeatureVariable.GetFeatureVariableTypeName(variableType)}'."); - return default(T); + Logger.Log(LogLevel.ERROR, + $@"Datafile has invalid format. Failing '{ + FeatureVariable.GetFeatureVariableTypeName(variableType)}'."); + 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) { Logger.Log(LogLevel.ERROR, - $@"No feature variable was found for key ""{variableKey}"" in feature flag ""{featureKey}""."); - return default(T); + $@"No feature variable was found for key ""{variableKey}"" in feature flag ""{ + featureKey}""."); + 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); + $@"Variable is of type ""{featureVariable.Type + }"", but you requested it as type ""{variableType}""."); + return default; } var featureEnabled = false; var variableValue = featureVariable.DefaultValue; - var decision = DecisionService.GetVariationForFeature(featureFlag, CreateUserContext(userId, userAttributes), config).ResultObject; + var decision = DecisionService.GetVariationForFeature(featureFlag, + CreateUserContextCopy(userId, userAttributes), + config). + ResultObject; if (decision?.Variation != null) { @@ -612,7 +635,12 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var { "featureKey", featureKey }, { "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 }, { "sourceInfo", sourceInfo }, @@ -698,15 +726,29 @@ public OptimizelyJSON GetFeatureVariableJSON(string featureKey, string variableK public OptimizelyUserContext CreateUserContext(string userId, UserAttributes userAttributes = null) { - var inputValues = new Dictionary + var inputValues = new Dictionary { { USER_ID, userId } }; + if (!ValidateStringInputs(inputValues)) { - { USER_ID, userId }, - }; + return null; + } + + return new OptimizelyUserContext(this, userId, userAttributes, ErrorHandler, Logger); + } + + private OptimizelyUserContext CreateUserContextCopy(string userId, + UserAttributes userAttributes = null + ) + { + var inputValues = new Dictionary { { USER_ID, userId } }; if (!ValidateStringInputs(inputValues)) + { return null; + } - return new OptimizelyUserContext(this, userId, userAttributes, ErrorHandler, Logger); + return new OptimizelyUserContext(this, userId, userAttributes, null, ErrorHandler, + Logger + ); } /// @@ -795,7 +837,7 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user, { foreach (var featureVariable in flag?.Variables) { - string variableValue = featureVariable.DefaultValue; + var variableValue = featureVariable.DefaultValue; if (featureEnabled) { var featureVariableUsageInstance = decision?.Variation.GetFeatureVariableUsageFromId(featureVariable.Id); @@ -808,7 +850,9 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user, var typeCastedValue = GetTypeCastedVariableValue(variableValue, featureVariable.Type); if (typeCastedValue is OptimizelyJSON) + { typeCastedValue = ((OptimizelyJSON)typeCastedValue).ToDictionary(); + } variableMap.Add(featureVariable.Key, typeCastedValue); } @@ -821,7 +865,10 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user, { decisionEventDispatched = SendImpressionEvent(decision?.Experiment, decision?.Variation, userId, userAttributes, config, key, decisionSource, 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. @@ -835,7 +882,7 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user, { "variationKey", variationKey }, { "ruleKey", ruleKey }, { "reasons", reasonsToReport }, - { "decisionEventDispatched", decisionEventDispatched } + { "decisionEventDispatched", decisionEventDispatched }, }; NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, DecisionNotificationTypes.FLAG, userId, @@ -889,7 +936,7 @@ internal Dictionary DecideForKeys(OptimizelyUserCont 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) || decision.Enabled) @@ -903,7 +950,7 @@ internal Dictionary DecideForKeys(OptimizelyUserCont private OptimizelyDecideOption[] GetAllOptions(OptimizelyDecideOption[] options) { - OptimizelyDecideOption[] copiedOptions = DefaultDecideOptions; + var copiedOptions = DefaultDecideOptions; if (options != null) { copiedOptions = options.Union(DefaultDecideOptions).ToArray(); @@ -975,7 +1022,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(); @@ -986,13 +1033,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; @@ -1034,7 +1085,9 @@ public OptimizelyJSON GetAllFeatureVariables(string featureKey, string userId, } if (!Validator.IsFeatureFlagValid(config, featureFlag)) + { return null; + } var featureEnabled = false; var decisionResult = DecisionService.GetVariationForFeature(featureFlag, CreateUserContext(userId, userAttributes), config); @@ -1061,7 +1114,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 = variation.GetFeatureVariableUsageFromId(featureVariable.Id); @@ -1074,7 +1127,9 @@ public OptimizelyJSON GetAllFeatureVariables(string featureKey, string userId, var typeCastedValue = GetTypeCastedVariableValue(variableValue, featureVariable.Type); if (typeCastedValue is OptimizelyJSON) + { typeCastedValue = ((OptimizelyJSON)typeCastedValue).ToDictionary(); + } valuesMap.Add(featureVariable.Key, typeCastedValue); } @@ -1135,7 +1190,7 @@ public OptimizelyConfig GetOptimizelyConfig() /// 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)) @@ -1167,17 +1222,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); + double.TryParse(value, System.Globalization.NumberStyles.Number, + 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; @@ -1191,8 +1247,10 @@ private object GetTypeCastedVariableValue(string value, string type) } if (result == null) - Logger.Log(LogLevel.ERROR, $@"Unable to cast variable value ""{value}"" to type ""{type}""."); - + { + 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 24842d14..69b1d6f8 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 1f2c3301..58cd4867 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 56875905..7e370457 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 bdf13915..b0ec5307 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 dfc571ac..7fe6c0c8 100644 --- a/OptimizelySDK/OptimizelyDecisions/OptimizelyDecision.cs +++ b/OptimizelySDK/OptimizelyDecisions/OptimizelyDecision.cs @@ -14,9 +14,9 @@ * limitations under the License. */ +using System.Collections.Generic; using OptimizelySDK.ErrorHandler; using OptimizelySDK.Logger; -using System.Collections.Generic; namespace OptimizelySDK.OptimizelyDecisions { @@ -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 8e78bcf8..e1aa280a 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 83d2901f..9be6ef07 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 5af90e12..df5c9df8 100644 --- a/OptimizelySDK/OptimizelyJSON.cs +++ b/OptimizelySDK/OptimizelyJSON.cs @@ -14,13 +14,13 @@ * limitations under the License. */ -using OptimizelySDK.Logger; using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; -using OptimizelySDK.ErrorHandler; using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Logger; namespace OptimizelySDK { @@ -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 c8791237..55d93b0c 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -42,18 +42,22 @@ - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True + + + ..\packages\Namotion.Reflection.2.1.0\lib\net45\Namotion.Reflection.dll - - ..\packages\NJsonSchema.8.30.6304.31883\lib\net45\NJsonSchema.dll - True + + ..\packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\NJsonSchema.10.8.0\lib\net45\NJsonSchema.dll + + diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index 103acdf8..039559a7 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.cs @@ -43,12 +43,14 @@ public class OptimizelyUserContext private ForcedDecisionsStore ForcedDecisionsStore { get; set; } - public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, IErrorHandler errorHandler, ILogger logger) : - this(optimizely, userId, userAttributes, null, errorHandler, logger) - { - } - - public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, IErrorHandler errorHandler, ILogger logger) + public OptimizelyUserContext(Optimizely optimizely, string userId, + UserAttributes userAttributes, IErrorHandler errorHandler, ILogger logger + ) : this(optimizely, userId, userAttributes, null, errorHandler, logger) { } + + public OptimizelyUserContext(Optimizely optimizely, string userId, + UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, + IErrorHandler errorHandler, ILogger logger + ) { ErrorHandler = errorHandler; Logger = logger; diff --git a/OptimizelySDK/OptlyConfig/OptimizelyAttribute.cs b/OptimizelySDK/OptlyConfig/OptimizelyAttribute.cs index 5e0fb921..eacfb57b 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 7b682898..4ea8648d 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 3f65afdd..29e47441 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyConfigService.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyConfigService.cs @@ -15,8 +15,8 @@ */ using System; -using System.Collections.Generic; using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Text; using Newtonsoft.Json; @@ -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 7b0cb84e..8b81b555 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 b69221e6..c35fff66 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,20 @@ 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) + [Obsolete] + 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 b1743554..d3315d2e 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 19b66482..406e44f7 100644 --- a/OptimizelySDK/OptlyConfig/OptimizelyVariation.cs +++ b/OptimizelySDK/OptlyConfig/OptimizelyVariation.cs @@ -14,8 +14,8 @@ * limitations under the License. */ -using Newtonsoft.Json; using System.Collections.Generic; +using Newtonsoft.Json; namespace OptimizelySDK.OptlyConfig { @@ -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/ProjectConfig.cs b/OptimizelySDK/ProjectConfig.cs index 9a6f0701..e75a9ae2 100644 --- a/OptimizelySDK/ProjectConfig.cs +++ b/OptimizelySDK/ProjectConfig.cs @@ -14,8 +14,8 @@ * limitations under the License. */ -using OptimizelySDK.Entity; using System.Collections.Generic; +using OptimizelySDK.Entity; namespace OptimizelySDK { @@ -32,7 +32,7 @@ public interface ProjectConfig string AccountId { get; set; } /// - /// Project ID of the Full Stack project. + /// Project ID of the Optimizely Feature Experimentation project. /// string ProjectId { get; set; } diff --git a/OptimizelySDK/Utils/ConditionParser.cs b/OptimizelySDK/Utils/ConditionParser.cs index d9a147ef..6c55ffeb 100644 --- a/OptimizelySDK/Utils/ConditionParser.cs +++ b/OptimizelySDK/Utils/ConditionParser.cs @@ -14,9 +14,9 @@ * limitations under the License. */ +using System.Collections.Generic; using Newtonsoft.Json.Linq; using OptimizelySDK.AudienceConditions; -using System.Collections.Generic; namespace OptimizelySDK.Utils { @@ -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 1d1b73c0..318ff1b6 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 9fc7a5ba..327e9ef2 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 305c16e3..4a217c3d 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 64482d33..a2a094a8 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 8b08cb93..f6f3f725 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 d36ee36a..bb5334c5 100644 --- a/OptimizelySDK/Utils/ExperimentUtils.cs +++ b/OptimizelySDK/Utils/ExperimentUtils.cs @@ -1,5 +1,6 @@ + /* - * Copyright 2017-2021, Optimizely + * Copyright 2017-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +26,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."); @@ -50,7 +50,8 @@ public static Result DoesUserMeetAudienceConditions(ProjectConfig config, UserAttributes userAttributes, string loggingKeyType, string loggingKey, - ILogger logger) + ILogger logger + ) { var reasons = new DecisionReasons(); if (userAttributes == null) @@ -60,21 +61,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, userAttributes, 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 6a854bf3..2ee82c50 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 fd79ca16..693cd0cd 100644 --- a/OptimizelySDK/Utils/Validator.cs +++ b/OptimizelySDK/Utils/Validator.cs @@ -37,11 +37,9 @@ public static bool ValidateJSONSchema(string configJson, string schemaJson = nul { try { - return !NJsonSchema.JsonSchema4 - .FromJsonAsync(schemaJson ?? Schema.GetSchemaJson()) - .Result - .Validate(configJson) - .Any(); + return !NJsonSchema.JsonSchema.FromJsonAsync(schemaJson ?? Schema.GetSchemaJson()). + Result.Validate(configJson). + Any(); } catch (Newtonsoft.Json.JsonReaderException) { diff --git a/OptimizelySDK/packages.config b/OptimizelySDK/packages.config index c8732913..44eaafa9 100644 --- a/OptimizelySDK/packages.config +++ b/OptimizelySDK/packages.config @@ -1,6 +1,7 @@  - - - - \ No newline at end of file + + + + + diff --git a/README.md b/README.md index 31cb90e0..eb0da22c 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,18 @@ [![NuGet](https://img.shields.io/nuget/v/Optimizely.SDK.svg?style=plastic)](https://www.nuget.org/packages/Optimizely.SDK/) [![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](http://www.apache.org/licenses/LICENSE-2.0) -This repository houses the .Net based C# SDK for use with Optimizely Full Stack and Optimizely Rollouts. +This repository houses the .Net based C# SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). -Optimizely Full Stack is A/B testing and feature flag management for product development teams. Experiment in any application. Make every feature on your roadmap an opportunity to learn. Learn more at https://www.optimizely.com/platform/full-stack/, or see the [documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). +Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams, letting you experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). -Optimizely Rollouts is free feature flags for development teams. Easily roll out and roll back features in any application without code deploys. Mitigate risk for every feature on your roadmap. Learn more at https://www.optimizely.com/rollouts/, or see the [documentation](https://docs.developers.optimizely.com/experimentation/v3.1.0-full-stack/docs/introduction-to-rollouts). +Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. -## Getting Started -### Installing the SDK +## Get Started + +Refer to the [C# SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/csharp-sdk) for detailed instructions on getting started with using the SDK. + +### Install the C# SDK The SDK can be installed through [NuGet](https://www.nuget.org): @@ -30,13 +33,16 @@ Simply compile and run the Sample application to see it in use. Note that the way the Demo App stores data in memory is not recommended for production use and is merely illustrates how to use the SDK. -### Using the SDK -#### Documentation +### Feature Management Access + +To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager. -See the Optimizely Full Stack C# SDK [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/csharp-sdk) to learn how to set up your first Full Stack project and use the SDK. +## Use the C# SDK -#### Initialization +See the Optimizely Feature Experimentation [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0-full-stack/docs/csharp-sdk) to learn how to set up your first C# project and use the SDK. + +### Initialization Create the Optimizely Client, for example: @@ -210,19 +216,39 @@ This option is used to specify whether to start the config manager on initializa This option is used to provide token for datafile belonging to a secure environment. -## Development - -### Unit tests - -The sample project contains unit tests as well which can be run from the built-in Visual Studio Test Runner. +## SDK Development ### Contributing Please see [CONTRIBUTING](CONTRIBUTING.md). -## Third Party Licenses +### Third Party Licenses Optimizely SDK uses third party software: [murmurhash-signed](https://www.nuget.org/packages/murmurhash-signed/), [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/), and [NJsonSchema](https://www.nuget.org/packages/NJsonSchema/). + +### Other Optimzely SDKs + +- Agent - https://github.com/optimizely/agent + +- Android - https://github.com/optimizely/android-sdk + +- Flutter - https://github.com/optimizely/optimizely-flutter-sdk + +- Go - https://github.com/optimizely/go-sdk + +- Java - https://github.com/optimizely/java-sdk + +- JavaScript - https://github.com/optimizely/javascript-sdk + +- PHP - https://github.com/optimizely/php-sdk + +- Python - https://github.com/optimizely/python-sdk + +- React - https://github.com/optimizely/react-sdk + +- Ruby - https://github.com/optimizely/ruby-sdk + +- Swift - https://github.com/optimizely/swift-sdk \ No newline at end of file diff --git a/docs/readme-sync/sdk-reference-guides/csharp-sdk/040 - optimizelyconfig-csharp.md b/docs/readme-sync/sdk-reference-guides/csharp-sdk/040 - optimizelyconfig-csharp.md index c9df3951..0bbda239 100644 --- a/docs/readme-sync/sdk-reference-guides/csharp-sdk/040 - optimizelyconfig-csharp.md +++ b/docs/readme-sync/sdk-reference-guides/csharp-sdk/040 - optimizelyconfig-csharp.md @@ -10,7 +10,7 @@ updatedAt: "2020-01-28T21:53:11.290Z" "title": "Overview" } [/block] -Full Stack SDKs open a well-defined set of public APIs, hiding all implementation details. However, some clients may need access to project configuration data within the "datafile". +Optimizely Feature Experimentation SDKs open a well-defined set of public APIs, hiding all implementation details. However, some clients may need access to project configuration data within the "datafile". In this document, we extend our public APIs to define data models and access methods, which clients can use to access project configuration data. diff --git a/docs/readme-sync/sdk-reference-guides/csharp-sdk/070 - event-batching-csharp.md b/docs/readme-sync/sdk-reference-guides/csharp-sdk/070 - event-batching-csharp.md index f1539996..236c2222 100644 --- a/docs/readme-sync/sdk-reference-guides/csharp-sdk/070 - event-batching-csharp.md +++ b/docs/readme-sync/sdk-reference-guides/csharp-sdk/070 - event-batching-csharp.md @@ -5,7 +5,7 @@ hidden: false createdAt: "2019-09-12T13:44:04.059Z" updatedAt: "2019-12-13T00:25:39.892Z" --- -The [Optimizely Full Stack C# SDK](https://github.com/optimizely/csharp-sdk) now batches impression and conversion events into a single payload before sending it to Optimizely. This is achieved through a new SDK component called the event processor. +The [Optimizely Feature Experimentation C# SDK](https://github.com/optimizely/csharp-sdk) now batches impression and conversion events into a single payload before sending it to Optimizely. This is achieved through a new SDK component called the event processor. Event batching has the advantage of reducing the number of outbound requests to Optimizely depending on how you define, configure, and use the event processor. It means less network traffic for the same number of Impression and conversion events tracked. diff --git a/docs/readme-sync/sdk-reference-guides/csharp-sdk/190 - track-csharp.md b/docs/readme-sync/sdk-reference-guides/csharp-sdk/190 - track-csharp.md index 37a6bf73..ae006140 100644 --- a/docs/readme-sync/sdk-reference-guides/csharp-sdk/190 - track-csharp.md +++ b/docs/readme-sync/sdk-reference-guides/csharp-sdk/190 - track-csharp.md @@ -91,7 +91,7 @@ The table lists other other Optimizely functionality that may be triggered by us "h-0": "Functionality", "h-1": "Description", "0-0": "Conversions", - "0-1": "Calling this method records a conversion and attributes it to the variations that the user has seen.\n \nFull Stack 3.x supports retroactive metrics calculation. You can create [metrics](doc:choose-metrics) on this conversion event and add metrics to experiments even after the conversion has been tracked.\n\nFor more information, see the paragraph **Events are always on** in the introduction of [Events: Tracking clicks, pageviews, and other visitor actions](https://help.optimizely.com/Measure_success%3A_Track_visitor_behaviors/Events%3A_Tracking_clicks%2C_pageviews%2C_and_other_visitor_actions).\n\n**Important!** \n - This method won't track events when the specified event key is invalid.\n - Changing the traffic allocation of running experiments affects how conversions are recorded and variations are attributed to users.", + "0-1": "Calling this method records a conversion and attributes it to the variations that the user has seen.\n \nOptimizely Feature Experimentation 3.x supports retroactive metrics calculation. You can create [metrics](doc:choose-metrics) on this conversion event and add metrics to experiments even after the conversion has been tracked.\n\nFor more information, see the paragraph **Events are always on** in the introduction of [Events: Tracking clicks, pageviews, and other visitor actions](https://help.optimizely.com/Measure_success%3A_Track_visitor_behaviors/Events%3A_Tracking_clicks%2C_pageviews%2C_and_other_visitor_actions).\n\n**Important!** \n - This method won't track events when the specified event key is invalid.\n - Changing the traffic allocation of running experiments affects how conversions are recorded and variations are attributed to users.", "1-0": "Impressions", "1-1": "Track doesn't trigger impressions.", "2-0": "Notification Listeners",